@mikode13/harness 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +285 -20
  2. package/dist/agent/domain/agent.d.ts +12 -0
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/agent/domain/approval.d.ts +14 -0
  5. package/dist/agent/domain/approval.js +28 -3
  6. package/dist/agent/domain/approval.js.map +1 -1
  7. package/dist/agent/domain/runContext.d.ts +31 -0
  8. package/dist/agent/domain/runContext.js +47 -0
  9. package/dist/agent/domain/runContext.js.map +1 -0
  10. package/dist/diff/domain/unifiedDiff.d.ts +19 -0
  11. package/dist/diff/domain/unifiedDiff.js +166 -0
  12. package/dist/diff/domain/unifiedDiff.js.map +1 -0
  13. package/dist/engines/domain/model/historyFollower.d.ts +41 -0
  14. package/dist/engines/domain/model/historyFollower.js +158 -0
  15. package/dist/engines/domain/model/historyFollower.js.map +1 -0
  16. package/dist/engines/domain/model/llmAgent.d.ts +42 -4
  17. package/dist/engines/domain/model/llmAgent.js +224 -36
  18. package/dist/engines/domain/model/llmAgent.js.map +1 -1
  19. package/dist/factory/infrastructure/agentFactory.d.ts +14 -0
  20. package/dist/factory/infrastructure/agentFactory.js +20 -2
  21. package/dist/factory/infrastructure/agentFactory.js.map +1 -1
  22. package/dist/factory/infrastructure/agentLLMFactory.d.ts +119 -14
  23. package/dist/factory/infrastructure/agentLLMFactory.js +284 -57
  24. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  25. package/dist/index.d.ts +11 -3
  26. package/dist/index.js +5 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/llm/domain/conversation.d.ts +44 -4
  29. package/dist/llm/domain/conversation.js +56 -7
  30. package/dist/llm/domain/conversation.js.map +1 -1
  31. package/dist/llm/domain/llm.d.ts +3 -1
  32. package/dist/llm/domain/llm.js.map +1 -1
  33. package/dist/llm/domain/tool.d.ts +13 -0
  34. package/dist/llm/domain/tool.js +12 -1
  35. package/dist/llm/domain/tool.js.map +1 -1
  36. package/dist/llm/infrastructure/claudeLLMClient.d.ts +3 -2
  37. package/dist/llm/infrastructure/claudeLLMClient.js +38 -10
  38. package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
  39. package/dist/llm/infrastructure/openAILLMClient.d.ts +2 -1
  40. package/dist/llm/infrastructure/openAILLMClient.js +66 -16
  41. package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
  42. package/dist/orchestration/domain/model/orchestratorAgent.d.ts +11 -2
  43. package/dist/orchestration/domain/model/orchestratorAgent.js +43 -4
  44. package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
  45. package/dist/recovery/domain/fileChanges.d.ts +31 -0
  46. package/dist/recovery/domain/fileChanges.js +48 -0
  47. package/dist/recovery/domain/fileChanges.js.map +1 -0
  48. package/dist/recovery/domain/history.d.ts +114 -0
  49. package/dist/recovery/domain/history.js +62 -0
  50. package/dist/recovery/domain/history.js.map +1 -0
  51. package/dist/recovery/domain/recoveryStore.d.ts +233 -0
  52. package/dist/recovery/domain/recoveryStore.js +32 -0
  53. package/dist/recovery/domain/recoveryStore.js.map +1 -0
  54. package/dist/recovery/domain/retention.d.ts +33 -0
  55. package/dist/recovery/domain/retention.js +90 -0
  56. package/dist/recovery/domain/retention.js.map +1 -0
  57. package/dist/recovery/domain/runChanges.d.ts +37 -0
  58. package/dist/recovery/domain/runChanges.js +109 -0
  59. package/dist/recovery/domain/runChanges.js.map +1 -0
  60. package/dist/recovery/domain/runTree.d.ts +32 -0
  61. package/dist/recovery/domain/runTree.js +90 -0
  62. package/dist/recovery/domain/runTree.js.map +1 -0
  63. package/dist/recovery/infrastructure/createHistory.d.ts +11 -0
  64. package/dist/recovery/infrastructure/createHistory.js +17 -0
  65. package/dist/recovery/infrastructure/createHistory.js.map +1 -0
  66. package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +133 -0
  67. package/dist/recovery/infrastructure/fileRecoveryStore.js +1043 -0
  68. package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -0
  69. package/dist/recovery/infrastructure/workspaceFiles.d.ts +30 -0
  70. package/dist/recovery/infrastructure/workspaceFiles.js +125 -0
  71. package/dist/recovery/infrastructure/workspaceFiles.js.map +1 -0
  72. package/dist/tools/domain/accessPolicy.d.ts +30 -0
  73. package/dist/tools/domain/accessPolicy.js +11 -0
  74. package/dist/tools/domain/accessPolicy.js.map +1 -0
  75. package/dist/tools/domain/commands.d.ts +93 -0
  76. package/dist/tools/domain/commands.js +191 -0
  77. package/dist/tools/domain/commands.js.map +1 -0
  78. package/dist/tools/domain/editResponse.d.ts +14 -0
  79. package/dist/tools/domain/editResponse.js +79 -0
  80. package/dist/tools/domain/editResponse.js.map +1 -0
  81. package/dist/tools/domain/fileEdits.d.ts +64 -0
  82. package/dist/tools/domain/fileEdits.js +21 -0
  83. package/dist/tools/domain/fileEdits.js.map +1 -0
  84. package/dist/tools/domain/lineLength.d.ts +7 -0
  85. package/dist/tools/domain/lineLength.js +26 -0
  86. package/dist/tools/domain/lineLength.js.map +1 -0
  87. package/dist/tools/domain/preparedCall.d.ts +31 -0
  88. package/dist/tools/domain/preparedCall.js +14 -0
  89. package/dist/tools/domain/preparedCall.js.map +1 -0
  90. package/dist/tools/domain/readRegistry.d.ts +46 -0
  91. package/dist/tools/domain/readRegistry.js +62 -0
  92. package/dist/tools/domain/readRegistry.js.map +1 -0
  93. package/dist/tools/domain/textLines.d.ts +26 -0
  94. package/dist/tools/domain/textLines.js +44 -0
  95. package/dist/tools/domain/textLines.js.map +1 -0
  96. package/dist/tools/domain/v4aPatch.d.ts +17 -0
  97. package/dist/tools/domain/v4aPatch.js +236 -0
  98. package/dist/tools/domain/v4aPatch.js.map +1 -0
  99. package/dist/tools/infrastructure/boundedWorkspace.d.ts +11 -2
  100. package/dist/tools/infrastructure/boundedWorkspace.js +9 -1
  101. package/dist/tools/infrastructure/boundedWorkspace.js.map +1 -1
  102. package/dist/tools/infrastructure/commandCheckpoint.d.ts +55 -0
  103. package/dist/tools/infrastructure/commandCheckpoint.js +178 -0
  104. package/dist/tools/infrastructure/commandCheckpoint.js.map +1 -0
  105. package/dist/tools/infrastructure/commandProcess.d.ts +47 -0
  106. package/dist/tools/infrastructure/commandProcess.js +217 -0
  107. package/dist/tools/infrastructure/commandProcess.js.map +1 -0
  108. package/dist/tools/infrastructure/commandTool.d.ts +36 -0
  109. package/dist/tools/infrastructure/commandTool.js +254 -0
  110. package/dist/tools/infrastructure/commandTool.js.map +1 -0
  111. package/dist/tools/infrastructure/createWorkspace.d.ts +10 -0
  112. package/dist/tools/infrastructure/createWorkspace.js +11 -3
  113. package/dist/tools/infrastructure/createWorkspace.js.map +1 -1
  114. package/dist/tools/infrastructure/defineTool.d.ts +14 -0
  115. package/dist/tools/infrastructure/defineTool.js +28 -5
  116. package/dist/tools/infrastructure/defineTool.js.map +1 -1
  117. package/dist/tools/infrastructure/editFormats.d.ts +22 -0
  118. package/dist/tools/infrastructure/editFormats.js +211 -0
  119. package/dist/tools/infrastructure/editFormats.js.map +1 -0
  120. package/dist/tools/infrastructure/fileEditor.d.ts +64 -0
  121. package/dist/tools/infrastructure/fileEditor.js +306 -0
  122. package/dist/tools/infrastructure/fileEditor.js.map +1 -0
  123. package/dist/tools/infrastructure/fileTools.d.ts +81 -0
  124. package/dist/tools/infrastructure/fileTools.js +152 -0
  125. package/dist/tools/infrastructure/fileTools.js.map +1 -0
  126. package/dist/tools/infrastructure/gitIgnoreRules.d.ts +15 -0
  127. package/dist/tools/infrastructure/gitIgnoreRules.js +74 -0
  128. package/dist/tools/infrastructure/gitIgnoreRules.js.map +1 -0
  129. package/dist/tools/infrastructure/gitWorkspace.d.ts +1 -1
  130. package/dist/tools/infrastructure/gitWorkspace.js +1 -1
  131. package/dist/tools/infrastructure/gitWorkspace.js.map +1 -1
  132. package/dist/tools/infrastructure/ignoreFileChanges.d.ts +14 -0
  133. package/dist/tools/infrastructure/ignoreFileChanges.js +62 -0
  134. package/dist/tools/infrastructure/ignoreFileChanges.js.map +1 -0
  135. package/dist/tools/infrastructure/policyWorkspace.d.ts +52 -0
  136. package/dist/tools/infrastructure/policyWorkspace.js +32 -0
  137. package/dist/tools/infrastructure/policyWorkspace.js.map +1 -0
  138. package/dist/tools/infrastructure/ripgrepWorkspace.d.ts +2 -1
  139. package/dist/tools/infrastructure/ripgrepWorkspace.js +1 -1
  140. package/dist/tools/infrastructure/ripgrepWorkspace.js.map +1 -1
  141. package/dist/tools/infrastructure/rootsAccessPolicy.d.ts +100 -0
  142. package/dist/tools/infrastructure/rootsAccessPolicy.js +301 -0
  143. package/dist/tools/infrastructure/rootsAccessPolicy.js.map +1 -0
  144. package/dist/tools/infrastructure/runProcess.d.ts +4 -1
  145. package/dist/tools/infrastructure/runProcess.js +4 -2
  146. package/dist/tools/infrastructure/runProcess.js.map +1 -1
  147. package/dist/tools/infrastructure/secretPaths.d.ts +8 -0
  148. package/dist/tools/infrastructure/secretPaths.js +76 -0
  149. package/dist/tools/infrastructure/secretPaths.js.map +1 -0
  150. package/dist/tools/infrastructure/showChangesTool.d.ts +12 -0
  151. package/dist/tools/infrastructure/showChangesTool.js +43 -0
  152. package/dist/tools/infrastructure/showChangesTool.js.map +1 -0
  153. package/dist/tools/infrastructure/trackedEdits.d.ts +33 -0
  154. package/dist/tools/infrastructure/trackedEdits.js +70 -0
  155. package/dist/tools/infrastructure/trackedEdits.js.map +1 -0
  156. package/dist/tools/infrastructure/trackedReadFile.d.ts +21 -0
  157. package/dist/tools/infrastructure/trackedReadFile.js +78 -0
  158. package/dist/tools/infrastructure/trackedReadFile.js.map +1 -0
  159. package/dist/tools/infrastructure/workspaceTools.d.ts +28 -0
  160. package/dist/tools/infrastructure/workspaceTools.js +53 -40
  161. package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
  162. package/dist/tools/infrastructure/workspaceWrites.d.ts +23 -0
  163. package/dist/tools/infrastructure/workspaceWrites.js +42 -0
  164. package/dist/tools/infrastructure/workspaceWrites.js.map +1 -0
  165. package/dist/tools/infrastructure/writeSession.d.ts +35 -0
  166. package/dist/tools/infrastructure/writeSession.js +81 -0
  167. package/dist/tools/infrastructure/writeSession.js.map +1 -0
  168. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"agentFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from '#src/agent/domain/agent';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { ClaudeAgent } from '#src/engines/claude/infrastructure/model/claudeAgent';\nimport { CodexAgent } from '#src/engines/codex/infrastructure/model/codexAgent';\nimport { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';\nimport {\n\texecutorInstructions,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './types.ts';\n\nexport function isAgentProvider(value: string): value is AgentProvider {\n\treturn isOneOf(agentProviders, value);\n}\n\nexport interface CreateAgentOptions {\n\t/** Defaults to `'opus'` on Anthropic and `'gpt-5.6-sol'` on OpenAI. */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Maps to the provider's permission-bypass mode. Defaults to `false`. */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultModels = {\n\tanthropic: 'opus',\n\topenai: 'gpt-5.6-sol',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\n/**\n * Builds the agent for a provider on its Agent SDK — Claude Code for Anthropic, Codex for\n * OpenAI — already wrapped in the harness's retry policy.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort\n * the provider does not support.\n * @throws {UnrecoverableError} when Codex cannot be set up, for example because its CLI binary\n * is missing.\n */\nexport function createAgent(\n\tprovider: AgentProvider,\n\t{ model, reasoningEffort, autoApprove, logger = new Logger() }: CreateAgentOptions = {},\n): Agent {\n\tlet engine: Agent;\n\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\tengine = new ClaudeAgent({\n\t\t\t\tmodel: model ?? defaultModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'openai':\n\t\t\tengine = new CodexAgent({\n\t\t\t\tmodel: model ?? defaultModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input that skipped `isAgentProvider`.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn new RetryingAgent({ inner: engine, logger });\n}\n\nexport interface Role {\n\tprovider: AgentProvider;\n\tmodel: AgentModel;\n\treasoningEffort: ReasoningEffort;\n}\n\n// A stronger model plans and reviews; a cheaper one executes the plan. Every model and effort\n// here is one both the Agent SDKs and the model APIs accept, so either path can run a role.\nconst orchestratorRoles = {\n\tdefault: {\n\t\tplanner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t},\n\tanthropic: {\n\t\tplanner: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'anthropic', model: 'sonnet', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t},\n\topenai: {\n\t\tplanner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t},\n} as const satisfies Record<\n\tAgentProvider | 'default',\n\tRecord<'planner' | 'executor' | 'reviewer', Role>\n>;\n\n/**\n * The roles for `provider`, or the default mix without one.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n */\nexport function orchestratorRolesFor(\n\tprovider: AgentProvider | undefined,\n): Record<'planner' | 'executor' | 'reviewer', Role> {\n\tif (provider !== undefined && !isAgentProvider(provider)) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`\"${String(provider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t);\n\t}\n\n\treturn orchestratorRoles[provider ?? 'default'];\n}\n\n/**\n * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n * @throws {UnrecoverableError} when an OpenAI role cannot be set up, for example because the\n * Codex CLI binary is missing.\n */\nexport function createOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tlogger = new Logger(),\n}: CreateOrchestratorOptions = {}): Agent {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst agentFor = ({ provider, model, reasoningEffort }: Role, instructions: string) =>\n\t\tnew InstructedAgent({\n\t\t\tinner: createAgent(provider, { model, reasoningEffort, autoApprove, logger }),\n\t\t\tinstructions,\n\t\t});\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: agentFor(roles.planner, systemPrompts.planner ?? plannerInstructions),\n\t\texecutorAgent: agentFor(roles.executor, systemPrompts.executor ?? executorInstructions),\n\t\treviewerAgent: agentFor(roles.reviewer, systemPrompts.reviewer ?? reviewerInstructions),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
