// Package context - default_bundle.go 定义 claude+programming 的默认 PromptBundle. // // 这是引擎内置的唯一 Bundle.内容来自早期方案,语义和逻辑一字未改. // // 设计决策: // // 1. 静态 sections:7 个角色/行为/工具指南段落,取自 prompts.go 的已有常量. // 这些内容在引擎生命周期内不变,可以安全地走 global cache scope. // // 2. 动态 sections:env_info(运行时环境),instructions(FLYTO.md 文件), // tool_descriptions(动态注册工具),summarize_tool_results,evolve_fragment,append_prompt. // 这些内容每次会话或每次调用后可能变化,走 ephemeral cache scope. // // 3. FLYTO.md instructions 用 DynamicSection(会话级缓存)而非 VolatileSection-- // 文件内容在一次会话中不会实时变化,不需要每轮重读. // // 注意:不要改变 defaultClaudeProgrammingBundle 中的静态文字内容-- // 这些是针对 claude 模型 + 编程场景深度优化的结果,改一个字都可能影响模型行为. package context import ( "context" "fmt" "strings" ) // defaultClaudeProgrammingBundle 是内置的 claude+programming PromptBundle. // // 升华改进(ELEVATED): 早期实现 getSystemPrompt() 是一个大函数,把静态和动态内容混在一起生成字符串数组. // 我们把静态内容封装为 StaticSection,动态内容封装为 DynamicSection(Compute 函数读 ctx 注入值)-- // 静态部分通过 SectionRegistry 缓存(避免重复字符串分配), // 动态部分按需计算(FLYTO.md 内容变化时只有对应 section 的缓存失效). // 替代方案:<保持大函数结构,每次 build 都全量重算> // - 否决原因:静态内容每次调用都重新拼接字符串,浪费 CPU; // 且无法支持 global cache scope(需要明确知道哪部分是静态的). type defaultClaudeProgrammingBundle struct{} // NewDefaultBundle 创建 claude+programming 的默认 Bundle 实例. func NewDefaultBundle() PromptBundle { return &defaultClaudeProgrammingBundle{} } // StaticSections 返回 7 个静态提示词段落(取自 prompts.go 常量). // // 精妙之处(CLEVER): 每个 section 的 Name 对应早期实现 getSystemPrompt 中的函数名-- // 如果将来要查找某个 section 的缓存条目,Name 就是索引 key,和 TS 对应关系清晰. // 例如 "doing_tasks" 对应 getSimpleDoingTasksSection(),"actions" 对应 getActionsSection(). func (b *defaultClaudeProgrammingBundle) StaticSections() []*Section { return []*Section{ StaticSection("intro", sectionIntro), StaticSection("system", sectionSystem), StaticSection("doing_tasks", sectionDoingTasks), StaticSection("actions", sectionActions), StaticSection("using_tools", sectionUsingTools), StaticSection("tone_and_style", sectionToneAndStyle), StaticSection("output_efficiency", sectionOutputEfficiency), StaticSection("git_protocol", sectionGitProtocol), } } // DynamicSections 返回动态提示词段落(运行时计算,会话级缓存). // // 组装顺序对应早期实现 边界后的 dynamicSections: // 1. instructions - FLYTO.md 项目指令(会话级缓存:文件内容不会在会话中途变化) // 2. tool_descs - 已注册工具的描述(会话级缓存:工具在会话中一般不变) // 3. env_info - 运行时环境信息(会话级缓存:cwd/platform 不变) // 4. summarize_tool_results - 固定提醒文字(静态内容用动态 section 是因为它很短,合并入动态块) // 5. evolve_fragment - 进化能力提示(可选,有就加) // 6. append_prompt - 用户追加提示(可选,有就加) func (b *defaultClaudeProgrammingBundle) DynamicSections() []*Section { return []*Section{ DynamicSection("instructions", computeInstructionsSection), DynamicSection("tool_descs", computeToolDescsSection), DynamicSection("env_info", computeEnvInfoSection), DynamicSection("summarize_tool_results", func(_ context.Context) string { return sectionSummarizeToolResults }), DynamicSection("evolve_fragment", computeEvolveFragmentSection), DynamicSection("append_prompt", computeAppendPromptSection), // MCP server connection status. Volatile: connections can change // mid-session (server crash, reconnect, Config.ReconcileMCPServers // hot-swap), so the model must see a fresh view each turn. The // CacheBreak path also fires a `section_cache_break` observer // event carrying NoCacheReason -- operators tracing prompt-cache // misses see exactly which section invalidated and why. // // MCP server 连接状态. Volatile: 连接可能会话中途变化 (server 崩溃 // 重连 / Config.ReconcileMCPServers 热插拔), 模型每轮必须拿到最新视图. // CacheBreak 路径同时发 `section_cache_break` observer 事件携带 // NoCacheReason -- 运维追踪 prompt-cache miss 能精确看到哪个 // section 打碎 cache 以及原因. VolatileSection( "mcp_servers", computeMCPServersSection, "MCP server connections can change mid-session (crash/reconnect/hot-swap)", ), } } // --------------------------------------------------------------------------- // DynamicSection 计算函数(从 context 读取注入值) // --------------------------------------------------------------------------- // computeInstructionsSection 读取 FLYTO.md 并格式化为提示词段落. // 所有搜索路径(~/.flyto/FLYTO.md,项目根等)由 LoadInstructions 负责. // 返回空字符串表示没有找到 FLYTO.md,该 section 被跳过. func computeInstructionsSection(ctx context.Context) string { cwdStr := CwdFromCtx(ctx) if cwdStr == "" { return "" } instructions := LoadInstructions(cwdStr) return formatInstructionsSection(instructions) } // computeToolDescsSection 从 context 读取工具描述列表,组装为段落. // 如果没有注入工具描述(nil 或空),跳过此 section. func computeToolDescsSection(ctx context.Context) string { descs := ToolDescriptionsFromCtx(ctx) if len(descs) == 0 { return "" } var sb strings.Builder sb.WriteString("# Available Tools\n\n") sb.WriteString("You have access to the following tools:\n\n") for _, td := range descs { sb.WriteString(fmt.Sprintf("- **%s**: %s\n", td.Name, td.Description)) } return sb.String() } // computeEnvInfoSection 收集运行时环境信息并格式化为段落. // 读取 cwd 和 modelID,调用 CollectEnvInfo 并在末尾追加模型描述. func computeEnvInfoSection(ctx context.Context) string { cwd := CwdFromCtx(ctx) if cwd == "" { return "" } modelID := ModelIDFromCtx(ctx) info := CollectEnvInfo(cwd) var sb strings.Builder sb.WriteString("# Environment\n\n") sb.WriteString(fmt.Sprintf(" - Primary working directory: %s\n", info.Cwd)) isGitStr := "false" if info.IsGitRepo { isGitStr = "true" } sb.WriteString(fmt.Sprintf(" - Is a git repository: %s\n", isGitStr)) sb.WriteString(fmt.Sprintf(" - Platform: %s\n", info.Platform)) if info.Shell != "" { sb.WriteString(fmt.Sprintf(" - Shell: %s\n", info.Shell)) } if info.OSVersion != "" { sb.WriteString(fmt.Sprintf(" - OS Version: %s\n", info.OSVersion)) } if info.IsGitRepo && info.GitBranch != "" { sb.WriteString(fmt.Sprintf(" - Git branch: %s\n", info.GitBranch)) } if info.IsGitRepo && info.GitStatus != "" { sb.WriteString(fmt.Sprintf(" - Git status: %s\n", info.GitStatus)) } // 模型描述(早期方案 computeSimpleEnvInfo 中的 modelDescription) if modelID != "" { sb.WriteString(fmt.Sprintf("\nYou are powered by the model with ID %s.\n", modelID)) } return strings.TrimRight(sb.String(), "\n") } // computeEvolveFragmentSection 从 context 读取进化能力提示片段. // 若未注入(为空),跳过此 section. func computeEvolveFragmentSection(ctx context.Context) string { return EvolveFragmentFromCtx(ctx) } // computeAppendPromptSection 从 context 读取用户追加提示. // 若未注入(为空),跳过此 section. func computeAppendPromptSection(ctx context.Context) string { return AppendPromptFromCtx(ctx) } // computeMCPServersSection renders the per-turn MCP server connection // snapshot into a short bulleted section. Returns empty string when no // MCP servers are configured, so the block is dropped. // // computeMCPServersSection 将 per-turn 的 MCP server 连接快照渲染成简短的 // 列表段落. 未配置 MCP server 时返回空串, 对应 block 被过滤. func computeMCPServersSection(ctx context.Context) string { statuses := MCPServerStatusesFromCtx(ctx) if len(statuses) == 0 { return "" } var sb strings.Builder sb.WriteString("# MCP Servers\n\n") for _, s := range statuses { state := "disconnected" if s.Connected { state = "connected" } sb.WriteString(fmt.Sprintf("- %s (%s)\n", s.Name, state)) } return strings.TrimRight(sb.String(), "\n") } // --------------------------------------------------------------------------- // NewDefaultBundleRegistry - 预装默认 Bundle 的注册表工厂 // --------------------------------------------------------------------------- // NewDefaultBundleRegistry 创建已注册默认 Bundle 的 BundleRegistry. // // 升华改进(ELEVATED): 提供工厂函数而非要求调用方手动 Register-- // 这样 engine.New() 只需一行即可获得完整可用的 Bundle 基础设施,不会因为忘记 Register 而运行时 nil panic. // 替代方案:<让调用方手动注册> // - 否决原因:用 BundleRegistry.Resolve 返回 nil,后续调用方必须 nil 检查,出错路径更多. func NewDefaultBundleRegistry() *BundleRegistry { r := NewBundleRegistry() r.Register(DefaultBundleKey, NewDefaultBundle()) return r }