1
+ {"version":3,"file":"agentFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/agentFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent } from '#src/agent/domain/agent';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport {\n\tClaudeAgent,\n\tclaudeModels,\n\tclaudeReasoningEfforts,\n} from '#src/engines/claude/infrastructure/model/claudeAgent';\nimport {\n\tCodexAgent,\n\tcodexModels,\n\tcodexReasoningEfforts,\n} from '#src/engines/codex/infrastructure/model/codexAgent';\nimport { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';\nimport {\n\texecutorInstructions,\n\tOrchestratorAgent,\n\tplannerInstructions,\n\treviewerInstructions,\n} from '#src/orchestration/domain/model/orchestratorAgent';\nimport { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';\nimport { RetryingAgent } from '#src/retry/domain/model/retryingAgent';\nimport { isOneOf } from '#src/shared/domain/isOneOf';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './types.ts';\n\nexport function isAgentProvider(value: string): value is AgentProvider {\n\treturn isOneOf(agentProviders, value);\n}\n\n/** The models each provider's Agent SDK accepts. */\nexport const agentModels = {\n\tanthropic: claudeModels,\n\topenai: codexModels,\n} as const satisfies Readonly<Record<AgentProvider, readonly AgentModel[]>>;\n\n/** The reasoning efforts each provider's Agent SDK accepts. */\nexport const agentReasoningEfforts = {\n\tanthropic: claudeReasoningEfforts,\n\topenai: codexReasoningEfforts,\n} as const satisfies Readonly<Record<AgentProvider, readonly ReasoningEffort[]>>;\n\n/** Narrows a model to one accepted by the selected provider's Agent SDK. */\nexport function isModelOf<Provider extends AgentProvider>(\n\tprovider: Provider,\n\tvalue: string,\n): value is (typeof agentModels)[Provider][number] {\n\treturn isOneOf(agentModels[provider], value);\n}\n\n/** Narrows a reasoning effort to one accepted by the selected provider's Agent SDK. */\nexport function isReasoningEffortOf<Provider extends AgentProvider>(\n\tprovider: Provider,\n\tvalue: string,\n): value is (typeof agentReasoningEfforts)[Provider][number] {\n\treturn isOneOf(agentReasoningEfforts[provider], value);\n}\n\nexport interface CreateAgentOptions {\n\t/** Defaults to `'opus'` on Anthropic and `'gpt-5.6-sol'` on OpenAI. */\n\tmodel?: AgentModel;\n\t/** Defaults to `'high'`. */\n\treasoningEffort?: ReasoningEffort;\n\t/** Maps to the provider's permission-bypass mode. Defaults to `false`. */\n\tautoApprove?: boolean;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}\n\nconst defaultModels = {\n\tanthropic: 'opus',\n\topenai: 'gpt-5.6-sol',\n} as const satisfies Record<AgentProvider, AgentModel>;\n\n/**\n * Builds the agent for a provider on its Agent SDK — Claude Code for Anthropic, Codex for\n * OpenAI — already wrapped in the harness's retry policy.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider, or a model or reasoning effort\n * the provider does not support.\n * @throws {UnrecoverableError} when Codex cannot be set up, for example because its CLI binary\n * is missing.\n */\nexport function createAgent(\n\tprovider: AgentProvider,\n\t{ model, reasoningEffort, autoApprove, logger = new Logger() }: CreateAgentOptions = {},\n): Agent {\n\tlet engine: Agent;\n\n\tswitch (provider) {\n\t\tcase 'anthropic':\n\t\t\tengine = new ClaudeAgent({\n\t\t\t\tmodel: model ?? defaultModels.anthropic,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase 'openai':\n\t\t\tengine = new CodexAgent({\n\t\t\t\tmodel: model ?? defaultModels.openai,\n\t\t\t\treasoningEffort,\n\t\t\t\tautoApprove,\n\t\t\t\tlogger,\n\t\t\t});\n\t\t\tbreak;\n\t\tdefault: {\n\t\t\t// Reachable from untyped input that skipped `isAgentProvider`.\n\t\t\tconst unknownProvider: never = provider;\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`\"${String(unknownProvider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn new RetryingAgent({ inner: engine, logger });\n}\n\nexport interface Role {\n\tprovider: AgentProvider;\n\tmodel: AgentModel;\n\treasoningEffort: ReasoningEffort;\n}\n\n// A stronger model plans and reviews; a cheaper one executes the plan. Every model and effort\n// here is one both the Agent SDKs and the model APIs accept, so either path can run a role.\nconst orchestratorRoles = {\n\tdefault: {\n\t\tplanner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t},\n\tanthropic: {\n\t\tplanner: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'anthropic', model: 'sonnet', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'anthropic', model: 'opus', reasoningEffort: 'high' },\n\t},\n\topenai: {\n\t\tplanner: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t\texecutor: { provider: 'openai', model: 'gpt-5.6-luna', reasoningEffort: 'xhigh' },\n\t\treviewer: { provider: 'openai', model: 'gpt-5.6-sol', reasoningEffort: 'high' },\n\t},\n} as const satisfies Record<\n\tAgentProvider | 'default',\n\tRecord<'planner' | 'executor' | 'reviewer', Role>\n>;\n\n/**\n * The roles for `provider`, or the default mix without one.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n */\nexport function orchestratorRolesFor(\n\tprovider: AgentProvider | undefined,\n): Record<'planner' | 'executor' | 'reviewer', Role> {\n\tif (provider !== undefined && !isAgentProvider(provider)) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`\"${String(provider)}\" is not an agent provider; expected one of: ${agentProviders.join(', ')}`,\n\t\t);\n\t}\n\n\treturn orchestratorRoles[provider ?? 'default'];\n}\n\n/**\n * Builds the planner → executor → reviewer workflow, each role an agent from `createAgent`.\n *\n * @throws {InvalidAgentConfigError} for an unknown provider.\n * @throws {UnrecoverableError} when an OpenAI role cannot be set up, for example because the\n * Codex CLI binary is missing.\n */\nexport function createOrchestrator({\n\tprovider,\n\tautoApprove,\n\tsystemPrompts = {},\n\tlogger = new Logger(),\n}: CreateOrchestratorOptions = {}): Agent {\n\tconst roles = orchestratorRolesFor(provider);\n\tconst agentFor = ({ provider, model, reasoningEffort }: Role, instructions: string) =>\n\t\tnew InstructedAgent({\n\t\t\tinner: createAgent(provider, { model, reasoningEffort, autoApprove, logger }),\n\t\t\tinstructions,\n\t\t});\n\n\treturn new OrchestratorAgent({\n\t\tplannerAgent: agentFor(roles.planner, systemPrompts.planner ?? plannerInstructions),\n\t\texecutorAgent: agentFor(roles.executor, systemPrompts.executor ?? executorInstructions),\n\t\treviewerAgent: agentFor(roles.reviewer, systemPrompts.reviewer ?? reviewerInstructions),\n\t\treviewerDecisionValidator: new ReviewerDecisionValidator(),\n\t\tlogger,\n\t});\n}\n"]}
@@ -1,7 +1,23 @@
1
1
  import type { Agent } from '#src/agent/domain/agent';
2
2
  import type { ILogger } from '#src/shared/domain/logger';
3
+ import { type CommandRole, type Commands } from '#src/tools/domain/commands';
3
4
  import type { Tool } from '#src/tools/domain/tool';
5
+ import { type FileTool, type WorkspaceOptions } from '#src/tools/infrastructure/fileTools';
4
6
  import { type AgentModel, type AgentProvider, type CreateOrchestratorOptions, type ReasoningEffort } from './types.ts';
7
+ /** The models each provider's model API accepts. */
8
+ export declare const llmModels: {
9
+ readonly anthropic: readonly ["opus", "fable", "sonnet", "haiku"];
10
+ readonly openai: readonly ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-luna", "gpt-5.6-terra"];
11
+ };
12
+ /** The reasoning efforts each provider's model API accepts. */
13
+ export declare const llmReasoningEfforts: {
14
+ readonly anthropic: readonly ["low", "medium", "high", "xhigh", "max"];
15
+ readonly openai: readonly ["low", "medium", "high", "xhigh", "max"];
16
+ };
17
+ /** Narrows a model to one accepted by the selected provider's model API. */
18
+ export declare function isLLMModelOf<Provider extends AgentProvider>(provider: Provider, value: string): value is (typeof llmModels)[Provider][number];
19
+ /** Narrows a reasoning effort to one accepted by the selected provider's model API. */
20
+ export declare function isLLMReasoningEffortOf<Provider extends AgentProvider>(provider: Provider, value: string): value is (typeof llmReasoningEfforts)[Provider][number];
5
21
  export interface CreateLLMAgentOptions {
6
22
  /**
7
23
  * Defaults to `'sonnet'` on Anthropic and `'gpt-5.6-luna'` on OpenAI. The provider's client
@@ -12,8 +28,26 @@ export interface CreateLLMAgentOptions {
12
28
  reasoningEffort?: ReasoningEffort;
13
29
  /** Sent as the model's instructions on every call. */
14
30
  systemPrompt: string;
15
- /** The tools the model may call. Defaults to none. */
16
- tools?: Tool[];
31
+ /** The tools the model may call: the consumer's own, and those of `createFileTools`. Defaults to none. */
32
+ tools?: (Tool | FileTool)[];
33
+ /**
34
+ * The folders the agent works on, the same as given to `createFileTools`. With it, the agent
35
+ * follows the workspace's history: after the user undoes runs, its next run starts from the
36
+ * conversation as it was, and is told what was undone. The system prompt is followed by the
37
+ * roots and how to name a file in them.
38
+ */
39
+ workspace?: WorkspaceOptions;
40
+ /**
41
+ * With `workspace`, a summary of what was undone, from the provider's cheap model, in place
42
+ * of the undone prompts. That is Claude Haiku without thinking, or `gpt-5.6-luna` at high
43
+ * effort, and the call is billed with the run's tokens. Defaults to `true`.
44
+ */
45
+ summarizeUndone?: boolean;
46
+ /**
47
+ * How many calls to the model one run may make before it fails. Defaults to 65, with no upper
48
+ * bound. A run that asks for exactly the same tool calls 5 times in a row fails before that.
49
+ */
50
+ maxSteps?: number;
17
51
  /**
18
52
  * Runs every tool call without asking, `destructive` ones included, for CI where no one can
19
53
  * answer. Without it, a `destructive` call goes to the run's `approve`, or is denied.
@@ -22,6 +56,35 @@ export interface CreateLLMAgentOptions {
22
56
  /** Defaults to warnings on stderr. */
23
57
  logger?: ILogger;
24
58
  }
59
+ /** A limit per role of `createLLMOrchestrator`; a role left out keeps its default. */
60
+ export type RoleSteps = Partial<Record<OrchestratorRole, number>>;
61
+ type OrchestratorRole = CommandRole;
62
+ export interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions {
63
+ /**
64
+ * The folders the agents work on. The executor changes files only in a `write` root, every
65
+ * change recorded in the workspace's history. Defaults to the current working directory,
66
+ * read only.
67
+ */
68
+ workspace?: WorkspaceOptions;
69
+ /**
70
+ * How many calls to the model one run of each role may make: one number for every role, or a
71
+ * number per role. Defaults to 80 for the executor and 65 for the planner and the reviewer,
72
+ * with no upper bound. A run that asks for exactly the same tool calls 5 times in a row fails
73
+ * before its limit.
74
+ */
75
+ maxSteps?: number | RoleSteps;
76
+ /**
77
+ * Commands the roles may run, such as the project's tests, through `runCommand`. Each goes to
78
+ * the roles its `roles` names, the executor by default. See `createFileTools`. Each needs a
79
+ * `write` root to run in.
80
+ */
81
+ commands?: Commands;
82
+ /**
83
+ * A summary of what the user undid, from the planner's provider's cheap model, for every
84
+ * role. See `CreateLLMAgentOptions.summarizeUndone`. Defaults to `true`.
85
+ */
86
+ summarizeUndone?: boolean;
87
+ }
25
88
  /**
26
89
  * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
27
90
  * the harness's retry policy. It needs the provider's API key and bills per token, where
@@ -34,18 +97,60 @@ export interface CreateLLMAgentOptions {
34
97
  * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
35
98
  * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
36
99
  */
37
- export declare function createLLMAgent(provider: AgentProvider, { model, reasoningEffort, systemPrompt, tools, autoApprove, logger, }: CreateLLMAgentOptions): Agent;
100
+ export declare function createLLMAgent(provider: AgentProvider, { model, reasoningEffort, systemPrompt, tools, workspace, summarizeUndone, maxSteps, autoApprove, logger, }: CreateLLMAgentOptions): Agent;
101
+ /**
102
+ * The file tools for an agent from `createLLMAgent` on `provider`, over `workspace`, in the
103
+ * provider's own format:
104
+ *
105
+ * - OpenAI: `listFiles`, `searchText`, `readFile` and its native `apply_patch`;
106
+ * - Anthropic: `listFiles`, `searchText`, its native text editor, whose `view` reads, and
107
+ * `delete_file`.
108
+ *
109
+ * Every one goes through the workspace's access policy: a path outside the roots, a secret,
110
+ * a file `.gitignore` excludes or the harness protects is refused. A change is made only in a
111
+ * `write` root, only from a version the agent read, and is recorded in the workspace's history,
112
+ * so it can be undone. Without a `write` root the tools only read. Give the agent the same
113
+ * `workspace`, so it follows what the history undoes.
114
+ *
115
+ * With `commands`, they include `runCommand`, which runs one of them by name, with only the
116
+ * parameters it declares and never through a shell. Each run asks the approver, showing the
117
+ * program exactly as it will run, and gets a small environment without the provider keys. The
118
+ * files of the `write` roots are captured before it and compared after, so what it changed is
119
+ * in the history too. It is trusted execution, not a sandbox: a command can reach whatever
120
+ * this user can. Not supported on Windows yet.
121
+ *
122
+ * @throws {InvalidAgentConfigError} for an unknown provider, a workspace with no root, or an
123
+ * invalid command, including one whose `cwd` is not in a `write` root, or any on Windows.
124
+ * @throws {UnrecoverableError} when no program can read the first root.
125
+ */
126
+ export declare function createFileTools(provider: AgentProvider, workspace: WorkspaceOptions, { logger, commands }?: {
127
+ logger?: ILogger;
128
+ commands?: Commands;
129
+ }): Promise<FileTool[]>;
38
130
  /**
39
- * Builds the planner → executor → reviewer workflow with the planner and reviewer on the model
40
- * APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its
41
- * system prompt, with the read-only repository tools for the current working directory. The
42
- * executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;
43
- * `autoApprove` reaches only it. Asynchronous because finding the program that reads the
44
- * repository is.
45
- *
46
- * @throws {InvalidAgentConfigError} for an unknown provider.
131
+ * Builds the planner → executor → reviewer workflow with every role on the model APIs, each
132
+ * holding its role's instructions, or `systemPrompts`, as its system prompt, followed by the
133
+ * workspace's roots.
134
+ *
135
+ * - The planner and the reviewer read: `listFiles`, `searchText`, `readFile`, and
136
+ * `showChanges`, the current task's changes as far as each may read them.
137
+ * - The executor gets its provider's file tools (see `createFileTools`), and changes files only
138
+ * in a `write` root; `autoApprove` reaches only it.
139
+ * - Each role has its own step limit: `maxSteps`, 80 for the executor and 65 for the others by
140
+ * default.
141
+ * - Each role gets `runCommand` with the `commands` whose `roles` name it, the executor by
142
+ * default. A command asks the approver in any role, and its files are captured around it.
143
+ * - The executor is told to finish only once the tests and checks pass, and the reviewer that
144
+ * they passed, when the executor has a command; without one, both are told the tests were
145
+ * not run, and the reviewer judges from the code whether they would pass.
146
+ *
147
+ * One run of the orchestrator is one run of the workspace's history, every round and retry
148
+ * included, and its `AgentResponse` names it. Every role follows what the user undoes.
149
+ * Asynchronous because opening the workspace is.
150
+ *
151
+ * @throws {InvalidAgentConfigError} for an unknown provider, or a workspace with no root.
47
152
  * @throws {UnrecoverableError} when a role cannot be set up, for example because
48
- * `OPENAI_API_KEY` or the Codex CLI binary is missing, or when no program can read the
49
- * repository.
153
+ * `OPENAI_API_KEY` is missing, or when no program can read the repository.
50
154
  */
51
- export declare function createLLMOrchestrator({ provider, autoApprove, systemPrompts, logger, }?: CreateOrchestratorOptions): Promise<Agent>;
155
+ export declare function createLLMOrchestrator({ provider, autoApprove, systemPrompts, workspace, maxSteps, commands, summarizeUndone, logger, }?: CreateLLMOrchestratorOptions): Promise<Agent>;
156
+ export {};
@@ -1,98 +1,325 @@
1
- import { InvalidAgentConfigError } from '#src/shared/domain/errors';
1
+ import { resolve } from 'node:path';
2
2
  import { LLMAgent } from '#src/engines/domain/model/llmAgent';
3
- import { ClaudeLLMClient } from '#src/llm/infrastructure/claudeLLMClient';
4
- import { OpenAILLMClient } from '#src/llm/infrastructure/openAILLMClient';
5
- import { InstructedAgent } from '#src/orchestration/domain/model/instructedAgent';
6
- import { executorInstructions, OrchestratorAgent, plannerInstructions, reviewerInstructions, } from '#src/orchestration/domain/model/orchestratorAgent';
3
+ import { ClaudeLLMClient, claudeLLMModels, claudeLLMReasoningEfforts, } from '#src/llm/infrastructure/claudeLLMClient';
4
+ import { OpenAILLMClient, openAIModels, openAIReasoningEfforts, } from '#src/llm/infrastructure/openAILLMClient';
5
+ import { executorInstructions, executorInstructionsWithoutCommands, OrchestratorAgent, plannerInstructions, reviewerInstructions, reviewerInstructionsWithoutCommands, } from '#src/orchestration/domain/model/orchestratorAgent';
7
6
  import { ReviewerDecisionValidator } from '#src/orchestration/infrastructure/model/reviewerDecisionValidator';
8
7
  import { RetryingAgent } from '#src/retry/domain/model/retryingAgent';
8
+ import { InvalidAgentConfigError } from '#src/shared/domain/errors';
9
+ import { isOneOf } from '#src/shared/domain/isOneOf';
9
10
  import { Logger } from '#src/shared/infrastructure/logger';
10
- import { createWorkspace } from '#src/tools/infrastructure/createWorkspace';
11
+ import { commandsFor, validateCommands, } from '#src/tools/domain/commands';
12
+ import { commandToolFor, fileToolsFor, openStore, openWorkspace, stateDirectoryOf, } from '#src/tools/infrastructure/fileTools';
13
+ import { createShowChangesTool } from '#src/tools/infrastructure/showChangesTool';
14
+ import { createTrackedReadFile } from '#src/tools/infrastructure/trackedReadFile';
11
15
  import { createWorkspaceTools } from '#src/tools/infrastructure/workspaceTools';
12
- import { createAgent, orchestratorRolesFor } from "./agentFactory.js";
16
+ import { isAgentProvider, orchestratorRolesFor } from "./agentFactory.js";
13
17
  import { agentProviders, } from "./types.js";
14
- const defaultLLMAgentModels = {
15
- anthropic: 'sonnet',
16
- openai: 'gpt-5.6-luna',
18
+ /** The models each provider's model API accepts. */
19
+ export const llmModels = {
20
+ anthropic: claudeLLMModels,
21
+ openai: openAIModels,
22
+ };
23
+ /** The reasoning efforts each provider's model API accepts. */
24
+ export const llmReasoningEfforts = {
25
+ anthropic: claudeLLMReasoningEfforts,
26
+ openai: openAIReasoningEfforts,
27
+ };
28
+ /** Narrows a model to one accepted by the selected provider's model API. */
29
+ export function isLLMModelOf(provider, value) {
30
+ return isOneOf(llmModels[provider], value);
31
+ }
32
+ /** Narrows a reasoning effort to one accepted by the selected provider's model API. */
33
+ export function isLLMReasoningEffortOf(provider, value) {
34
+ return isOneOf(llmReasoningEfforts[provider], value);
35
+ }
36
+ // The executor reads, edits and checks, so it needs more calls than the roles that only read.
37
+ const defaultRoleSteps = {
38
+ planner: 65,
39
+ executor: 80,
40
+ reviewer: 65,
17
41
  };
18
42
  /**
19
- * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
20
- * the harness's retry policy. It needs the provider's API key and bills per token, where
21
- * `createAgent` uses the Agent SDK's login. Each tool judges the risk of each call; a
22
- * `destructive` one runs only when the run's `approve` allows it, or with `autoApprove`.
43
+ * Each role's step limit, from one number for all of them or a number per role.
23
44
  *
24
- * @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its
25
- * client does not support, or two tools that share a name.
26
- * @throws {UnrecoverableError} when the client cannot be set up, for example because
27
- * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
28
- * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
45
+ * @throws {InvalidAgentConfigError} for a value that is neither, and for a role that does not
46
+ * exist, which is a typo that would otherwise leave that role on its default without a word.
29
47
  */
30
- export function createLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, autoApprove, logger = new Logger(), }) {
31
- let llmClient;
48
+ function stepsPerRole(maxSteps) {
49
+ if (maxSteps === undefined)
50
+ return defaultRoleSteps;
51
+ if (typeof maxSteps === 'number') {
52
+ return { planner: maxSteps, executor: maxSteps, reviewer: maxSteps };
53
+ }
54
+ // Untyped input, such as the CLI's configuration file, can hold `null` or a list here.
55
+ if (typeof maxSteps !== 'object' || maxSteps === null || Array.isArray(maxSteps)) {
56
+ throw new InvalidAgentConfigError('maxSteps must be a number or an object of steps per role, such as { "executor": 120 }');
57
+ }
58
+ const unknown = Object.keys(maxSteps).filter(role => !(role in defaultRoleSteps));
59
+ if (unknown.length > 0) {
60
+ throw new InvalidAgentConfigError(`maxSteps names roles that do not exist: ${unknown.join(', ')}; expected planner, executor or reviewer`);
61
+ }
62
+ // A role given as `undefined`, which untyped input can do, keeps its default.
63
+ const given = Object.entries(maxSteps).filter(([, steps]) => steps !== undefined);
64
+ return { ...defaultRoleSteps, ...Object.fromEntries(given) };
65
+ }
66
+ const defaultLLMAgentModels = {
67
+ anthropic: 'sonnet',
68
+ openai: 'gpt-5.6-luna',
69
+ };
70
+ function unknownProvider(provider) {
71
+ return new InvalidAgentConfigError(`"${String(provider)}" is not an agent provider; expected one of: ${agentProviders.join(', ')}`);
72
+ }
73
+ function llmClientFor(provider, { model, reasoningEffort, systemPrompt, logger, }) {
32
74
  switch (provider) {
33
75
  case 'anthropic':
34
- llmClient = new ClaudeLLMClient({
76
+ return new ClaudeLLMClient({
35
77
  model: model ?? defaultLLMAgentModels.anthropic,
36
78
  reasoningEffort,
37
79
  systemPrompt,
38
80
  logger,
39
81
  });
40
- break;
41
82
  case 'openai':
42
- llmClient = new OpenAILLMClient({
83
+ return new OpenAILLMClient({
43
84
  model: model ?? defaultLLMAgentModels.openai,
44
85
  reasoningEffort,
45
86
  systemPrompt,
46
87
  logger,
47
88
  });
48
- break;
49
- default: {
89
+ default:
50
90
  // Reachable from untyped input.
51
- const unknownProvider = provider;
52
- throw new InvalidAgentConfigError(`"${String(unknownProvider)}" is not an agent provider; expected one of: ${agentProviders.join(', ')}`);
53
- }
91
+ throw unknownProvider(provider);
92
+ }
93
+ }
94
+ /** The provider's cheap model, for the summary of what was undone. */
95
+ function summarizerFor(provider, logger) {
96
+ return provider === 'anthropic'
97
+ ? new ClaudeLLMClient({ model: 'haiku', systemPrompt: '', logger })
98
+ : new OpenAILLMClient({
99
+ model: 'gpt-5.6-luna',
100
+ reasoningEffort: 'high',
101
+ systemPrompt: '',
102
+ logger,
103
+ });
104
+ }
105
+ /**
106
+ * The workspace's history, opened on first use, so a factory that builds an agent stays
107
+ * synchronous. Every agent and every file tool of one workspace reads the same files on disk.
108
+ */
109
+ function historyOf(workspace) {
110
+ // Checked now: an agent with no root to follow is a setup error, not a first-run one.
111
+ if (workspace.roots.length === 0) {
112
+ throw new InvalidAgentConfigError('A workspace needs at least one root');
54
113
  }
114
+ let store;
115
+ const open = () => {
116
+ store ??= stateDirectoryOf(workspace)
117
+ .then(directory => openStore(workspace, directory))
118
+ .catch((error) => {
119
+ // Not kept: the next run tries again, as one whose disk was full for a moment.
120
+ store = undefined;
121
+ throw error;
122
+ });
123
+ return store;
124
+ };
125
+ return {
126
+ listRuns: async () => (await open()).listRuns(),
127
+ listRevisions: async () => (await open()).listRevisions(),
128
+ readRun: async (runId) => (await open()).readRun(runId),
129
+ };
130
+ }
131
+ /**
132
+ * What the model is told about the workspace after its instructions: the roots, which it may
133
+ * change, and how to name a file. A host path in an error sends the model looking for it, so
134
+ * the real root is given here once.
135
+ */
136
+ function workspaceGuidance(roots) {
137
+ const [first, ...others] = roots.map(root => ({ ...root, path: resolve(root.path) }));
138
+ if (!first)
139
+ return '';
140
+ const described = [first, ...others].map(({ path, access }) => `- ${path}${access === 'write' ? '' : ' (read only)'}`);
141
+ return [
142
+ 'The workspace:',
143
+ ...described,
144
+ `Name a file by its path relative to ${first.path}, such as src/index.ts, and a file in another folder of the workspace by its full path. Read a file before you change it.`,
145
+ ].join('\n');
146
+ }
147
+ function withGuidance(systemPrompt, guidance) {
148
+ return guidance === '' ? systemPrompt : `${systemPrompt}\n\n${guidance}`;
149
+ }
150
+ /** An `LLMAgent` on `provider`, wrapped in the harness's retry policy. */
151
+ function buildLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, history, summarizer, maxSteps, autoApprove, logger, }) {
152
+ const llmClient = llmClientFor(provider, { model, reasoningEffort, systemPrompt, logger });
55
153
  // LLMAgent records nothing from a failed call, so the original prompt is the whole story.
56
154
  return new RetryingAgent({
57
- inner: new LLMAgent({ llmClient, tools, autoApprove }),
155
+ inner: new LLMAgent({
156
+ llmClient,
157
+ tools,
158
+ autoApprove,
159
+ logger,
160
+ ...(history ? { history } : {}),
161
+ ...(summarizer ? { summarizer } : {}),
162
+ ...(maxSteps === undefined ? {} : { maxSteps }),
163
+ }),
58
164
  logger,
59
165
  noteFailures: false,
60
166
  });
61
167
  }
62
- // Sent on every call after the role's instructions, so it stays short: it only says how to find
63
- // one's way around the repository without reading all of it.
64
- const repositoryGuidance = 'Paths are relative to the repository root. Start with AGENTS.md and the architecture document it links to learn where things live, then read only the files your task needs.';
65
168
  /**
66
- * Builds the planner → executor → reviewer workflow with the planner and reviewer on the model
67
- * APIs, from `createLLMAgent`, each holding its role's instructions, or `systemPrompts`, as its
68
- * system prompt, with the read-only repository tools for the current working directory. The
69
- * executor stays on its Agent SDK, from `createAgent`, until an agent of ours can change files;
70
- * `autoApprove` reaches only it. Asynchronous because finding the program that reads the
71
- * repository is.
169
+ * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
170
+ * the harness's retry policy. It needs the provider's API key and bills per token, where
171
+ * `createAgent` uses the Agent SDK's login. Each tool judges the risk of each call; a
172
+ * `destructive` one runs only when the run's `approve` allows it, or with `autoApprove`.
173
+ *
174
+ * @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its
175
+ * client does not support, or two tools that share a name.
176
+ * @throws {UnrecoverableError} when the client cannot be set up, for example because
177
+ * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
178
+ * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
179
+ */
180
+ export function createLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools = [], workspace, summarizeUndone = true, maxSteps, autoApprove, logger = new Logger(), }) {
181
+ return buildLLMAgent(provider, {
182
+ model,
183
+ reasoningEffort,
184
+ systemPrompt: workspace
185
+ ? withGuidance(systemPrompt, workspaceGuidance(workspace.roots))
186
+ : systemPrompt,
187
+ // A file tool is a harness tool under its public name.
188
+ tools: tools,
189
+ ...(workspace ? { history: historyOf(workspace) } : {}),
190
+ ...(workspace && summarizeUndone ? { summarizer: summarizerFor(provider, logger) } : {}),
191
+ maxSteps,
192
+ autoApprove,
193
+ logger,
194
+ });
195
+ }
196
+ /**
197
+ * The file tools for an agent from `createLLMAgent` on `provider`, over `workspace`, in the
198
+ * provider's own format:
199
+ *
200
+ * - OpenAI: `listFiles`, `searchText`, `readFile` and its native `apply_patch`;
201
+ * - Anthropic: `listFiles`, `searchText`, its native text editor, whose `view` reads, and
202
+ * `delete_file`.
203
+ *
204
+ * Every one goes through the workspace's access policy: a path outside the roots, a secret,
205
+ * a file `.gitignore` excludes or the harness protects is refused. A change is made only in a
206
+ * `write` root, only from a version the agent read, and is recorded in the workspace's history,
207
+ * so it can be undone. Without a `write` root the tools only read. Give the agent the same
208
+ * `workspace`, so it follows what the history undoes.
209
+ *
210
+ * With `commands`, they include `runCommand`, which runs one of them by name, with only the
211
+ * parameters it declares and never through a shell. Each run asks the approver, showing the
212
+ * program exactly as it will run, and gets a small environment without the provider keys. The
213
+ * files of the `write` roots are captured before it and compared after, so what it changed is
214
+ * in the history too. It is trusted execution, not a sandbox: a command can reach whatever
215
+ * this user can. Not supported on Windows yet.
72
216
  *
73
- * @throws {InvalidAgentConfigError} for an unknown provider.
217
+ * @throws {InvalidAgentConfigError} for an unknown provider, a workspace with no root, or an
218
+ * invalid command, including one whose `cwd` is not in a `write` root, or any on Windows.
219
+ * @throws {UnrecoverableError} when no program can read the first root.
220
+ */
221
+ export async function createFileTools(provider, workspace, { logger = new Logger(), commands } = {}) {
222
+ // Reachable from untyped input.
223
+ if (!isAgentProvider(provider))
224
+ throw unknownProvider(provider);
225
+ if (commands !== undefined)
226
+ validateCommands(commands);
227
+ const opened = await openWorkspace(workspace, logger);
228
+ return (await executorTools(provider, opened, commands));
229
+ }
230
+ /** The executor's file tools, and `runCommand` when there are commands. */
231
+ async function executorTools(provider, workspace, commands) {
232
+ const tools = fileToolsFor(provider, workspace);
233
+ // Every command may belong to other roles, leaving none for this one.
234
+ if (commands === undefined || Object.keys(commands).length === 0)
235
+ return tools;
236
+ return [...tools, await commandToolFor(workspace, commands)];
237
+ }
238
+ /**
239
+ * What the planner and the reviewer read with: no edits, the current task's changes, and the
240
+ * commands given to their role.
241
+ */
242
+ async function readingTools(workspace, role, commands) {
243
+ const { read, policy, store } = workspace;
244
+ const finding = createWorkspaceTools(read).filter(tool => tool.name !== 'readFile');
245
+ const tools = [
246
+ ...finding,
247
+ createTrackedReadFile(policy),
248
+ createShowChangesTool({ store, policy }),
249
+ ];
250
+ const own = commands && commandsFor(commands, role);
251
+ if (!own || Object.keys(own).length === 0)
252
+ return tools;
253
+ return [...tools, await commandToolFor(workspace, own)];
254
+ }
255
+ // Sent after the workspace's roots, so it stays short: it only says how to find one's way
256
+ // around the repository without reading all of it.
257
+ const repositoryGuidance = 'Start with AGENTS.md and the architecture document it links to learn where things live, then read only the files your task needs. When you know you need several files, or several searches, ask for all of them in one answer rather than one at a time: each answer is a step, and steps are limited.';
258
+ /**
259
+ * Builds the planner → executor → reviewer workflow with every role on the model APIs, each
260
+ * holding its role's instructions, or `systemPrompts`, as its system prompt, followed by the
261
+ * workspace's roots.
262
+ *
263
+ * - The planner and the reviewer read: `listFiles`, `searchText`, `readFile`, and
264
+ * `showChanges`, the current task's changes as far as each may read them.
265
+ * - The executor gets its provider's file tools (see `createFileTools`), and changes files only
266
+ * in a `write` root; `autoApprove` reaches only it.
267
+ * - Each role has its own step limit: `maxSteps`, 80 for the executor and 65 for the others by
268
+ * default.
269
+ * - Each role gets `runCommand` with the `commands` whose `roles` name it, the executor by
270
+ * default. A command asks the approver in any role, and its files are captured around it.
271
+ * - The executor is told to finish only once the tests and checks pass, and the reviewer that
272
+ * they passed, when the executor has a command; without one, both are told the tests were
273
+ * not run, and the reviewer judges from the code whether they would pass.
274
+ *
275
+ * One run of the orchestrator is one run of the workspace's history, every round and retry
276
+ * included, and its `AgentResponse` names it. Every role follows what the user undoes.
277
+ * Asynchronous because opening the workspace is.
278
+ *
279
+ * @throws {InvalidAgentConfigError} for an unknown provider, or a workspace with no root.
74
280
  * @throws {UnrecoverableError} when a role cannot be set up, for example because
75
- * `OPENAI_API_KEY` or the Codex CLI binary is missing, or when no program can read the
76
- * repository.
281
+ * `OPENAI_API_KEY` is missing, or when no program can read the repository.
77
282
  */
78
- export async function createLLMOrchestrator({ provider, autoApprove, systemPrompts = {}, logger = new Logger(), } = {}) {
283
+ export async function createLLMOrchestrator({ provider, autoApprove, systemPrompts = {}, workspace = { roots: [{ path: process.cwd(), access: 'read' }] }, maxSteps, commands, summarizeUndone = true, logger = new Logger(), } = {}) {
79
284
  const roles = orchestratorRolesFor(provider);
80
- const tools = createWorkspaceTools(await createWorkspace({ root: process.cwd(), logger }));
81
- const llmAgentFor = ({ provider, model, reasoningEffort }, systemPrompt) => createLLMAgent(provider, { model, reasoningEffort, systemPrompt, tools, logger });
82
- // A caller's own prompt replaces the whole of ours, guidance included.
83
- const withGuidance = (instructions) => `${instructions}\n\n${repositoryGuidance}`;
285
+ const steps = stepsPerRole(maxSteps);
286
+ const opened = await openWorkspace(workspace, logger);
287
+ const guidance = workspaceGuidance(workspace.roots);
288
+ const summarizer = summarizeUndone ? summarizerFor(roles.planner.provider, logger) : undefined;
289
+ const shared = { history: opened.store, ...(summarizer ? { summarizer } : {}), logger };
290
+ // A caller's own prompt replaces our instructions and guidance, not the workspace: the model
291
+ // needs the roots to name a file, as `createLLMAgent` gives them.
292
+ const prompt = (own, instructions) => own === undefined
293
+ ? withGuidance(instructions, `${guidance}\n${repositoryGuidance}`)
294
+ : withGuidance(own, guidance);
295
+ // Every command, checked once, before any role takes its share.
296
+ if (commands !== undefined)
297
+ validateCommands(commands);
298
+ const reader = async ({ provider, model, reasoningEffort }, role, systemPrompt) => buildLLMAgent(provider, {
299
+ model,
300
+ reasoningEffort,
301
+ systemPrompt,
302
+ tools: await readingTools(opened, role, commands),
303
+ maxSteps: steps[role],
304
+ ...shared,
305
+ });
306
+ const { executor } = roles;
307
+ const executorToolset = await executorTools(executor.provider, opened, commands && commandsFor(commands, 'executor'));
308
+ // The executor and the reviewer are told whether the tests were run, which only an executor
309
+ // with a command can do.
310
+ const executorRuns = executorToolset.some(tool => tool.name === 'runCommand');
84
311
  return new OrchestratorAgent({
85
- plannerAgent: llmAgentFor(roles.planner, systemPrompts.planner ?? withGuidance(plannerInstructions)),
86
- executorAgent: new InstructedAgent({
87
- inner: createAgent(roles.executor.provider, {
88
- model: roles.executor.model,
89
- reasoningEffort: roles.executor.reasoningEffort,
90
- autoApprove,
91
- logger,
92
- }),
93
- instructions: systemPrompts.executor ?? executorInstructions,
312
+ plannerAgent: await reader(roles.planner, 'planner', prompt(systemPrompts.planner, plannerInstructions)),
313
+ executorAgent: buildLLMAgent(executor.provider, {
314
+ model: executor.model,
315
+ reasoningEffort: executor.reasoningEffort,
316
+ systemPrompt: prompt(systemPrompts.executor, executorRuns ? executorInstructions : executorInstructionsWithoutCommands),
317
+ tools: executorToolset,
318
+ maxSteps: steps.executor,
319
+ autoApprove,
320
+ ...shared,
94
321
  }),
95
- reviewerAgent: llmAgentFor(roles.reviewer, systemPrompts.reviewer ?? withGuidance(reviewerInstructions)),
322
+ reviewerAgent: await reader(roles.reviewer, 'reviewer', prompt(systemPrompts.reviewer, executorRuns ? reviewerInstructions : reviewerInstructionsWithoutCommands)),
96
323
  reviewerDecisionValidator: new ReviewerDecisionValidator(),
97
324
  logger,
98
325
  });