muonroi-cli 1.2.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.
- package/LICENSE +21 -0
- package/README.md +380 -0
- package/dist/__test-stubs__/ee-server.d.ts +27 -0
- package/dist/__test-stubs__/ee-server.js +138 -0
- package/dist/__test-stubs__/ee-server.js.map +1 -0
- package/dist/billing/index.d.ts +5 -0
- package/dist/billing/index.js +2 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/cloud/index.d.ts +5 -0
- package/dist/cloud/index.js +2 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/daemon/scheduler.d.ts +15 -0
- package/dist/daemon/scheduler.js +126 -0
- package/dist/daemon/scheduler.js.map +1 -0
- package/dist/daemon/scheduler.test.d.ts +1 -0
- package/dist/daemon/scheduler.test.js +103 -0
- package/dist/daemon/scheduler.test.js.map +1 -0
- package/dist/ee/__tests__/pipeline.integration.test.d.ts +14 -0
- package/dist/ee/__tests__/pipeline.integration.test.js +151 -0
- package/dist/ee/__tests__/pipeline.integration.test.js.map +1 -0
- package/dist/ee/auth.d.ts +19 -0
- package/dist/ee/auth.js +48 -0
- package/dist/ee/auth.js.map +1 -0
- package/dist/ee/auth.test.d.ts +1 -0
- package/dist/ee/auth.test.js +59 -0
- package/dist/ee/auth.test.js.map +1 -0
- package/dist/ee/bridge.d.ts +68 -0
- package/dist/ee/bridge.js +177 -0
- package/dist/ee/bridge.js.map +1 -0
- package/dist/ee/bridge.test.d.ts +1 -0
- package/dist/ee/bridge.test.js +231 -0
- package/dist/ee/bridge.test.js.map +1 -0
- package/dist/ee/client.d.ts +22 -0
- package/dist/ee/client.js +464 -0
- package/dist/ee/client.js.map +1 -0
- package/dist/ee/client.test.d.ts +1 -0
- package/dist/ee/client.test.js +151 -0
- package/dist/ee/client.test.js.map +1 -0
- package/dist/ee/embedding-cache.d.ts +7 -0
- package/dist/ee/embedding-cache.js +33 -0
- package/dist/ee/embedding-cache.js.map +1 -0
- package/dist/ee/extract-session.d.ts +17 -0
- package/dist/ee/extract-session.js +53 -0
- package/dist/ee/extract-session.js.map +1 -0
- package/dist/ee/extract-session.test.d.ts +1 -0
- package/dist/ee/extract-session.test.js +197 -0
- package/dist/ee/extract-session.test.js.map +1 -0
- package/dist/ee/health.d.ts +29 -0
- package/dist/ee/health.js +64 -0
- package/dist/ee/health.js.map +1 -0
- package/dist/ee/index.d.ts +10 -0
- package/dist/ee/index.js +9 -0
- package/dist/ee/index.js.map +1 -0
- package/dist/ee/intercept.d.ts +46 -0
- package/dist/ee/intercept.js +117 -0
- package/dist/ee/intercept.js.map +1 -0
- package/dist/ee/intercept.test.d.ts +1 -0
- package/dist/ee/intercept.test.js +170 -0
- package/dist/ee/intercept.test.js.map +1 -0
- package/dist/ee/judge.d.ts +36 -0
- package/dist/ee/judge.js +56 -0
- package/dist/ee/judge.js.map +1 -0
- package/dist/ee/judge.test.d.ts +1 -0
- package/dist/ee/judge.test.js +139 -0
- package/dist/ee/judge.test.js.map +1 -0
- package/dist/ee/offline-queue.d.ts +37 -0
- package/dist/ee/offline-queue.js +163 -0
- package/dist/ee/offline-queue.js.map +1 -0
- package/dist/ee/offline-queue.test.d.ts +1 -0
- package/dist/ee/offline-queue.test.js +246 -0
- package/dist/ee/offline-queue.test.js.map +1 -0
- package/dist/ee/posttool.d.ts +11 -0
- package/dist/ee/posttool.js +16 -0
- package/dist/ee/posttool.js.map +1 -0
- package/dist/ee/posttool.test.d.ts +1 -0
- package/dist/ee/posttool.test.js +70 -0
- package/dist/ee/posttool.test.js.map +1 -0
- package/dist/ee/prompt-stale.d.ts +20 -0
- package/dist/ee/prompt-stale.js +37 -0
- package/dist/ee/prompt-stale.js.map +1 -0
- package/dist/ee/prompt-stale.test.d.ts +8 -0
- package/dist/ee/prompt-stale.test.js +76 -0
- package/dist/ee/prompt-stale.test.js.map +1 -0
- package/dist/ee/render.d.ts +20 -0
- package/dist/ee/render.js +30 -0
- package/dist/ee/render.js.map +1 -0
- package/dist/ee/render.test.d.ts +1 -0
- package/dist/ee/render.test.js +61 -0
- package/dist/ee/render.test.js.map +1 -0
- package/dist/ee/scope.d.ts +6 -0
- package/dist/ee/scope.js +92 -0
- package/dist/ee/scope.js.map +1 -0
- package/dist/ee/scope.test.d.ts +1 -0
- package/dist/ee/scope.test.js +97 -0
- package/dist/ee/scope.test.js.map +1 -0
- package/dist/ee/tenant.d.ts +2 -0
- package/dist/ee/tenant.js +13 -0
- package/dist/ee/tenant.js.map +1 -0
- package/dist/ee/touch.test.d.ts +1 -0
- package/dist/ee/touch.test.js +60 -0
- package/dist/ee/touch.test.js.map +1 -0
- package/dist/ee/types.d.ts +300 -0
- package/dist/ee/types.js +14 -0
- package/dist/ee/types.js.map +1 -0
- package/dist/flow/__tests__/migration.test.d.ts +1 -0
- package/dist/flow/__tests__/migration.test.js +105 -0
- package/dist/flow/__tests__/migration.test.js.map +1 -0
- package/dist/flow/__tests__/parser.test.d.ts +1 -0
- package/dist/flow/__tests__/parser.test.js +68 -0
- package/dist/flow/__tests__/parser.test.js.map +1 -0
- package/dist/flow/__tests__/run-manager.test.d.ts +1 -0
- package/dist/flow/__tests__/run-manager.test.js +83 -0
- package/dist/flow/__tests__/run-manager.test.js.map +1 -0
- package/dist/flow/__tests__/scaffold.test.d.ts +1 -0
- package/dist/flow/__tests__/scaffold.test.js +47 -0
- package/dist/flow/__tests__/scaffold.test.js.map +1 -0
- package/dist/flow/__tests__/warning-persist.test.d.ts +5 -0
- package/dist/flow/__tests__/warning-persist.test.js +96 -0
- package/dist/flow/__tests__/warning-persist.test.js.map +1 -0
- package/dist/flow/artifact-io.d.ts +16 -0
- package/dist/flow/artifact-io.js +35 -0
- package/dist/flow/artifact-io.js.map +1 -0
- package/dist/flow/compaction/__tests__/compress.test.d.ts +1 -0
- package/dist/flow/compaction/__tests__/compress.test.js +63 -0
- package/dist/flow/compaction/__tests__/compress.test.js.map +1 -0
- package/dist/flow/compaction/__tests__/extract.test.d.ts +1 -0
- package/dist/flow/compaction/__tests__/extract.test.js +66 -0
- package/dist/flow/compaction/__tests__/extract.test.js.map +1 -0
- package/dist/flow/compaction/__tests__/preserve.test.d.ts +1 -0
- package/dist/flow/compaction/__tests__/preserve.test.js +62 -0
- package/dist/flow/compaction/__tests__/preserve.test.js.map +1 -0
- package/dist/flow/compaction/compress.d.ts +22 -0
- package/dist/flow/compaction/compress.js +47 -0
- package/dist/flow/compaction/compress.js.map +1 -0
- package/dist/flow/compaction/extract.d.ts +23 -0
- package/dist/flow/compaction/extract.js +46 -0
- package/dist/flow/compaction/extract.js.map +1 -0
- package/dist/flow/compaction/index.d.ts +23 -0
- package/dist/flow/compaction/index.js +63 -0
- package/dist/flow/compaction/index.js.map +1 -0
- package/dist/flow/compaction/preserve.d.ts +24 -0
- package/dist/flow/compaction/preserve.js +35 -0
- package/dist/flow/compaction/preserve.js.map +1 -0
- package/dist/flow/index.d.ts +12 -0
- package/dist/flow/index.js +16 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/migration.d.ts +23 -0
- package/dist/flow/migration.js +126 -0
- package/dist/flow/migration.js.map +1 -0
- package/dist/flow/parser.d.ts +27 -0
- package/dist/flow/parser.js +68 -0
- package/dist/flow/parser.js.map +1 -0
- package/dist/flow/run-manager.d.ts +36 -0
- package/dist/flow/run-manager.js +110 -0
- package/dist/flow/run-manager.js.map +1 -0
- package/dist/flow/scaffold.d.ts +22 -0
- package/dist/flow/scaffold.js +47 -0
- package/dist/flow/scaffold.js.map +1 -0
- package/dist/flow/warning-persist.d.ts +9 -0
- package/dist/flow/warning-persist.js +75 -0
- package/dist/flow/warning-persist.js.map +1 -0
- package/dist/gsd/__tests__/types.test.d.ts +1 -0
- package/dist/gsd/__tests__/types.test.js +65 -0
- package/dist/gsd/__tests__/types.test.js.map +1 -0
- package/dist/gsd/index.d.ts +1 -0
- package/dist/gsd/index.js +2 -0
- package/dist/gsd/index.js.map +1 -0
- package/dist/gsd/types.d.ts +4 -0
- package/dist/gsd/types.js +39 -0
- package/dist/gsd/types.js.map +1 -0
- package/dist/headless/output.d.ts +68 -0
- package/dist/headless/output.js +206 -0
- package/dist/headless/output.js.map +1 -0
- package/dist/headless/output.test.d.ts +1 -0
- package/dist/headless/output.test.js +178 -0
- package/dist/headless/output.test.js.map +1 -0
- package/dist/hooks/config.d.ts +18 -0
- package/dist/hooks/config.js +39 -0
- package/dist/hooks/config.js.map +1 -0
- package/dist/hooks/index.d.ts +44 -0
- package/dist/hooks/index.js +187 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/types.d.ts +130 -0
- package/dist/hooks/types.js +47 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +512 -0
- package/dist/index.js.map +1 -0
- package/dist/lsp/builtins.d.ts +9 -0
- package/dist/lsp/builtins.js +351 -0
- package/dist/lsp/builtins.js.map +1 -0
- package/dist/lsp/builtins.test.d.ts +1 -0
- package/dist/lsp/builtins.test.js +91 -0
- package/dist/lsp/builtins.test.js.map +1 -0
- package/dist/lsp/client.d.ts +20 -0
- package/dist/lsp/client.js +290 -0
- package/dist/lsp/client.js.map +1 -0
- package/dist/lsp/manager.d.ts +20 -0
- package/dist/lsp/manager.js +235 -0
- package/dist/lsp/manager.js.map +1 -0
- package/dist/lsp/manager.test.d.ts +1 -0
- package/dist/lsp/manager.test.js +133 -0
- package/dist/lsp/manager.test.js.map +1 -0
- package/dist/lsp/npm-cache.d.ts +2 -0
- package/dist/lsp/npm-cache.js +105 -0
- package/dist/lsp/npm-cache.js.map +1 -0
- package/dist/lsp/npm-cache.test.d.ts +1 -0
- package/dist/lsp/npm-cache.test.js +53 -0
- package/dist/lsp/npm-cache.test.js.map +1 -0
- package/dist/lsp/runtime.d.ts +6 -0
- package/dist/lsp/runtime.js +53 -0
- package/dist/lsp/runtime.js.map +1 -0
- package/dist/lsp/smoke.test.d.ts +1 -0
- package/dist/lsp/smoke.test.js +56 -0
- package/dist/lsp/smoke.test.js.map +1 -0
- package/dist/lsp/types.d.ts +83 -0
- package/dist/lsp/types.js +12 -0
- package/dist/lsp/types.js.map +1 -0
- package/dist/mcp/auto-setup.d.ts +2 -0
- package/dist/mcp/auto-setup.js +76 -0
- package/dist/mcp/auto-setup.js.map +1 -0
- package/dist/mcp/catalog.d.ts +10 -0
- package/dist/mcp/catalog.js +121 -0
- package/dist/mcp/catalog.js.map +1 -0
- package/dist/mcp/oauth-callback.d.ts +9 -0
- package/dist/mcp/oauth-callback.js +49 -0
- package/dist/mcp/oauth-callback.js.map +1 -0
- package/dist/mcp/oauth-provider.d.ts +33 -0
- package/dist/mcp/oauth-provider.js +94 -0
- package/dist/mcp/oauth-provider.js.map +1 -0
- package/dist/mcp/parse-headers.d.ts +2 -0
- package/dist/mcp/parse-headers.js +33 -0
- package/dist/mcp/parse-headers.js.map +1 -0
- package/dist/mcp/parse-headers.test.d.ts +1 -0
- package/dist/mcp/parse-headers.test.js +43 -0
- package/dist/mcp/parse-headers.test.js.map +1 -0
- package/dist/mcp/runtime.d.ts +11 -0
- package/dist/mcp/runtime.js +81 -0
- package/dist/mcp/runtime.js.map +1 -0
- package/dist/mcp/smoke.test.d.ts +1 -0
- package/dist/mcp/smoke.test.js +159 -0
- package/dist/mcp/smoke.test.js.map +1 -0
- package/dist/mcp/validate.d.ts +9 -0
- package/dist/mcp/validate.js +39 -0
- package/dist/mcp/validate.js.map +1 -0
- package/dist/models/__tests__/registry.test.d.ts +1 -0
- package/dist/models/__tests__/registry.test.js +74 -0
- package/dist/models/__tests__/registry.test.js.map +1 -0
- package/dist/models/catalog-client.d.ts +21 -0
- package/dist/models/catalog-client.js +53 -0
- package/dist/models/catalog-client.js.map +1 -0
- package/dist/models/classify-tier.d.ts +2 -0
- package/dist/models/classify-tier.js +34 -0
- package/dist/models/classify-tier.js.map +1 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +2 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/registry.d.ts +19 -0
- package/dist/models/registry.js +65 -0
- package/dist/models/registry.js.map +1 -0
- package/dist/ops/bug-report.d.ts +28 -0
- package/dist/ops/bug-report.js +67 -0
- package/dist/ops/bug-report.js.map +1 -0
- package/dist/ops/bug-report.test.d.ts +1 -0
- package/dist/ops/bug-report.test.js +148 -0
- package/dist/ops/bug-report.test.js.map +1 -0
- package/dist/ops/doctor.d.ts +16 -0
- package/dist/ops/doctor.js +162 -0
- package/dist/ops/doctor.js.map +1 -0
- package/dist/ops/doctor.test.d.ts +1 -0
- package/dist/ops/doctor.test.js +95 -0
- package/dist/ops/doctor.test.js.map +1 -0
- package/dist/orchestrator/__tests__/flow-resume.test.d.ts +5 -0
- package/dist/orchestrator/__tests__/flow-resume.test.js +61 -0
- package/dist/orchestrator/__tests__/flow-resume.test.js.map +1 -0
- package/dist/orchestrator/__tests__/route-feedback.test.d.ts +1 -0
- package/dist/orchestrator/__tests__/route-feedback.test.js +47 -0
- package/dist/orchestrator/__tests__/route-feedback.test.js.map +1 -0
- package/dist/orchestrator/abort.d.ts +29 -0
- package/dist/orchestrator/abort.js +31 -0
- package/dist/orchestrator/abort.js.map +1 -0
- package/dist/orchestrator/abort.test.d.ts +1 -0
- package/dist/orchestrator/abort.test.js +34 -0
- package/dist/orchestrator/abort.test.js.map +1 -0
- package/dist/orchestrator/agent.test.d.ts +1 -0
- package/dist/orchestrator/agent.test.js +126 -0
- package/dist/orchestrator/agent.test.js.map +1 -0
- package/dist/orchestrator/cleanup.test.d.ts +1 -0
- package/dist/orchestrator/cleanup.test.js +67 -0
- package/dist/orchestrator/cleanup.test.js.map +1 -0
- package/dist/orchestrator/compaction.d.ts +36 -0
- package/dist/orchestrator/compaction.js +375 -0
- package/dist/orchestrator/compaction.js.map +1 -0
- package/dist/orchestrator/compaction.test.d.ts +1 -0
- package/dist/orchestrator/compaction.test.js +105 -0
- package/dist/orchestrator/compaction.test.js.map +1 -0
- package/dist/orchestrator/delegations.d.ts +49 -0
- package/dist/orchestrator/delegations.js +283 -0
- package/dist/orchestrator/delegations.js.map +1 -0
- package/dist/orchestrator/delegations.test.d.ts +1 -0
- package/dist/orchestrator/delegations.test.js +107 -0
- package/dist/orchestrator/delegations.test.js.map +1 -0
- package/dist/orchestrator/flow-resume.d.ts +25 -0
- package/dist/orchestrator/flow-resume.js +53 -0
- package/dist/orchestrator/flow-resume.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts +239 -0
- package/dist/orchestrator/orchestrator.js +3209 -0
- package/dist/orchestrator/orchestrator.js.map +1 -0
- package/dist/orchestrator/pending-calls.d.ts +75 -0
- package/dist/orchestrator/pending-calls.js +178 -0
- package/dist/orchestrator/pending-calls.js.map +1 -0
- package/dist/orchestrator/pending-calls.test.d.ts +1 -0
- package/dist/orchestrator/pending-calls.test.js +188 -0
- package/dist/orchestrator/pending-calls.test.js.map +1 -0
- package/dist/orchestrator/reasoning.d.ts +3 -0
- package/dist/orchestrator/reasoning.js +53 -0
- package/dist/orchestrator/reasoning.js.map +1 -0
- package/dist/orchestrator/reasoning.test.d.ts +1 -0
- package/dist/orchestrator/reasoning.test.js +26 -0
- package/dist/orchestrator/reasoning.test.js.map +1 -0
- package/dist/orchestrator/sandbox.test.d.ts +1 -0
- package/dist/orchestrator/sandbox.test.js +94 -0
- package/dist/orchestrator/sandbox.test.js.map +1 -0
- package/dist/pil/__tests__/budget.test.d.ts +1 -0
- package/dist/pil/__tests__/budget.test.js +33 -0
- package/dist/pil/__tests__/budget.test.js.map +1 -0
- package/dist/pil/__tests__/layer1-intent.test.d.ts +1 -0
- package/dist/pil/__tests__/layer1-intent.test.js +199 -0
- package/dist/pil/__tests__/layer1-intent.test.js.map +1 -0
- package/dist/pil/__tests__/layer2-personality.test.d.ts +1 -0
- package/dist/pil/__tests__/layer2-personality.test.js +57 -0
- package/dist/pil/__tests__/layer2-personality.test.js.map +1 -0
- package/dist/pil/__tests__/layer3-ee-injection.test.d.ts +1 -0
- package/dist/pil/__tests__/layer3-ee-injection.test.js +96 -0
- package/dist/pil/__tests__/layer3-ee-injection.test.js.map +1 -0
- package/dist/pil/__tests__/layer4-gsd.test.d.ts +1 -0
- package/dist/pil/__tests__/layer4-gsd.test.js +68 -0
- package/dist/pil/__tests__/layer4-gsd.test.js.map +1 -0
- package/dist/pil/__tests__/layer5-context.test.d.ts +1 -0
- package/dist/pil/__tests__/layer5-context.test.js +100 -0
- package/dist/pil/__tests__/layer5-context.test.js.map +1 -0
- package/dist/pil/__tests__/layer6-output.test.d.ts +1 -0
- package/dist/pil/__tests__/layer6-output.test.js +115 -0
- package/dist/pil/__tests__/layer6-output.test.js.map +1 -0
- package/dist/pil/__tests__/ollama-classify.test.d.ts +1 -0
- package/dist/pil/__tests__/ollama-classify.test.js +68 -0
- package/dist/pil/__tests__/ollama-classify.test.js.map +1 -0
- package/dist/pil/__tests__/orchestrator-integration.test.d.ts +10 -0
- package/dist/pil/__tests__/orchestrator-integration.test.js +98 -0
- package/dist/pil/__tests__/orchestrator-integration.test.js.map +1 -0
- package/dist/pil/__tests__/pipeline.test.d.ts +1 -0
- package/dist/pil/__tests__/pipeline.test.js +150 -0
- package/dist/pil/__tests__/pipeline.test.js.map +1 -0
- package/dist/pil/__tests__/response-tools.test.d.ts +1 -0
- package/dist/pil/__tests__/response-tools.test.js +133 -0
- package/dist/pil/__tests__/response-tools.test.js.map +1 -0
- package/dist/pil/__tests__/schema.test.d.ts +1 -0
- package/dist/pil/__tests__/schema.test.js +112 -0
- package/dist/pil/__tests__/schema.test.js.map +1 -0
- package/dist/pil/__tests__/store.test.d.ts +1 -0
- package/dist/pil/__tests__/store.test.js +44 -0
- package/dist/pil/__tests__/store.test.js.map +1 -0
- package/dist/pil/__tests__/task-tier-map.test.d.ts +1 -0
- package/dist/pil/__tests__/task-tier-map.test.js +33 -0
- package/dist/pil/__tests__/task-tier-map.test.js.map +1 -0
- package/dist/pil/budget.d.ts +8 -0
- package/dist/pil/budget.js +17 -0
- package/dist/pil/budget.js.map +1 -0
- package/dist/pil/index.d.ts +11 -0
- package/dist/pil/index.js +11 -0
- package/dist/pil/index.js.map +1 -0
- package/dist/pil/layer1-intent.d.ts +13 -0
- package/dist/pil/layer1-intent.js +142 -0
- package/dist/pil/layer1-intent.js.map +1 -0
- package/dist/pil/layer2-personality.d.ts +2 -0
- package/dist/pil/layer2-personality.js +29 -0
- package/dist/pil/layer2-personality.js.map +1 -0
- package/dist/pil/layer3-ee-injection.d.ts +10 -0
- package/dist/pil/layer3-ee-injection.js +72 -0
- package/dist/pil/layer3-ee-injection.js.map +1 -0
- package/dist/pil/layer4-gsd.d.ts +2 -0
- package/dist/pil/layer4-gsd.js +64 -0
- package/dist/pil/layer4-gsd.js.map +1 -0
- package/dist/pil/layer5-context.d.ts +2 -0
- package/dist/pil/layer5-context.js +124 -0
- package/dist/pil/layer5-context.js.map +1 -0
- package/dist/pil/layer6-output.d.ts +18 -0
- package/dist/pil/layer6-output.js +105 -0
- package/dist/pil/layer6-output.js.map +1 -0
- package/dist/pil/ollama-classify.d.ts +13 -0
- package/dist/pil/ollama-classify.js +38 -0
- package/dist/pil/ollama-classify.js.map +1 -0
- package/dist/pil/pipeline.d.ts +16 -0
- package/dist/pil/pipeline.js +96 -0
- package/dist/pil/pipeline.js.map +1 -0
- package/dist/pil/response-tools.d.ts +63 -0
- package/dist/pil/response-tools.js +87 -0
- package/dist/pil/response-tools.js.map +1 -0
- package/dist/pil/schema.d.ts +80 -0
- package/dist/pil/schema.js +43 -0
- package/dist/pil/schema.js.map +1 -0
- package/dist/pil/store.d.ts +12 -0
- package/dist/pil/store.js +21 -0
- package/dist/pil/store.js.map +1 -0
- package/dist/pil/task-tier-map.d.ts +30 -0
- package/dist/pil/task-tier-map.js +83 -0
- package/dist/pil/task-tier-map.js.map +1 -0
- package/dist/pil/timeout.d.ts +7 -0
- package/dist/pil/timeout.js +10 -0
- package/dist/pil/timeout.js.map +1 -0
- package/dist/pil/types.d.ts +39 -0
- package/dist/pil/types.js +7 -0
- package/dist/pil/types.js.map +1 -0
- package/dist/providers/__test-utils__/load-fixture.d.ts +9 -0
- package/dist/providers/__test-utils__/load-fixture.js +34 -0
- package/dist/providers/__test-utils__/load-fixture.js.map +1 -0
- package/dist/providers/__tests__/runtime-integration.test.d.ts +1 -0
- package/dist/providers/__tests__/runtime-integration.test.js +71 -0
- package/dist/providers/__tests__/runtime-integration.test.js.map +1 -0
- package/dist/providers/__tests__/runtime.test.d.ts +1 -0
- package/dist/providers/__tests__/runtime.test.js +76 -0
- package/dist/providers/__tests__/runtime.test.js.map +1 -0
- package/dist/providers/adapter.d.ts +15 -0
- package/dist/providers/adapter.js +45 -0
- package/dist/providers/adapter.js.map +1 -0
- package/dist/providers/adapter.test.d.ts +1 -0
- package/dist/providers/adapter.test.js +19 -0
- package/dist/providers/adapter.test.js.map +1 -0
- package/dist/providers/anthropic.d.ts +53 -0
- package/dist/providers/anthropic.js +141 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/errors.d.ts +18 -0
- package/dist/providers/errors.js +36 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/errors.test.d.ts +1 -0
- package/dist/providers/errors.test.js +66 -0
- package/dist/providers/errors.test.js.map +1 -0
- package/dist/providers/gemini.d.ts +11 -0
- package/dist/providers/gemini.js +33 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/gemini.test.d.ts +1 -0
- package/dist/providers/gemini.test.js +37 -0
- package/dist/providers/gemini.test.js.map +1 -0
- package/dist/providers/index.d.ts +11 -0
- package/dist/providers/index.js +15 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/keychain.d.ts +32 -0
- package/dist/providers/keychain.js +127 -0
- package/dist/providers/keychain.js.map +1 -0
- package/dist/providers/keychain.test.d.ts +1 -0
- package/dist/providers/keychain.test.js +80 -0
- package/dist/providers/keychain.test.js.map +1 -0
- package/dist/providers/ollama.d.ts +13 -0
- package/dist/providers/ollama.js +33 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/ollama.test.d.ts +1 -0
- package/dist/providers/ollama.test.js +37 -0
- package/dist/providers/ollama.test.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +15 -0
- package/dist/providers/openai-compatible.js +45 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/providers/openai-compatible.test.d.ts +1 -0
- package/dist/providers/openai-compatible.test.js +53 -0
- package/dist/providers/openai-compatible.test.js.map +1 -0
- package/dist/providers/openai.d.ts +11 -0
- package/dist/providers/openai.js +33 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openai.test.d.ts +1 -0
- package/dist/providers/openai.test.js +53 -0
- package/dist/providers/openai.test.js.map +1 -0
- package/dist/providers/patch-zod-schema.d.ts +24 -0
- package/dist/providers/patch-zod-schema.js +121 -0
- package/dist/providers/patch-zod-schema.js.map +1 -0
- package/dist/providers/pricing.d.ts +21 -0
- package/dist/providers/pricing.js +50 -0
- package/dist/providers/pricing.js.map +1 -0
- package/dist/providers/pricing.test.d.ts +1 -0
- package/dist/providers/pricing.test.js +47 -0
- package/dist/providers/pricing.test.js.map +1 -0
- package/dist/providers/runtime.d.ts +21 -0
- package/dist/providers/runtime.js +83 -0
- package/dist/providers/runtime.js.map +1 -0
- package/dist/providers/stream-loop.d.ts +16 -0
- package/dist/providers/stream-loop.js +63 -0
- package/dist/providers/stream-loop.js.map +1 -0
- package/dist/providers/types.d.ts +106 -0
- package/dist/providers/types.js +12 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/vision-proxy.d.ts +23 -0
- package/dist/providers/vision-proxy.js +152 -0
- package/dist/providers/vision-proxy.js.map +1 -0
- package/dist/providers/vision-proxy.test.d.ts +1 -0
- package/dist/providers/vision-proxy.test.js +136 -0
- package/dist/providers/vision-proxy.test.js.map +1 -0
- package/dist/router/classifier/grammars.d.ts +3 -0
- package/dist/router/classifier/grammars.js +14 -0
- package/dist/router/classifier/grammars.js.map +1 -0
- package/dist/router/classifier/index.d.ts +3 -0
- package/dist/router/classifier/index.js +19 -0
- package/dist/router/classifier/index.js.map +1 -0
- package/dist/router/classifier/index.test.d.ts +1 -0
- package/dist/router/classifier/index.test.js +29 -0
- package/dist/router/classifier/index.test.js.map +1 -0
- package/dist/router/classifier/regex.d.ts +2 -0
- package/dist/router/classifier/regex.js +45 -0
- package/dist/router/classifier/regex.js.map +1 -0
- package/dist/router/classifier/regex.test.d.ts +1 -0
- package/dist/router/classifier/regex.test.js +42 -0
- package/dist/router/classifier/regex.test.js.map +1 -0
- package/dist/router/classifier/tree-sitter.d.ts +5 -0
- package/dist/router/classifier/tree-sitter.js +84 -0
- package/dist/router/classifier/tree-sitter.js.map +1 -0
- package/dist/router/classifier/tree-sitter.test.d.ts +1 -0
- package/dist/router/classifier/tree-sitter.test.js +23 -0
- package/dist/router/classifier/tree-sitter.test.js.map +1 -0
- package/dist/router/cold.d.ts +7 -0
- package/dist/router/cold.js +21 -0
- package/dist/router/cold.js.map +1 -0
- package/dist/router/cold.test.d.ts +1 -0
- package/dist/router/cold.test.js +56 -0
- package/dist/router/cold.test.js.map +1 -0
- package/dist/router/decide.d.ts +42 -0
- package/dist/router/decide.js +300 -0
- package/dist/router/decide.js.map +1 -0
- package/dist/router/decide.test.d.ts +1 -0
- package/dist/router/decide.test.js +116 -0
- package/dist/router/decide.test.js.map +1 -0
- package/dist/router/health.d.ts +10 -0
- package/dist/router/health.js +44 -0
- package/dist/router/health.js.map +1 -0
- package/dist/router/health.test.d.ts +1 -0
- package/dist/router/health.test.js +52 -0
- package/dist/router/health.test.js.map +1 -0
- package/dist/router/store.d.ts +24 -0
- package/dist/router/store.js +27 -0
- package/dist/router/store.js.map +1 -0
- package/dist/router/types.d.ts +18 -0
- package/dist/router/types.js +2 -0
- package/dist/router/types.js.map +1 -0
- package/dist/router/warm.d.ts +7 -0
- package/dist/router/warm.js +40 -0
- package/dist/router/warm.js.map +1 -0
- package/dist/router/warm.test.d.ts +1 -0
- package/dist/router/warm.test.js +144 -0
- package/dist/router/warm.test.js.map +1 -0
- package/dist/storage/__tests__/migrations.test.d.ts +12 -0
- package/dist/storage/__tests__/migrations.test.js +357 -0
- package/dist/storage/__tests__/migrations.test.js.map +1 -0
- package/dist/storage/atomic-io.d.ts +18 -0
- package/dist/storage/atomic-io.js +65 -0
- package/dist/storage/atomic-io.js.map +1 -0
- package/dist/storage/atomic-io.test.d.ts +1 -0
- package/dist/storage/atomic-io.test.js +49 -0
- package/dist/storage/atomic-io.test.js.map +1 -0
- package/dist/storage/config.d.ts +22 -0
- package/dist/storage/config.js +35 -0
- package/dist/storage/config.js.map +1 -0
- package/dist/storage/config.test.d.ts +1 -0
- package/dist/storage/config.test.js +29 -0
- package/dist/storage/config.test.js.map +1 -0
- package/dist/storage/db.d.ts +18 -0
- package/dist/storage/db.js +71 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.js +10 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/migrations.d.ts +2 -0
- package/dist/storage/migrations.js +138 -0
- package/dist/storage/migrations.js.map +1 -0
- package/dist/storage/session-dir.d.ts +27 -0
- package/dist/storage/session-dir.js +36 -0
- package/dist/storage/session-dir.js.map +1 -0
- package/dist/storage/sessions.d.ts +16 -0
- package/dist/storage/sessions.js +142 -0
- package/dist/storage/sessions.js.map +1 -0
- package/dist/storage/tool-results.d.ts +4 -0
- package/dist/storage/tool-results.js +50 -0
- package/dist/storage/tool-results.js.map +1 -0
- package/dist/storage/transcript-view.d.ts +14 -0
- package/dist/storage/transcript-view.js +23 -0
- package/dist/storage/transcript-view.js.map +1 -0
- package/dist/storage/transcript.d.ts +12 -0
- package/dist/storage/transcript.js +269 -0
- package/dist/storage/transcript.js.map +1 -0
- package/dist/storage/transcript.test.d.ts +1 -0
- package/dist/storage/transcript.test.js +22 -0
- package/dist/storage/transcript.test.js.map +1 -0
- package/dist/storage/usage-cap.d.ts +34 -0
- package/dist/storage/usage-cap.js +54 -0
- package/dist/storage/usage-cap.js.map +1 -0
- package/dist/storage/usage-cap.test.d.ts +1 -0
- package/dist/storage/usage-cap.test.js +51 -0
- package/dist/storage/usage-cap.test.js.map +1 -0
- package/dist/storage/usage.d.ts +11 -0
- package/dist/storage/usage.js +65 -0
- package/dist/storage/usage.js.map +1 -0
- package/dist/storage/workspaces.d.ts +9 -0
- package/dist/storage/workspaces.js +60 -0
- package/dist/storage/workspaces.js.map +1 -0
- package/dist/tools/bash.d.ts +46 -0
- package/dist/tools/bash.js +541 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/bash.test.d.ts +1 -0
- package/dist/tools/bash.test.js +240 -0
- package/dist/tools/bash.test.js.map +1 -0
- package/dist/tools/computer.d.ts +88 -0
- package/dist/tools/computer.js +443 -0
- package/dist/tools/computer.js.map +1 -0
- package/dist/tools/computer.test.d.ts +1 -0
- package/dist/tools/computer.test.js +142 -0
- package/dist/tools/computer.test.js.map +1 -0
- package/dist/tools/file.d.ts +17 -0
- package/dist/tools/file.js +101 -0
- package/dist/tools/file.js.map +1 -0
- package/dist/tools/file.test.d.ts +1 -0
- package/dist/tools/file.test.js +58 -0
- package/dist/tools/file.test.js.map +1 -0
- package/dist/tools/grep.d.ts +8 -0
- package/dist/tools/grep.js +144 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.js +245 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/schedule.d.ts +85 -0
- package/dist/tools/schedule.js +498 -0
- package/dist/tools/schedule.js.map +1 -0
- package/dist/tools/schedule.test.d.ts +1 -0
- package/dist/tools/schedule.test.js +118 -0
- package/dist/tools/schedule.test.js.map +1 -0
- package/dist/types/index.d.ts +279 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/agents-modal.d.ts +36 -0
- package/dist/ui/agents-modal.js +54 -0
- package/dist/ui/agents-modal.js.map +1 -0
- package/dist/ui/app.d.ts +31 -0
- package/dist/ui/app.js +3957 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/components/SuggestionOverlay.d.ts +6 -0
- package/dist/ui/components/SuggestionOverlay.js +12 -0
- package/dist/ui/components/SuggestionOverlay.js.map +1 -0
- package/dist/ui/components/btw-overlay.d.ts +11 -0
- package/dist/ui/components/btw-overlay.js +16 -0
- package/dist/ui/components/btw-overlay.js.map +1 -0
- package/dist/ui/hooks/useTypeahead.d.ts +18 -0
- package/dist/ui/hooks/useTypeahead.js +111 -0
- package/dist/ui/hooks/useTypeahead.js.map +1 -0
- package/dist/ui/markdown.d.ts +5 -0
- package/dist/ui/markdown.js +38 -0
- package/dist/ui/markdown.js.map +1 -0
- package/dist/ui/mcp-modal-types.d.ts +24 -0
- package/dist/ui/mcp-modal-types.js +13 -0
- package/dist/ui/mcp-modal-types.js.map +1 -0
- package/dist/ui/mcp-modal.d.ts +33 -0
- package/dist/ui/mcp-modal.js +94 -0
- package/dist/ui/mcp-modal.js.map +1 -0
- package/dist/ui/plan.d.ts +24 -0
- package/dist/ui/plan.js +122 -0
- package/dist/ui/plan.js.map +1 -0
- package/dist/ui/schedule-modal.d.ts +15 -0
- package/dist/ui/schedule-modal.js +36 -0
- package/dist/ui/schedule-modal.js.map +1 -0
- package/dist/ui/slash/__tests__/clear.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/clear.test.js +58 -0
- package/dist/ui/slash/__tests__/clear.test.js.map +1 -0
- package/dist/ui/slash/__tests__/compact.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/compact.test.js +43 -0
- package/dist/ui/slash/__tests__/compact.test.js.map +1 -0
- package/dist/ui/slash/__tests__/cost.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/cost.test.js +54 -0
- package/dist/ui/slash/__tests__/cost.test.js.map +1 -0
- package/dist/ui/slash/__tests__/discuss.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/discuss.test.js +83 -0
- package/dist/ui/slash/__tests__/discuss.test.js.map +1 -0
- package/dist/ui/slash/__tests__/execute.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/execute.test.js +71 -0
- package/dist/ui/slash/__tests__/execute.test.js.map +1 -0
- package/dist/ui/slash/__tests__/expand.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/expand.test.js +67 -0
- package/dist/ui/slash/__tests__/expand.test.js.map +1 -0
- package/dist/ui/slash/__tests__/optimize.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/optimize.test.js +130 -0
- package/dist/ui/slash/__tests__/optimize.test.js.map +1 -0
- package/dist/ui/slash/__tests__/plan.test.d.ts +1 -0
- package/dist/ui/slash/__tests__/plan.test.js +79 -0
- package/dist/ui/slash/__tests__/plan.test.js.map +1 -0
- package/dist/ui/slash/clear.d.ts +11 -0
- package/dist/ui/slash/clear.js +77 -0
- package/dist/ui/slash/clear.js.map +1 -0
- package/dist/ui/slash/compact.d.ts +11 -0
- package/dist/ui/slash/compact.js +40 -0
- package/dist/ui/slash/compact.js.map +1 -0
- package/dist/ui/slash/cost.d.ts +11 -0
- package/dist/ui/slash/cost.js +55 -0
- package/dist/ui/slash/cost.js.map +1 -0
- package/dist/ui/slash/council.d.ts +2 -0
- package/dist/ui/slash/council.js +22 -0
- package/dist/ui/slash/council.js.map +1 -0
- package/dist/ui/slash/debug.d.ts +45 -0
- package/dist/ui/slash/debug.js +111 -0
- package/dist/ui/slash/debug.js.map +1 -0
- package/dist/ui/slash/discuss.d.ts +11 -0
- package/dist/ui/slash/discuss.js +59 -0
- package/dist/ui/slash/discuss.js.map +1 -0
- package/dist/ui/slash/ee.d.ts +10 -0
- package/dist/ui/slash/ee.js +247 -0
- package/dist/ui/slash/ee.js.map +1 -0
- package/dist/ui/slash/execute.d.ts +12 -0
- package/dist/ui/slash/execute.js +36 -0
- package/dist/ui/slash/execute.js.map +1 -0
- package/dist/ui/slash/expand.d.ts +11 -0
- package/dist/ui/slash/expand.js +42 -0
- package/dist/ui/slash/expand.js.map +1 -0
- package/dist/ui/slash/optimize.d.ts +12 -0
- package/dist/ui/slash/optimize.js +37 -0
- package/dist/ui/slash/optimize.js.map +1 -0
- package/dist/ui/slash/plan.d.ts +11 -0
- package/dist/ui/slash/plan.js +48 -0
- package/dist/ui/slash/plan.js.map +1 -0
- package/dist/ui/slash/registry.d.ts +23 -0
- package/dist/ui/slash/registry.js +26 -0
- package/dist/ui/slash/registry.js.map +1 -0
- package/dist/ui/slash/route.d.ts +12 -0
- package/dist/ui/slash/route.js +35 -0
- package/dist/ui/slash/route.js.map +1 -0
- package/dist/ui/slash/route.test.d.ts +1 -0
- package/dist/ui/slash/route.test.js +70 -0
- package/dist/ui/slash/route.test.js.map +1 -0
- package/dist/ui/status-bar/index.d.ts +14 -0
- package/dist/ui/status-bar/index.js +74 -0
- package/dist/ui/status-bar/index.js.map +1 -0
- package/dist/ui/status-bar/index.test.d.ts +1 -0
- package/dist/ui/status-bar/index.test.js +80 -0
- package/dist/ui/status-bar/index.test.js.map +1 -0
- package/dist/ui/status-bar/store.d.ts +38 -0
- package/dist/ui/status-bar/store.js +144 -0
- package/dist/ui/status-bar/store.js.map +1 -0
- package/dist/ui/status-bar/store.test.d.ts +1 -0
- package/dist/ui/status-bar/store.test.js +116 -0
- package/dist/ui/status-bar/store.test.js.map +1 -0
- package/dist/ui/status-bar/tier-badge.d.ts +11 -0
- package/dist/ui/status-bar/tier-badge.js +19 -0
- package/dist/ui/status-bar/tier-badge.js.map +1 -0
- package/dist/ui/status-bar/tier-badge.test.d.ts +1 -0
- package/dist/ui/status-bar/tier-badge.test.js +34 -0
- package/dist/ui/status-bar/tier-badge.test.js.map +1 -0
- package/dist/ui/status-bar/usd-meter.d.ts +13 -0
- package/dist/ui/status-bar/usd-meter.js +13 -0
- package/dist/ui/status-bar/usd-meter.js.map +1 -0
- package/dist/ui/status-bar/usd-meter.test.d.ts +1 -0
- package/dist/ui/status-bar/usd-meter.test.js +32 -0
- package/dist/ui/status-bar/usd-meter.test.js.map +1 -0
- package/dist/ui/terminal-selection-text.d.ts +23 -0
- package/dist/ui/terminal-selection-text.js +59 -0
- package/dist/ui/terminal-selection-text.js.map +1 -0
- package/dist/ui/theme.d.ts +53 -0
- package/dist/ui/theme.js +53 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/usage/downgrade.d.ts +37 -0
- package/dist/usage/downgrade.js +49 -0
- package/dist/usage/downgrade.js.map +1 -0
- package/dist/usage/downgrade.test.d.ts +1 -0
- package/dist/usage/downgrade.test.js +67 -0
- package/dist/usage/downgrade.test.js.map +1 -0
- package/dist/usage/estimator.d.ts +17 -0
- package/dist/usage/estimator.js +28 -0
- package/dist/usage/estimator.js.map +1 -0
- package/dist/usage/estimator.test.d.ts +1 -0
- package/dist/usage/estimator.test.js +38 -0
- package/dist/usage/estimator.test.js.map +1 -0
- package/dist/usage/ledger.d.ts +42 -0
- package/dist/usage/ledger.js +181 -0
- package/dist/usage/ledger.js.map +1 -0
- package/dist/usage/ledger.test.d.ts +1 -0
- package/dist/usage/ledger.test.js +171 -0
- package/dist/usage/ledger.test.js.map +1 -0
- package/dist/usage/midstream.d.ts +24 -0
- package/dist/usage/midstream.js +45 -0
- package/dist/usage/midstream.js.map +1 -0
- package/dist/usage/midstream.test.d.ts +1 -0
- package/dist/usage/midstream.test.js +47 -0
- package/dist/usage/midstream.test.js.map +1 -0
- package/dist/usage/thresholds.d.ts +38 -0
- package/dist/usage/thresholds.js +54 -0
- package/dist/usage/thresholds.js.map +1 -0
- package/dist/usage/thresholds.test.d.ts +1 -0
- package/dist/usage/thresholds.test.js +77 -0
- package/dist/usage/thresholds.test.js.map +1 -0
- package/dist/usage/types.d.ts +30 -0
- package/dist/usage/types.js +21 -0
- package/dist/usage/types.js.map +1 -0
- package/dist/utils/at-mentions.d.ts +12 -0
- package/dist/utils/at-mentions.js +92 -0
- package/dist/utils/at-mentions.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts +13 -0
- package/dist/utils/clipboard-image.js +121 -0
- package/dist/utils/clipboard-image.js.map +1 -0
- package/dist/utils/file-index.d.ts +13 -0
- package/dist/utils/file-index.js +143 -0
- package/dist/utils/file-index.js.map +1 -0
- package/dist/utils/git-root.d.ts +1 -0
- package/dist/utils/git-root.js +16 -0
- package/dist/utils/git-root.js.map +1 -0
- package/dist/utils/host-clipboard.d.ts +4 -0
- package/dist/utils/host-clipboard.js +32 -0
- package/dist/utils/host-clipboard.js.map +1 -0
- package/dist/utils/install-manager.d.ts +56 -0
- package/dist/utils/install-manager.js +340 -0
- package/dist/utils/install-manager.js.map +1 -0
- package/dist/utils/install-manager.test.d.ts +1 -0
- package/dist/utils/install-manager.test.js +127 -0
- package/dist/utils/install-manager.test.js.map +1 -0
- package/dist/utils/instructions.d.ts +3 -0
- package/dist/utils/instructions.js +107 -0
- package/dist/utils/instructions.js.map +1 -0
- package/dist/utils/instructions.test.d.ts +1 -0
- package/dist/utils/instructions.test.js +68 -0
- package/dist/utils/instructions.test.js.map +1 -0
- package/dist/utils/permission-mode.d.ts +24 -0
- package/dist/utils/permission-mode.js +29 -0
- package/dist/utils/permission-mode.js.map +1 -0
- package/dist/utils/permission-mode.test.d.ts +1 -0
- package/dist/utils/permission-mode.test.js +97 -0
- package/dist/utils/permission-mode.test.js.map +1 -0
- package/dist/utils/redactor.d.ts +57 -0
- package/dist/utils/redactor.js +179 -0
- package/dist/utils/redactor.js.map +1 -0
- package/dist/utils/redactor.test.d.ts +1 -0
- package/dist/utils/redactor.test.js +84 -0
- package/dist/utils/redactor.test.js.map +1 -0
- package/dist/utils/settings.d.ts +179 -0
- package/dist/utils/settings.js +579 -0
- package/dist/utils/settings.js.map +1 -0
- package/dist/utils/settings.test.d.ts +1 -0
- package/dist/utils/settings.test.js +160 -0
- package/dist/utils/settings.test.js.map +1 -0
- package/dist/utils/side-question.d.ts +10 -0
- package/dist/utils/side-question.js +24 -0
- package/dist/utils/side-question.js.map +1 -0
- package/dist/utils/skills.d.ts +20 -0
- package/dist/utils/skills.js +194 -0
- package/dist/utils/skills.js.map +1 -0
- package/dist/utils/skills.test.d.ts +1 -0
- package/dist/utils/skills.test.js +45 -0
- package/dist/utils/skills.test.js.map +1 -0
- package/dist/utils/subagent-display.d.ts +1 -0
- package/dist/utils/subagent-display.js +20 -0
- package/dist/utils/subagent-display.js.map +1 -0
- package/dist/utils/subagent-display.test.d.ts +1 -0
- package/dist/utils/subagent-display.test.js +21 -0
- package/dist/utils/subagent-display.test.js.map +1 -0
- package/dist/utils/subagents-settings.test.d.ts +1 -0
- package/dist/utils/subagents-settings.test.js +58 -0
- package/dist/utils/subagents-settings.test.js.map +1 -0
- package/dist/utils/telegram-audio-settings.test.d.ts +1 -0
- package/dist/utils/telegram-audio-settings.test.js +39 -0
- package/dist/utils/telegram-audio-settings.test.js.map +1 -0
- package/dist/utils/update-checker.d.ts +11 -0
- package/dist/utils/update-checker.js +22 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/dist/utils/update-checker.test.d.ts +1 -0
- package/dist/utils/update-checker.test.js +125 -0
- package/dist/utils/update-checker.test.js.map +1 -0
- package/dist/verify/checkpoint.d.ts +11 -0
- package/dist/verify/checkpoint.js +202 -0
- package/dist/verify/checkpoint.js.map +1 -0
- package/dist/verify/checkpoint.test.d.ts +1 -0
- package/dist/verify/checkpoint.test.js +160 -0
- package/dist/verify/checkpoint.test.js.map +1 -0
- package/dist/verify/entrypoint.d.ts +30 -0
- package/dist/verify/entrypoint.js +349 -0
- package/dist/verify/entrypoint.js.map +1 -0
- package/dist/verify/entrypoint.test.d.ts +1 -0
- package/dist/verify/entrypoint.test.js +233 -0
- package/dist/verify/entrypoint.test.js.map +1 -0
- package/dist/verify/environment.d.ts +9 -0
- package/dist/verify/environment.js +116 -0
- package/dist/verify/environment.js.map +1 -0
- package/dist/verify/environment.test.d.ts +1 -0
- package/dist/verify/environment.test.js +94 -0
- package/dist/verify/environment.test.js.map +1 -0
- package/dist/verify/evidence.d.ts +10 -0
- package/dist/verify/evidence.js +94 -0
- package/dist/verify/evidence.js.map +1 -0
- package/dist/verify/orchestrator.d.ts +25 -0
- package/dist/verify/orchestrator.js +87 -0
- package/dist/verify/orchestrator.js.map +1 -0
- package/dist/verify/orchestrator.test.d.ts +1 -0
- package/dist/verify/orchestrator.test.js +126 -0
- package/dist/verify/orchestrator.test.js.map +1 -0
- package/dist/verify/recipes.d.ts +20 -0
- package/dist/verify/recipes.js +451 -0
- package/dist/verify/recipes.js.map +1 -0
- package/dist/verify/retry.d.ts +4 -0
- package/dist/verify/retry.js +50 -0
- package/dist/verify/retry.js.map +1 -0
- package/dist/verify/runtime-prep.test.d.ts +1 -0
- package/dist/verify/runtime-prep.test.js +38 -0
- package/dist/verify/runtime-prep.test.js.map +1 -0
- package/package.json +93 -0
- package/src/__test-stubs__/ee-server.ts +173 -0
- package/src/billing/index.ts +5 -0
- package/src/bun-sqlite.d.ts +15 -0
- package/src/cloud/index.ts +5 -0
- package/src/daemon/scheduler.test.ts +128 -0
- package/src/daemon/scheduler.ts +152 -0
- package/src/ee/.gitkeep +0 -0
- package/src/ee/__tests__/pipeline.integration.test.ts +193 -0
- package/src/ee/auth.test.ts +76 -0
- package/src/ee/auth.ts +59 -0
- package/src/ee/bridge.test.ts +290 -0
- package/src/ee/bridge.ts +254 -0
- package/src/ee/client.test.ts +171 -0
- package/src/ee/client.ts +512 -0
- package/src/ee/embedding-cache.ts +42 -0
- package/src/ee/extract-session.test.ts +236 -0
- package/src/ee/extract-session.ts +69 -0
- package/src/ee/health.ts +83 -0
- package/src/ee/index.ts +32 -0
- package/src/ee/intercept.test.ts +191 -0
- package/src/ee/intercept.ts +132 -0
- package/src/ee/judge.test.ts +157 -0
- package/src/ee/judge.ts +68 -0
- package/src/ee/offline-queue.test.ts +351 -0
- package/src/ee/offline-queue.ts +212 -0
- package/src/ee/posttool.test.ts +81 -0
- package/src/ee/posttool.ts +16 -0
- package/src/ee/prompt-stale.test.ts +87 -0
- package/src/ee/prompt-stale.ts +39 -0
- package/src/ee/render.test.ts +71 -0
- package/src/ee/render.ts +47 -0
- package/src/ee/scope.test.ts +112 -0
- package/src/ee/scope.ts +93 -0
- package/src/ee/tenant.ts +14 -0
- package/src/ee/touch.test.ts +71 -0
- package/src/ee/types.ts +322 -0
- package/src/flow/.gitkeep +0 -0
- package/src/flow/__tests__/migration.test.ts +133 -0
- package/src/flow/__tests__/parser.test.ts +77 -0
- package/src/flow/__tests__/run-manager.test.ts +95 -0
- package/src/flow/__tests__/scaffold.test.ts +57 -0
- package/src/flow/__tests__/warning-persist.test.ts +112 -0
- package/src/flow/artifact-io.ts +41 -0
- package/src/flow/compaction/__tests__/compress.test.ts +69 -0
- package/src/flow/compaction/__tests__/extract.test.ts +74 -0
- package/src/flow/compaction/__tests__/preserve.test.ts +69 -0
- package/src/flow/compaction/compress.ts +67 -0
- package/src/flow/compaction/extract.ts +60 -0
- package/src/flow/compaction/index.ts +86 -0
- package/src/flow/compaction/preserve.ts +48 -0
- package/src/flow/index.ts +18 -0
- package/src/flow/migration.ts +139 -0
- package/src/flow/parser.ts +78 -0
- package/src/flow/run-manager.ts +129 -0
- package/src/flow/scaffold.ts +52 -0
- package/src/flow/warning-persist.ts +84 -0
- package/src/gsd/.gitkeep +0 -0
- package/src/gsd/__tests__/types.test.ts +77 -0
- package/src/gsd/index.ts +1 -0
- package/src/gsd/types.ts +47 -0
- package/src/headless/output.test.ts +201 -0
- package/src/headless/output.ts +312 -0
- package/src/hooks/config.ts +41 -0
- package/src/hooks/index.ts +250 -0
- package/src/hooks/types.ts +221 -0
- package/src/index.ts +655 -0
- package/src/lsp/builtins.test.ts +104 -0
- package/src/lsp/builtins.ts +409 -0
- package/src/lsp/client.ts +342 -0
- package/src/lsp/manager.test.ts +164 -0
- package/src/lsp/manager.ts +293 -0
- package/src/lsp/npm-cache.test.ts +68 -0
- package/src/lsp/npm-cache.ts +108 -0
- package/src/lsp/runtime.ts +70 -0
- package/src/lsp/smoke.test.ts +72 -0
- package/src/lsp/types.ts +116 -0
- package/src/mcp/auto-setup.ts +80 -0
- package/src/mcp/catalog.ts +138 -0
- package/src/mcp/oauth-callback.ts +63 -0
- package/src/mcp/oauth-provider.ts +130 -0
- package/src/mcp/parse-headers.test.ts +54 -0
- package/src/mcp/parse-headers.ts +35 -0
- package/src/mcp/runtime.ts +113 -0
- package/src/mcp/smoke.test.ts +170 -0
- package/src/mcp/validate.ts +48 -0
- package/src/models/__tests__/registry.test.ts +95 -0
- package/src/models/catalog-client.ts +83 -0
- package/src/models/catalog.json +314 -0
- package/src/models/classify-tier.ts +37 -0
- package/src/models/index.ts +9 -0
- package/src/models/registry.ts +73 -0
- package/src/ops/bug-report.test.ts +172 -0
- package/src/ops/bug-report.ts +80 -0
- package/src/ops/doctor.test.ts +107 -0
- package/src/ops/doctor.ts +172 -0
- package/src/orchestrator/__tests__/flow-resume.test.ts +71 -0
- package/src/orchestrator/__tests__/route-feedback.test.ts +55 -0
- package/src/orchestrator/abort.test.ts +37 -0
- package/src/orchestrator/abort.ts +51 -0
- package/src/orchestrator/agent.test.ts +138 -0
- package/src/orchestrator/cleanup.test.ts +88 -0
- package/src/orchestrator/compaction.test.ts +134 -0
- package/src/orchestrator/compaction.ts +487 -0
- package/src/orchestrator/delegations.test.ts +145 -0
- package/src/orchestrator/delegations.ts +364 -0
- package/src/orchestrator/flow-resume.ts +55 -0
- package/src/orchestrator/orchestrator.ts +3965 -0
- package/src/orchestrator/pending-calls.test.ts +226 -0
- package/src/orchestrator/pending-calls.ts +240 -0
- package/src/orchestrator/reasoning.test.ts +29 -0
- package/src/orchestrator/reasoning.ts +64 -0
- package/src/orchestrator/sandbox.test.ts +115 -0
- package/src/pil/__tests__/budget.test.ts +39 -0
- package/src/pil/__tests__/layer1-intent.test.ts +243 -0
- package/src/pil/__tests__/layer2-personality.test.ts +63 -0
- package/src/pil/__tests__/layer3-ee-injection.test.ts +117 -0
- package/src/pil/__tests__/layer4-gsd.test.ts +76 -0
- package/src/pil/__tests__/layer5-context.test.ts +116 -0
- package/src/pil/__tests__/layer6-output.test.ts +139 -0
- package/src/pil/__tests__/ollama-classify.test.ts +81 -0
- package/src/pil/__tests__/orchestrator-integration.test.ts +107 -0
- package/src/pil/__tests__/pipeline.test.ts +173 -0
- package/src/pil/__tests__/response-tools.test.ts +165 -0
- package/src/pil/__tests__/schema.test.ts +128 -0
- package/src/pil/__tests__/store.test.ts +49 -0
- package/src/pil/__tests__/task-tier-map.test.ts +41 -0
- package/src/pil/budget.ts +18 -0
- package/src/pil/index.ts +12 -0
- package/src/pil/layer1-intent.ts +155 -0
- package/src/pil/layer2-personality.ts +37 -0
- package/src/pil/layer3-ee-injection.ts +79 -0
- package/src/pil/layer4-gsd.ts +77 -0
- package/src/pil/layer5-context.ts +125 -0
- package/src/pil/layer6-output.ts +113 -0
- package/src/pil/ollama-classify.ts +49 -0
- package/src/pil/pipeline.ts +112 -0
- package/src/pil/response-tools.ts +110 -0
- package/src/pil/schema.ts +49 -0
- package/src/pil/store.ts +29 -0
- package/src/pil/task-tier-map.ts +90 -0
- package/src/pil/timeout.ts +10 -0
- package/src/pil/types.ts +42 -0
- package/src/providers/.gitkeep +0 -0
- package/src/providers/__test-utils__/load-fixture.ts +36 -0
- package/src/providers/__tests__/runtime-integration.test.ts +80 -0
- package/src/providers/__tests__/runtime.test.ts +86 -0
- package/src/providers/adapter.test.ts +21 -0
- package/src/providers/adapter.ts +48 -0
- package/src/providers/anthropic.ts +169 -0
- package/src/providers/errors.test.ts +76 -0
- package/src/providers/errors.ts +46 -0
- package/src/providers/gemini.test.ts +46 -0
- package/src/providers/gemini.ts +37 -0
- package/src/providers/index.ts +32 -0
- package/src/providers/keychain.test.ts +95 -0
- package/src/providers/keychain.ts +133 -0
- package/src/providers/ollama.test.ts +46 -0
- package/src/providers/ollama.ts +36 -0
- package/src/providers/openai-compatible.test.ts +63 -0
- package/src/providers/openai-compatible.ts +50 -0
- package/src/providers/openai.test.ts +65 -0
- package/src/providers/openai.ts +37 -0
- package/src/providers/patch-zod-schema.ts +129 -0
- package/src/providers/pricing.test.ts +56 -0
- package/src/providers/pricing.ts +55 -0
- package/src/providers/runtime.ts +111 -0
- package/src/providers/stream-loop.ts +69 -0
- package/src/providers/types.ts +111 -0
- package/src/providers/vision-proxy.test.ts +167 -0
- package/src/providers/vision-proxy.ts +209 -0
- package/src/router/.gitkeep +0 -0
- package/src/router/classifier/grammars.ts +17 -0
- package/src/router/classifier/index.test.ts +33 -0
- package/src/router/classifier/index.ts +21 -0
- package/src/router/classifier/regex.test.ts +47 -0
- package/src/router/classifier/regex.ts +54 -0
- package/src/router/classifier/tree-sitter.test.ts +26 -0
- package/src/router/classifier/tree-sitter.ts +87 -0
- package/src/router/cold.test.ts +62 -0
- package/src/router/cold.ts +27 -0
- package/src/router/decide.test.ts +135 -0
- package/src/router/decide.ts +354 -0
- package/src/router/health.test.ts +59 -0
- package/src/router/health.ts +49 -0
- package/src/router/store.ts +47 -0
- package/src/router/types.ts +20 -0
- package/src/router/warm.test.ts +167 -0
- package/src/router/warm.ts +47 -0
- package/src/storage/__tests__/migrations.test.ts +395 -0
- package/src/storage/atomic-io.test.ts +61 -0
- package/src/storage/atomic-io.ts +62 -0
- package/src/storage/config.test.ts +33 -0
- package/src/storage/config.ts +49 -0
- package/src/storage/db.ts +95 -0
- package/src/storage/index.ts +19 -0
- package/src/storage/migrations.ts +143 -0
- package/src/storage/session-dir.ts +37 -0
- package/src/storage/sessions.ts +178 -0
- package/src/storage/tool-results.ts +56 -0
- package/src/storage/transcript-view.ts +45 -0
- package/src/storage/transcript.test.ts +24 -0
- package/src/storage/transcript.ts +358 -0
- package/src/storage/usage-cap.test.ts +59 -0
- package/src/storage/usage-cap.ts +81 -0
- package/src/storage/usage.ts +115 -0
- package/src/storage/workspaces.ts +84 -0
- package/src/tools/bash.test.ts +336 -0
- package/src/tools/bash.ts +612 -0
- package/src/tools/computer.test.ts +187 -0
- package/src/tools/computer.ts +632 -0
- package/src/tools/file.test.ts +95 -0
- package/src/tools/file.ts +128 -0
- package/src/tools/grep.ts +187 -0
- package/src/tools/registry.ts +282 -0
- package/src/tools/schedule.test.ts +143 -0
- package/src/tools/schedule.ts +610 -0
- package/src/types/index.ts +311 -0
- package/src/ui/agents-modal.tsx +272 -0
- package/src/ui/app.tsx +6277 -0
- package/src/ui/components/SuggestionOverlay.tsx +38 -0
- package/src/ui/components/btw-overlay.tsx +66 -0
- package/src/ui/hooks/useTypeahead.ts +146 -0
- package/src/ui/markdown.tsx +49 -0
- package/src/ui/mcp-modal-types.ts +33 -0
- package/src/ui/mcp-modal.tsx +456 -0
- package/src/ui/plan.tsx +346 -0
- package/src/ui/schedule-modal.tsx +126 -0
- package/src/ui/slash/__tests__/clear.test.ts +86 -0
- package/src/ui/slash/__tests__/compact.test.ts +56 -0
- package/src/ui/slash/__tests__/cost.test.ts +62 -0
- package/src/ui/slash/__tests__/discuss.test.ts +101 -0
- package/src/ui/slash/__tests__/execute.test.ts +86 -0
- package/src/ui/slash/__tests__/expand.test.ts +86 -0
- package/src/ui/slash/__tests__/optimize.test.ts +155 -0
- package/src/ui/slash/__tests__/plan.test.ts +95 -0
- package/src/ui/slash/clear.ts +89 -0
- package/src/ui/slash/compact.ts +46 -0
- package/src/ui/slash/cost.ts +59 -0
- package/src/ui/slash/council.ts +27 -0
- package/src/ui/slash/debug.ts +153 -0
- package/src/ui/slash/discuss.ts +71 -0
- package/src/ui/slash/ee.ts +271 -0
- package/src/ui/slash/execute.ts +44 -0
- package/src/ui/slash/expand.ts +51 -0
- package/src/ui/slash/optimize.ts +47 -0
- package/src/ui/slash/plan.ts +62 -0
- package/src/ui/slash/registry.ts +38 -0
- package/src/ui/slash/route.test.ts +82 -0
- package/src/ui/slash/route.ts +43 -0
- package/src/ui/status-bar/index.test.tsx +90 -0
- package/src/ui/status-bar/index.tsx +94 -0
- package/src/ui/status-bar/store.test.ts +131 -0
- package/src/ui/status-bar/store.ts +166 -0
- package/src/ui/status-bar/tier-badge.test.tsx +38 -0
- package/src/ui/status-bar/tier-badge.tsx +29 -0
- package/src/ui/status-bar/usd-meter.test.tsx +37 -0
- package/src/ui/status-bar/usd-meter.tsx +22 -0
- package/src/ui/terminal-selection-text.ts +72 -0
- package/src/ui/theme.ts +54 -0
- package/src/usage/.gitkeep +0 -0
- package/src/usage/downgrade.test.ts +82 -0
- package/src/usage/downgrade.ts +70 -0
- package/src/usage/estimator.test.ts +43 -0
- package/src/usage/estimator.ts +34 -0
- package/src/usage/ledger.test.ts +200 -0
- package/src/usage/ledger.ts +213 -0
- package/src/usage/midstream.test.ts +55 -0
- package/src/usage/midstream.ts +51 -0
- package/src/usage/thresholds.test.ts +83 -0
- package/src/usage/thresholds.ts +74 -0
- package/src/usage/types.ts +40 -0
- package/src/utils/at-mentions.ts +120 -0
- package/src/utils/clipboard-image.ts +114 -0
- package/src/utils/file-index.ts +152 -0
- package/src/utils/git-root.ts +17 -0
- package/src/utils/host-clipboard.ts +30 -0
- package/src/utils/install-manager.test.ts +167 -0
- package/src/utils/install-manager.ts +429 -0
- package/src/utils/instructions.test.ts +84 -0
- package/src/utils/instructions.ts +116 -0
- package/src/utils/permission-mode.test.ts +121 -0
- package/src/utils/permission-mode.ts +37 -0
- package/src/utils/redactor.test.ts +100 -0
- package/src/utils/redactor.ts +223 -0
- package/src/utils/settings.test.ts +181 -0
- package/src/utils/settings.ts +787 -0
- package/src/utils/side-question.ts +39 -0
- package/src/utils/skills.test.ts +58 -0
- package/src/utils/skills.ts +207 -0
- package/src/utils/subagent-display.test.ts +23 -0
- package/src/utils/subagent-display.ts +11 -0
- package/src/utils/subagents-settings.test.ts +77 -0
- package/src/utils/telegram-audio-settings.test.ts +44 -0
- package/src/utils/update-checker.test.ts +182 -0
- package/src/utils/update-checker.ts +33 -0
- package/src/verify/checkpoint.test.ts +186 -0
- package/src/verify/checkpoint.ts +239 -0
- package/src/verify/entrypoint.test.ts +293 -0
- package/src/verify/entrypoint.ts +439 -0
- package/src/verify/environment.test.ts +119 -0
- package/src/verify/environment.ts +115 -0
- package/src/verify/evidence.ts +104 -0
- package/src/verify/orchestrator.test.ts +159 -0
- package/src/verify/orchestrator.ts +129 -0
- package/src/verify/recipes.ts +516 -0
- package/src/verify/retry.ts +62 -0
- package/src/verify/runtime-prep.test.ts +47 -0
|
@@ -0,0 +1,3965 @@
|
|
|
1
|
+
// Multi-provider wired — runtime dispatch via providers/runtime.ts.
|
|
2
|
+
|
|
3
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
4
|
+
import { convertToBase64 } from "@ai-sdk/provider-utils";
|
|
5
|
+
import { generateText, type ModelMessage, stepCountIs, streamText, type ToolSet } from "ai";
|
|
6
|
+
import { createRun, getActiveRunId, setActiveRunId } from "../flow/run-manager.js";
|
|
7
|
+
import { ensureFlowDir } from "../flow/scaffold.js";
|
|
8
|
+
import { executeEventHooks } from "../hooks/index";
|
|
9
|
+
import type { PreToolUseHookInput, PostToolUseHookInput } from "../hooks/types";
|
|
10
|
+
import { bootstrapEEClient, getDefaultEEClient, getLastSurfacedState } from "../ee/intercept.js";
|
|
11
|
+
import { getTenantId } from "../ee/tenant.js";
|
|
12
|
+
import { routeFeedback, routeModel } from "../ee/bridge.js";
|
|
13
|
+
import { reportRouteOutcome } from "../router/decide.js";
|
|
14
|
+
import { routerStore } from "../router/store.js";
|
|
15
|
+
import { statusBarStore } from "../ui/status-bar/store.js";
|
|
16
|
+
import { taskTypeToTier, taskTypeToMaxTokens, taskTypeToReasoningEffort } from "../pil/task-tier-map.js";
|
|
17
|
+
import type {
|
|
18
|
+
NotificationHookInput,
|
|
19
|
+
PostCompactHookInput,
|
|
20
|
+
PreCompactHookInput,
|
|
21
|
+
SessionEndHookInput,
|
|
22
|
+
SessionStartHookInput,
|
|
23
|
+
StopFailureHookInput,
|
|
24
|
+
StopHookInput,
|
|
25
|
+
SubagentStartHookInput,
|
|
26
|
+
SubagentStopHookInput,
|
|
27
|
+
TaskCompletedHookInput,
|
|
28
|
+
TaskCreatedHookInput,
|
|
29
|
+
UserPromptSubmitHookInput,
|
|
30
|
+
} from "../hooks/types";
|
|
31
|
+
import { shutdownWorkspaceLspManager } from "../lsp/runtime";
|
|
32
|
+
import { extractSession } from "../ee/extract-session.js";
|
|
33
|
+
import { ensureDefaultMcpServers } from "../mcp/auto-setup.js";
|
|
34
|
+
import { buildMcpToolSet } from "../mcp/runtime";
|
|
35
|
+
import { createBuiltinTools } from "../tools/registry.js";
|
|
36
|
+
import { loadKeyForProvider } from "../providers/keychain.js";
|
|
37
|
+
import { captureToolSchemas } from "../providers/patch-zod-schema.js";
|
|
38
|
+
import { getModelInfo, normalizeModelId } from "../models/registry.js";
|
|
39
|
+
import { needsVisionProxy, proxyVision } from "../providers/vision-proxy.js";
|
|
40
|
+
import { isDebugEnabled, recordTurnTrace, type PipelineStep, type TurnTrace } from "../ui/slash/debug.js";
|
|
41
|
+
import { applyPilSuffix, getResponseToolSet, isResponseTool, getResponseTaskType, runPipeline } from "../pil/index.js";
|
|
42
|
+
import {
|
|
43
|
+
appendCompaction,
|
|
44
|
+
appendMessages,
|
|
45
|
+
appendSystemMessage,
|
|
46
|
+
buildChatEntries,
|
|
47
|
+
getNextMessageSequence,
|
|
48
|
+
getSessionTotalTokens,
|
|
49
|
+
loadTranscript,
|
|
50
|
+
loadTranscriptState,
|
|
51
|
+
recordUsageEvent,
|
|
52
|
+
SessionStore,
|
|
53
|
+
} from "../storage/index";
|
|
54
|
+
import { BashTool } from "../tools/bash";
|
|
55
|
+
import { type ScheduleDaemonStatus, ScheduleManager, type StoredSchedule } from "../tools/schedule";
|
|
56
|
+
import type {
|
|
57
|
+
AgentMode,
|
|
58
|
+
ChatEntry,
|
|
59
|
+
ModelInfo,
|
|
60
|
+
Plan,
|
|
61
|
+
SessionInfo,
|
|
62
|
+
SessionSnapshot,
|
|
63
|
+
StreamChunk,
|
|
64
|
+
SubagentStatus,
|
|
65
|
+
TaskRequest,
|
|
66
|
+
ToolCall,
|
|
67
|
+
ToolResult,
|
|
68
|
+
UsageSource,
|
|
69
|
+
VerifyRecipe,
|
|
70
|
+
WorkspaceInfo,
|
|
71
|
+
} from "../types/index";
|
|
72
|
+
import { loadCustomInstructions } from "../utils/instructions";
|
|
73
|
+
import { type PermissionMode, toolNeedsApproval } from "../utils/permission-mode.js";
|
|
74
|
+
import {
|
|
75
|
+
type CustomSubagentConfig,
|
|
76
|
+
getCurrentModel,
|
|
77
|
+
getModeSpecificModel,
|
|
78
|
+
getCouncilRounds,
|
|
79
|
+
getRoleModel,
|
|
80
|
+
getRoleModels,
|
|
81
|
+
isAutoCompactAfterTurnEnabled,
|
|
82
|
+
isAutoCouncilEnabled,
|
|
83
|
+
loadMcpServers,
|
|
84
|
+
loadValidSubAgents,
|
|
85
|
+
type ModelRole,
|
|
86
|
+
type SandboxMode,
|
|
87
|
+
type SandboxSettings,
|
|
88
|
+
} from "../utils/settings";
|
|
89
|
+
import { runSideQuestion, type SideQuestionResult } from "../utils/side-question";
|
|
90
|
+
import { discoverSkills, formatSkillsForPrompt } from "../utils/skills";
|
|
91
|
+
import { buildVerifyDetectPrompt, normalizeVerifyRecipe, prepareVerifySandbox } from "../verify/entrypoint";
|
|
92
|
+
import { runVerifyOrchestration } from "../verify/orchestrator";
|
|
93
|
+
import {
|
|
94
|
+
type CompactionSettings,
|
|
95
|
+
createCompactionSummaryMessage,
|
|
96
|
+
DEFAULT_KEEP_RECENT_TOKENS,
|
|
97
|
+
DEFAULT_RESERVE_TOKENS,
|
|
98
|
+
POST_TURN_MIN_TOKENS,
|
|
99
|
+
estimateConversationTokens,
|
|
100
|
+
generateCompactionSummary,
|
|
101
|
+
prepareCompaction,
|
|
102
|
+
relaxCompactionSettings,
|
|
103
|
+
shouldCompactContext,
|
|
104
|
+
} from "./compaction";
|
|
105
|
+
import { DelegationManager } from "./delegations";
|
|
106
|
+
import { loadFlowResumeDigest } from "./flow-resume.js";
|
|
107
|
+
import { stableCallId } from "./pending-calls.js";
|
|
108
|
+
import { containsEncryptedReasoning, sanitizeModelMessages } from "./reasoning";
|
|
109
|
+
import {
|
|
110
|
+
createProviderFactory,
|
|
111
|
+
resolveModelRuntime as resolveRuntime,
|
|
112
|
+
detectProviderForModel,
|
|
113
|
+
type ProviderFactory,
|
|
114
|
+
type ResolvedModelRuntime as RuntimeResult,
|
|
115
|
+
} from "../providers/runtime.js";
|
|
116
|
+
import type { ProviderId } from "../providers/types.js";
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Re-export types from shared runtime module for back-compat
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
export type { ProviderFactory as LegacyProvider, ResolvedModelRuntime } from "../providers/runtime.js";
|
|
123
|
+
type LegacyProvider = ProviderFactory;
|
|
124
|
+
type ResolvedModelRuntime = RuntimeResult;
|
|
125
|
+
|
|
126
|
+
/** @deprecated Use ModelInfo from "../types/index" instead. */
|
|
127
|
+
export type ModelInfoStub = ModelInfo;
|
|
128
|
+
|
|
129
|
+
// Batch API type stubs
|
|
130
|
+
export interface BatchClientOptions {
|
|
131
|
+
apiKey: string;
|
|
132
|
+
baseURL?: string;
|
|
133
|
+
signal?: AbortSignal;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface BatchChatMessage {
|
|
137
|
+
role: string;
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
+
content: any;
|
|
140
|
+
tool_call_id?: string;
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
142
|
+
tool_calls?: any[];
|
|
143
|
+
name?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface BatchFunctionTool {
|
|
147
|
+
type: "function";
|
|
148
|
+
function: { name: string; description?: string; parameters?: unknown };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface BatchToolCall {
|
|
152
|
+
id: string;
|
|
153
|
+
type: "function";
|
|
154
|
+
function: { name: string; arguments: string };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface BatchChatCompletionRequest {
|
|
158
|
+
model: string;
|
|
159
|
+
messages: BatchChatMessage[];
|
|
160
|
+
tools?: BatchFunctionTool[];
|
|
161
|
+
temperature?: number;
|
|
162
|
+
max_tokens?: number;
|
|
163
|
+
reasoning_effort?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface BatchChatCompletionResponse {
|
|
167
|
+
choices: Array<{
|
|
168
|
+
message: { role: string; content: string | null; tool_calls?: BatchToolCall[] };
|
|
169
|
+
finish_reason: string;
|
|
170
|
+
}>;
|
|
171
|
+
usage?: {
|
|
172
|
+
prompt_tokens: number;
|
|
173
|
+
completion_tokens: number;
|
|
174
|
+
total_tokens: number;
|
|
175
|
+
input_tokens?: number;
|
|
176
|
+
output_tokens?: number;
|
|
177
|
+
cost_in_usd_ticks?: number;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Provider implementations
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a provider factory for the given provider ID using the shared runtime module.
|
|
187
|
+
*/
|
|
188
|
+
function createProvider(providerId: ProviderId, apiKey: string, baseURL?: string): LegacyProvider {
|
|
189
|
+
return createProviderFactory(providerId, { apiKey, baseURL }).factory;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Generate a session title using the Anthropic provider.
|
|
194
|
+
* Kept as a lightweight stub for Phase 0 — title generation ships in Phase 1.
|
|
195
|
+
*/
|
|
196
|
+
function genTitle(
|
|
197
|
+
_provider: LegacyProvider,
|
|
198
|
+
userMessage: string,
|
|
199
|
+
): Promise<{ title: string; modelId: string; usage?: { totalTokens?: number } }> {
|
|
200
|
+
// Phase 0 stub: return a truncated version of the first user message as title.
|
|
201
|
+
// Phase 1 will replace this with a real LLM-based title generation call.
|
|
202
|
+
const title = userMessage.slice(0, 60).trim() || "New session";
|
|
203
|
+
return Promise.resolve({ title, modelId: DEFAULT_MODEL });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolve a model ID to a runnable AI SDK LanguageModel.
|
|
208
|
+
* Uses the Anthropic provider factory created by createProvider().
|
|
209
|
+
*/
|
|
210
|
+
function resolveModelRuntime(provider: LegacyProvider, modelId: string): ResolvedModelRuntime {
|
|
211
|
+
return resolveRuntime(provider, modelId);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
215
|
+
|
|
216
|
+
async function toolSetToBatchTools(_tools: ToolSet): Promise<BatchFunctionTool[]> {
|
|
217
|
+
// Batch API not supported with Anthropic in Phase 0. Phase 1 may add this.
|
|
218
|
+
throw new Error("Batch API not available in Phase 0. Use standard streaming mode.");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function createBatch(_opts: BatchClientOptions & { name?: string }): Promise<{ batch_id: string }> {
|
|
222
|
+
throw new Error("Batch API not available in Phase 0. Use standard streaming mode.");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function addBatchRequests(
|
|
226
|
+
_opts: BatchClientOptions & { batchId: string; batchRequests: unknown[] },
|
|
227
|
+
): Promise<void> {
|
|
228
|
+
throw new Error("Batch API not available in Phase 0. Use standard streaming mode.");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function pollBatchRequestResult(
|
|
232
|
+
_opts: BatchClientOptions & { batchId: string; batchRequestId: string },
|
|
233
|
+
): Promise<unknown> {
|
|
234
|
+
throw new Error("Batch API not available in Phase 0. Use standard streaming mode.");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getBatchChatCompletion(_result: unknown): BatchChatCompletionResponse {
|
|
238
|
+
throw new Error("Batch API not available in Phase 0. Use standard streaming mode.");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function createTools(
|
|
242
|
+
_bash: unknown,
|
|
243
|
+
_provider: LegacyProvider,
|
|
244
|
+
_mode: unknown,
|
|
245
|
+
_opts?: {
|
|
246
|
+
runTask?: (request: TaskRequest, abortSignal?: AbortSignal) => Promise<ToolResult>;
|
|
247
|
+
runDelegation?: (request: TaskRequest, abortSignal?: AbortSignal) => Promise<ToolResult>;
|
|
248
|
+
readDelegation?: (id: string) => Promise<ToolResult>;
|
|
249
|
+
listDelegations?: () => Promise<ToolResult>;
|
|
250
|
+
scheduleManager?: unknown;
|
|
251
|
+
subagents?: unknown[];
|
|
252
|
+
sendTelegramFile?: (filePath: string) => Promise<ToolResult>;
|
|
253
|
+
sessionId?: string;
|
|
254
|
+
},
|
|
255
|
+
): ToolSet {
|
|
256
|
+
return createBuiltinTools(
|
|
257
|
+
_bash as BashTool,
|
|
258
|
+
(_mode ?? "agent") as AgentMode,
|
|
259
|
+
{
|
|
260
|
+
runTask: _opts?.runTask,
|
|
261
|
+
runDelegation: _opts?.runDelegation,
|
|
262
|
+
readDelegation: _opts?.readDelegation,
|
|
263
|
+
listDelegations: _opts?.listDelegations,
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function buildVisionUserMessages(_prompt: string, _cwd: string, _signal?: AbortSignal): Promise<ModelMessage[]> {
|
|
269
|
+
// Vision input is an anti-feature per PROJECT.md Out-of-Scope. Always throws.
|
|
270
|
+
throw new Error("Vision input is not supported in muonroi-cli (anti-feature per PROJECT.md).");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// END Plan 00-05 provider implementations
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
const MAX_TOOL_ROUNDS = 75;
|
|
278
|
+
const VISION_MODEL = "grok-4-1-fast-reasoning";
|
|
279
|
+
const COMPUTER_MODEL = "grok-4.20-0309-reasoning";
|
|
280
|
+
|
|
281
|
+
interface AgentOptions {
|
|
282
|
+
persistSession?: boolean;
|
|
283
|
+
session?: string;
|
|
284
|
+
sandboxMode?: SandboxMode;
|
|
285
|
+
sandboxSettings?: SandboxSettings;
|
|
286
|
+
batchApi?: boolean;
|
|
287
|
+
/** Optional external AbortContext (from src/index.ts SIGINT handler). When provided,
|
|
288
|
+
* the orchestrator uses its signal instead of creating a new AbortController per turn.
|
|
289
|
+
* TUI-04: Ctrl+C mid-tool-call abort safety. */
|
|
290
|
+
abortContext?: import("./abort.js").AbortContext;
|
|
291
|
+
/** Optional PendingCallsLog for Pitfall 9 staged-write tracking per tool call. */
|
|
292
|
+
pendingCalls?: import("./pending-calls.js").PendingCallsLog;
|
|
293
|
+
/** Permission mode controlling which tool calls require manual approval.
|
|
294
|
+
* safe (default) = confirm all; auto-edit = auto-approve file ops; yolo = auto-approve all. */
|
|
295
|
+
permissionMode?: PermissionMode;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type ProcessMessageFinishReason = "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other";
|
|
299
|
+
|
|
300
|
+
export interface ProcessMessageUsage {
|
|
301
|
+
inputTokens?: number;
|
|
302
|
+
outputTokens?: number;
|
|
303
|
+
totalTokens?: number;
|
|
304
|
+
costUsdTicks?: number;
|
|
305
|
+
cacheReadTokens?: number;
|
|
306
|
+
cacheCreationTokens?: number;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export interface ProcessMessageStepStart {
|
|
310
|
+
stepNumber: number;
|
|
311
|
+
timestamp: number;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface ProcessMessageStepFinish {
|
|
315
|
+
stepNumber: number;
|
|
316
|
+
timestamp: number;
|
|
317
|
+
finishReason: ProcessMessageFinishReason;
|
|
318
|
+
usage: ProcessMessageUsage;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface ProcessMessageToolStart {
|
|
322
|
+
toolCall: ToolCall;
|
|
323
|
+
timestamp: number;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface ProcessMessageToolFinish {
|
|
327
|
+
toolCall: ToolCall;
|
|
328
|
+
toolResult: ToolResult;
|
|
329
|
+
timestamp: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export interface ProcessMessageError {
|
|
333
|
+
message: string;
|
|
334
|
+
timestamp: number;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface ProcessMessageObserver {
|
|
338
|
+
onStepStart?(info: ProcessMessageStepStart): void;
|
|
339
|
+
onStepFinish?(info: ProcessMessageStepFinish): void;
|
|
340
|
+
onToolStart?(info: ProcessMessageToolStart): void;
|
|
341
|
+
onToolFinish?(info: ProcessMessageToolFinish): void;
|
|
342
|
+
onError?(info: ProcessMessageError): void;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const ENVIRONMENT = `ENVIRONMENT:
|
|
346
|
+
You are running inside a terminal (CLI). Your text output is rendered in a plain terminal — not a browser, not a rich text editor.
|
|
347
|
+
- Use plain text only. No markdown tables, no HTML, no images, no colored text.
|
|
348
|
+
- Use simple markers like dashes (-) or asterisks (*) for lists.
|
|
349
|
+
- Use indentation and blank lines for structure.
|
|
350
|
+
- Keep lines under 100 characters when possible.
|
|
351
|
+
- Use backticks for inline code and triple backticks for code blocks — these are rendered.
|
|
352
|
+
- Never use unicode box-drawing, fancy borders, or ASCII art in your responses.`;
|
|
353
|
+
|
|
354
|
+
const MODE_PROMPTS: Record<AgentMode, string> = {
|
|
355
|
+
agent: `You are muonroi-cli in Agent mode — a powerful AI coding agent. You execute tasks directly using tools.
|
|
356
|
+
|
|
357
|
+
${ENVIRONMENT}
|
|
358
|
+
|
|
359
|
+
TOOLS:
|
|
360
|
+
- read_file: Read file contents with start_line/end_line for iterative reading. Use for examining code.
|
|
361
|
+
- grep: Fast regex content search across the codebase. Prefer this over bash for finding patterns in files. Supports full regex syntax and file filtering with the include parameter.
|
|
362
|
+
- lsp: Experimental semantic code intelligence for definitions, references, hover, symbols, implementations, and call hierarchy when a matching language server is available.
|
|
363
|
+
- write_file: Create new files or overwrite existing ones with full content.
|
|
364
|
+
- edit_file: Replace a unique string in a file with new content. The old_string must be unique — include enough context lines.
|
|
365
|
+
- bash: Execute shell commands. Set background=true for long-running processes (dev servers, watchers, builds). Returns a process ID immediately.
|
|
366
|
+
- process_logs: View recent output from a background process by ID.
|
|
367
|
+
- process_stop: Stop a background process by ID.
|
|
368
|
+
- process_list: List all background processes with status and uptime.
|
|
369
|
+
- wallet_info: Check the local wallet address, chain, and current ETH/USDC balances.
|
|
370
|
+
- wallet_history: Show recent x402 payment history from the audit log.
|
|
371
|
+
- fetch_payment_info: Inspect a URL for x402 payment requirements without paying. Returns payment options and a brin security score. Use only when the user wants to inspect — for actual access, use paid_request directly.
|
|
372
|
+
- paid_request: Access an x402-protected URL using the local wallet. Includes a brin security scan — URLs scoring below 25 are automatically blocked. The user will be prompted to approve the payment before it executes. Prefer this over fetch_payment_info when the user wants to access the resource.
|
|
373
|
+
- task: Delegate a focused foreground task to a sub-agent. Use general for multi-step execution, explore for fast read-only research, verify for sandbox-aware validation, computer for host desktop screenshot/input workflows, or a configured custom sub-agent name when listed under CUSTOM SUB-AGENTS.
|
|
374
|
+
- delegate: Launch a read-only background agent for longer research while you continue working.
|
|
375
|
+
- delegation_read: Retrieve a completed background delegation result by ID.
|
|
376
|
+
- delegation_list: List running and completed background delegations. Do not poll it repeatedly.
|
|
377
|
+
- schedule_create: Create a recurring or one-time scheduled headless run.
|
|
378
|
+
- schedule_list: List saved schedules and their status.
|
|
379
|
+
- schedule_remove: Remove a saved schedule.
|
|
380
|
+
- schedule_read_log: Read recent log output from a schedule.
|
|
381
|
+
- schedule_daemon_status: Check whether the schedule daemon is running.
|
|
382
|
+
- schedule_daemon_start: Start the schedule daemon in the background.
|
|
383
|
+
- schedule_daemon_stop: Stop the schedule daemon.
|
|
384
|
+
- search_web: Search the web for current information, documentation, APIs, tutorials, etc.
|
|
385
|
+
- search_x: Search X/Twitter for real-time posts, discussions, opinions, and trends.
|
|
386
|
+
- generate_image: Generate a new image or edit an existing image. It saves image files locally and returns their paths.
|
|
387
|
+
- generate_video: Generate a new video or animate an existing image. It saves video files locally and returns their paths.
|
|
388
|
+
- computer_snapshot: Capture an accessibility-tree snapshot with stable refs like @e1 for desktop interaction.
|
|
389
|
+
- computer_screenshot: Capture a host desktop screenshot for visual confirmation or fallback inspection.
|
|
390
|
+
- computer_click: Click a desktop element by ref, or coordinates as a fallback.
|
|
391
|
+
- computer_mouse_move: Hover a desktop element by ref, or coordinates as a fallback.
|
|
392
|
+
- computer_type: Type text into a specific desktop element ref.
|
|
393
|
+
- computer_press: Press a key or key chord in the focused host application.
|
|
394
|
+
- computer_scroll: Scroll a desktop element by ref.
|
|
395
|
+
- computer_launch: Launch an application and wait for its window to appear.
|
|
396
|
+
- computer_list_windows: List visible windows and their ids.
|
|
397
|
+
- computer_focus_window: Bring a target window to the front.
|
|
398
|
+
- computer_wait: Wait for time, elements, windows, or text during desktop workflows.
|
|
399
|
+
- computer_get: Read a property from a desktop element ref.
|
|
400
|
+
- MCP tools: Enabled servers appear as tools named like mcp_<server>__<tool>.
|
|
401
|
+
|
|
402
|
+
WORKFLOW:
|
|
403
|
+
1. Understand the request
|
|
404
|
+
2. Decide whether a sub-agent should handle the first investigation pass
|
|
405
|
+
3. Use read_file, grep, lsp, and bash to explore the codebase directly when the task is small or tightly scoped
|
|
406
|
+
4. Use bash with background=true for dev servers, watchers, or any long-running process — then continue working
|
|
407
|
+
5. Use delegate for read-only work that can run in parallel, then continue productive work
|
|
408
|
+
6. Use edit_file for targeted changes, write_file for new files or full rewrites
|
|
409
|
+
7. Verify changes by reading modified files
|
|
410
|
+
8. Run tests or builds with bash to confirm correctness
|
|
411
|
+
9. Use search_web or search_x when you need up-to-date information
|
|
412
|
+
|
|
413
|
+
DEFAULT DELEGATION POLICY:
|
|
414
|
+
- Prefer the task tool by default for code review, code quality analysis, architecture research, root-cause investigation, bug triage, verification, or any request that likely needs reading multiple files before acting.
|
|
415
|
+
- Prefer delegate for longer-running read-only exploration when you can keep making progress without blocking.
|
|
416
|
+
- Use the explore sub-agent for read-only investigation, reviews, research, and "how does this work?" tasks.
|
|
417
|
+
- Use the general sub-agent for delegated work that may need editing files, running commands, or producing a concrete implementation.
|
|
418
|
+
- Use the verify sub-agent for sandbox-aware build, test, app boot, and smoke validation work.
|
|
419
|
+
- Use the computer sub-agent for host desktop interaction workflows that need screenshots, clicks, typing, keypresses, or scrolling.
|
|
420
|
+
- Use a matching custom sub-agent when the task fits one of the configured specializations.
|
|
421
|
+
- Never use delegate for tasks that should edit files or make shell changes.
|
|
422
|
+
- When a background delegation is running, do not wait idly and do not spam delegation_list(). Continue useful work.
|
|
423
|
+
- Do not wait for the user to explicitly ask for a sub-agent when delegation would clearly help.
|
|
424
|
+
- Skip delegation only when the task is trivial, single-file, or you already have the exact answer.
|
|
425
|
+
|
|
426
|
+
EXAMPLES:
|
|
427
|
+
- "review this change" -> delegate to explore first
|
|
428
|
+
- "research how auth works" -> delegate to explore first
|
|
429
|
+
- "investigate why this test fails" -> delegate to explore first, then continue with findings
|
|
430
|
+
- "refactor this module" -> delegate a focused part to general when helpful
|
|
431
|
+
- "verify this feature locally" -> use verify
|
|
432
|
+
- "open the host app and click through it" -> use computer
|
|
433
|
+
- "generate a logo" -> use generate_image
|
|
434
|
+
- "animate this still image" -> use generate_video
|
|
435
|
+
- Recurring specialized workflows -> use the matching custom sub-agent via task
|
|
436
|
+
- "every weekday at 9am run this check" -> use schedule_create with a cron expression
|
|
437
|
+
- "run this once automatically" -> use schedule_create with the right timing
|
|
438
|
+
- "make sure scheduled jobs keep running" -> use schedule_daemon_status and schedule_daemon_start
|
|
439
|
+
|
|
440
|
+
IMPORTANT:
|
|
441
|
+
- Prefer edit_file for surgical changes to existing files — it shows a clean diff.
|
|
442
|
+
- Prefer grep over bash for searching file contents. Use bash only for find, ls, git, and other shell commands.
|
|
443
|
+
- Prefer lsp over text search when you need exact definitions, references, implementations, or call hierarchy and a server is available.
|
|
444
|
+
- Use write_file only for new files or when most of the file is changing.
|
|
445
|
+
- Use read_file instead of cat/head/tail for reading files.
|
|
446
|
+
- When the user asks for an automated recurring or one-time run, use the schedule tools instead of only describing the setup.
|
|
447
|
+
- After creating a recurring schedule, check the daemon status and start it with \`schedule_daemon_start\` if needed.
|
|
448
|
+
|
|
449
|
+
Be direct. Execute, don't just describe. Show results, not plans.`,
|
|
450
|
+
|
|
451
|
+
plan: `You are muonroi-cli in Plan mode — you analyze and plan but DO NOT execute changes.
|
|
452
|
+
|
|
453
|
+
${ENVIRONMENT}
|
|
454
|
+
|
|
455
|
+
TOOLS:
|
|
456
|
+
- read_file: Read file contents for analysis.
|
|
457
|
+
- grep: Fast regex content search across the codebase. Prefer this over bash for finding patterns in files.
|
|
458
|
+
- lsp: Experimental semantic code intelligence for read-only planning and research.
|
|
459
|
+
- bash: ONLY for searching (find, ls), git inspection — NEVER modify files.
|
|
460
|
+
- task: Delegate a focused task to a sub-agent when deeper research or specialized analysis would help.
|
|
461
|
+
- generate_plan: ALWAYS use this to present your plan. Creates an interactive UI with steps and questions.
|
|
462
|
+
|
|
463
|
+
BEHAVIOR:
|
|
464
|
+
- Explore the codebase first using read_file, grep, and bash to understand the current state
|
|
465
|
+
- Prefer lsp for exact symbol navigation when a matching server is available
|
|
466
|
+
- ALWAYS call generate_plan to present your plan — never just describe it in text
|
|
467
|
+
- Include clear, ordered steps with affected file paths
|
|
468
|
+
- Include questions when you need user input on approach, trade-offs, or preferences
|
|
469
|
+
- Use "select" questions for single-choice decisions, "multiselect" for picking multiple options, and "text" for free-form input
|
|
470
|
+
- Highlight potential risks, edge cases, and dependencies in the plan summary
|
|
471
|
+
- NEVER create, modify, or delete files — only read and analyze`,
|
|
472
|
+
|
|
473
|
+
ask: `You are muonroi-cli in Ask mode — you answer questions clearly and thoroughly.
|
|
474
|
+
|
|
475
|
+
${ENVIRONMENT}
|
|
476
|
+
|
|
477
|
+
TOOLS:
|
|
478
|
+
- read_file: Read file contents for context.
|
|
479
|
+
- grep: Fast regex content search across the codebase. Prefer this over bash for finding patterns in files.
|
|
480
|
+
- lsp: Experimental semantic code intelligence for definitions, references, hover, and symbols.
|
|
481
|
+
- bash: ONLY for searching (find, ls), git inspection — NEVER modify.
|
|
482
|
+
- task: Delegate a focused task to a sub-agent when specialized analysis or deeper investigation would help.
|
|
483
|
+
|
|
484
|
+
BEHAVIOR:
|
|
485
|
+
- Answer the user's question directly and thoroughly
|
|
486
|
+
- Use tools to gather context when needed, preferring lsp for exact symbol questions when available
|
|
487
|
+
- Provide code examples when helpful
|
|
488
|
+
- NEVER create, modify, or delete files
|
|
489
|
+
- Focus on explanation, not execution`,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
function findCustomSubagent(
|
|
493
|
+
agent: string,
|
|
494
|
+
subagents: CustomSubagentConfig[] = loadValidSubAgents(),
|
|
495
|
+
): CustomSubagentConfig | undefined {
|
|
496
|
+
return (
|
|
497
|
+
subagents.find((item) => item.name === agent) ??
|
|
498
|
+
subagents.find((item) => item.name.toLowerCase() === agent.toLowerCase())
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function formatCustomSubagentsPromptSection(subagents: CustomSubagentConfig[]): string {
|
|
503
|
+
if (subagents.length === 0) return "";
|
|
504
|
+
|
|
505
|
+
const lines = subagents.map((agent) => {
|
|
506
|
+
const instruction = agent.instruction.trim() || "(none)";
|
|
507
|
+
return `### ${agent.name}\n- model: ${agent.model}\n- instruction:\n${instruction}`;
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return `\n\nCUSTOM SUB-AGENTS:\nUser-defined foreground sub-agents from ~/.muonroi-cli/user-settings.json. When one matches the task, call the task tool with agent set to the exact name.\n\n${lines.join("\n\n")}\n`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface SystemPromptParts {
|
|
514
|
+
staticPrefix: string;
|
|
515
|
+
dynamicSuffix: string;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const NON_ANTHROPIC_TOOL_PREAMBLE = `\n\nIMPORTANT — TOOL CALLING:
|
|
519
|
+
You MUST invoke tools ONLY via the structured function calling API provided to you.
|
|
520
|
+
NEVER output XML tags like <tool_name>, <bash>, <read_file>, or <delegate> as text.
|
|
521
|
+
If you want to call a tool, use the function calling mechanism — do NOT write tool invocations as text in your response.
|
|
522
|
+
Any XML-like tool invocation in your text output will be ignored by the system.\n`;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Strip the TOOLS: listing section from system prompt.
|
|
526
|
+
* Non-Anthropic models receive tool definitions via the API's structured `tools` parameter;
|
|
527
|
+
* keeping the text listing causes them to output raw XML instead of structured tool calls.
|
|
528
|
+
*/
|
|
529
|
+
function stripToolsSection(text: string): string {
|
|
530
|
+
return text.replace(/\nTOOLS:\n[\s\S]*?\n(?=WORKFLOW:|BEHAVIOR:|IMPORTANT:|DEFAULT DELEGATION|EXAMPLES:|$)/g, "\n");
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function buildSystemPromptParts(
|
|
534
|
+
cwd: string,
|
|
535
|
+
mode: AgentMode,
|
|
536
|
+
sandboxMode: SandboxMode,
|
|
537
|
+
planContext?: string | null,
|
|
538
|
+
subagents?: CustomSubagentConfig[],
|
|
539
|
+
sandboxSettings?: SandboxSettings,
|
|
540
|
+
providerId?: string,
|
|
541
|
+
): SystemPromptParts {
|
|
542
|
+
const custom = loadCustomInstructions(cwd);
|
|
543
|
+
const customSection = custom
|
|
544
|
+
? `\n\nCUSTOM INSTRUCTIONS:\n${custom}\n\nFollow the above alongside standard instructions.\n`
|
|
545
|
+
: "";
|
|
546
|
+
|
|
547
|
+
const skillsText = formatSkillsForPrompt(discoverSkills(cwd));
|
|
548
|
+
const skillsSection = skillsText ? `\n\n${skillsText}\n` : "";
|
|
549
|
+
const subagentsSection = formatCustomSubagentsPromptSection(subagents ?? loadValidSubAgents());
|
|
550
|
+
const sandboxSection = formatSandboxPromptSection(sandboxMode, sandboxSettings);
|
|
551
|
+
|
|
552
|
+
let modePrompt = MODE_PROMPTS[mode];
|
|
553
|
+
if (providerId && providerId !== "anthropic") {
|
|
554
|
+
modePrompt = stripToolsSection(modePrompt) + NON_ANTHROPIC_TOOL_PREAMBLE;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const staticPrefix = `${modePrompt}${sandboxSection}${customSection}${skillsSection}${subagentsSection}`;
|
|
558
|
+
|
|
559
|
+
const planSection = planContext
|
|
560
|
+
? `\n\nAPPROVED PLAN:\nThe following plan has been approved by the user. Execute it now.\n${planContext}\n`
|
|
561
|
+
: "";
|
|
562
|
+
|
|
563
|
+
const dynamicSuffix = `${planSection}\n\nCurrent working directory: ${cwd}`;
|
|
564
|
+
|
|
565
|
+
return { staticPrefix, dynamicSuffix };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function buildSystemPrompt(
|
|
569
|
+
cwd: string,
|
|
570
|
+
mode: AgentMode,
|
|
571
|
+
sandboxMode: SandboxMode,
|
|
572
|
+
planContext?: string | null,
|
|
573
|
+
subagents?: CustomSubagentConfig[],
|
|
574
|
+
sandboxSettings?: SandboxSettings,
|
|
575
|
+
providerId?: string,
|
|
576
|
+
): string {
|
|
577
|
+
const { staticPrefix, dynamicSuffix } = buildSystemPromptParts(cwd, mode, sandboxMode, planContext, subagents, sandboxSettings, providerId);
|
|
578
|
+
return `${staticPrefix}${dynamicSuffix}`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function buildSubagentPrompt(
|
|
582
|
+
request: TaskRequest,
|
|
583
|
+
cwd: string,
|
|
584
|
+
custom: CustomSubagentConfig | null,
|
|
585
|
+
sandboxMode: SandboxMode,
|
|
586
|
+
subagents?: CustomSubagentConfig[],
|
|
587
|
+
sandboxSettings?: SandboxSettings,
|
|
588
|
+
providerId?: string,
|
|
589
|
+
): string {
|
|
590
|
+
const isExplore = request.agent === "explore";
|
|
591
|
+
const isVision = request.agent === "vision";
|
|
592
|
+
const isVerify = request.agent === "verify";
|
|
593
|
+
const isVerifyDetect = request.agent === "verify-detect";
|
|
594
|
+
const isVerifyManifest = request.agent === "verify-manifest";
|
|
595
|
+
const isComputer = request.agent === "computer";
|
|
596
|
+
const mode: AgentMode = isExplore || isVerifyDetect ? "ask" : "agent";
|
|
597
|
+
const role = custom
|
|
598
|
+
? `You are the custom sub-agent "${custom.name}". You can investigate, edit files, and run commands unless the delegated task says otherwise.`
|
|
599
|
+
: request.agent === "explore"
|
|
600
|
+
? "You are the Explore sub-agent. You are read-only and focus on fast codebase research."
|
|
601
|
+
: isVision
|
|
602
|
+
? "You are the Vision sub-agent."
|
|
603
|
+
: isVerifyDetect
|
|
604
|
+
? "You are the Verify Detect sub-agent. You inspect a repository to produce a structured verification recipe. You are read-only."
|
|
605
|
+
: isVerifyManifest
|
|
606
|
+
? "You are the Verify Manifest sub-agent. You inspect a repository and create or update .muonroi-cli/environment.json so verification can run reproducibly."
|
|
607
|
+
: isVerify
|
|
608
|
+
? "You are the Verify sub-agent. You specialize in sandbox-aware local verification using builds, tests, app boot checks, and optional browser smoke tests."
|
|
609
|
+
: isComputer
|
|
610
|
+
? "You are the Computer sub-agent. You specialize in host desktop automation using accessibility snapshots, semantic element refs, screenshots, and careful mouse and keyboard actions."
|
|
611
|
+
: "You are the General sub-agent. You can investigate, edit files, and run commands to complete delegated work.";
|
|
612
|
+
|
|
613
|
+
const rules = isExplore
|
|
614
|
+
? [
|
|
615
|
+
"Do not create, modify, or delete files.",
|
|
616
|
+
"Prefer `read_file` and search commands over broad shell exploration.",
|
|
617
|
+
"Return concise findings for the parent agent.",
|
|
618
|
+
]
|
|
619
|
+
: isVerifyDetect
|
|
620
|
+
? [
|
|
621
|
+
"Do not create, modify, or delete files.",
|
|
622
|
+
"Read config files, package manifests, scripts, and source layout to understand the project.",
|
|
623
|
+
"Return ONLY a valid JSON object with the VerifyRecipe schema. No markdown, no prose, no explanation outside the JSON.",
|
|
624
|
+
]
|
|
625
|
+
: isVerifyManifest
|
|
626
|
+
? [
|
|
627
|
+
"Focus on creating or updating .muonroi-cli/environment.json as the primary verification contract for this repository.",
|
|
628
|
+
"Read package.json and key config files to understand the project, then write .muonroi-cli/environment.json.",
|
|
629
|
+
"Prefer editing only .muonroi-cli/environment.json unless the delegated task explicitly requires something else.",
|
|
630
|
+
"",
|
|
631
|
+
"SANDBOX ENVIRONMENT (Shuru):",
|
|
632
|
+
"- OS: Debian GNU/Linux 13 (trixie)",
|
|
633
|
+
"- Architecture: aarch64 (ARM64)",
|
|
634
|
+
"- Pre-installed: NOTHING. No node, npm, npx, bun, python3, pip, go, cargo, java, or any runtime.",
|
|
635
|
+
"- Only basic system tools exist (sh, apt-get, curl, etc).",
|
|
636
|
+
"- Network access is available during bootstrap and install.",
|
|
637
|
+
"- The workspace is mounted at /workspace.",
|
|
638
|
+
"",
|
|
639
|
+
"MANIFEST REQUIREMENTS:",
|
|
640
|
+
"- bootstrapCommands: MUST install every runtime and build tool the project needs from scratch via apt-get or curl.",
|
|
641
|
+
"- For Node.js/Next.js/Vite/etc: `apt-get update && apt-get install -y curl unzip ca-certificates git python3 make g++ pkg-config nodejs npm`",
|
|
642
|
+
"- For Bun projects: also `curl -fsSL https://bun.sh/install | bash` and shellInitCommands with BUN_INSTALL/PATH exports.",
|
|
643
|
+
"- For Python: `apt-get update && apt-get install -y python3 python3-pip python3-venv ca-certificates git`",
|
|
644
|
+
"- For Go: `apt-get update && apt-get install -y golang ca-certificates git`",
|
|
645
|
+
"- For Rust: `apt-get update && apt-get install -y curl ca-certificates git build-essential && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`",
|
|
646
|
+
"- installCommands: The package install command (npm install, pip install, etc).",
|
|
647
|
+
"- buildCommands: Build commands if applicable.",
|
|
648
|
+
"- testCommands: Test/lint commands if applicable.",
|
|
649
|
+
"- startCommand + startPort: How to start the app for smoke testing.",
|
|
650
|
+
"- smokeKind: 'http' if the app has a web UI, 'cli' for CLI tools, 'none' otherwise.",
|
|
651
|
+
"- Do NOT leave bootstrapCommands empty. The sandbox has nothing.",
|
|
652
|
+
"",
|
|
653
|
+
"Return a concise summary of what you wrote and why.",
|
|
654
|
+
]
|
|
655
|
+
: isVision
|
|
656
|
+
? ["Validate the image."]
|
|
657
|
+
: isComputer
|
|
658
|
+
? [
|
|
659
|
+
"Operate carefully on the HOST desktop, not inside the shell sandbox.",
|
|
660
|
+
"Start with `computer_snapshot` when possible. It returns stable refs like @e1 that remain valid until the next snapshot.",
|
|
661
|
+
"Prefer accessibility refs over coordinates. Use `computer_click`, `computer_type`, `computer_scroll`, and `computer_get` with refs from the latest snapshot.",
|
|
662
|
+
"After any meaningful UI transition, launch, dialog open, or menu change, take another `computer_snapshot` before reusing old refs.",
|
|
663
|
+
"Use `computer_launch`, `computer_list_windows`, `computer_focus_window`, and `computer_wait` to manage apps and window state.",
|
|
664
|
+
"Use `computer_press` for shortcuts like Enter or cmd+k. Use `computer_screenshot` only for visual confirmation or when the accessibility tree is insufficient.",
|
|
665
|
+
"If `agent-desktop` is unavailable, permissions are missing, refs go stale, or the state is ambiguous, stop and return the blocker clearly to the parent agent.",
|
|
666
|
+
"Do not perform destructive or high-risk desktop actions unless the delegated task explicitly requires them.",
|
|
667
|
+
]
|
|
668
|
+
: isVerify
|
|
669
|
+
? [
|
|
670
|
+
"You are a QA engineer. Your job is to prove the app works end-to-end, not just that it builds.",
|
|
671
|
+
"Do not make durable source edits unless the delegated task explicitly asks for fixes.",
|
|
672
|
+
"",
|
|
673
|
+
"MANDATORY VERIFICATION STEPS (do ALL of these in order):",
|
|
674
|
+
"1. Install dependencies (run installCommands from the recipe).",
|
|
675
|
+
"2. Build the project (run buildCommands from the recipe).",
|
|
676
|
+
"3. Run tests/lint if available (run testCommands from the recipe).",
|
|
677
|
+
"4. Start the app (run startCommand from the recipe in the background).",
|
|
678
|
+
"5. Wait for the app to be ready (curl readiness check or agent-browser wait).",
|
|
679
|
+
"6. Run browser smoke tests like a real human QA tester:",
|
|
680
|
+
" - Open the app in the browser, record a video, take screenshots.",
|
|
681
|
+
" - Navigate the app: click links, buttons, menus. Verify pages load.",
|
|
682
|
+
" - Check for JavaScript console errors.",
|
|
683
|
+
" - Spend 3-5 interactions testing the critical path.",
|
|
684
|
+
"7. Stop recording, close browser, then stop the dev server.",
|
|
685
|
+
"",
|
|
686
|
+
"Do NOT stop after build/lint. Starting the app and testing it in the browser is the most important part.",
|
|
687
|
+
"agent-browser commands run on the HOST, not inside the sandbox. They WILL work. Do not skip them.",
|
|
688
|
+
"Return a concise verification report. Keep it compact but always include Evidence with artifact file paths.",
|
|
689
|
+
]
|
|
690
|
+
: [
|
|
691
|
+
"Work only on the delegated task below.",
|
|
692
|
+
"Use tools directly instead of narrating your intent.",
|
|
693
|
+
"Return a concise summary for the parent agent with key outcomes and any open risks.",
|
|
694
|
+
];
|
|
695
|
+
|
|
696
|
+
const instructionLines = custom?.instruction.trim() ? ["", "SUB-AGENT INSTRUCTIONS:", custom.instruction.trim()] : [];
|
|
697
|
+
|
|
698
|
+
return [
|
|
699
|
+
role,
|
|
700
|
+
...instructionLines,
|
|
701
|
+
"",
|
|
702
|
+
"You are helping a parent agent. Do not address the end user directly.",
|
|
703
|
+
"Focus tightly on the delegated scope and summarize what matters back to the parent agent.",
|
|
704
|
+
"",
|
|
705
|
+
...rules,
|
|
706
|
+
"",
|
|
707
|
+
`Delegated task: ${request.description}`,
|
|
708
|
+
"",
|
|
709
|
+
buildSystemPrompt(cwd, mode, sandboxMode, undefined, subagents, sandboxSettings, providerId),
|
|
710
|
+
].join("\n");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function formatSandboxPromptSection(sandboxMode: SandboxMode, settings?: SandboxSettings): string {
|
|
714
|
+
if (sandboxMode === "off") return "";
|
|
715
|
+
|
|
716
|
+
const s = settings ?? {};
|
|
717
|
+
let networkLine: string;
|
|
718
|
+
if (s.allowNet) {
|
|
719
|
+
networkLine = s.allowedHosts?.length
|
|
720
|
+
? `- Network access is restricted to: ${s.allowedHosts.join(", ")}.`
|
|
721
|
+
: "- Network access is enabled.";
|
|
722
|
+
} else {
|
|
723
|
+
networkLine = "- Network is disabled.";
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const lines = [
|
|
727
|
+
"",
|
|
728
|
+
"SANDBOX MODE:",
|
|
729
|
+
"- Bash commands run inside a Shuru sandbox.",
|
|
730
|
+
networkLine,
|
|
731
|
+
"- The current workspace is mounted inside the sandbox at `/workspace`.",
|
|
732
|
+
"- Shell-side workspace file changes do not persist back to the host in this version.",
|
|
733
|
+
"- Use `read_file`, `edit_file`, and `write_file` for durable source edits.",
|
|
734
|
+
"- If a task needs a host-persistent shell mutation, explain that sandbox mode blocks that workflow and ask whether to disable sandbox mode.",
|
|
735
|
+
];
|
|
736
|
+
|
|
737
|
+
if (s.ports?.length) {
|
|
738
|
+
lines.push(`- Port forwards: ${s.ports.join(", ")}.`);
|
|
739
|
+
}
|
|
740
|
+
if (s.from) {
|
|
741
|
+
lines.push(`- Starting from checkpoint: ${s.from}.`);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return lines.join("\n");
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function applyModelConstraints(system: string, modelId: string): string {
|
|
748
|
+
const modelInfo = getModelInfo(modelId);
|
|
749
|
+
if (modelInfo?.supportsClientTools !== false) {
|
|
750
|
+
return system;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return [
|
|
754
|
+
system,
|
|
755
|
+
"",
|
|
756
|
+
"MODEL CONSTRAINTS:",
|
|
757
|
+
"- The selected model does not support client-side CLI tool calls in this environment.",
|
|
758
|
+
"- Do not call bash, read_file, lsp, write_file, edit_file, task, delegate, delegation, or MCP tools.",
|
|
759
|
+
"- Answer directly using only the conversation context already provided.",
|
|
760
|
+
].join("\n");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export class Agent {
|
|
764
|
+
private provider: LegacyProvider | null = null;
|
|
765
|
+
private providerId: ProviderId = "anthropic";
|
|
766
|
+
private apiKey: string | null = null;
|
|
767
|
+
private baseURL: string | null = null;
|
|
768
|
+
private bash: BashTool;
|
|
769
|
+
private delegations: DelegationManager;
|
|
770
|
+
private schedules: ScheduleManager;
|
|
771
|
+
private sessionStore: SessionStore | null = null;
|
|
772
|
+
private workspace: WorkspaceInfo | null = null;
|
|
773
|
+
private session: SessionInfo | null = null;
|
|
774
|
+
private messages: ModelMessage[] = [];
|
|
775
|
+
private messageSeqs: Array<number | null> = [];
|
|
776
|
+
private abortController: AbortController | null = null;
|
|
777
|
+
private maxToolRounds: number;
|
|
778
|
+
private mode: AgentMode = "agent";
|
|
779
|
+
private modelId: string;
|
|
780
|
+
private maxTokens: number;
|
|
781
|
+
private planContext: string | null = null;
|
|
782
|
+
private subagentStatusListeners = new Set<(status: SubagentStatus | null) => void>();
|
|
783
|
+
private sendTelegramFile: ((filePath: string) => Promise<ToolResult>) | null = null;
|
|
784
|
+
private batchApi = false;
|
|
785
|
+
private sessionStartHookFired = false;
|
|
786
|
+
/** PIL context for current turn — set after runPipeline, cleared after recordUsage. */
|
|
787
|
+
private _pilActive = false;
|
|
788
|
+
private _pilEnrichmentDelta = 0;
|
|
789
|
+
/** External abort context from src/index.ts SIGINT handler (TUI-04). */
|
|
790
|
+
private externalAbortContext: import("./abort.js").AbortContext | null = null;
|
|
791
|
+
/** Pending calls log for Pitfall 9 staged-write tracking. */
|
|
792
|
+
private pendingCalls: import("./pending-calls.js").PendingCallsLog | null = null;
|
|
793
|
+
/** Active permission mode — controls which tool calls auto-approve vs require user confirmation. */
|
|
794
|
+
private permissionMode: PermissionMode = "safe";
|
|
795
|
+
/** Flow run init promise — awaited before first message turn. */
|
|
796
|
+
private _flowReady: Promise<void> | null = null;
|
|
797
|
+
/** Active .muonroi-flow/ run ID for this session. */
|
|
798
|
+
private _activeRunId: string | null = null;
|
|
799
|
+
/** Resume digest loaded from active flow run state.md. */
|
|
800
|
+
private _resumeDigest: string | null = null;
|
|
801
|
+
/** Whether compaction already ran during the current turn (prevents double-compact). */
|
|
802
|
+
private _compactedThisTurn = false;
|
|
803
|
+
|
|
804
|
+
constructor(
|
|
805
|
+
apiKey: string | undefined,
|
|
806
|
+
baseURL?: string,
|
|
807
|
+
model?: string,
|
|
808
|
+
maxToolRounds?: number,
|
|
809
|
+
options: AgentOptions = {},
|
|
810
|
+
) {
|
|
811
|
+
this.baseURL = baseURL || null;
|
|
812
|
+
this.bash = new BashTool(process.cwd(), {
|
|
813
|
+
sandboxMode: options.sandboxMode ?? "off",
|
|
814
|
+
sandboxSettings: options.sandboxSettings,
|
|
815
|
+
});
|
|
816
|
+
this.delegations = new DelegationManager(() => this.bash.getCwd());
|
|
817
|
+
|
|
818
|
+
const initialMode: AgentMode = "agent";
|
|
819
|
+
this.modelId = normalizeModelId(model || getCurrentModel(initialMode));
|
|
820
|
+
this.providerId = detectProviderForModel(this.modelId);
|
|
821
|
+
if (apiKey) {
|
|
822
|
+
this.setApiKey(apiKey, baseURL);
|
|
823
|
+
}
|
|
824
|
+
this.schedules = new ScheduleManager(
|
|
825
|
+
() => this.bash.getCwd(),
|
|
826
|
+
() => this.modelId,
|
|
827
|
+
);
|
|
828
|
+
this.maxToolRounds = maxToolRounds || MAX_TOOL_ROUNDS;
|
|
829
|
+
const envMax = Number(process.env.MUONROI_MAX_TOKENS);
|
|
830
|
+
this.maxTokens = Number.isFinite(envMax) && envMax > 0 ? envMax : 16_384;
|
|
831
|
+
this.batchApi = options.batchApi ?? false;
|
|
832
|
+
// TUI-04: wire external abort context and pending calls log if provided.
|
|
833
|
+
this.externalAbortContext = options.abortContext ?? null;
|
|
834
|
+
this.pendingCalls = options.pendingCalls ?? null;
|
|
835
|
+
this.permissionMode = options.permissionMode ?? "safe";
|
|
836
|
+
ensureDefaultMcpServers();
|
|
837
|
+
|
|
838
|
+
if (options.persistSession !== false) {
|
|
839
|
+
this.sessionStore = new SessionStore(this.bash.getCwd());
|
|
840
|
+
this.workspace = this.sessionStore.getWorkspace();
|
|
841
|
+
this.session = this.sessionStore.openSession(options.session, this.modelId, this.mode, this.bash.getCwd());
|
|
842
|
+
this.mode = this.session.mode;
|
|
843
|
+
const transcript = loadTranscriptState(this.session.id);
|
|
844
|
+
this.messages = transcript.messages;
|
|
845
|
+
this.messageSeqs = transcript.seqs;
|
|
846
|
+
this.sessionStore.setModel(this.session.id, this.modelId);
|
|
847
|
+
|
|
848
|
+
// Flow run setup — fire-and-forget, awaited before first message turn.
|
|
849
|
+
this._flowReady = this._initFlow();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Initialize .muonroi-flow/ run for this session.
|
|
855
|
+
* Fail-open: any error sets _activeRunId = null silently.
|
|
856
|
+
*/
|
|
857
|
+
private async _initFlow(): Promise<void> {
|
|
858
|
+
bootstrapEEClient().catch(() => {});
|
|
859
|
+
try {
|
|
860
|
+
const flowDir = await ensureFlowDir(this.bash.getCwd());
|
|
861
|
+
const existing = await getActiveRunId(flowDir);
|
|
862
|
+
if (existing) {
|
|
863
|
+
this._activeRunId = existing;
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const run = await createRun(flowDir);
|
|
867
|
+
await setActiveRunId(flowDir, run.id);
|
|
868
|
+
this._activeRunId = run.id;
|
|
869
|
+
} catch {
|
|
870
|
+
this._activeRunId = null;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Load resume digest for PIL context injection (fail-open).
|
|
874
|
+
try {
|
|
875
|
+
this._resumeDigest = await loadFlowResumeDigest(this.bash.getCwd());
|
|
876
|
+
} catch {
|
|
877
|
+
this._resumeDigest = null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
getModel(): string {
|
|
882
|
+
return this.modelId;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
getActiveRunId(): string | null {
|
|
886
|
+
return this._activeRunId;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
setModel(model: string): void {
|
|
890
|
+
this.modelId = normalizeModelId(model);
|
|
891
|
+
const newProviderId = detectProviderForModel(this.modelId);
|
|
892
|
+
if (newProviderId !== this.providerId && this.apiKey) {
|
|
893
|
+
this.providerId = newProviderId;
|
|
894
|
+
const effectiveBaseURL = this.providerId !== "anthropic" && this.baseURL === "https://api.anthropic.com"
|
|
895
|
+
? undefined
|
|
896
|
+
: (this.baseURL ?? undefined);
|
|
897
|
+
this.provider = createProvider(this.providerId, this.apiKey, effectiveBaseURL);
|
|
898
|
+
}
|
|
899
|
+
if (this.sessionStore && this.session) {
|
|
900
|
+
this.sessionStore.setModel(this.session.id, this.modelId);
|
|
901
|
+
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
getMode(): AgentMode {
|
|
906
|
+
return this.mode;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
getSandboxMode(): SandboxMode {
|
|
910
|
+
return this.bash.getSandboxMode();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
setSandboxMode(mode: SandboxMode): void {
|
|
914
|
+
this.bash.setSandboxMode(mode);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
getSandboxSettings(): SandboxSettings {
|
|
918
|
+
return this.bash.getSandboxSettings();
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
setSandboxSettings(settings: SandboxSettings): void {
|
|
922
|
+
this.bash.setSandboxSettings(settings);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
setMode(mode: AgentMode): void {
|
|
926
|
+
if (mode !== this.mode) {
|
|
927
|
+
this.mode = mode;
|
|
928
|
+
const modeModel = getModeSpecificModel(mode);
|
|
929
|
+
if (modeModel) {
|
|
930
|
+
this.modelId = normalizeModelId(modeModel);
|
|
931
|
+
}
|
|
932
|
+
if (this.sessionStore && this.session) {
|
|
933
|
+
this.sessionStore.setMode(this.session.id, mode);
|
|
934
|
+
this.sessionStore.setModel(this.session.id, this.modelId);
|
|
935
|
+
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
setPlanContext(ctx: string | null): void {
|
|
941
|
+
this.planContext = ctx;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
setSendTelegramFile(fn: ((filePath: string) => Promise<ToolResult>) | null): void {
|
|
945
|
+
this.sendTelegramFile = fn;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
hasApiKey(): boolean {
|
|
949
|
+
return !!this.apiKey;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
setApiKey(apiKey: string, baseURL?: string): void {
|
|
953
|
+
this.apiKey = apiKey;
|
|
954
|
+
this.baseURL = baseURL || null;
|
|
955
|
+
// Only pass baseURL to provider factory if it's an explicit override,
|
|
956
|
+
// not the default Anthropic URL (which would break non-Anthropic providers).
|
|
957
|
+
const effectiveBaseURL = this.providerId !== "anthropic" && baseURL === "https://api.anthropic.com"
|
|
958
|
+
? undefined
|
|
959
|
+
: baseURL;
|
|
960
|
+
this.provider = createProvider(this.providerId, apiKey, effectiveBaseURL);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
setProviderAndKey(providerId: ProviderId, apiKey: string, baseURL?: string): void {
|
|
964
|
+
this.providerId = providerId;
|
|
965
|
+
this.setApiKey(apiKey, baseURL);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
getProviderId(): ProviderId {
|
|
969
|
+
return this.providerId;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
getCwd(): string {
|
|
973
|
+
return this.bash.getCwd();
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
getMessages(): ModelMessage[] {
|
|
977
|
+
return this.messages;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async listSchedules(): Promise<StoredSchedule[]> {
|
|
981
|
+
return this.schedules.list();
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
async removeSchedule(id: string): Promise<string> {
|
|
985
|
+
const removed = await this.schedules.remove(id);
|
|
986
|
+
return removed ? `Removed schedule "${removed.name}".` : `Schedule "${id}" not found.`;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
async getScheduleDaemonStatus(): Promise<ScheduleDaemonStatus> {
|
|
990
|
+
return this.schedules.getDaemonStatus();
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
getContextStats(
|
|
994
|
+
contextWindow: number,
|
|
995
|
+
inFlightText = "",
|
|
996
|
+
): {
|
|
997
|
+
contextWindow: number;
|
|
998
|
+
usedTokens: number;
|
|
999
|
+
remainingTokens: number;
|
|
1000
|
+
ratioUsed: number;
|
|
1001
|
+
ratioRemaining: number;
|
|
1002
|
+
} {
|
|
1003
|
+
const system = buildSystemPrompt(
|
|
1004
|
+
this.bash.getCwd(),
|
|
1005
|
+
this.mode,
|
|
1006
|
+
this.bash.getSandboxMode(),
|
|
1007
|
+
this.planContext,
|
|
1008
|
+
undefined,
|
|
1009
|
+
this.bash.getSandboxSettings(),
|
|
1010
|
+
this.providerId,
|
|
1011
|
+
);
|
|
1012
|
+
const usedTokens = Math.min(contextWindow, estimateConversationTokens(system, this.messages, inFlightText));
|
|
1013
|
+
const remainingTokens = Math.max(0, contextWindow - usedTokens);
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
contextWindow,
|
|
1017
|
+
usedTokens,
|
|
1018
|
+
remainingTokens,
|
|
1019
|
+
ratioUsed: usedTokens / contextWindow,
|
|
1020
|
+
ratioRemaining: remainingTokens / contextWindow,
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async generateTitle(userMessage: string): Promise<string> {
|
|
1025
|
+
const provider = this.provider;
|
|
1026
|
+
if (!provider) {
|
|
1027
|
+
return "New session";
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const generated = await genTitle(provider, userMessage);
|
|
1031
|
+
this.recordUsage(generated.usage, "title", generated.modelId);
|
|
1032
|
+
if (this.sessionStore && this.session && !this.session.title && generated.title) {
|
|
1033
|
+
this.sessionStore.setTitle(this.session.id, generated.title);
|
|
1034
|
+
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
|
1035
|
+
}
|
|
1036
|
+
return generated.title;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
async askSideQuestion(question: string, signal?: AbortSignal): Promise<SideQuestionResult> {
|
|
1040
|
+
if (!this.provider) {
|
|
1041
|
+
return { response: "No API key configured." };
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const contextParts: string[] = [];
|
|
1045
|
+
let charBudget = 2000;
|
|
1046
|
+
for (let i = this.messages.length - 1; i >= 0 && charBudget > 0; i--) {
|
|
1047
|
+
const msg = this.messages[i];
|
|
1048
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
1049
|
+
const text =
|
|
1050
|
+
typeof msg.content === "string"
|
|
1051
|
+
? msg.content
|
|
1052
|
+
: Array.isArray(msg.content)
|
|
1053
|
+
? msg.content
|
|
1054
|
+
.filter((p: { type: string }) => p.type === "text")
|
|
1055
|
+
.map((p: { type: string; text?: string }) => p.text ?? "")
|
|
1056
|
+
.join("")
|
|
1057
|
+
: "";
|
|
1058
|
+
if (!text) continue;
|
|
1059
|
+
const snippet = text.length > 400 ? `${text.slice(0, 400)}…` : text;
|
|
1060
|
+
contextParts.unshift(`[${msg.role}]: ${snippet}`);
|
|
1061
|
+
charBudget -= snippet.length;
|
|
1062
|
+
}
|
|
1063
|
+
const conversationContext = contextParts.join("\n\n");
|
|
1064
|
+
|
|
1065
|
+
const result = await runSideQuestion(question, this.provider, this.modelId, conversationContext, signal);
|
|
1066
|
+
this.recordUsage(result.usage, "other");
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
abort(): void {
|
|
1071
|
+
this.abortController?.abort();
|
|
1072
|
+
this.emitSubagentStatus(null);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
async cleanup(): Promise<void> {
|
|
1076
|
+
await Promise.allSettled([
|
|
1077
|
+
this.bash.cleanup(),
|
|
1078
|
+
shutdownWorkspaceLspManager(this.bash.getCwd()),
|
|
1079
|
+
extractSession(this.messages, this.bash.getCwd(), "cli-exit", this.getSessionId()),
|
|
1080
|
+
]);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
respondToToolApproval(approvalId: string, approved: boolean): void {
|
|
1084
|
+
const toolApprovalResponse: ModelMessage = {
|
|
1085
|
+
role: "tool",
|
|
1086
|
+
content: [
|
|
1087
|
+
{
|
|
1088
|
+
type: "tool-approval-response" as const,
|
|
1089
|
+
approvalId,
|
|
1090
|
+
approved,
|
|
1091
|
+
},
|
|
1092
|
+
],
|
|
1093
|
+
};
|
|
1094
|
+
this.messages.push(toolApprovalResponse);
|
|
1095
|
+
this.messageSeqs.push(null);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async clearHistory(): Promise<void> {
|
|
1099
|
+
// D-09: Extract messages accumulated since last clear BEFORE reset
|
|
1100
|
+
await extractSession(this.messages, this.bash.getCwd(), "cli-clear", this.getSessionId())
|
|
1101
|
+
.catch(() => {}); // D-05: redundant safety — extractSession already swallows
|
|
1102
|
+
this.startNewSession();
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
startNewSession(): SessionSnapshot | null {
|
|
1106
|
+
if (this.sessionStartHookFired) {
|
|
1107
|
+
const endInput: SessionEndHookInput = {
|
|
1108
|
+
hook_event_name: "SessionEnd",
|
|
1109
|
+
session_id: this.session?.id,
|
|
1110
|
+
cwd: this.bash.getCwd(),
|
|
1111
|
+
};
|
|
1112
|
+
this.fireHook(endInput).catch(() => {});
|
|
1113
|
+
this.sessionStartHookFired = false;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Reset token counters and cost for the new session
|
|
1117
|
+
statusBarStore.setState({
|
|
1118
|
+
in_tokens: 0,
|
|
1119
|
+
out_tokens: 0,
|
|
1120
|
+
cache_read_tokens: 0,
|
|
1121
|
+
cache_creation_tokens: 0,
|
|
1122
|
+
session_usd: 0,
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
if (!this.sessionStore) {
|
|
1126
|
+
this.messages = [];
|
|
1127
|
+
this.messageSeqs = [];
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
this.sessionStore = new SessionStore(this.bash.getCwd());
|
|
1132
|
+
this.workspace = this.sessionStore.getWorkspace();
|
|
1133
|
+
this.session = this.sessionStore.createSession(this.modelId, this.mode, this.bash.getCwd());
|
|
1134
|
+
this.messages = [];
|
|
1135
|
+
this.messageSeqs = [];
|
|
1136
|
+
return this.getSessionSnapshot();
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
getSessionInfo(): SessionInfo | null {
|
|
1140
|
+
return this.session;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
getSessionId(): string | null {
|
|
1144
|
+
return this.session?.id || null;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
getSessionTitle(): string | null {
|
|
1148
|
+
return this.session?.title || null;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
getChatEntries(): ChatEntry[] {
|
|
1152
|
+
if (!this.session) return [];
|
|
1153
|
+
return buildChatEntries(this.session.id);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
getSessionSnapshot(): SessionSnapshot | null {
|
|
1157
|
+
if (!this.session || !this.workspace) return null;
|
|
1158
|
+
return {
|
|
1159
|
+
workspace: this.workspace,
|
|
1160
|
+
session: this.session,
|
|
1161
|
+
messages: loadTranscript(this.session.id),
|
|
1162
|
+
entries: buildChatEntries(this.session.id),
|
|
1163
|
+
totalTokens: getSessionTotalTokens(this.session.id),
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
onSubagentStatus(listener: (status: SubagentStatus | null) => void): () => void {
|
|
1168
|
+
this.subagentStatusListeners.add(listener);
|
|
1169
|
+
return () => {
|
|
1170
|
+
this.subagentStatusListeners.delete(listener);
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
private emitSubagentStatus(status: SubagentStatus | null): void {
|
|
1175
|
+
for (const listener of this.subagentStatusListeners) {
|
|
1176
|
+
listener(status);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
private discardAbortedTurn(userMessage: ModelMessage): void {
|
|
1181
|
+
const idx = this.messages.lastIndexOf(userMessage);
|
|
1182
|
+
if (idx >= 0) {
|
|
1183
|
+
this.messages.splice(idx, 1);
|
|
1184
|
+
this.messageSeqs.splice(idx, 1);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
private recordUsage(
|
|
1189
|
+
usage?: { totalTokens?: number; inputTokens?: number; outputTokens?: number; cacheReadTokens?: number; cacheCreationTokens?: number },
|
|
1190
|
+
source: UsageSource = "message",
|
|
1191
|
+
model = this.modelId,
|
|
1192
|
+
): void {
|
|
1193
|
+
if (!usage) return;
|
|
1194
|
+
if (this.session) {
|
|
1195
|
+
const pilActive = source === "message" ? this._pilActive : false;
|
|
1196
|
+
const enrichmentDelta = source === "message" ? this._pilEnrichmentDelta : 0;
|
|
1197
|
+
recordUsageEvent(this.session.id, source, model, usage, null, pilActive, enrichmentDelta);
|
|
1198
|
+
if (source === "message") {
|
|
1199
|
+
this._pilActive = false;
|
|
1200
|
+
this._pilEnrichmentDelta = 0;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
// Update status bar token counters + provider/model + cache metrics + cost
|
|
1204
|
+
const prev = statusBarStore.getState();
|
|
1205
|
+
const info = getModelInfo(model);
|
|
1206
|
+
const totalInput = usage.inputTokens ?? 0;
|
|
1207
|
+
const cacheRead = usage.cacheReadTokens ?? 0;
|
|
1208
|
+
const cacheCreate = usage.cacheCreationTokens ?? 0;
|
|
1209
|
+
const output = usage.outputTokens ?? 0;
|
|
1210
|
+
const priceIn = info?.inputPrice ?? 0;
|
|
1211
|
+
const priceCached = info?.cachedInputPrice ?? priceIn * 0.1;
|
|
1212
|
+
const priceOut = info?.outputPrice ?? 0;
|
|
1213
|
+
// API inputTokens includes cacheRead — subtract to get non-cached portion
|
|
1214
|
+
const nonCachedInput = Math.max(0, totalInput - cacheRead - cacheCreate);
|
|
1215
|
+
const turnCostMicros = nonCachedInput * priceIn + cacheRead * priceCached + cacheCreate * priceIn + output * priceOut;
|
|
1216
|
+
statusBarStore.setState({
|
|
1217
|
+
in_tokens: prev.in_tokens + totalInput,
|
|
1218
|
+
out_tokens: prev.out_tokens + output,
|
|
1219
|
+
cache_read_tokens: (prev.cache_read_tokens ?? 0) + cacheRead,
|
|
1220
|
+
cache_creation_tokens: (prev.cache_creation_tokens ?? 0) + cacheCreate,
|
|
1221
|
+
session_usd: prev.session_usd + turnCostMicros / 1_000_000,
|
|
1222
|
+
provider: this.providerId,
|
|
1223
|
+
model,
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
async consumeBackgroundNotifications(): Promise<string[]> {
|
|
1228
|
+
try {
|
|
1229
|
+
const notifications = await this.delegations.consumeNotifications();
|
|
1230
|
+
for (const notification of notifications) {
|
|
1231
|
+
this.messages.push({ role: "system", content: notification.message });
|
|
1232
|
+
let seq: number | null = null;
|
|
1233
|
+
if (this.session) {
|
|
1234
|
+
seq = appendSystemMessage(this.session.id, notification.message);
|
|
1235
|
+
}
|
|
1236
|
+
this.messageSeqs.push(seq);
|
|
1237
|
+
|
|
1238
|
+
const notifInput: NotificationHookInput = {
|
|
1239
|
+
hook_event_name: "Notification",
|
|
1240
|
+
message: notification.message,
|
|
1241
|
+
session_id: this.session?.id,
|
|
1242
|
+
cwd: this.bash.getCwd(),
|
|
1243
|
+
};
|
|
1244
|
+
this.fireHook(notifInput).catch(() => {});
|
|
1245
|
+
}
|
|
1246
|
+
return notifications.map((notification) => notification.message);
|
|
1247
|
+
} catch {
|
|
1248
|
+
return [];
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
private getBatchClientOptions(signal?: AbortSignal): BatchClientOptions {
|
|
1253
|
+
if (!this.apiKey) {
|
|
1254
|
+
throw new Error("API key required. Add an API key to continue.");
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
return {
|
|
1258
|
+
apiKey: this.apiKey,
|
|
1259
|
+
baseURL: this.baseURL ?? undefined,
|
|
1260
|
+
signal,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
private async executeBatchToolCall(
|
|
1265
|
+
tools: ToolSet,
|
|
1266
|
+
toolCall: ToolCall,
|
|
1267
|
+
messages: ModelMessage[],
|
|
1268
|
+
signal?: AbortSignal,
|
|
1269
|
+
): Promise<{ input: unknown; result: ToolResult }> {
|
|
1270
|
+
const tool = tools[toolCall.function.name];
|
|
1271
|
+
if (!tool || tool.type === "provider" || typeof tool.execute !== "function") {
|
|
1272
|
+
return {
|
|
1273
|
+
input: parseToolArgumentsOrRaw(toolCall.function.arguments),
|
|
1274
|
+
result: {
|
|
1275
|
+
success: false,
|
|
1276
|
+
output: `Tool "${toolCall.function.name}" is unavailable in batch mode.`,
|
|
1277
|
+
},
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
let parsedInput: unknown;
|
|
1282
|
+
try {
|
|
1283
|
+
parsedInput = toolCall.function.arguments.trim() ? JSON.parse(toolCall.function.arguments) : {};
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1286
|
+
return {
|
|
1287
|
+
input: toolCall.function.arguments,
|
|
1288
|
+
result: {
|
|
1289
|
+
success: false,
|
|
1290
|
+
output: `Tool "${toolCall.function.name}" received invalid JSON arguments: ${message}`,
|
|
1291
|
+
},
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
try {
|
|
1296
|
+
const output = await tool.execute(parsedInput as never, {
|
|
1297
|
+
toolCallId: toolCall.id,
|
|
1298
|
+
messages,
|
|
1299
|
+
abortSignal: signal,
|
|
1300
|
+
});
|
|
1301
|
+
return {
|
|
1302
|
+
input: parsedInput,
|
|
1303
|
+
result: toToolResult(output),
|
|
1304
|
+
};
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
if (signal?.aborted) {
|
|
1307
|
+
throw error;
|
|
1308
|
+
}
|
|
1309
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1310
|
+
return {
|
|
1311
|
+
input: parsedInput,
|
|
1312
|
+
result: {
|
|
1313
|
+
success: false,
|
|
1314
|
+
output: `Tool "${toolCall.function.name}" failed: ${message}`,
|
|
1315
|
+
},
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
private async runTaskRequestBatch(args: {
|
|
1321
|
+
request: TaskRequest;
|
|
1322
|
+
childMessages: ModelMessage[];
|
|
1323
|
+
childSystem: string;
|
|
1324
|
+
childRuntime: ReturnType<typeof resolveModelRuntime>;
|
|
1325
|
+
childTools: ToolSet;
|
|
1326
|
+
maxSteps: number;
|
|
1327
|
+
initialDetail: string;
|
|
1328
|
+
onActivity?: (detail: string) => void;
|
|
1329
|
+
signal?: AbortSignal;
|
|
1330
|
+
}): Promise<ToolResult> {
|
|
1331
|
+
const {
|
|
1332
|
+
request,
|
|
1333
|
+
childMessages,
|
|
1334
|
+
childSystem,
|
|
1335
|
+
childRuntime,
|
|
1336
|
+
childTools,
|
|
1337
|
+
maxSteps,
|
|
1338
|
+
initialDetail,
|
|
1339
|
+
onActivity,
|
|
1340
|
+
signal,
|
|
1341
|
+
} = args;
|
|
1342
|
+
|
|
1343
|
+
if (childRuntime.modelInfo?.responsesOnly) {
|
|
1344
|
+
throw new Error("Batch mode currently supports chat-completions models only.");
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const batchTools =
|
|
1348
|
+
childRuntime.modelInfo?.supportsClientTools === false ? [] : await toolSetToBatchTools(childTools);
|
|
1349
|
+
const batch = await createBatch({
|
|
1350
|
+
...this.getBatchClientOptions(signal),
|
|
1351
|
+
name: buildBatchName(`task-${request.agent}`, request.description),
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
const turnMessages: ModelMessage[] = [];
|
|
1355
|
+
const totalUsage: ProcessMessageUsage = {};
|
|
1356
|
+
let assistantText = "";
|
|
1357
|
+
let lastActivity = initialDetail;
|
|
1358
|
+
|
|
1359
|
+
for (let round = 0; round < maxSteps; round++) {
|
|
1360
|
+
const batchRequestId = `task-${Date.now()}-${round + 1}`;
|
|
1361
|
+
await addBatchRequests({
|
|
1362
|
+
...this.getBatchClientOptions(signal),
|
|
1363
|
+
batchId: batch.batch_id,
|
|
1364
|
+
batchRequests: [
|
|
1365
|
+
{
|
|
1366
|
+
batch_request_id: batchRequestId,
|
|
1367
|
+
batch_request: {
|
|
1368
|
+
chat_get_completion: buildBatchChatCompletionRequest({
|
|
1369
|
+
modelId: childRuntime.modelId,
|
|
1370
|
+
system: childSystem,
|
|
1371
|
+
messages: [...childMessages, ...turnMessages],
|
|
1372
|
+
temperature: request.agent === "explore" ? 0.2 : 0.5,
|
|
1373
|
+
maxOutputTokens:
|
|
1374
|
+
childRuntime.modelInfo?.supportsMaxOutputTokens === false
|
|
1375
|
+
? undefined
|
|
1376
|
+
: Math.min(this.maxTokens, 8_192),
|
|
1377
|
+
reasoningEffort: childRuntime.providerOptions?.xai.reasoningEffort,
|
|
1378
|
+
tools: batchTools,
|
|
1379
|
+
}),
|
|
1380
|
+
},
|
|
1381
|
+
},
|
|
1382
|
+
],
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
const result = await pollBatchRequestResult({
|
|
1386
|
+
...this.getBatchClientOptions(signal),
|
|
1387
|
+
batchId: batch.batch_id,
|
|
1388
|
+
batchRequestId,
|
|
1389
|
+
});
|
|
1390
|
+
const response = getBatchChatCompletion(result);
|
|
1391
|
+
accumulateUsage(totalUsage, getBatchUsage(response));
|
|
1392
|
+
|
|
1393
|
+
const choice = response.choices[0];
|
|
1394
|
+
if (!choice) {
|
|
1395
|
+
throw new Error("Batch response did not contain any choices.");
|
|
1396
|
+
}
|
|
1397
|
+
const content = choice?.message.content ?? "";
|
|
1398
|
+
if (content) {
|
|
1399
|
+
assistantText += content;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const requestMessages = [...childMessages, ...turnMessages];
|
|
1403
|
+
const toolCalls = (choice?.message.tool_calls ?? []).map(toLocalToolCall);
|
|
1404
|
+
const assistantMessage = buildAssistantBatchMessage(content, toolCalls);
|
|
1405
|
+
if (assistantMessage) {
|
|
1406
|
+
turnMessages.push(assistantMessage);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (toolCalls.length === 0) {
|
|
1410
|
+
if (hasUsage(totalUsage)) {
|
|
1411
|
+
this.recordUsage(totalUsage, "task", childRuntime.modelId);
|
|
1412
|
+
}
|
|
1413
|
+
const output = assistantText.trim() || `Task completed. Last action: ${lastActivity}`;
|
|
1414
|
+
return {
|
|
1415
|
+
success: true,
|
|
1416
|
+
output,
|
|
1417
|
+
task: {
|
|
1418
|
+
agent: request.agent,
|
|
1419
|
+
description: request.description,
|
|
1420
|
+
summary: firstLine(output),
|
|
1421
|
+
activity: lastActivity,
|
|
1422
|
+
},
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const toolParts: ExecutedBatchTool[] = [];
|
|
1427
|
+
for (const toolCall of toolCalls) {
|
|
1428
|
+
const nextActivity = formatSubagentActivity(
|
|
1429
|
+
toolCall.function.name,
|
|
1430
|
+
parseToolArgumentsOrRaw(toolCall.function.arguments),
|
|
1431
|
+
);
|
|
1432
|
+
lastActivity = nextActivity;
|
|
1433
|
+
onActivity?.(nextActivity);
|
|
1434
|
+
|
|
1435
|
+
const executed = await this.executeBatchToolCall(childTools, toolCall, requestMessages, signal);
|
|
1436
|
+
toolParts.push({
|
|
1437
|
+
toolCall,
|
|
1438
|
+
input: executed.input,
|
|
1439
|
+
toolResult: executed.result,
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const toolMessage = buildToolBatchMessage(toolParts);
|
|
1444
|
+
if (toolMessage) {
|
|
1445
|
+
turnMessages.push(toolMessage);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
if (hasUsage(totalUsage)) {
|
|
1450
|
+
this.recordUsage(totalUsage, "task", childRuntime.modelId);
|
|
1451
|
+
}
|
|
1452
|
+
const output = assistantText.trim() || `Task stopped after ${maxSteps} batch rounds. Last action: ${lastActivity}`;
|
|
1453
|
+
return {
|
|
1454
|
+
success: false,
|
|
1455
|
+
output,
|
|
1456
|
+
task: {
|
|
1457
|
+
agent: request.agent,
|
|
1458
|
+
description: request.description,
|
|
1459
|
+
summary: output,
|
|
1460
|
+
activity: lastActivity,
|
|
1461
|
+
},
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async runTaskRequest(
|
|
1466
|
+
request: TaskRequest,
|
|
1467
|
+
onActivity?: (detail: string) => void,
|
|
1468
|
+
abortSignal?: AbortSignal,
|
|
1469
|
+
): Promise<ToolResult> {
|
|
1470
|
+
const provider = this.requireProvider();
|
|
1471
|
+
const signal = abortSignal;
|
|
1472
|
+
const agentKey = String(request.agent);
|
|
1473
|
+
const isExplore = agentKey === "explore";
|
|
1474
|
+
const isGeneral = agentKey === "general";
|
|
1475
|
+
const isVision = agentKey === "vision";
|
|
1476
|
+
const isVerify = agentKey === "verify";
|
|
1477
|
+
const isVerifyDetect = agentKey === "verify-detect";
|
|
1478
|
+
const isVerifyManifest = agentKey === "verify-manifest";
|
|
1479
|
+
const isComputer = agentKey === "computer";
|
|
1480
|
+
const subagents = loadValidSubAgents();
|
|
1481
|
+
const custom =
|
|
1482
|
+
!isExplore && !isGeneral && !isVision && !isVerify && !isVerifyDetect && !isVerifyManifest && !isComputer
|
|
1483
|
+
? findCustomSubagent(agentKey, subagents)
|
|
1484
|
+
: undefined;
|
|
1485
|
+
|
|
1486
|
+
if (
|
|
1487
|
+
!isExplore &&
|
|
1488
|
+
!isGeneral &&
|
|
1489
|
+
!isVision &&
|
|
1490
|
+
!isVerify &&
|
|
1491
|
+
!isVerifyDetect &&
|
|
1492
|
+
!isVerifyManifest &&
|
|
1493
|
+
!isComputer &&
|
|
1494
|
+
!custom
|
|
1495
|
+
) {
|
|
1496
|
+
const message = `Unknown sub-agent "${agentKey}". Use general, explore, vision, verify, verify-detect, verify-manifest, computer, or a configured name from ~/.muonroi-cli/user-settings.json.`;
|
|
1497
|
+
return {
|
|
1498
|
+
success: false,
|
|
1499
|
+
output: message,
|
|
1500
|
+
task: {
|
|
1501
|
+
agent: agentKey,
|
|
1502
|
+
description: request.description,
|
|
1503
|
+
summary: message,
|
|
1504
|
+
},
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
const childMode: AgentMode = isExplore || isVerifyDetect ? "ask" : "agent";
|
|
1509
|
+
const verifySandboxOverrides: SandboxSettings = isVerify
|
|
1510
|
+
? { allowNet: true, allowedHosts: undefined, allowEphemeralInstall: true, hostBrowserCommandsOnHost: true }
|
|
1511
|
+
: {};
|
|
1512
|
+
let verifyPreparedSettings: SandboxSettings | null = null;
|
|
1513
|
+
let verifyPreparedRecipe: VerifyRecipe | null = null;
|
|
1514
|
+
if (isVerify) {
|
|
1515
|
+
const prepared = await prepareVerifySandbox(
|
|
1516
|
+
this.bash.getCwd(),
|
|
1517
|
+
{ ...this.bash.getSandboxSettings(), ...verifySandboxOverrides },
|
|
1518
|
+
undefined,
|
|
1519
|
+
onActivity,
|
|
1520
|
+
);
|
|
1521
|
+
verifyPreparedSettings = prepared.sandboxSettings;
|
|
1522
|
+
verifyPreparedRecipe = prepared.profile.recipe;
|
|
1523
|
+
}
|
|
1524
|
+
const childBash = new BashTool(this.bash.getCwd(), {
|
|
1525
|
+
sandboxMode: isVerify ? "shuru" : this.bash.getSandboxMode(),
|
|
1526
|
+
sandboxSettings: isVerify
|
|
1527
|
+
? (verifyPreparedSettings ?? { ...this.bash.getSandboxSettings(), ...verifySandboxOverrides })
|
|
1528
|
+
: this.bash.getSandboxSettings(),
|
|
1529
|
+
});
|
|
1530
|
+
const childBaseTools = createTools(childBash, provider, childMode);
|
|
1531
|
+
const initialDetail = isExplore
|
|
1532
|
+
? "Scanning the codebase"
|
|
1533
|
+
: isVerifyDetect
|
|
1534
|
+
? "Detecting verification recipe"
|
|
1535
|
+
: isVerifyManifest
|
|
1536
|
+
? "Creating verification manifest"
|
|
1537
|
+
: isVerify
|
|
1538
|
+
? "Preparing verification pass"
|
|
1539
|
+
: isComputer
|
|
1540
|
+
? "Preparing computer control pass"
|
|
1541
|
+
: "Planning delegated work";
|
|
1542
|
+
let assistantText = "";
|
|
1543
|
+
let lastActivity = initialDetail;
|
|
1544
|
+
let childTools: ToolSet = childBaseTools;
|
|
1545
|
+
let closeMcp: (() => Promise<void>) | undefined;
|
|
1546
|
+
const childModelId = normalizeModelId(
|
|
1547
|
+
isVision
|
|
1548
|
+
? VISION_MODEL
|
|
1549
|
+
: isComputer
|
|
1550
|
+
? COMPUTER_MODEL
|
|
1551
|
+
: isExplore
|
|
1552
|
+
? DEFAULT_MODEL
|
|
1553
|
+
: custom
|
|
1554
|
+
? custom.model
|
|
1555
|
+
: this.modelId,
|
|
1556
|
+
);
|
|
1557
|
+
const childRuntime = isVision
|
|
1558
|
+
? { ...resolveModelRuntime(provider, childModelId), model: provider.responses?.(childModelId) ?? provider(childModelId) }
|
|
1559
|
+
: resolveModelRuntime(provider, childModelId);
|
|
1560
|
+
if (isComputer && childRuntime.modelInfo?.supportsClientTools === false) {
|
|
1561
|
+
return {
|
|
1562
|
+
success: false,
|
|
1563
|
+
output:
|
|
1564
|
+
"Computer sub-agent requires a tool-capable model, but the selected runtime does not support client tools.",
|
|
1565
|
+
task: {
|
|
1566
|
+
agent: agentKey,
|
|
1567
|
+
description: request.description,
|
|
1568
|
+
summary: "Computer sub-agent could not start because the chosen model does not support tools.",
|
|
1569
|
+
},
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
const childSystem = applyModelConstraints(
|
|
1573
|
+
buildSubagentPrompt(
|
|
1574
|
+
request,
|
|
1575
|
+
childBash.getCwd(),
|
|
1576
|
+
custom ?? null,
|
|
1577
|
+
childBash.getSandboxMode(),
|
|
1578
|
+
subagents,
|
|
1579
|
+
childBash.getSandboxSettings(),
|
|
1580
|
+
childRuntime.modelInfo?.provider ?? this.providerId,
|
|
1581
|
+
),
|
|
1582
|
+
childRuntime.modelId,
|
|
1583
|
+
);
|
|
1584
|
+
|
|
1585
|
+
onActivity?.(initialDetail);
|
|
1586
|
+
|
|
1587
|
+
try {
|
|
1588
|
+
if (childMode === "agent" && childRuntime.modelInfo?.supportsClientTools !== false) {
|
|
1589
|
+
const mcpBundle = await buildMcpToolSet(loadMcpServers(), {
|
|
1590
|
+
onOAuthRequired: (_serverId, url) => {
|
|
1591
|
+
const urlStr = url.toString();
|
|
1592
|
+
import("child_process").then(({ exec }) => {
|
|
1593
|
+
const cmd = process.platform === "win32" ? `start "" "${urlStr}"`
|
|
1594
|
+
: process.platform === "darwin" ? `open "${urlStr}"`
|
|
1595
|
+
: `xdg-open "${urlStr}"`;
|
|
1596
|
+
exec(cmd);
|
|
1597
|
+
});
|
|
1598
|
+
},
|
|
1599
|
+
});
|
|
1600
|
+
closeMcp = mcpBundle.close;
|
|
1601
|
+
childTools = { ...childBaseTools, ...mcpBundle.tools };
|
|
1602
|
+
captureToolSchemas(childTools);
|
|
1603
|
+
if (mcpBundle.errors.length > 0) {
|
|
1604
|
+
lastActivity = `MCP unavailable: ${mcpBundle.errors.join(" | ")}`;
|
|
1605
|
+
onActivity?.(lastActivity);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const childPrompt =
|
|
1610
|
+
isVerify && verifyPreparedRecipe
|
|
1611
|
+
? `${request.prompt}\n\nPrepared verify recipe JSON (use this as the primary execution recipe and keep .muonroi-cli/environment.json aligned with it if present):\n${JSON.stringify(verifyPreparedRecipe, null, 2)}`
|
|
1612
|
+
: request.prompt;
|
|
1613
|
+
|
|
1614
|
+
const childMessages = isVision
|
|
1615
|
+
? await buildVisionUserMessages(request.prompt, childBash.getCwd(), signal)
|
|
1616
|
+
: [{ role: "user" as const, content: childPrompt }];
|
|
1617
|
+
|
|
1618
|
+
if (this.batchApi) {
|
|
1619
|
+
return await this.runTaskRequestBatch({
|
|
1620
|
+
request,
|
|
1621
|
+
childMessages,
|
|
1622
|
+
childSystem,
|
|
1623
|
+
childRuntime,
|
|
1624
|
+
childTools,
|
|
1625
|
+
maxSteps: Math.min(this.maxToolRounds, isExplore ? 60 : 120),
|
|
1626
|
+
initialDetail,
|
|
1627
|
+
onActivity,
|
|
1628
|
+
signal,
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const result = streamText({
|
|
1633
|
+
model: childRuntime.model,
|
|
1634
|
+
system: childSystem,
|
|
1635
|
+
messages: childMessages,
|
|
1636
|
+
tools: childRuntime.modelInfo?.supportsClientTools === false ? {} : childTools,
|
|
1637
|
+
stopWhen: stepCountIs(Math.min(this.maxToolRounds, isExplore ? 60 : 120)),
|
|
1638
|
+
maxRetries: 0,
|
|
1639
|
+
abortSignal: signal,
|
|
1640
|
+
temperature: isExplore ? 0.2 : 0.5,
|
|
1641
|
+
...(childRuntime.modelInfo?.supportsMaxOutputTokens === false
|
|
1642
|
+
? {}
|
|
1643
|
+
: { maxOutputTokens: Math.min(this.maxTokens, 8_192) }),
|
|
1644
|
+
...(childRuntime.providerOptions ? { providerOptions: childRuntime.providerOptions } : {}),
|
|
1645
|
+
onFinish: ({ totalUsage, providerMetadata }) => {
|
|
1646
|
+
const anthropicMeta = providerMetadata?.anthropic as Record<string, unknown> | undefined;
|
|
1647
|
+
const cacheReadTokens = asNumber(anthropicMeta?.cacheReadInputTokens) ?? 0;
|
|
1648
|
+
const cacheCreationTokens = asNumber(anthropicMeta?.cacheCreationInputTokens) ?? 0;
|
|
1649
|
+
this.recordUsage({ ...totalUsage, cacheReadTokens, cacheCreationTokens }, "task", childRuntime.modelId);
|
|
1650
|
+
},
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
for await (const part of result.fullStream) {
|
|
1654
|
+
if (signal?.aborted) {
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if (part.type === "text-delta") {
|
|
1659
|
+
assistantText += part.text;
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if (part.type === "tool-call") {
|
|
1664
|
+
lastActivity = formatSubagentActivity(part.toolName, part.input);
|
|
1665
|
+
onActivity?.(lastActivity);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (signal?.aborted) {
|
|
1670
|
+
return { success: false, output: "[Cancelled]" };
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
await result.response;
|
|
1674
|
+
|
|
1675
|
+
const output = assistantText.trim() || `Task completed. Last action: ${lastActivity}`;
|
|
1676
|
+
return {
|
|
1677
|
+
success: true,
|
|
1678
|
+
output,
|
|
1679
|
+
task: {
|
|
1680
|
+
agent: request.agent,
|
|
1681
|
+
description: request.description,
|
|
1682
|
+
summary: firstLine(output),
|
|
1683
|
+
activity: lastActivity,
|
|
1684
|
+
},
|
|
1685
|
+
};
|
|
1686
|
+
} catch (err: unknown) {
|
|
1687
|
+
if (signal?.aborted) throw err;
|
|
1688
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1689
|
+
const output = `Task failed: ${msg}`;
|
|
1690
|
+
return {
|
|
1691
|
+
success: false,
|
|
1692
|
+
output,
|
|
1693
|
+
task: {
|
|
1694
|
+
agent: request.agent,
|
|
1695
|
+
description: request.description,
|
|
1696
|
+
summary: output,
|
|
1697
|
+
activity: lastActivity,
|
|
1698
|
+
},
|
|
1699
|
+
};
|
|
1700
|
+
} finally {
|
|
1701
|
+
await closeMcp?.().catch(() => {});
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
private async runTask(request: TaskRequest, abortSignal?: AbortSignal): Promise<ToolResult> {
|
|
1706
|
+
const startInput: SubagentStartHookInput = {
|
|
1707
|
+
hook_event_name: "SubagentStart",
|
|
1708
|
+
agent_type: request.agent,
|
|
1709
|
+
description: request.description,
|
|
1710
|
+
session_id: this.session?.id,
|
|
1711
|
+
cwd: this.bash.getCwd(),
|
|
1712
|
+
};
|
|
1713
|
+
await this.fireHook(startInput, abortSignal).catch(() => {});
|
|
1714
|
+
|
|
1715
|
+
let result: ToolResult;
|
|
1716
|
+
try {
|
|
1717
|
+
result = await this.runTaskRequest(
|
|
1718
|
+
request,
|
|
1719
|
+
(detail) => {
|
|
1720
|
+
if (abortSignal?.aborted) return;
|
|
1721
|
+
this.emitSubagentStatus({
|
|
1722
|
+
agent: request.agent,
|
|
1723
|
+
description: request.description,
|
|
1724
|
+
detail,
|
|
1725
|
+
});
|
|
1726
|
+
},
|
|
1727
|
+
abortSignal,
|
|
1728
|
+
);
|
|
1729
|
+
} finally {
|
|
1730
|
+
this.emitSubagentStatus(null);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
const stopInput: SubagentStopHookInput = {
|
|
1734
|
+
hook_event_name: "SubagentStop",
|
|
1735
|
+
agent_type: request.agent,
|
|
1736
|
+
description: request.description,
|
|
1737
|
+
success: result.success,
|
|
1738
|
+
session_id: this.session?.id,
|
|
1739
|
+
cwd: this.bash.getCwd(),
|
|
1740
|
+
};
|
|
1741
|
+
await this.fireHook(stopInput, abortSignal).catch(() => {});
|
|
1742
|
+
|
|
1743
|
+
return result;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
private async runDelegation(request: TaskRequest, abortSignal?: AbortSignal): Promise<ToolResult> {
|
|
1747
|
+
const taskCreatedInput: TaskCreatedHookInput = {
|
|
1748
|
+
hook_event_name: "TaskCreated",
|
|
1749
|
+
agent_type: request.agent,
|
|
1750
|
+
description: request.description,
|
|
1751
|
+
session_id: this.session?.id,
|
|
1752
|
+
cwd: this.bash.getCwd(),
|
|
1753
|
+
};
|
|
1754
|
+
await this.fireHook(taskCreatedInput, abortSignal).catch(() => {});
|
|
1755
|
+
|
|
1756
|
+
let result: ToolResult;
|
|
1757
|
+
try {
|
|
1758
|
+
if (abortSignal?.aborted) {
|
|
1759
|
+
return { success: false, output: "[Cancelled]" };
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
result = await this.delegations.start(request, {
|
|
1763
|
+
model: this.modelId,
|
|
1764
|
+
sandboxMode: this.bash.getSandboxMode(),
|
|
1765
|
+
sandboxSettings: this.bash.getSandboxSettings(),
|
|
1766
|
+
maxToolRounds: this.maxToolRounds,
|
|
1767
|
+
maxTokens: this.maxTokens,
|
|
1768
|
+
batchApi: this.batchApi,
|
|
1769
|
+
});
|
|
1770
|
+
} catch (err: unknown) {
|
|
1771
|
+
if (abortSignal?.aborted) throw err;
|
|
1772
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1773
|
+
result = {
|
|
1774
|
+
success: false,
|
|
1775
|
+
output: `Delegation failed: ${msg}`,
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const taskCompletedInput: TaskCompletedHookInput = {
|
|
1780
|
+
hook_event_name: "TaskCompleted",
|
|
1781
|
+
agent_type: request.agent,
|
|
1782
|
+
description: request.description,
|
|
1783
|
+
success: result.success,
|
|
1784
|
+
session_id: this.session?.id,
|
|
1785
|
+
cwd: this.bash.getCwd(),
|
|
1786
|
+
};
|
|
1787
|
+
await this.fireHook(taskCompletedInput, abortSignal).catch(() => {});
|
|
1788
|
+
|
|
1789
|
+
return result;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
private async readDelegation(id: string): Promise<ToolResult> {
|
|
1793
|
+
try {
|
|
1794
|
+
return {
|
|
1795
|
+
success: true,
|
|
1796
|
+
output: await this.delegations.read(id),
|
|
1797
|
+
};
|
|
1798
|
+
} catch (err: unknown) {
|
|
1799
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1800
|
+
return {
|
|
1801
|
+
success: false,
|
|
1802
|
+
output: `Failed to read delegation: ${msg}`,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
private async listDelegations(): Promise<ToolResult> {
|
|
1808
|
+
try {
|
|
1809
|
+
const delegations = await this.delegations.list();
|
|
1810
|
+
if (delegations.length === 0) {
|
|
1811
|
+
return {
|
|
1812
|
+
success: true,
|
|
1813
|
+
output: "No delegations found for this project.",
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const lines = delegations.map((delegation) => {
|
|
1818
|
+
const title = delegation.description || delegation.id;
|
|
1819
|
+
return `- \`${delegation.id}\` [${delegation.status}] ${title}\n ${delegation.summary}`;
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
return {
|
|
1823
|
+
success: true,
|
|
1824
|
+
output: `## Delegations\n\n${lines.join("\n")}`,
|
|
1825
|
+
};
|
|
1826
|
+
} catch (err: unknown) {
|
|
1827
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1828
|
+
return {
|
|
1829
|
+
success: false,
|
|
1830
|
+
output: `Failed to list delegations: ${msg}`,
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
private getCompactionSettings(): CompactionSettings {
|
|
1836
|
+
return {
|
|
1837
|
+
reserveTokens: Math.max(this.maxTokens, DEFAULT_RESERVE_TOKENS),
|
|
1838
|
+
keepRecentTokens: DEFAULT_KEEP_RECENT_TOKENS,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
private async compactForContext(
|
|
1843
|
+
provider: LegacyProvider,
|
|
1844
|
+
system: string,
|
|
1845
|
+
contextWindow: number,
|
|
1846
|
+
signal: AbortSignal,
|
|
1847
|
+
settings = this.getCompactionSettings(),
|
|
1848
|
+
force = false,
|
|
1849
|
+
): Promise<boolean> {
|
|
1850
|
+
if (!this.session) return false;
|
|
1851
|
+
|
|
1852
|
+
const preparation = prepareCompaction(this.messages, system, settings);
|
|
1853
|
+
if (!preparation) return false;
|
|
1854
|
+
if (!force && !shouldCompactContext(preparation.tokensBefore, contextWindow, settings)) {
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
const trigger = force ? "manual" : "auto";
|
|
1859
|
+
|
|
1860
|
+
// Fire-and-forget: notify EE of stale suggestions before compaction
|
|
1861
|
+
const { surfacedIds, timestamp } = getLastSurfacedState();
|
|
1862
|
+
if (surfacedIds.length > 0) {
|
|
1863
|
+
getDefaultEEClient()
|
|
1864
|
+
.promptStale({
|
|
1865
|
+
state: { surfacedIds, timestamp },
|
|
1866
|
+
nextPromptMeta: { trigger: "auto-compact", cwd: this.bash.getCwd(), tenantId: getTenantId() },
|
|
1867
|
+
})
|
|
1868
|
+
.catch(() => {});
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
const preCompactInput: PreCompactHookInput = {
|
|
1872
|
+
hook_event_name: "PreCompact",
|
|
1873
|
+
trigger,
|
|
1874
|
+
session_id: this.session?.id,
|
|
1875
|
+
cwd: this.bash.getCwd(),
|
|
1876
|
+
};
|
|
1877
|
+
await this.fireHook(preCompactInput, signal).catch(() => {});
|
|
1878
|
+
|
|
1879
|
+
const keptSeqs = this.messageSeqs.slice(preparation.firstKeptIndex);
|
|
1880
|
+
const firstKeptSeq = keptSeqs.find((seq): seq is number => seq !== null) ?? getNextMessageSequence(this.session.id);
|
|
1881
|
+
const summary = await generateCompactionSummary(provider, this.modelId, preparation, undefined, signal);
|
|
1882
|
+
|
|
1883
|
+
appendCompaction(this.session.id, firstKeptSeq, summary, preparation.tokensBefore);
|
|
1884
|
+
this.messages = [createCompactionSummaryMessage(summary), ...preparation.keptMessages];
|
|
1885
|
+
this.messageSeqs = [null, ...keptSeqs];
|
|
1886
|
+
|
|
1887
|
+
const postCompactInput: PostCompactHookInput = {
|
|
1888
|
+
hook_event_name: "PostCompact",
|
|
1889
|
+
trigger,
|
|
1890
|
+
session_id: this.session?.id,
|
|
1891
|
+
cwd: this.bash.getCwd(),
|
|
1892
|
+
};
|
|
1893
|
+
await this.fireHook(postCompactInput, signal).catch(() => {});
|
|
1894
|
+
|
|
1895
|
+
this._compactedThisTurn = true;
|
|
1896
|
+
return true;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
private async postTurnCompact(
|
|
1900
|
+
provider: LegacyProvider,
|
|
1901
|
+
system: string,
|
|
1902
|
+
contextWindow: number,
|
|
1903
|
+
signal: AbortSignal,
|
|
1904
|
+
): Promise<void> {
|
|
1905
|
+
if (this._compactedThisTurn) return;
|
|
1906
|
+
if (!isAutoCompactAfterTurnEnabled()) return;
|
|
1907
|
+
const tokens = estimateConversationTokens(system, this.messages);
|
|
1908
|
+
if (tokens < POST_TURN_MIN_TOKENS) return;
|
|
1909
|
+
await this.compactForContext(provider, system, contextWindow, signal, this.getCompactionSettings(), true).catch(
|
|
1910
|
+
() => {},
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
private _councilStats = { calls: 0, startMs: 0 };
|
|
1915
|
+
|
|
1916
|
+
private async _councilGenerate(
|
|
1917
|
+
modelId: string,
|
|
1918
|
+
system: string,
|
|
1919
|
+
prompt: string,
|
|
1920
|
+
maxTokens = 2048,
|
|
1921
|
+
): Promise<string> {
|
|
1922
|
+
const providerId = detectProviderForModel(modelId);
|
|
1923
|
+
const key = await loadKeyForProvider(providerId);
|
|
1924
|
+
const provider = createProvider(providerId, key);
|
|
1925
|
+
const runtime = resolveModelRuntime(provider, modelId);
|
|
1926
|
+
const { text } = await generateText({
|
|
1927
|
+
model: runtime.model,
|
|
1928
|
+
system,
|
|
1929
|
+
prompt,
|
|
1930
|
+
maxOutputTokens: maxTokens,
|
|
1931
|
+
temperature: 0.7,
|
|
1932
|
+
...(runtime.providerOptions ? { providerOptions: runtime.providerOptions } : {}),
|
|
1933
|
+
});
|
|
1934
|
+
this._councilStats.calls++;
|
|
1935
|
+
return text;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
private _buildDiscussPrompt(
|
|
1939
|
+
phase: "open" | "respond" | "followup" | "convergence-check",
|
|
1940
|
+
ctx: {
|
|
1941
|
+
speakerRole: string;
|
|
1942
|
+
partnerRole: string;
|
|
1943
|
+
topic: string;
|
|
1944
|
+
speakerPosition?: string;
|
|
1945
|
+
partnerPosition?: string;
|
|
1946
|
+
exchangeHistory?: string;
|
|
1947
|
+
round?: number;
|
|
1948
|
+
},
|
|
1949
|
+
): { system: string; prompt: string } {
|
|
1950
|
+
switch (phase) {
|
|
1951
|
+
case "open":
|
|
1952
|
+
return {
|
|
1953
|
+
system:
|
|
1954
|
+
`You are a ${ctx.speakerRole} specialist. You are entering a discussion with a ${ctx.partnerRole} specialist about a technical topic.\n\n` +
|
|
1955
|
+
`Share your analysis naturally — explain your reasoning, the trade-offs you see, and what concerns you.\n` +
|
|
1956
|
+
`End by asking the ${ctx.partnerRole} for their perspective on your analysis. What do they see differently?`,
|
|
1957
|
+
prompt: `Topic for discussion:\n${ctx.topic}`,
|
|
1958
|
+
};
|
|
1959
|
+
|
|
1960
|
+
case "respond":
|
|
1961
|
+
return {
|
|
1962
|
+
system:
|
|
1963
|
+
`You are a ${ctx.speakerRole} specialist in a discussion with a ${ctx.partnerRole} specialist.\n\n` +
|
|
1964
|
+
`A colleague shared their analysis below. Give your honest take:\n` +
|
|
1965
|
+
`- Where you agree, say so briefly and build on it\n` +
|
|
1966
|
+
`- Where you disagree, explain why with your own reasoning — not to attack, but to offer a different lens\n` +
|
|
1967
|
+
`- Share what you think they might be missing from your ${ctx.speakerRole} perspective\n\n` +
|
|
1968
|
+
`End with a question back to them: based on your analysis, what's their view? Do they agree, or do they see it differently?`,
|
|
1969
|
+
prompt:
|
|
1970
|
+
`Their analysis (${ctx.partnerRole}):\n${ctx.partnerPosition}\n\n` +
|
|
1971
|
+
`Your own analysis for context:\n${ctx.speakerPosition}`,
|
|
1972
|
+
};
|
|
1973
|
+
|
|
1974
|
+
case "followup":
|
|
1975
|
+
return {
|
|
1976
|
+
system:
|
|
1977
|
+
`You are a ${ctx.speakerRole} specialist continuing a discussion (round ${ctx.round}) with a ${ctx.partnerRole} specialist.\n\n` +
|
|
1978
|
+
`Read their latest response and the exchange so far. Then:\n` +
|
|
1979
|
+
`- If they raised valid points, acknowledge them and update your thinking\n` +
|
|
1980
|
+
`- If you still disagree on something, explain why — bring new evidence or a different angle, not the same argument again\n` +
|
|
1981
|
+
`- If you've changed your mind on something, say so explicitly\n\n` +
|
|
1982
|
+
`End with: do you agree with where we've landed? Or is there something we're still seeing differently?`,
|
|
1983
|
+
prompt:
|
|
1984
|
+
`Discussion so far:\n${ctx.exchangeHistory}\n\n` +
|
|
1985
|
+
`Their latest response (${ctx.partnerRole}):\n${ctx.partnerPosition}`,
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1988
|
+
case "convergence-check":
|
|
1989
|
+
return {
|
|
1990
|
+
system:
|
|
1991
|
+
`Analyze this discussion between a ${ctx.speakerRole} and a ${ctx.partnerRole}. ` +
|
|
1992
|
+
`Respond with ONLY a JSON object, no other text:\n` +
|
|
1993
|
+
`{"converged": true/false, "reason": "one sentence explaining why"}`,
|
|
1994
|
+
prompt: `Discussion:\n${ctx.exchangeHistory}`,
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
async *runCouncilRound(
|
|
2000
|
+
topic: string,
|
|
2001
|
+
observer?: ProcessMessageObserver,
|
|
2002
|
+
rounds?: number,
|
|
2003
|
+
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
2004
|
+
const maxRounds = rounds ?? getCouncilRounds();
|
|
2005
|
+
const ALL_ROLES: ModelRole[] = ["implement", "verify", "research"];
|
|
2006
|
+
this._councilStats = { calls: 0, startMs: Date.now() };
|
|
2007
|
+
|
|
2008
|
+
// Resolve available role models upfront
|
|
2009
|
+
const candidates: Array<{ role: ModelRole; model: string }> = [];
|
|
2010
|
+
for (const role of ALL_ROLES) {
|
|
2011
|
+
const modelId = getRoleModel(role);
|
|
2012
|
+
if (!modelId) continue;
|
|
2013
|
+
const canReach = await loadKeyForProvider(detectProviderForModel(modelId)).then(() => true).catch(() => false);
|
|
2014
|
+
if (canReach) candidates.push({ role, model: modelId });
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
if (candidates.length < 2) {
|
|
2018
|
+
yield { type: "content", content: "\nNeed at least 2 reachable role models. Configure `roleModels` + provider keys in user-settings.json.\n" };
|
|
2019
|
+
yield { type: "done" };
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// ── Phase 1: Parallel opening statements ──
|
|
2024
|
+
const p1Start = Date.now();
|
|
2025
|
+
yield { type: "content", content: "\n## Phase 1 — Opening Analysis\n" };
|
|
2026
|
+
|
|
2027
|
+
const openingPromises = candidates.map(({ role, model }) => {
|
|
2028
|
+
const { system, prompt } = this._buildDiscussPrompt("open", {
|
|
2029
|
+
speakerRole: role,
|
|
2030
|
+
partnerRole: candidates.find((c) => c.role !== role)?.role ?? "colleague",
|
|
2031
|
+
topic,
|
|
2032
|
+
});
|
|
2033
|
+
return this._councilGenerate(model, system, prompt)
|
|
2034
|
+
.then((text) => ({ role, model, position: text, error: null as string | null }))
|
|
2035
|
+
.catch((err: unknown) => ({
|
|
2036
|
+
role,
|
|
2037
|
+
model,
|
|
2038
|
+
position: "",
|
|
2039
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2040
|
+
}));
|
|
2041
|
+
});
|
|
2042
|
+
|
|
2043
|
+
const openings = await Promise.all(openingPromises);
|
|
2044
|
+
const active: Array<{ role: ModelRole; model: string; position: string }> = [];
|
|
2045
|
+
|
|
2046
|
+
for (const o of openings) {
|
|
2047
|
+
yield { type: "content", content: `\n### [${o.role}] ${o.model}\n` };
|
|
2048
|
+
if (o.error) {
|
|
2049
|
+
yield { type: "content", content: `[Error: ${o.error}]\n` };
|
|
2050
|
+
} else {
|
|
2051
|
+
active.push({ role: o.role, model: o.model, position: o.position });
|
|
2052
|
+
yield { type: "content", content: o.position + "\n" };
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
yield { type: "content", content: `\n> Phase 1: ${active.length} participants, ${((Date.now() - p1Start) / 1000).toFixed(1)}s (parallel)\n` };
|
|
2057
|
+
|
|
2058
|
+
if (active.length < 2) {
|
|
2059
|
+
yield { type: "content", content: "\nNot enough successful openings for discussion.\n" };
|
|
2060
|
+
yield { type: "done" };
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// ── Phase 2: Discussion rounds with parallel pair debates ──
|
|
2065
|
+
const exchangeLogs: Map<string, string[]> = new Map();
|
|
2066
|
+
const pairConverged: Map<string, boolean> = new Map();
|
|
2067
|
+
|
|
2068
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
2069
|
+
const p2Start = Date.now();
|
|
2070
|
+
yield { type: "content", content: `\n## Phase 2 — Discussion Round ${round}/${maxRounds}\n` };
|
|
2071
|
+
|
|
2072
|
+
// Build independent pairs
|
|
2073
|
+
const pairs: Array<{ a: (typeof active)[0]; b: (typeof active)[0]; key: string }> = [];
|
|
2074
|
+
for (let i = 0; i < active.length; i++) {
|
|
2075
|
+
const a = active[i];
|
|
2076
|
+
const b = active[(i + 1) % active.length];
|
|
2077
|
+
const key = `${a.role}<>${b.role}`;
|
|
2078
|
+
if (pairConverged.get(key)) continue;
|
|
2079
|
+
if (!exchangeLogs.has(key)) exchangeLogs.set(key, []);
|
|
2080
|
+
pairs.push({ a, b, key });
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
if (pairs.length === 0) break;
|
|
2084
|
+
|
|
2085
|
+
// Run pair debates in parallel
|
|
2086
|
+
const pairResults = await Promise.all(
|
|
2087
|
+
pairs.map(async ({ a, b, key }) => {
|
|
2088
|
+
const log = exchangeLogs.get(key)!;
|
|
2089
|
+
const chunks: Array<{ label: string; text: string }> = [];
|
|
2090
|
+
|
|
2091
|
+
try {
|
|
2092
|
+
let aResponse: string;
|
|
2093
|
+
let bResponse: string;
|
|
2094
|
+
|
|
2095
|
+
if (round === 1) {
|
|
2096
|
+
const aPrompt = this._buildDiscussPrompt("respond", {
|
|
2097
|
+
speakerRole: a.role, partnerRole: b.role, topic,
|
|
2098
|
+
speakerPosition: a.position, partnerPosition: b.position,
|
|
2099
|
+
});
|
|
2100
|
+
aResponse = await this._councilGenerate(a.model, aPrompt.system, aPrompt.prompt);
|
|
2101
|
+
log.push(`[${a.role}]: ${aResponse}`);
|
|
2102
|
+
chunks.push({ label: `[${a.role}] → [${b.role}]`, text: aResponse });
|
|
2103
|
+
|
|
2104
|
+
const bPrompt = this._buildDiscussPrompt("respond", {
|
|
2105
|
+
speakerRole: b.role, partnerRole: a.role, topic,
|
|
2106
|
+
speakerPosition: b.position, partnerPosition: aResponse,
|
|
2107
|
+
});
|
|
2108
|
+
bResponse = await this._councilGenerate(b.model, bPrompt.system, bPrompt.prompt);
|
|
2109
|
+
log.push(`[${b.role}]: ${bResponse}`);
|
|
2110
|
+
chunks.push({ label: `[${b.role}] → [${a.role}]`, text: bResponse });
|
|
2111
|
+
} else {
|
|
2112
|
+
const historyText = log.join("\n\n");
|
|
2113
|
+
const aPrompt = this._buildDiscussPrompt("followup", {
|
|
2114
|
+
speakerRole: a.role, partnerRole: b.role, topic,
|
|
2115
|
+
partnerPosition: b.position, exchangeHistory: historyText, round,
|
|
2116
|
+
});
|
|
2117
|
+
aResponse = await this._councilGenerate(a.model, aPrompt.system, aPrompt.prompt);
|
|
2118
|
+
log.push(`[${a.role}] (round ${round}): ${aResponse}`);
|
|
2119
|
+
chunks.push({ label: `[${a.role}] → [${b.role}]`, text: aResponse });
|
|
2120
|
+
|
|
2121
|
+
const bPrompt = this._buildDiscussPrompt("followup", {
|
|
2122
|
+
speakerRole: b.role, partnerRole: a.role, topic,
|
|
2123
|
+
partnerPosition: aResponse, exchangeHistory: historyText, round,
|
|
2124
|
+
});
|
|
2125
|
+
bResponse = await this._councilGenerate(b.model, bPrompt.system, bPrompt.prompt);
|
|
2126
|
+
log.push(`[${b.role}] (round ${round}): ${bResponse}`);
|
|
2127
|
+
chunks.push({ label: `[${b.role}] → [${a.role}]`, text: bResponse });
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
b.position = bResponse;
|
|
2131
|
+
a.position = aResponse;
|
|
2132
|
+
|
|
2133
|
+
// Convergence check
|
|
2134
|
+
const convPrompt = this._buildDiscussPrompt("convergence-check", {
|
|
2135
|
+
speakerRole: a.role, partnerRole: b.role, topic,
|
|
2136
|
+
exchangeHistory: log.slice(-4).join("\n\n"),
|
|
2137
|
+
});
|
|
2138
|
+
let converged = false;
|
|
2139
|
+
let convReason = "";
|
|
2140
|
+
try {
|
|
2141
|
+
const raw = await this._councilGenerate(a.model, convPrompt.system, convPrompt.prompt, 256);
|
|
2142
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
2143
|
+
if (match) {
|
|
2144
|
+
const parsed = JSON.parse(match[0]) as { converged?: boolean; reason?: string };
|
|
2145
|
+
converged = parsed.converged === true;
|
|
2146
|
+
convReason = parsed.reason ?? "";
|
|
2147
|
+
}
|
|
2148
|
+
} catch { /* not converged */ }
|
|
2149
|
+
|
|
2150
|
+
return { key, chunks, converged, convReason, error: null as string | null };
|
|
2151
|
+
} catch (err: unknown) {
|
|
2152
|
+
return {
|
|
2153
|
+
key, chunks, converged: false, convReason: "",
|
|
2154
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
}),
|
|
2158
|
+
);
|
|
2159
|
+
|
|
2160
|
+
// Emit results (sequential yield — maintains readable order)
|
|
2161
|
+
let allConverged = true;
|
|
2162
|
+
for (const pr of pairResults) {
|
|
2163
|
+
for (const chunk of pr.chunks) {
|
|
2164
|
+
yield { type: "content", content: `\n### ${chunk.label}\n${chunk.text}\n` };
|
|
2165
|
+
}
|
|
2166
|
+
if (pr.error) {
|
|
2167
|
+
yield { type: "content", content: `[Discussion error: ${pr.error}]\n` };
|
|
2168
|
+
allConverged = false;
|
|
2169
|
+
} else if (pr.converged) {
|
|
2170
|
+
pairConverged.set(pr.key, true);
|
|
2171
|
+
yield { type: "content", content: `\n> ✓ ${pr.key.replace("<>", " ↔ ")} converged: ${pr.convReason}\n` };
|
|
2172
|
+
} else {
|
|
2173
|
+
allConverged = false;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
yield { type: "content", content: `\n> Round ${round}: ${((Date.now() - p2Start) / 1000).toFixed(1)}s (${pairs.length} pairs parallel)\n` };
|
|
2178
|
+
|
|
2179
|
+
if (allConverged) {
|
|
2180
|
+
yield { type: "content", content: `\n> All pairs converged at round ${round}. Moving to synthesis.\n` };
|
|
2181
|
+
break;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// ── Phase 3: Leader synthesis ──
|
|
2186
|
+
const p3Start = Date.now();
|
|
2187
|
+
yield { type: "content", content: "\n## Phase 3 — Leader Synthesis\n" };
|
|
2188
|
+
|
|
2189
|
+
const leaderModelId = getRoleModel("leader") ?? this.modelId;
|
|
2190
|
+
yield { type: "content", content: `\n### [leader] ${leaderModelId}\n` };
|
|
2191
|
+
|
|
2192
|
+
const allExchanges = [...exchangeLogs.entries()]
|
|
2193
|
+
.map(([pair, log]) => `### Discussion: ${pair}\n${log.join("\n\n")}`)
|
|
2194
|
+
.join("\n\n---\n\n");
|
|
2195
|
+
|
|
2196
|
+
const finalPositions = active
|
|
2197
|
+
.map((p) => `**${p.role}** (${p.model}): ${p.position.slice(0, 500)}...`)
|
|
2198
|
+
.join("\n\n");
|
|
2199
|
+
|
|
2200
|
+
let synthesisText = "";
|
|
2201
|
+
try {
|
|
2202
|
+
synthesisText = await this._councilGenerate(
|
|
2203
|
+
leaderModelId,
|
|
2204
|
+
"You are the team lead. Multiple specialists just had a structured discussion. Synthesize:\n" +
|
|
2205
|
+
"1. What everyone agreed on — these are your strongest foundations\n" +
|
|
2206
|
+
"2. Where genuine disagreement remains — these are real trade-offs, not resolvable by more discussion\n" +
|
|
2207
|
+
"3. Your decisive recommendation based on the full discussion\n" +
|
|
2208
|
+
"4. Specific next steps and risks to watch\n\n" +
|
|
2209
|
+
"Be decisive. The discussion has happened — now it's time to decide.",
|
|
2210
|
+
`Topic: ${topic}\n\nFinal positions:\n${finalPositions}\n\nFull discussion:\n${allExchanges}`,
|
|
2211
|
+
4096,
|
|
2212
|
+
);
|
|
2213
|
+
yield { type: "content", content: synthesisText + "\n" };
|
|
2214
|
+
} catch (err: unknown) {
|
|
2215
|
+
yield { type: "content", content: `[Synthesis error: ${err instanceof Error ? err.message : err}]\n` };
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// ── Stats + Memory ──
|
|
2219
|
+
const totalMs = Date.now() - this._councilStats.startMs;
|
|
2220
|
+
yield {
|
|
2221
|
+
type: "content",
|
|
2222
|
+
content:
|
|
2223
|
+
`\n---\n` +
|
|
2224
|
+
`> Council stats: ${this._councilStats.calls} API calls, ${(totalMs / 1000).toFixed(1)}s total, ` +
|
|
2225
|
+
`${active.length} participants, synthesis ${((Date.now() - p3Start) / 1000).toFixed(1)}s\n`,
|
|
2226
|
+
};
|
|
2227
|
+
|
|
2228
|
+
// Save council result to session for memory across conversations
|
|
2229
|
+
if (this.session && this.sessionStore) {
|
|
2230
|
+
const councilRecord = {
|
|
2231
|
+
topic,
|
|
2232
|
+
participants: active.map((a) => ({ role: a.role, model: a.model })),
|
|
2233
|
+
finalPositions: active.map((a) => ({ role: a.role, position: a.position.slice(0, 1000) })),
|
|
2234
|
+
synthesis: synthesisText.slice(0, 2000),
|
|
2235
|
+
convergedPairs: [...pairConverged.entries()].filter(([, v]) => v).map(([k]) => k),
|
|
2236
|
+
stats: { calls: this._councilStats.calls, durationMs: totalMs },
|
|
2237
|
+
timestamp: new Date().toISOString(),
|
|
2238
|
+
};
|
|
2239
|
+
try {
|
|
2240
|
+
appendSystemMessage(this.session.id, `[Council Memory] ${JSON.stringify(councilRecord)}`);
|
|
2241
|
+
} catch { /* non-critical */ }
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
yield { type: "done" };
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
private async *processMessageBatchTurn(args: {
|
|
2248
|
+
userModelMessage: ModelMessage;
|
|
2249
|
+
observer?: ProcessMessageObserver;
|
|
2250
|
+
provider: LegacyProvider;
|
|
2251
|
+
subagents: CustomSubagentConfig[];
|
|
2252
|
+
system: string;
|
|
2253
|
+
runtime: ReturnType<typeof resolveModelRuntime>;
|
|
2254
|
+
modelInfo: ReturnType<typeof getModelInfo>;
|
|
2255
|
+
signal: AbortSignal;
|
|
2256
|
+
}): AsyncGenerator<StreamChunk, void, unknown> {
|
|
2257
|
+
const { userModelMessage, observer, provider, subagents, system, runtime, modelInfo, signal } = args;
|
|
2258
|
+
let attemptedOverflowRecovery = false;
|
|
2259
|
+
|
|
2260
|
+
while (true) {
|
|
2261
|
+
this._compactedThisTurn = false;
|
|
2262
|
+
let closeMcp: (() => Promise<void>) | undefined;
|
|
2263
|
+
const turnMessages: ModelMessage[] = [];
|
|
2264
|
+
const totalUsage: ProcessMessageUsage = {};
|
|
2265
|
+
|
|
2266
|
+
try {
|
|
2267
|
+
const settings = attemptedOverflowRecovery
|
|
2268
|
+
? relaxCompactionSettings(this.getCompactionSettings())
|
|
2269
|
+
: this.getCompactionSettings();
|
|
2270
|
+
if (modelInfo?.contextWindow) {
|
|
2271
|
+
await this.compactForContext(
|
|
2272
|
+
provider,
|
|
2273
|
+
system,
|
|
2274
|
+
modelInfo.contextWindow,
|
|
2275
|
+
signal,
|
|
2276
|
+
settings,
|
|
2277
|
+
attemptedOverflowRecovery,
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
if (runtime.modelInfo?.responsesOnly) {
|
|
2282
|
+
throw new Error("Batch mode currently supports chat-completions models only.");
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
const baseTools = createTools(this.bash, provider, this.mode, {
|
|
2286
|
+
runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
|
|
2287
|
+
runDelegation: (request, abortSignal) =>
|
|
2288
|
+
this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
|
|
2289
|
+
readDelegation: (id) => this.readDelegation(id),
|
|
2290
|
+
listDelegations: () => this.listDelegations(),
|
|
2291
|
+
scheduleManager: this.schedules,
|
|
2292
|
+
subagents,
|
|
2293
|
+
sendTelegramFile: this.sendTelegramFile ?? undefined,
|
|
2294
|
+
sessionId: this.session?.id ?? undefined,
|
|
2295
|
+
});
|
|
2296
|
+
let tools: ToolSet = runtime.modelInfo?.supportsClientTools === false ? {} : baseTools;
|
|
2297
|
+
if (this.mode === "agent" && runtime.modelInfo?.supportsClientTools !== false) {
|
|
2298
|
+
const mcpBundle = await buildMcpToolSet(loadMcpServers(), {
|
|
2299
|
+
onOAuthRequired: (_serverId, url) => {
|
|
2300
|
+
const urlStr = url.toString();
|
|
2301
|
+
import("child_process").then(({ exec }) => {
|
|
2302
|
+
const cmd = process.platform === "win32" ? `start "" "${urlStr}"`
|
|
2303
|
+
: process.platform === "darwin" ? `open "${urlStr}"`
|
|
2304
|
+
: `xdg-open "${urlStr}"`;
|
|
2305
|
+
exec(cmd);
|
|
2306
|
+
});
|
|
2307
|
+
},
|
|
2308
|
+
});
|
|
2309
|
+
closeMcp = mcpBundle.close;
|
|
2310
|
+
tools = { ...baseTools, ...mcpBundle.tools };
|
|
2311
|
+
if (mcpBundle.errors.length > 0) {
|
|
2312
|
+
yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
const batchTools = runtime.modelInfo?.supportsClientTools === false ? [] : await toolSetToBatchTools(tools);
|
|
2317
|
+
const batch = await createBatch({
|
|
2318
|
+
...this.getBatchClientOptions(signal),
|
|
2319
|
+
name: buildBatchName("session", this.getSessionId() || runtime.modelId),
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
for (let round = 0; round < this.maxToolRounds; round++) {
|
|
2323
|
+
const stepNumber = round + 1;
|
|
2324
|
+
notifyObserver(observer?.onStepStart, {
|
|
2325
|
+
stepNumber,
|
|
2326
|
+
timestamp: Date.now(),
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
const batchRequestId = `turn-${Date.now()}-${stepNumber}`;
|
|
2330
|
+
await addBatchRequests({
|
|
2331
|
+
...this.getBatchClientOptions(signal),
|
|
2332
|
+
batchId: batch.batch_id,
|
|
2333
|
+
batchRequests: [
|
|
2334
|
+
{
|
|
2335
|
+
batch_request_id: batchRequestId,
|
|
2336
|
+
batch_request: {
|
|
2337
|
+
chat_get_completion: buildBatchChatCompletionRequest({
|
|
2338
|
+
modelId: runtime.modelId,
|
|
2339
|
+
system,
|
|
2340
|
+
messages: [...this.messages, ...turnMessages],
|
|
2341
|
+
temperature: 0.7,
|
|
2342
|
+
maxOutputTokens: runtime.modelInfo?.supportsMaxOutputTokens === false ? undefined : this.maxTokens,
|
|
2343
|
+
reasoningEffort: runtime.providerOptions?.xai.reasoningEffort,
|
|
2344
|
+
tools: batchTools,
|
|
2345
|
+
}),
|
|
2346
|
+
},
|
|
2347
|
+
},
|
|
2348
|
+
],
|
|
2349
|
+
});
|
|
2350
|
+
|
|
2351
|
+
const result = await pollBatchRequestResult({
|
|
2352
|
+
...this.getBatchClientOptions(signal),
|
|
2353
|
+
batchId: batch.batch_id,
|
|
2354
|
+
batchRequestId,
|
|
2355
|
+
});
|
|
2356
|
+
const response = getBatchChatCompletion(result);
|
|
2357
|
+
const choice = response.choices[0];
|
|
2358
|
+
if (!choice) {
|
|
2359
|
+
throw new Error("Batch response did not contain any choices.");
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
const usage = getBatchUsage(response);
|
|
2363
|
+
accumulateUsage(totalUsage, usage);
|
|
2364
|
+
const finishReason = getBatchFinishReason(choice.finish_reason);
|
|
2365
|
+
|
|
2366
|
+
const content = choice.message.content ?? "";
|
|
2367
|
+
if (content) {
|
|
2368
|
+
yield { type: "content", content };
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
const requestMessages = [...this.messages, ...turnMessages];
|
|
2372
|
+
const toolCalls = (choice.message.tool_calls ?? []).map(toLocalToolCall);
|
|
2373
|
+
const assistantMessage = buildAssistantBatchMessage(content, toolCalls);
|
|
2374
|
+
if (assistantMessage) {
|
|
2375
|
+
turnMessages.push(assistantMessage);
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
if (toolCalls.length === 0) {
|
|
2379
|
+
notifyObserver(observer?.onStepFinish, {
|
|
2380
|
+
stepNumber,
|
|
2381
|
+
timestamp: Date.now(),
|
|
2382
|
+
finishReason,
|
|
2383
|
+
usage,
|
|
2384
|
+
});
|
|
2385
|
+
if (hasUsage(totalUsage)) {
|
|
2386
|
+
this.recordUsage(totalUsage, "message", runtime.modelId);
|
|
2387
|
+
}
|
|
2388
|
+
this.appendCompletedTurn(userModelMessage, turnMessages);
|
|
2389
|
+
if (modelInfo?.contextWindow) {
|
|
2390
|
+
await this.postTurnCompact(provider, system, modelInfo.contextWindow, signal);
|
|
2391
|
+
}
|
|
2392
|
+
yield { type: "done" };
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
yield { type: "tool_calls", toolCalls };
|
|
2397
|
+
|
|
2398
|
+
const toolParts: ExecutedBatchTool[] = [];
|
|
2399
|
+
for (const toolCall of toolCalls) {
|
|
2400
|
+
notifyObserver(observer?.onToolStart, {
|
|
2401
|
+
toolCall,
|
|
2402
|
+
timestamp: Date.now(),
|
|
2403
|
+
});
|
|
2404
|
+
|
|
2405
|
+
const executed = await this.executeBatchToolCall(tools, toolCall, requestMessages, signal);
|
|
2406
|
+
notifyObserver(observer?.onToolFinish, {
|
|
2407
|
+
toolCall,
|
|
2408
|
+
toolResult: executed.result,
|
|
2409
|
+
timestamp: Date.now(),
|
|
2410
|
+
});
|
|
2411
|
+
yield { type: "tool_result", toolCall, toolResult: executed.result };
|
|
2412
|
+
toolParts.push({
|
|
2413
|
+
toolCall,
|
|
2414
|
+
input: executed.input,
|
|
2415
|
+
toolResult: executed.result,
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
const toolMessage = buildToolBatchMessage(toolParts);
|
|
2420
|
+
if (toolMessage) {
|
|
2421
|
+
turnMessages.push(toolMessage);
|
|
2422
|
+
}
|
|
2423
|
+
notifyObserver(observer?.onStepFinish, {
|
|
2424
|
+
stepNumber,
|
|
2425
|
+
timestamp: Date.now(),
|
|
2426
|
+
finishReason,
|
|
2427
|
+
usage,
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const message = `Error: Reached max tool rounds (${this.maxToolRounds}) in batch mode.`;
|
|
2432
|
+
notifyObserver(observer?.onError, {
|
|
2433
|
+
message,
|
|
2434
|
+
timestamp: Date.now(),
|
|
2435
|
+
});
|
|
2436
|
+
if (hasUsage(totalUsage)) {
|
|
2437
|
+
this.recordUsage(totalUsage, "message", runtime.modelId);
|
|
2438
|
+
}
|
|
2439
|
+
this.appendCompletedTurn(userModelMessage, turnMessages);
|
|
2440
|
+
yield { type: "error", content: message };
|
|
2441
|
+
yield { type: "done" };
|
|
2442
|
+
return;
|
|
2443
|
+
} catch (err: unknown) {
|
|
2444
|
+
if (signal.aborted) {
|
|
2445
|
+
this.discardAbortedTurn(userModelMessage);
|
|
2446
|
+
yield { type: "content", content: "\n\n[Cancelled]" };
|
|
2447
|
+
yield { type: "done" };
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
if (!attemptedOverflowRecovery && turnMessages.length === 0 && modelInfo && isContextLimitError(err)) {
|
|
2452
|
+
attemptedOverflowRecovery = true;
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
const authError = isAuthenticationError(err);
|
|
2457
|
+
const friendly = humanizeApiError(err);
|
|
2458
|
+
notifyObserver(observer?.onError, {
|
|
2459
|
+
message: friendly,
|
|
2460
|
+
timestamp: Date.now(),
|
|
2461
|
+
});
|
|
2462
|
+
if (hasUsage(totalUsage)) {
|
|
2463
|
+
this.recordUsage(totalUsage, "message", runtime.modelId);
|
|
2464
|
+
}
|
|
2465
|
+
this.appendCompletedTurn(userModelMessage, turnMessages);
|
|
2466
|
+
yield {
|
|
2467
|
+
type: "error",
|
|
2468
|
+
content: friendly,
|
|
2469
|
+
isAuthError: authError,
|
|
2470
|
+
};
|
|
2471
|
+
yield { type: "done" };
|
|
2472
|
+
return;
|
|
2473
|
+
} finally {
|
|
2474
|
+
await closeMcp?.().catch(() => {});
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
private appendCompletedTurn(userMessage: ModelMessage, newMessages: ModelMessage[]): void {
|
|
2480
|
+
if (newMessages.length === 0) return;
|
|
2481
|
+
|
|
2482
|
+
const userIndex = this.messages.lastIndexOf(userMessage);
|
|
2483
|
+
if (!this.sessionStore || !this.session) {
|
|
2484
|
+
if (userIndex >= 0 && this.messageSeqs[userIndex] == null) {
|
|
2485
|
+
this.messageSeqs[userIndex] = null;
|
|
2486
|
+
}
|
|
2487
|
+
this.messages.push(...newMessages);
|
|
2488
|
+
this.messageSeqs.push(...newMessages.map(() => null));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
const insertedSeqs = appendMessages(this.session.id, [userMessage, ...newMessages]);
|
|
2493
|
+
if (userIndex >= 0) {
|
|
2494
|
+
this.messageSeqs[userIndex] = insertedSeqs[0] ?? this.messageSeqs[userIndex];
|
|
2495
|
+
}
|
|
2496
|
+
this.messages.push(...newMessages);
|
|
2497
|
+
this.messageSeqs.push(...insertedSeqs.slice(1));
|
|
2498
|
+
this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
|
|
2499
|
+
this.session = this.sessionStore.getRequiredSession(this.session.id);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
private fireHook(
|
|
2503
|
+
input: Parameters<typeof executeEventHooks>[0],
|
|
2504
|
+
signal?: AbortSignal,
|
|
2505
|
+
): Promise<Awaited<ReturnType<typeof executeEventHooks>>> {
|
|
2506
|
+
return executeEventHooks(input, this.bash.getCwd(), signal);
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
async *processMessage(
|
|
2510
|
+
userMessage: string,
|
|
2511
|
+
observer?: ProcessMessageObserver,
|
|
2512
|
+
images?: Array<{ path: string; mediaType: string; base64: string }>,
|
|
2513
|
+
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
2514
|
+
// TUI-04: prefer the external AbortContext (from SIGINT handler) so that
|
|
2515
|
+
// Ctrl+C mid-tool-call triggers a single, unified abort across all I/O.
|
|
2516
|
+
// If no external context, fall back to creating a local AbortController.
|
|
2517
|
+
if (this.externalAbortContext) {
|
|
2518
|
+
// Wrap the external signal in a local controller so existing cleanup
|
|
2519
|
+
// paths (this.abortController = null) still work without side-effects.
|
|
2520
|
+
this.abortController = new AbortController();
|
|
2521
|
+
// Forward external abort to the local controller.
|
|
2522
|
+
this.externalAbortContext.signal.addEventListener(
|
|
2523
|
+
"abort",
|
|
2524
|
+
() => {
|
|
2525
|
+
this.abortController?.abort(this.externalAbortContext?.reason());
|
|
2526
|
+
},
|
|
2527
|
+
{ once: true },
|
|
2528
|
+
);
|
|
2529
|
+
} else {
|
|
2530
|
+
this.abortController = new AbortController();
|
|
2531
|
+
}
|
|
2532
|
+
const signal = this.abortController.signal;
|
|
2533
|
+
this.emitSubagentStatus(null);
|
|
2534
|
+
|
|
2535
|
+
// Ensure flow run is ready before processing (fail-open).
|
|
2536
|
+
await this._flowReady?.catch(() => {});
|
|
2537
|
+
|
|
2538
|
+
if (!this.sessionStartHookFired) {
|
|
2539
|
+
this.sessionStartHookFired = true;
|
|
2540
|
+
const isResume = this.messages.length > 0;
|
|
2541
|
+
const sessionStartInput: SessionStartHookInput = {
|
|
2542
|
+
hook_event_name: "SessionStart",
|
|
2543
|
+
source: isResume ? "resume" : "startup",
|
|
2544
|
+
session_id: this.session?.id,
|
|
2545
|
+
cwd: this.bash.getCwd(),
|
|
2546
|
+
};
|
|
2547
|
+
await this.fireHook(sessionStartInput, signal).catch(() => {});
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
const promptInput: UserPromptSubmitHookInput = {
|
|
2551
|
+
hook_event_name: "UserPromptSubmit",
|
|
2552
|
+
user_prompt: userMessage,
|
|
2553
|
+
session_id: this.session?.id,
|
|
2554
|
+
cwd: this.bash.getCwd(),
|
|
2555
|
+
};
|
|
2556
|
+
await this.fireHook(promptInput, signal).catch(() => {});
|
|
2557
|
+
|
|
2558
|
+
await this.consumeBackgroundNotifications();
|
|
2559
|
+
|
|
2560
|
+
const _debugOn = isDebugEnabled();
|
|
2561
|
+
const _debugSteps: PipelineStep[] = [];
|
|
2562
|
+
const _debugTurnId = this.messages.filter((m) => m.role === "user").length + 1;
|
|
2563
|
+
|
|
2564
|
+
// PIL: enrich prompt before pushing to messages (D-01, D-03, D-04)
|
|
2565
|
+
// Promise.race timeout of 200ms is inside runPipeline — fail-open guaranteed
|
|
2566
|
+
const _pilStart = Date.now();
|
|
2567
|
+
const pilCtx = await runPipeline(userMessage, {
|
|
2568
|
+
resumeDigest: this._resumeDigest,
|
|
2569
|
+
activeRunId: this._activeRunId,
|
|
2570
|
+
}).catch(() => ({
|
|
2571
|
+
raw: userMessage,
|
|
2572
|
+
enriched: userMessage,
|
|
2573
|
+
taskType: null,
|
|
2574
|
+
domain: null,
|
|
2575
|
+
confidence: 0,
|
|
2576
|
+
outputStyle: null,
|
|
2577
|
+
tokenBudget: 500,
|
|
2578
|
+
metrics: null,
|
|
2579
|
+
layers: [],
|
|
2580
|
+
gsdPhase: null,
|
|
2581
|
+
activeRunId: null,
|
|
2582
|
+
}));
|
|
2583
|
+
const enrichedMessage = pilCtx.enriched;
|
|
2584
|
+
this._pilActive = pilCtx.taskType !== null;
|
|
2585
|
+
this._pilEnrichmentDelta =
|
|
2586
|
+
pilCtx.metrics?.estimatedTokensSaved ?? Math.round((enrichedMessage.length - userMessage.length) / 4);
|
|
2587
|
+
|
|
2588
|
+
if (_debugOn) {
|
|
2589
|
+
const appliedLayers = pilCtx.layers?.filter((l) => l.applied).map((l) => l.name) ?? [];
|
|
2590
|
+
_debugSteps.push({
|
|
2591
|
+
name: "PIL Pipeline",
|
|
2592
|
+
duration_ms: Date.now() - _pilStart,
|
|
2593
|
+
input_summary: `"${userMessage.slice(0, 60)}${userMessage.length > 60 ? "..." : ""}"`,
|
|
2594
|
+
output_summary: `task=${pilCtx.taskType ?? "none"} domain=${pilCtx.domain ?? "none"} layers=[${appliedLayers.join(",")}]`,
|
|
2595
|
+
tokens_saved: this._pilEnrichmentDelta > 0 ? this._pilEnrichmentDelta : undefined,
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
// ROUTE-11: Per-turn model routing via decide() — picks cheapest capable model
|
|
2600
|
+
const turnStartMs = Date.now();
|
|
2601
|
+
let turnModelId = this.modelId;
|
|
2602
|
+
let taskHash: string | null = null;
|
|
2603
|
+
const _routeStart = Date.now();
|
|
2604
|
+
try {
|
|
2605
|
+
const { decide } = await import("../router/decide.js");
|
|
2606
|
+
const routeDecision = await decide(userMessage, {
|
|
2607
|
+
tenantId: "local",
|
|
2608
|
+
cwd: this.bash.getCwd(),
|
|
2609
|
+
defaultModel: this.modelId,
|
|
2610
|
+
defaultProvider: this.providerId,
|
|
2611
|
+
pil: {
|
|
2612
|
+
domain: pilCtx.domain,
|
|
2613
|
+
taskType: pilCtx.taskType,
|
|
2614
|
+
confidence: pilCtx.confidence,
|
|
2615
|
+
gsdPhase: pilCtx.gsdPhase ?? null,
|
|
2616
|
+
activeRunId: pilCtx.activeRunId ?? null,
|
|
2617
|
+
recentTurnsSummary: this._buildRecentTurnsSummary(),
|
|
2618
|
+
projectSize: this._estimateProjectSize(),
|
|
2619
|
+
filesTouched: this._countFilesTouched(),
|
|
2620
|
+
mode: this.mode,
|
|
2621
|
+
},
|
|
2622
|
+
});
|
|
2623
|
+
if (routeDecision.model && routeDecision.model !== "HALT") {
|
|
2624
|
+
turnModelId = routeDecision.model;
|
|
2625
|
+
}
|
|
2626
|
+
taskHash = routeDecision.taskHash ?? null;
|
|
2627
|
+
// Update status bar with router switch info
|
|
2628
|
+
if (turnModelId !== this.modelId) {
|
|
2629
|
+
statusBarStore.setState({ routed_from: this.modelId, model: turnModelId });
|
|
2630
|
+
}
|
|
2631
|
+
if (_debugOn) {
|
|
2632
|
+
_debugSteps.push({
|
|
2633
|
+
name: "Router",
|
|
2634
|
+
duration_ms: Date.now() - _routeStart,
|
|
2635
|
+
input_summary: `default=${this.modelId}`,
|
|
2636
|
+
output_summary: turnModelId !== this.modelId ? `routed→${turnModelId}` : `kept ${turnModelId}`,
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
} catch {
|
|
2640
|
+
// Router unavailable — use session default model
|
|
2641
|
+
const eeRoute = await routeModel(userMessage, {}, "claude").catch(() => null);
|
|
2642
|
+
taskHash = eeRoute?.taskHash ?? null;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// Re-detect provider if router picked a model from a different provider
|
|
2646
|
+
const turnProviderId = detectProviderForModel(turnModelId);
|
|
2647
|
+
let turnProvider: LegacyProvider;
|
|
2648
|
+
if (turnProviderId !== this.providerId) {
|
|
2649
|
+
const turnKey = await loadKeyForProvider(turnProviderId).catch(() => null);
|
|
2650
|
+
if (turnKey) {
|
|
2651
|
+
turnProvider = createProvider(turnProviderId, turnKey);
|
|
2652
|
+
} else {
|
|
2653
|
+
turnModelId = this.modelId;
|
|
2654
|
+
turnProvider = this.requireProvider();
|
|
2655
|
+
}
|
|
2656
|
+
} else {
|
|
2657
|
+
turnProvider = this.requireProvider();
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
let userModelMessage: ModelMessage;
|
|
2661
|
+
if (images?.length) {
|
|
2662
|
+
const parts: Array<{ type: "text"; text: string } | { type: "image"; image: string; mediaType: string }> = [
|
|
2663
|
+
{ type: "text", text: enrichedMessage },
|
|
2664
|
+
];
|
|
2665
|
+
for (const img of images) {
|
|
2666
|
+
parts.push({ type: "image", image: img.base64, mediaType: img.mediaType });
|
|
2667
|
+
}
|
|
2668
|
+
userModelMessage = { role: "user", content: parts };
|
|
2669
|
+
} else {
|
|
2670
|
+
userModelMessage = { role: "user", content: enrichedMessage };
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
// Vision proxy: convert images to text for models that don't support vision
|
|
2674
|
+
if (images?.length && needsVisionProxy(turnModelId)) {
|
|
2675
|
+
try {
|
|
2676
|
+
const proxyResult = await proxyVision([userModelMessage], turnModelId, signal);
|
|
2677
|
+
if (proxyResult.proxied) {
|
|
2678
|
+
userModelMessage = proxyResult.messages[0];
|
|
2679
|
+
yield { type: "content", content: `[Vision proxy: ${proxyResult.imageCount} image(s) → ${turnModelId} via SiliconFlow]\n` };
|
|
2680
|
+
}
|
|
2681
|
+
} catch {
|
|
2682
|
+
yield { type: "content", content: "[Vision proxy: failed, images dropped]\n" };
|
|
2683
|
+
userModelMessage = { role: "user", content: enrichedMessage };
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
this.messages.push(userModelMessage);
|
|
2688
|
+
this.messageSeqs.push(null);
|
|
2689
|
+
|
|
2690
|
+
const provider = turnProvider;
|
|
2691
|
+
const subagents = loadValidSubAgents();
|
|
2692
|
+
const _pilResponseTools = getResponseToolSet(pilCtx);
|
|
2693
|
+
const _hasResponseTools = Object.keys(_pilResponseTools).length > 0;
|
|
2694
|
+
const systemParts = buildSystemPromptParts(
|
|
2695
|
+
this.bash.getCwd(),
|
|
2696
|
+
this.mode,
|
|
2697
|
+
this.bash.getSandboxMode(),
|
|
2698
|
+
this.planContext,
|
|
2699
|
+
subagents,
|
|
2700
|
+
this.bash.getSandboxSettings(),
|
|
2701
|
+
this.providerId,
|
|
2702
|
+
);
|
|
2703
|
+
const system = applyModelConstraints(
|
|
2704
|
+
applyPilSuffix(
|
|
2705
|
+
`${systemParts.staticPrefix}${systemParts.dynamicSuffix}`,
|
|
2706
|
+
pilCtx,
|
|
2707
|
+
_hasResponseTools,
|
|
2708
|
+
),
|
|
2709
|
+
turnModelId,
|
|
2710
|
+
);
|
|
2711
|
+
const runtime = resolveModelRuntime(provider, turnModelId);
|
|
2712
|
+
const modelInfo = runtime.modelInfo;
|
|
2713
|
+
this.planContext = null;
|
|
2714
|
+
let attemptedOverflowRecovery = false;
|
|
2715
|
+
|
|
2716
|
+
// Auto-council: for plan/analyze tasks with high confidence, run multi-model debate
|
|
2717
|
+
const autoCouncilTypes = new Set(["plan", "analyze"]);
|
|
2718
|
+
const councilRoles = getRoleModels();
|
|
2719
|
+
const configuredRoleCount = Object.values(councilRoles).filter(Boolean).length;
|
|
2720
|
+
if (
|
|
2721
|
+
isAutoCouncilEnabled() &&
|
|
2722
|
+
pilCtx.taskType &&
|
|
2723
|
+
autoCouncilTypes.has(pilCtx.taskType) &&
|
|
2724
|
+
pilCtx.confidence >= 0.75 &&
|
|
2725
|
+
configuredRoleCount >= 2
|
|
2726
|
+
) {
|
|
2727
|
+
yield { type: "content", content: `\n[Auto-council triggered: ${pilCtx.taskType} task detected with ${(pilCtx.confidence * 100).toFixed(0)}% confidence]\n` };
|
|
2728
|
+
yield* this.runCouncilRound(userMessage, observer);
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
if (this.batchApi) {
|
|
2733
|
+
try {
|
|
2734
|
+
yield* this.processMessageBatchTurn({
|
|
2735
|
+
userModelMessage,
|
|
2736
|
+
observer,
|
|
2737
|
+
provider,
|
|
2738
|
+
subagents,
|
|
2739
|
+
system,
|
|
2740
|
+
runtime,
|
|
2741
|
+
modelInfo,
|
|
2742
|
+
signal,
|
|
2743
|
+
});
|
|
2744
|
+
} finally {
|
|
2745
|
+
if (this.abortController?.signal === signal) {
|
|
2746
|
+
this.abortController = null;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
try {
|
|
2753
|
+
while (true) {
|
|
2754
|
+
this._compactedThisTurn = false;
|
|
2755
|
+
let assistantText = "";
|
|
2756
|
+
let reasoningPreview = "";
|
|
2757
|
+
let encryptedReasoningHidden = false;
|
|
2758
|
+
let streamOk = false;
|
|
2759
|
+
let closeMcp: (() => Promise<void>) | undefined;
|
|
2760
|
+
let stepNumber = -1;
|
|
2761
|
+
const activeToolCalls: ToolCall[] = [];
|
|
2762
|
+
|
|
2763
|
+
try {
|
|
2764
|
+
const settings = attemptedOverflowRecovery
|
|
2765
|
+
? relaxCompactionSettings(this.getCompactionSettings())
|
|
2766
|
+
: this.getCompactionSettings();
|
|
2767
|
+
if (modelInfo?.contextWindow) {
|
|
2768
|
+
await this.compactForContext(
|
|
2769
|
+
provider,
|
|
2770
|
+
system,
|
|
2771
|
+
modelInfo.contextWindow,
|
|
2772
|
+
signal,
|
|
2773
|
+
settings,
|
|
2774
|
+
attemptedOverflowRecovery,
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
const baseTools = createTools(this.bash, provider, this.mode, {
|
|
2779
|
+
runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
|
|
2780
|
+
runDelegation: (request, abortSignal) =>
|
|
2781
|
+
this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
|
|
2782
|
+
readDelegation: (id) => this.readDelegation(id),
|
|
2783
|
+
listDelegations: () => this.listDelegations(),
|
|
2784
|
+
scheduleManager: this.schedules,
|
|
2785
|
+
subagents,
|
|
2786
|
+
sendTelegramFile: this.sendTelegramFile ?? undefined,
|
|
2787
|
+
sessionId: this.session?.id ?? undefined,
|
|
2788
|
+
});
|
|
2789
|
+
let tools: ToolSet = runtime.modelInfo?.supportsClientTools === false ? {} : baseTools;
|
|
2790
|
+
if (this.mode === "agent" && runtime.modelInfo?.supportsClientTools !== false) {
|
|
2791
|
+
const mcpBundle = await buildMcpToolSet(loadMcpServers(), {
|
|
2792
|
+
onOAuthRequired: (_serverId, url) => {
|
|
2793
|
+
const urlStr = url.toString();
|
|
2794
|
+
import("child_process").then(({ exec }) => {
|
|
2795
|
+
const cmd = process.platform === "win32" ? `start "" "${urlStr}"`
|
|
2796
|
+
: process.platform === "darwin" ? `open "${urlStr}"`
|
|
2797
|
+
: `xdg-open "${urlStr}"`;
|
|
2798
|
+
exec(cmd);
|
|
2799
|
+
});
|
|
2800
|
+
},
|
|
2801
|
+
});
|
|
2802
|
+
closeMcp = mcpBundle.close;
|
|
2803
|
+
tools = { ...baseTools, ...mcpBundle.tools };
|
|
2804
|
+
captureToolSchemas(tools);
|
|
2805
|
+
if (mcpBundle.errors.length > 0) {
|
|
2806
|
+
yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
// PIL response tools: inject structured output tool when taskType detected
|
|
2811
|
+
if (_hasResponseTools && runtime.modelInfo?.supportsClientTools !== false) {
|
|
2812
|
+
tools = { ...tools, ..._pilResponseTools };
|
|
2813
|
+
captureToolSchemas(_pilResponseTools);
|
|
2814
|
+
}
|
|
2815
|
+
let responseToolCalled = false;
|
|
2816
|
+
|
|
2817
|
+
// Build provider options, optionally adding reasoning budget for capable models
|
|
2818
|
+
const baseProviderOpts = runtime.providerOptions ?? {};
|
|
2819
|
+
const providerOpts = runtime.modelInfo?.reasoning
|
|
2820
|
+
? {
|
|
2821
|
+
...baseProviderOpts,
|
|
2822
|
+
anthropic: {
|
|
2823
|
+
...(baseProviderOpts.anthropic ?? {}),
|
|
2824
|
+
thinking: {
|
|
2825
|
+
type: "enabled" as const,
|
|
2826
|
+
budgetTokens:
|
|
2827
|
+
taskTypeToReasoningEffort(pilCtx.taskType) === "high"
|
|
2828
|
+
? 32_768
|
|
2829
|
+
: taskTypeToReasoningEffort(pilCtx.taskType) === "medium"
|
|
2830
|
+
? 8_192
|
|
2831
|
+
: 2_048,
|
|
2832
|
+
},
|
|
2833
|
+
},
|
|
2834
|
+
}
|
|
2835
|
+
: baseProviderOpts;
|
|
2836
|
+
// Use catalog's thinkingType field instead of regex matching
|
|
2837
|
+
const thinkingModelInfo = getModelInfo(runtime.modelId);
|
|
2838
|
+
if (providerOpts.anthropic?.thinking?.type === "enabled" && thinkingModelInfo?.thinkingType === "adaptive") {
|
|
2839
|
+
providerOpts.anthropic.thinking = { type: "adaptive" as any };
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
// Multi-provider caching: OpenAI stored completions, DeepSeek prefix cache
|
|
2843
|
+
const turnProvider = runtime.modelInfo?.provider ?? this.providerId;
|
|
2844
|
+
if (turnProvider === "openai") {
|
|
2845
|
+
providerOpts.openai = { ...(providerOpts.openai ?? {}), store: true };
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
const systemForModel = runtime.modelId.startsWith("claude")
|
|
2849
|
+
? [
|
|
2850
|
+
{ role: "system" as const, content: systemParts.staticPrefix, providerOptions: { anthropic: { cacheControl: { type: "ephemeral" as const } } } },
|
|
2851
|
+
{ role: "system" as const, content: system.slice(systemParts.staticPrefix.length) },
|
|
2852
|
+
]
|
|
2853
|
+
: system;
|
|
2854
|
+
|
|
2855
|
+
const result = streamText({
|
|
2856
|
+
model: runtime.model,
|
|
2857
|
+
system: systemForModel,
|
|
2858
|
+
messages: this.messages,
|
|
2859
|
+
tools,
|
|
2860
|
+
toolChoice:
|
|
2861
|
+
_hasResponseTools && runtime.modelInfo?.supportsClientTools !== false
|
|
2862
|
+
? "auto"
|
|
2863
|
+
: undefined,
|
|
2864
|
+
stopWhen: stepCountIs(this.maxToolRounds),
|
|
2865
|
+
maxRetries: 0,
|
|
2866
|
+
abortSignal: signal,
|
|
2867
|
+
temperature: 0.7,
|
|
2868
|
+
...(runtime.modelInfo?.supportsMaxOutputTokens === false ? {} : { maxOutputTokens: taskTypeToMaxTokens(pilCtx.taskType) }),
|
|
2869
|
+
...(Object.keys(providerOpts).length > 0 ? { providerOptions: providerOpts } : {}),
|
|
2870
|
+
experimental_onStepStart: (event: unknown) => {
|
|
2871
|
+
stepNumber = getStepNumber(event, stepNumber + 1);
|
|
2872
|
+
notifyObserver(observer?.onStepStart, {
|
|
2873
|
+
stepNumber,
|
|
2874
|
+
timestamp: Date.now(),
|
|
2875
|
+
});
|
|
2876
|
+
},
|
|
2877
|
+
onStepFinish: (event: unknown) => {
|
|
2878
|
+
const currentStep = getStepNumber(event, Math.max(stepNumber, 0));
|
|
2879
|
+
stepNumber = Math.max(stepNumber, currentStep);
|
|
2880
|
+
const stepUsage = getUsage(event);
|
|
2881
|
+
notifyObserver(observer?.onStepFinish, {
|
|
2882
|
+
stepNumber: currentStep,
|
|
2883
|
+
timestamp: Date.now(),
|
|
2884
|
+
finishReason: getFinishReason(event),
|
|
2885
|
+
usage: stepUsage,
|
|
2886
|
+
});
|
|
2887
|
+
// Realtime status bar update per step
|
|
2888
|
+
if (stepUsage.inputTokens || stepUsage.outputTokens) {
|
|
2889
|
+
this.recordUsage(stepUsage, "message", runtime.modelId);
|
|
2890
|
+
}
|
|
2891
|
+
},
|
|
2892
|
+
onFinish: ({ providerMetadata }) => {
|
|
2893
|
+
// Cache metrics only available in onFinish (Anthropic provider metadata)
|
|
2894
|
+
const anthropicMeta = providerMetadata?.anthropic as Record<string, unknown> | undefined;
|
|
2895
|
+
const cacheReadTokens = asNumber(anthropicMeta?.cacheReadInputTokens) ?? 0;
|
|
2896
|
+
const cacheCreationTokens = asNumber(anthropicMeta?.cacheCreationInputTokens) ?? 0;
|
|
2897
|
+
if (cacheReadTokens || cacheCreationTokens) {
|
|
2898
|
+
const prev = statusBarStore.getState();
|
|
2899
|
+
statusBarStore.setState({
|
|
2900
|
+
cache_read_tokens: (prev.cache_read_tokens ?? 0) + cacheReadTokens,
|
|
2901
|
+
cache_creation_tokens: (prev.cache_creation_tokens ?? 0) + cacheCreationTokens,
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
},
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
for await (const part of result.fullStream) {
|
|
2908
|
+
if (signal.aborted) {
|
|
2909
|
+
yield { type: "content", content: "\n\n[Cancelled]" };
|
|
2910
|
+
break;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
switch (part.type) {
|
|
2914
|
+
case "text-delta":
|
|
2915
|
+
assistantText += part.text;
|
|
2916
|
+
yield { type: "content", content: part.text };
|
|
2917
|
+
break;
|
|
2918
|
+
|
|
2919
|
+
case "reasoning-delta":
|
|
2920
|
+
reasoningPreview = `${reasoningPreview}${part.text}`.slice(-256);
|
|
2921
|
+
if (containsEncryptedReasoning(reasoningPreview)) {
|
|
2922
|
+
if (!encryptedReasoningHidden) {
|
|
2923
|
+
encryptedReasoningHidden = true;
|
|
2924
|
+
yield { type: "reasoning", content: "[Encrypted reasoning hidden]" };
|
|
2925
|
+
}
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2928
|
+
yield { type: "reasoning", content: part.text };
|
|
2929
|
+
break;
|
|
2930
|
+
|
|
2931
|
+
case "tool-call": {
|
|
2932
|
+
const tc = toToolCall(part);
|
|
2933
|
+
activeToolCalls.push(tc);
|
|
2934
|
+
|
|
2935
|
+
// EE PreToolUse hook: fire intercept before tool execution.
|
|
2936
|
+
{
|
|
2937
|
+
const preInput: PreToolUseHookInput = {
|
|
2938
|
+
hook_event_name: "PreToolUse",
|
|
2939
|
+
tool_name: tc.function.name,
|
|
2940
|
+
tool_input: JSON.parse(tc.function.arguments || "{}"),
|
|
2941
|
+
session_id: this.session?.id,
|
|
2942
|
+
cwd: this.bash.getCwd(),
|
|
2943
|
+
};
|
|
2944
|
+
const preResult = await this.fireHook(preInput, signal).catch(() => ({
|
|
2945
|
+
blocked: false,
|
|
2946
|
+
blockingErrors: [],
|
|
2947
|
+
preventContinuation: false,
|
|
2948
|
+
additionalContexts: [] as string[],
|
|
2949
|
+
results: [],
|
|
2950
|
+
}));
|
|
2951
|
+
for (const ctx of preResult.additionalContexts ?? []) {
|
|
2952
|
+
yield { type: "content", content: `${ctx}\n` };
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// Pitfall 9: log the pending call so reconcile() can recover any
|
|
2957
|
+
// staged .tmp files if the process is killed before tool-result.
|
|
2958
|
+
if (this.pendingCalls) {
|
|
2959
|
+
const turnId = this.session?.id ?? "anon";
|
|
2960
|
+
const callId = stableCallId(turnId, tc.function.name, tc.function.arguments);
|
|
2961
|
+
// Phase 0: predictStagedPaths = [] for all tools (refined in Phase 1).
|
|
2962
|
+
void this.pendingCalls.begin({ call_id: callId, tool_name: tc.function.name }).catch(() => {});
|
|
2963
|
+
// Attach callId to the ToolCall so tool-result can end it.
|
|
2964
|
+
(tc as ToolCall & { _pendingCallId?: string })._pendingCallId = callId;
|
|
2965
|
+
}
|
|
2966
|
+
notifyObserver(observer?.onToolStart, {
|
|
2967
|
+
toolCall: tc,
|
|
2968
|
+
timestamp: Date.now(),
|
|
2969
|
+
});
|
|
2970
|
+
yield { type: "tool_calls", toolCalls: [tc] };
|
|
2971
|
+
break;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
case "tool-result": {
|
|
2975
|
+
const tc: ToolCall = {
|
|
2976
|
+
id: part.toolCallId,
|
|
2977
|
+
type: "function",
|
|
2978
|
+
function: { name: part.toolName, arguments: JSON.stringify(part.input ?? {}) },
|
|
2979
|
+
};
|
|
2980
|
+
const tr = toToolResult(part.output);
|
|
2981
|
+
// Pitfall 9: settle the pending call log entry.
|
|
2982
|
+
if (this.pendingCalls) {
|
|
2983
|
+
const pending = activeToolCalls.find((t) => t.id === part.toolCallId);
|
|
2984
|
+
const callId = (pending as ToolCall & { _pendingCallId?: string })?._pendingCallId;
|
|
2985
|
+
if (callId) {
|
|
2986
|
+
const endStatus = signal.aborted ? "aborted" : "settled";
|
|
2987
|
+
void this.pendingCalls.end(callId, endStatus).catch(() => {});
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
// EE PostToolUse hook: fire-and-forget after tool execution.
|
|
2991
|
+
{
|
|
2992
|
+
const postInput: PostToolUseHookInput = {
|
|
2993
|
+
hook_event_name: "PostToolUse",
|
|
2994
|
+
tool_name: part.toolName,
|
|
2995
|
+
tool_input: (part.input as Record<string, unknown>) ?? {},
|
|
2996
|
+
tool_output: typeof tr.output === "string" ? { text: tr.output } : ((tr.output as unknown as Record<string, unknown>) ?? {}),
|
|
2997
|
+
session_id: this.session?.id,
|
|
2998
|
+
cwd: this.bash.getCwd(),
|
|
2999
|
+
};
|
|
3000
|
+
await this.fireHook(postInput, signal).catch(() => {});
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
// Response tool: yield as structured_response instead of tool_result
|
|
3004
|
+
if (isResponseTool(part.toolName)) {
|
|
3005
|
+
responseToolCalled = true;
|
|
3006
|
+
const taskType = getResponseTaskType(part.toolName);
|
|
3007
|
+
yield {
|
|
3008
|
+
type: "structured_response" as StreamChunk["type"],
|
|
3009
|
+
structuredResponse: {
|
|
3010
|
+
taskType: taskType ?? part.toolName,
|
|
3011
|
+
data: (part.output ?? {}) as Record<string, unknown>,
|
|
3012
|
+
},
|
|
3013
|
+
};
|
|
3014
|
+
notifyObserver(observer?.onToolFinish, { toolCall: tc, toolResult: tr, timestamp: Date.now() });
|
|
3015
|
+
break;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
notifyObserver(observer?.onToolFinish, {
|
|
3019
|
+
toolCall: tc,
|
|
3020
|
+
toolResult: tr,
|
|
3021
|
+
timestamp: Date.now(),
|
|
3022
|
+
});
|
|
3023
|
+
yield { type: "tool_result", toolCall: tc, toolResult: tr };
|
|
3024
|
+
break;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
case "tool-approval-request": {
|
|
3028
|
+
const approvalPart = part as unknown as {
|
|
3029
|
+
approvalId: string;
|
|
3030
|
+
toolCall: { toolCallId: string; toolName: string; input: unknown };
|
|
3031
|
+
};
|
|
3032
|
+
const toolCallId = approvalPart.toolCall?.toolCallId ?? "";
|
|
3033
|
+
const pendingTc = activeToolCalls.find((tc) => tc.id === toolCallId);
|
|
3034
|
+
const tcForChunk = pendingTc ?? {
|
|
3035
|
+
id: toolCallId,
|
|
3036
|
+
type: "function" as const,
|
|
3037
|
+
function: {
|
|
3038
|
+
name: approvalPart.toolCall?.toolName ?? "paid_request",
|
|
3039
|
+
arguments: JSON.stringify(approvalPart.toolCall?.input ?? {}),
|
|
3040
|
+
},
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
// Payment pre-check disabled — Stripe billing pending.
|
|
3044
|
+
const paymentPrecheck: import("../types/index").PaymentPrecheck | undefined = undefined;
|
|
3045
|
+
|
|
3046
|
+
// Plan 03-01: check permission mode before yielding approval request to UI.
|
|
3047
|
+
// auto-edit auto-approves file ops; yolo auto-approves everything.
|
|
3048
|
+
const toolName = approvalPart.toolCall?.toolName ?? "";
|
|
3049
|
+
if (!toolNeedsApproval(toolName, this.permissionMode)) {
|
|
3050
|
+
// Auto-approve: respond directly without surfacing to UI.
|
|
3051
|
+
this.respondToToolApproval(approvalPart.approvalId, true);
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
yield {
|
|
3056
|
+
type: "tool_approval_request",
|
|
3057
|
+
approvalId: approvalPart.approvalId,
|
|
3058
|
+
toolCall: tcForChunk,
|
|
3059
|
+
paymentPrecheck,
|
|
3060
|
+
};
|
|
3061
|
+
break;
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
case "error": {
|
|
3065
|
+
const authError = isAuthenticationError(part.error);
|
|
3066
|
+
const friendly = humanizeApiError(part.error);
|
|
3067
|
+
notifyObserver(observer?.onError, {
|
|
3068
|
+
message: friendly,
|
|
3069
|
+
timestamp: Date.now(),
|
|
3070
|
+
});
|
|
3071
|
+
yield {
|
|
3072
|
+
type: "error",
|
|
3073
|
+
content: friendly,
|
|
3074
|
+
isAuthError: authError,
|
|
3075
|
+
};
|
|
3076
|
+
break;
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
case "abort":
|
|
3080
|
+
yield { type: "content", content: "\n\n[Cancelled]" };
|
|
3081
|
+
break;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
if (signal.aborted) {
|
|
3086
|
+
this.discardAbortedTurn(userModelMessage);
|
|
3087
|
+
yield { type: "done" };
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
try {
|
|
3092
|
+
const response = await result.response;
|
|
3093
|
+
if (!signal.aborted) {
|
|
3094
|
+
this.appendCompletedTurn(userModelMessage, sanitizeModelMessages(response.messages));
|
|
3095
|
+
streamOk = true;
|
|
3096
|
+
}
|
|
3097
|
+
} catch (responseError: unknown) {
|
|
3098
|
+
if (
|
|
3099
|
+
!attemptedOverflowRecovery &&
|
|
3100
|
+
!assistantText.trim() &&
|
|
3101
|
+
modelInfo &&
|
|
3102
|
+
isContextLimitError(responseError)
|
|
3103
|
+
) {
|
|
3104
|
+
attemptedOverflowRecovery = true;
|
|
3105
|
+
continue;
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
if (signal.aborted) {
|
|
3110
|
+
this.discardAbortedTurn(userModelMessage);
|
|
3111
|
+
yield { type: "done" };
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
if (!streamOk && assistantText.trim()) {
|
|
3116
|
+
this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// Fallback: model responded in text despite tool_choice=required
|
|
3120
|
+
// Attempt JSON extraction from assistant text → yield as structured_response
|
|
3121
|
+
if (_hasResponseTools && !responseToolCalled && pilCtx.taskType && assistantText.trim()) {
|
|
3122
|
+
try {
|
|
3123
|
+
const jsonMatch = assistantText.match(/\{[\s\S]*\}/);
|
|
3124
|
+
if (jsonMatch) {
|
|
3125
|
+
const parsed = JSON.parse(jsonMatch[0]) as Record<string, unknown>;
|
|
3126
|
+
if (Object.keys(parsed).length > 0) {
|
|
3127
|
+
responseToolCalled = true;
|
|
3128
|
+
yield {
|
|
3129
|
+
type: "structured_response" as StreamChunk["type"],
|
|
3130
|
+
structuredResponse: {
|
|
3131
|
+
taskType: pilCtx.taskType,
|
|
3132
|
+
data: parsed,
|
|
3133
|
+
},
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
} catch {
|
|
3138
|
+
// JSON parse failed — leave as text-fallback
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// Track PIL output mode for /optimize metrics
|
|
3143
|
+
{
|
|
3144
|
+
const { setLastOutputMode } = await import("../pil/store.js");
|
|
3145
|
+
if (!_hasResponseTools) setLastOutputMode("conversational");
|
|
3146
|
+
else if (responseToolCalled) setLastOutputMode("structured");
|
|
3147
|
+
else setLastOutputMode("text-fallback");
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
// ROUTE-11: Fire routeFeedback after turn completes (success path).
|
|
3151
|
+
// Must come AFTER posttool calls (posttool fires during tool-result processing above).
|
|
3152
|
+
// Fire-and-forget — no await. Skipped when taskHash is null (bridge absent).
|
|
3153
|
+
{
|
|
3154
|
+
const turnDuration = Date.now() - turnStartMs;
|
|
3155
|
+
if (taskHash) {
|
|
3156
|
+
const tier = taskTypeToTier(pilCtx.taskType);
|
|
3157
|
+
void routeFeedback(
|
|
3158
|
+
taskHash,
|
|
3159
|
+
tier,
|
|
3160
|
+
runtime.modelId,
|
|
3161
|
+
"success", // Phase 6: all normal completions = 'success'
|
|
3162
|
+
0, // retryCount: 0 for first attempt
|
|
3163
|
+
turnDuration,
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3166
|
+
// HTTP path: also report via router store taskHash (covers warm/cold EE routes)
|
|
3167
|
+
const storeHash = routerStore.getState().taskHash;
|
|
3168
|
+
if (storeHash) {
|
|
3169
|
+
reportRouteOutcome(storeHash, "success", turnDuration);
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
const stopInput: StopHookInput = {
|
|
3174
|
+
hook_event_name: "Stop",
|
|
3175
|
+
session_id: this.session?.id,
|
|
3176
|
+
cwd: this.bash.getCwd(),
|
|
3177
|
+
};
|
|
3178
|
+
await this.fireHook(stopInput, signal).catch(() => {});
|
|
3179
|
+
|
|
3180
|
+
// Debug trace: emit pipeline summary
|
|
3181
|
+
if (_debugOn) {
|
|
3182
|
+
const sb = statusBarStore.getState();
|
|
3183
|
+
const defaultInfo = getModelInfo(this.modelId);
|
|
3184
|
+
const usedInfo = getModelInfo(turnModelId);
|
|
3185
|
+
const routerSaved = (defaultInfo && usedInfo && defaultInfo.outputPrice > usedInfo.outputPrice)
|
|
3186
|
+
? (sb.out_tokens * (defaultInfo.outputPrice - usedInfo.outputPrice)) / 1_000_000
|
|
3187
|
+
: 0;
|
|
3188
|
+
const cacheSaved = sb.cache_read_tokens > 0 && defaultInfo
|
|
3189
|
+
? (sb.cache_read_tokens * (defaultInfo.inputPrice - (defaultInfo.cachedInputPrice ?? defaultInfo.inputPrice * 0.1))) / 1_000_000
|
|
3190
|
+
: 0;
|
|
3191
|
+
const trace: TurnTrace = {
|
|
3192
|
+
turn_id: _debugTurnId,
|
|
3193
|
+
timestamp: turnStartMs,
|
|
3194
|
+
raw_prompt: userMessage,
|
|
3195
|
+
steps: _debugSteps,
|
|
3196
|
+
model_requested: this.modelId,
|
|
3197
|
+
model_used: turnModelId,
|
|
3198
|
+
routed: turnModelId !== this.modelId,
|
|
3199
|
+
input_tokens: sb.in_tokens,
|
|
3200
|
+
output_tokens: sb.out_tokens,
|
|
3201
|
+
cache_read_tokens: sb.cache_read_tokens,
|
|
3202
|
+
cost_usd: sb.session_usd,
|
|
3203
|
+
estimated_savings: {
|
|
3204
|
+
pil_tokens_saved: this._pilEnrichmentDelta > 0 ? this._pilEnrichmentDelta : 0,
|
|
3205
|
+
cache_tokens_saved: sb.cache_read_tokens,
|
|
3206
|
+
router_cost_saved_usd: routerSaved,
|
|
3207
|
+
total_tokens_saved: (this._pilEnrichmentDelta > 0 ? this._pilEnrichmentDelta : 0) + sb.cache_read_tokens,
|
|
3208
|
+
total_cost_saved_usd: routerSaved + cacheSaved,
|
|
3209
|
+
},
|
|
3210
|
+
};
|
|
3211
|
+
recordTurnTrace(trace);
|
|
3212
|
+
|
|
3213
|
+
const traceLines: string[] = [];
|
|
3214
|
+
traceLines.push("\n┌─ Pipeline Trace ─────────────────────────");
|
|
3215
|
+
for (const step of _debugSteps) {
|
|
3216
|
+
const dur = step.duration_ms < 1 ? "<1ms" : `${step.duration_ms}ms`;
|
|
3217
|
+
const saved = step.tokens_saved ? ` (saved ~${step.tokens_saved} tok)` : "";
|
|
3218
|
+
traceLines.push(`│ ▸ ${step.name} [${dur}]${saved}`);
|
|
3219
|
+
traceLines.push(`│ ${step.output_summary}`);
|
|
3220
|
+
}
|
|
3221
|
+
const routeLabel = trace.routed ? `${trace.model_requested}→${trace.model_used}` : trace.model_used;
|
|
3222
|
+
traceLines.push(`│ Model: ${routeLabel} | ↑${sb.in_tokens} ↓${sb.out_tokens} | $${sb.session_usd.toFixed(4)}`);
|
|
3223
|
+
if (trace.estimated_savings.total_cost_saved_usd > 0) {
|
|
3224
|
+
traceLines.push(`│ Savings: ~${trace.estimated_savings.total_tokens_saved} tok, ~$${trace.estimated_savings.total_cost_saved_usd.toFixed(4)}`);
|
|
3225
|
+
}
|
|
3226
|
+
traceLines.push("└──────────────────────────────────────────\n");
|
|
3227
|
+
yield { type: "content", content: traceLines.join("\n") };
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
if (modelInfo?.contextWindow) {
|
|
3231
|
+
await this.postTurnCompact(provider, system, modelInfo.contextWindow, signal);
|
|
3232
|
+
}
|
|
3233
|
+
yield { type: "done" };
|
|
3234
|
+
return;
|
|
3235
|
+
} catch (err: unknown) {
|
|
3236
|
+
if (signal.aborted) {
|
|
3237
|
+
this.discardAbortedTurn(userModelMessage);
|
|
3238
|
+
// ROUTE-11: Fire routeFeedback for cancelled turns (abort path).
|
|
3239
|
+
// Fire-and-forget — no await. Skipped when taskHash is null.
|
|
3240
|
+
{
|
|
3241
|
+
const turnDuration = Date.now() - turnStartMs;
|
|
3242
|
+
if (taskHash) {
|
|
3243
|
+
const tier = taskTypeToTier(pilCtx.taskType);
|
|
3244
|
+
void routeFeedback(
|
|
3245
|
+
taskHash,
|
|
3246
|
+
tier,
|
|
3247
|
+
runtime.modelId,
|
|
3248
|
+
"cancelled",
|
|
3249
|
+
0,
|
|
3250
|
+
turnDuration,
|
|
3251
|
+
);
|
|
3252
|
+
}
|
|
3253
|
+
const storeHash = routerStore.getState().taskHash;
|
|
3254
|
+
if (storeHash) {
|
|
3255
|
+
reportRouteOutcome(storeHash, "cancelled", turnDuration);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
yield { type: "content", content: "\n\n[Cancelled]" };
|
|
3259
|
+
yield { type: "done" };
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
if (!attemptedOverflowRecovery && !assistantText.trim() && modelInfo && isContextLimitError(err)) {
|
|
3264
|
+
attemptedOverflowRecovery = true;
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
const authError = isAuthenticationError(err);
|
|
3269
|
+
const friendly = humanizeApiError(err);
|
|
3270
|
+
notifyObserver(observer?.onError, {
|
|
3271
|
+
message: friendly,
|
|
3272
|
+
timestamp: Date.now(),
|
|
3273
|
+
});
|
|
3274
|
+
yield {
|
|
3275
|
+
type: "error",
|
|
3276
|
+
content: friendly,
|
|
3277
|
+
isAuthError: authError,
|
|
3278
|
+
};
|
|
3279
|
+
if (assistantText.trim()) {
|
|
3280
|
+
this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
// ROUTE-11: Fire routeFeedback for failed turns (error path).
|
|
3284
|
+
// Must come AFTER posttool calls. Fire-and-forget — no await.
|
|
3285
|
+
{
|
|
3286
|
+
const turnDuration = Date.now() - turnStartMs;
|
|
3287
|
+
if (taskHash) {
|
|
3288
|
+
const tier = taskTypeToTier(pilCtx.taskType);
|
|
3289
|
+
void routeFeedback(
|
|
3290
|
+
taskHash,
|
|
3291
|
+
tier,
|
|
3292
|
+
runtime.modelId,
|
|
3293
|
+
"fail",
|
|
3294
|
+
0,
|
|
3295
|
+
turnDuration,
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3298
|
+
const storeHash = routerStore.getState().taskHash;
|
|
3299
|
+
if (storeHash) {
|
|
3300
|
+
reportRouteOutcome(storeHash, "fail", turnDuration);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
const stopFailureInput: StopFailureHookInput = {
|
|
3305
|
+
hook_event_name: "StopFailure",
|
|
3306
|
+
error: friendly,
|
|
3307
|
+
session_id: this.session?.id,
|
|
3308
|
+
cwd: this.bash.getCwd(),
|
|
3309
|
+
};
|
|
3310
|
+
await this.fireHook(stopFailureInput, signal).catch(() => {});
|
|
3311
|
+
|
|
3312
|
+
if (modelInfo?.contextWindow) {
|
|
3313
|
+
await this.postTurnCompact(provider, system, modelInfo.contextWindow, signal);
|
|
3314
|
+
}
|
|
3315
|
+
yield { type: "done" };
|
|
3316
|
+
return;
|
|
3317
|
+
} finally {
|
|
3318
|
+
await closeMcp?.().catch(() => {});
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
} finally {
|
|
3322
|
+
if (this.abortController?.signal === signal) {
|
|
3323
|
+
this.abortController = null;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
private _buildRecentTurnsSummary(): string | null {
|
|
3329
|
+
if (this.messages.length < 2) return null;
|
|
3330
|
+
const recent = this.messages.slice(-6);
|
|
3331
|
+
const parts: string[] = [];
|
|
3332
|
+
for (const msg of recent) {
|
|
3333
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
3334
|
+
const text = typeof msg.content === "string"
|
|
3335
|
+
? msg.content
|
|
3336
|
+
: Array.isArray(msg.content)
|
|
3337
|
+
? msg.content.filter((p: { type: string }) => p.type === "text").map((p: { type: string; text?: string }) => p.text ?? "").join("")
|
|
3338
|
+
: "";
|
|
3339
|
+
if (!text) continue;
|
|
3340
|
+
const snippet = text.length > 80 ? `${text.slice(0, 77)}...` : text;
|
|
3341
|
+
parts.push(`[${msg.role}]: ${snippet}`);
|
|
3342
|
+
}
|
|
3343
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
private _estimateProjectSize(): "small" | "medium" | "large" | null {
|
|
3347
|
+
try {
|
|
3348
|
+
const fs = require("fs");
|
|
3349
|
+
const path = require("path");
|
|
3350
|
+
const cwd = this.bash.getCwd();
|
|
3351
|
+
const srcDir = path.join(cwd, "src");
|
|
3352
|
+
if (!fs.existsSync(srcDir)) return null;
|
|
3353
|
+
let count = 0;
|
|
3354
|
+
const walk = (dir: string) => {
|
|
3355
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
3356
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
3357
|
+
if (entry.isDirectory()) walk(path.join(dir, entry.name));
|
|
3358
|
+
else if (/\.(ts|tsx|js|jsx|py|go|rs)$/.test(entry.name)) count++;
|
|
3359
|
+
if (count > 200) return;
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
walk(srcDir);
|
|
3363
|
+
if (count <= 20) return "small";
|
|
3364
|
+
if (count <= 100) return "medium";
|
|
3365
|
+
return "large";
|
|
3366
|
+
} catch {
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
private _countFilesTouched(): number {
|
|
3372
|
+
let count = 0;
|
|
3373
|
+
for (const msg of this.messages) {
|
|
3374
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
|
|
3375
|
+
for (const part of msg.content) {
|
|
3376
|
+
if ((part as { type: string }).type === "tool-call") {
|
|
3377
|
+
const tc = part as { type: string; toolName?: string; args?: Record<string, unknown> };
|
|
3378
|
+
if (tc.toolName === "write_file" || tc.toolName === "edit_file" || tc.toolName === "bash") count++;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
return count;
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
private requireProvider(): LegacyProvider {
|
|
3386
|
+
if (!this.provider) {
|
|
3387
|
+
throw new Error("API key required. Add an API key to continue.");
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
return this.provider;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
async detectVerifyRecipe(settings?: SandboxSettings, abortSignal?: AbortSignal): Promise<VerifyRecipe | null> {
|
|
3394
|
+
try {
|
|
3395
|
+
const result = await this.runTaskRequest(
|
|
3396
|
+
{
|
|
3397
|
+
agent: "verify-detect",
|
|
3398
|
+
description: "Detect verification recipe",
|
|
3399
|
+
prompt: buildVerifyDetectPrompt(this.bash.getCwd(), settings ?? this.bash.getSandboxSettings()),
|
|
3400
|
+
},
|
|
3401
|
+
undefined,
|
|
3402
|
+
abortSignal,
|
|
3403
|
+
);
|
|
3404
|
+
if (!result.success || !result.output) return null;
|
|
3405
|
+
const maybeJson = extractJsonObject(result.output);
|
|
3406
|
+
if (!maybeJson) return null;
|
|
3407
|
+
return normalizeVerifyRecipe(JSON.parse(maybeJson));
|
|
3408
|
+
} catch {
|
|
3409
|
+
return null;
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
async runVerify(onProgress?: (detail: string) => void, abortSignal?: AbortSignal): Promise<ToolResult> {
|
|
3414
|
+
this.abortController = new AbortController();
|
|
3415
|
+
const signal = abortSignal ?? this.abortController.signal;
|
|
3416
|
+
const userModelMessage: ModelMessage = { role: "user", content: "/verify" };
|
|
3417
|
+
this.messages.push(userModelMessage);
|
|
3418
|
+
this.messageSeqs.push(null);
|
|
3419
|
+
|
|
3420
|
+
try {
|
|
3421
|
+
await this.consumeBackgroundNotifications();
|
|
3422
|
+
const result = await runVerifyOrchestration(this, { onProgress, abortSignal: signal });
|
|
3423
|
+
const assistantText = result.output || result.error || "Verification completed.";
|
|
3424
|
+
this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
|
|
3425
|
+
return result;
|
|
3426
|
+
} catch (err: unknown) {
|
|
3427
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3428
|
+
const failureText = signal.aborted ? "Verification aborted." : `Verification failed: ${msg}`;
|
|
3429
|
+
this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: failureText }]);
|
|
3430
|
+
return { success: false, output: failureText };
|
|
3431
|
+
} finally {
|
|
3432
|
+
if (this.abortController?.signal === signal) {
|
|
3433
|
+
this.abortController = null;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
interface ExecutedBatchTool {
|
|
3440
|
+
toolCall: ToolCall;
|
|
3441
|
+
input: unknown;
|
|
3442
|
+
toolResult: ToolResult;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
3446
|
+
|
|
3447
|
+
function extractJsonObject(text: string): string | null {
|
|
3448
|
+
const start = text.indexOf("{");
|
|
3449
|
+
const end = text.lastIndexOf("}");
|
|
3450
|
+
if (start < 0 || end < start) return null;
|
|
3451
|
+
return text.slice(start, end + 1);
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
function buildBatchName(prefix: string, label: string): string {
|
|
3455
|
+
const compact =
|
|
3456
|
+
label
|
|
3457
|
+
.replace(/\s+/g, "-")
|
|
3458
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "")
|
|
3459
|
+
.slice(0, 48) || "run";
|
|
3460
|
+
return `muonroi-cli-${prefix}-${compact}`;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
function buildBatchChatCompletionRequest(args: {
|
|
3464
|
+
modelId: string;
|
|
3465
|
+
system: string;
|
|
3466
|
+
messages: ModelMessage[];
|
|
3467
|
+
temperature: number;
|
|
3468
|
+
maxOutputTokens?: number;
|
|
3469
|
+
reasoningEffort?: BatchChatCompletionRequest["reasoning_effort"];
|
|
3470
|
+
tools: BatchFunctionTool[];
|
|
3471
|
+
}): BatchChatCompletionRequest {
|
|
3472
|
+
return {
|
|
3473
|
+
model: args.modelId,
|
|
3474
|
+
messages: toBatchChatMessages(args.system, args.messages),
|
|
3475
|
+
temperature: args.temperature,
|
|
3476
|
+
...(args.maxOutputTokens != null ? { max_completion_tokens: args.maxOutputTokens } : {}),
|
|
3477
|
+
...(args.reasoningEffort ? { reasoning_effort: args.reasoningEffort } : {}),
|
|
3478
|
+
...(args.tools.length > 0 ? { tools: args.tools } : {}),
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
function toBatchChatMessages(system: string, messages: ModelMessage[]): BatchChatMessage[] {
|
|
3483
|
+
const batchMessages: BatchChatMessage[] = [{ role: "system", content: system }];
|
|
3484
|
+
|
|
3485
|
+
for (const message of messages) {
|
|
3486
|
+
const { role, content } = message;
|
|
3487
|
+
|
|
3488
|
+
switch (role) {
|
|
3489
|
+
case "system":
|
|
3490
|
+
batchMessages.push({ role: "system", content });
|
|
3491
|
+
break;
|
|
3492
|
+
|
|
3493
|
+
case "user": {
|
|
3494
|
+
if (typeof content === "string") {
|
|
3495
|
+
batchMessages.push({ role: "user", content });
|
|
3496
|
+
break;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
if (!Array.isArray(content)) {
|
|
3500
|
+
break;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
if (content.length === 1 && content[0]?.type === "text") {
|
|
3504
|
+
batchMessages.push({ role: "user", content: content[0].text });
|
|
3505
|
+
break;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
const userContent: Array<{ type: "text"; text: string } | { type: "image_url"; image_url: { url: string } }> =
|
|
3509
|
+
[];
|
|
3510
|
+
for (const part of content) {
|
|
3511
|
+
switch (part.type) {
|
|
3512
|
+
case "text":
|
|
3513
|
+
userContent.push({ type: "text", text: part.text });
|
|
3514
|
+
break;
|
|
3515
|
+
|
|
3516
|
+
case "image": {
|
|
3517
|
+
const mediaType = part.mediaType === "image/*" || !part.mediaType ? "image/jpeg" : part.mediaType;
|
|
3518
|
+
const data =
|
|
3519
|
+
part.image instanceof URL
|
|
3520
|
+
? part.image.toString()
|
|
3521
|
+
: `data:${mediaType};base64,${toBase64DataContent(part.image)}`;
|
|
3522
|
+
userContent.push({ type: "image_url", image_url: { url: data } });
|
|
3523
|
+
break;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
case "file": {
|
|
3527
|
+
if (!part.mediaType.startsWith("image/")) {
|
|
3528
|
+
break;
|
|
3529
|
+
}
|
|
3530
|
+
const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType;
|
|
3531
|
+
const data =
|
|
3532
|
+
part.data instanceof URL
|
|
3533
|
+
? part.data.toString()
|
|
3534
|
+
: `data:${mediaType};base64,${toBase64DataContent(part.data)}`;
|
|
3535
|
+
userContent.push({ type: "image_url", image_url: { url: data } });
|
|
3536
|
+
break;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
batchMessages.push({
|
|
3541
|
+
role: "user",
|
|
3542
|
+
content: userContent,
|
|
3543
|
+
});
|
|
3544
|
+
break;
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
case "assistant": {
|
|
3548
|
+
if (typeof content === "string") {
|
|
3549
|
+
batchMessages.push({ role: "assistant", content });
|
|
3550
|
+
break;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
if (!Array.isArray(content)) {
|
|
3554
|
+
break;
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
let assistantText = "";
|
|
3558
|
+
const toolCalls: BatchToolCall[] = [];
|
|
3559
|
+
for (const part of content) {
|
|
3560
|
+
if (part.type === "text") {
|
|
3561
|
+
assistantText += part.text;
|
|
3562
|
+
} else if (part.type === "tool-call") {
|
|
3563
|
+
toolCalls.push({
|
|
3564
|
+
id: part.toolCallId,
|
|
3565
|
+
type: "function",
|
|
3566
|
+
function: {
|
|
3567
|
+
name: part.toolName,
|
|
3568
|
+
arguments: JSON.stringify(part.input),
|
|
3569
|
+
},
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
if (assistantText || toolCalls.length > 0) {
|
|
3575
|
+
batchMessages.push({
|
|
3576
|
+
role: "assistant",
|
|
3577
|
+
content: assistantText,
|
|
3578
|
+
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
|
3579
|
+
});
|
|
3580
|
+
}
|
|
3581
|
+
break;
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
case "tool":
|
|
3585
|
+
for (const part of content) {
|
|
3586
|
+
if (part.type === "tool-approval-response") {
|
|
3587
|
+
continue;
|
|
3588
|
+
}
|
|
3589
|
+
batchMessages.push({
|
|
3590
|
+
role: "tool",
|
|
3591
|
+
tool_call_id: part.toolCallId,
|
|
3592
|
+
content: toolOutputToText(part.output),
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
break;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
return batchMessages;
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
function toBase64DataContent(value: string | Uint8Array | ArrayBuffer): string {
|
|
3603
|
+
return convertToBase64(value instanceof ArrayBuffer ? new Uint8Array(value) : value);
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
function toolOutputToText(output: {
|
|
3607
|
+
type: "text" | "json" | "execution-denied" | "error-text" | "error-json" | "content";
|
|
3608
|
+
value?: unknown;
|
|
3609
|
+
reason?: string;
|
|
3610
|
+
}): string {
|
|
3611
|
+
switch (output.type) {
|
|
3612
|
+
case "text":
|
|
3613
|
+
case "error-text":
|
|
3614
|
+
return String(output.value ?? "");
|
|
3615
|
+
case "execution-denied":
|
|
3616
|
+
return output.reason ?? "Tool execution denied.";
|
|
3617
|
+
case "json":
|
|
3618
|
+
case "error-json":
|
|
3619
|
+
case "content":
|
|
3620
|
+
return JSON.stringify(output.value ?? null);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
function getBatchUsage(response: BatchChatCompletionResponse): ProcessMessageUsage {
|
|
3625
|
+
const usage = response.usage as BatchChatCompletionResponse["usage"] | undefined;
|
|
3626
|
+
const inputTokens = asNumber(usage?.input_tokens) ?? asNumber(usage?.prompt_tokens);
|
|
3627
|
+
const outputTokens = asNumber(usage?.output_tokens) ?? asNumber(usage?.completion_tokens);
|
|
3628
|
+
const totalTokens = asNumber(usage?.total_tokens) ?? sumDefined(inputTokens, outputTokens);
|
|
3629
|
+
const u = usage as Record<string, unknown> | undefined;
|
|
3630
|
+
return {
|
|
3631
|
+
inputTokens,
|
|
3632
|
+
outputTokens,
|
|
3633
|
+
totalTokens,
|
|
3634
|
+
costUsdTicks: asNumber(usage?.cost_in_usd_ticks),
|
|
3635
|
+
cacheReadTokens: asNumber(u?.cache_read_input_tokens),
|
|
3636
|
+
cacheCreationTokens: asNumber(u?.cache_creation_input_tokens),
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
function accumulateUsage(target: ProcessMessageUsage, usage: ProcessMessageUsage): void {
|
|
3641
|
+
target.inputTokens = (target.inputTokens ?? 0) + (usage.inputTokens ?? 0);
|
|
3642
|
+
target.outputTokens = (target.outputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
3643
|
+
target.totalTokens = (target.totalTokens ?? 0) + (usage.totalTokens ?? 0);
|
|
3644
|
+
target.costUsdTicks = (target.costUsdTicks ?? 0) + (usage.costUsdTicks ?? 0);
|
|
3645
|
+
target.cacheReadTokens = (target.cacheReadTokens ?? 0) + (usage.cacheReadTokens ?? 0);
|
|
3646
|
+
target.cacheCreationTokens = (target.cacheCreationTokens ?? 0) + (usage.cacheCreationTokens ?? 0);
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
function hasUsage(usage: ProcessMessageUsage): boolean {
|
|
3650
|
+
return Boolean(
|
|
3651
|
+
(usage.inputTokens ?? 0) || (usage.outputTokens ?? 0) || (usage.totalTokens ?? 0) || (usage.costUsdTicks ?? 0),
|
|
3652
|
+
);
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
function getBatchFinishReason(finishReason: string | null | undefined): ProcessMessageFinishReason {
|
|
3656
|
+
switch (finishReason) {
|
|
3657
|
+
case "stop":
|
|
3658
|
+
case "length":
|
|
3659
|
+
case "content-filter":
|
|
3660
|
+
case "tool-calls":
|
|
3661
|
+
case "error":
|
|
3662
|
+
case "other":
|
|
3663
|
+
return finishReason;
|
|
3664
|
+
case "tool_calls":
|
|
3665
|
+
return "tool-calls";
|
|
3666
|
+
default:
|
|
3667
|
+
return "other";
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
function toLocalToolCall(toolCall: BatchToolCall): ToolCall {
|
|
3672
|
+
return {
|
|
3673
|
+
id: toolCall.id,
|
|
3674
|
+
type: "function",
|
|
3675
|
+
function: {
|
|
3676
|
+
name: toolCall.function.name,
|
|
3677
|
+
arguments: toolCall.function.arguments,
|
|
3678
|
+
},
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
function buildAssistantBatchMessage(content: string, toolCalls: ToolCall[]): ModelMessage | null {
|
|
3683
|
+
if (toolCalls.length === 0) {
|
|
3684
|
+
return content ? { role: "assistant", content } : null;
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
const parts: Array<
|
|
3688
|
+
{ type: "text"; text: string } | { type: "tool-call"; toolCallId: string; toolName: string; input: unknown }
|
|
3689
|
+
> = [];
|
|
3690
|
+
if (content) {
|
|
3691
|
+
parts.push({ type: "text", text: content });
|
|
3692
|
+
}
|
|
3693
|
+
for (const toolCall of toolCalls) {
|
|
3694
|
+
parts.push({
|
|
3695
|
+
type: "tool-call",
|
|
3696
|
+
toolCallId: toolCall.id,
|
|
3697
|
+
toolName: toolCall.function.name,
|
|
3698
|
+
input: parseToolArgumentsOrRaw(toolCall.function.arguments),
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
return { role: "assistant", content: parts };
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
function buildToolBatchMessage(toolParts: ExecutedBatchTool[]): ModelMessage | null {
|
|
3705
|
+
if (toolParts.length === 0) {
|
|
3706
|
+
return null;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
return {
|
|
3710
|
+
role: "tool",
|
|
3711
|
+
content: toolParts.map((part) => ({
|
|
3712
|
+
type: "tool-result" as const,
|
|
3713
|
+
toolCallId: part.toolCall.id,
|
|
3714
|
+
toolName: part.toolCall.function.name,
|
|
3715
|
+
output: part.toolResult.success
|
|
3716
|
+
? ({ type: "json", value: toSerializableValue(part.toolResult) } as const)
|
|
3717
|
+
: ({ type: "error-json", value: toSerializableValue(part.toolResult) } as const),
|
|
3718
|
+
})),
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
function parseToolArgumentsOrRaw(raw: string): unknown {
|
|
3723
|
+
try {
|
|
3724
|
+
return raw.trim() ? JSON.parse(raw) : {};
|
|
3725
|
+
} catch {
|
|
3726
|
+
return raw;
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
function toSerializableValue(value: unknown): JsonValue {
|
|
3731
|
+
try {
|
|
3732
|
+
return JSON.parse(JSON.stringify(value ?? null)) as JsonValue;
|
|
3733
|
+
} catch {
|
|
3734
|
+
return String(value);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
function asNumber(value: unknown): number | undefined {
|
|
3739
|
+
return typeof value === "number" ? value : undefined;
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
function sumDefined(left?: number, right?: number): number | undefined {
|
|
3743
|
+
if (left == null && right == null) {
|
|
3744
|
+
return undefined;
|
|
3745
|
+
}
|
|
3746
|
+
return (left ?? 0) + (right ?? 0);
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
function toToolCall(part: { toolCallId: string; toolName: string; args?: unknown; input?: unknown }): ToolCall {
|
|
3750
|
+
return {
|
|
3751
|
+
id: part.toolCallId,
|
|
3752
|
+
type: "function",
|
|
3753
|
+
function: {
|
|
3754
|
+
name: part.toolName,
|
|
3755
|
+
arguments: JSON.stringify(part.input ?? part.args ?? {}),
|
|
3756
|
+
},
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
function notifyObserver<T>(listener: ((payload: T) => void) | undefined, payload: T): void {
|
|
3761
|
+
if (!listener) {
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
try {
|
|
3766
|
+
listener(payload);
|
|
3767
|
+
} catch {
|
|
3768
|
+
// Observer failures should never break generation.
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
function getStepNumber(event: unknown, fallback: number): number {
|
|
3773
|
+
if (event && typeof event === "object" && "stepNumber" in event && typeof event.stepNumber === "number") {
|
|
3774
|
+
return event.stepNumber;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
return fallback;
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
function getFinishReason(event: unknown): ProcessMessageFinishReason {
|
|
3781
|
+
if (event && typeof event === "object" && "finishReason" in event) {
|
|
3782
|
+
switch (event.finishReason) {
|
|
3783
|
+
case "stop":
|
|
3784
|
+
case "length":
|
|
3785
|
+
case "content-filter":
|
|
3786
|
+
case "tool-calls":
|
|
3787
|
+
case "error":
|
|
3788
|
+
case "other":
|
|
3789
|
+
return event.finishReason;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
return "other";
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
function getUsage(event: unknown): ProcessMessageUsage {
|
|
3797
|
+
if (!(event && typeof event === "object" && "usage" in event)) {
|
|
3798
|
+
return {};
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
const usage = event.usage;
|
|
3802
|
+
if (!usage || typeof usage !== "object") {
|
|
3803
|
+
return {};
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
const u = usage as Record<string, unknown>;
|
|
3807
|
+
return {
|
|
3808
|
+
inputTokens: typeof u.inputTokens === "number" ? u.inputTokens : undefined,
|
|
3809
|
+
outputTokens: typeof u.outputTokens === "number" ? u.outputTokens : undefined,
|
|
3810
|
+
totalTokens: typeof u.totalTokens === "number" ? u.totalTokens : undefined,
|
|
3811
|
+
cacheReadTokens: typeof u.cacheReadTokens === "number" ? u.cacheReadTokens : undefined,
|
|
3812
|
+
cacheCreationTokens: typeof u.cacheCreationTokens === "number" ? u.cacheCreationTokens : undefined,
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
function toToolResult(output: unknown): ToolResult {
|
|
3817
|
+
if (output && typeof output === "object" && "success" in output) {
|
|
3818
|
+
const r = output as {
|
|
3819
|
+
success: boolean;
|
|
3820
|
+
output?: string;
|
|
3821
|
+
error?: string;
|
|
3822
|
+
diff?: ToolResult["diff"];
|
|
3823
|
+
plan?: Plan;
|
|
3824
|
+
task?: ToolResult["task"];
|
|
3825
|
+
delegation?: ToolResult["delegation"];
|
|
3826
|
+
backgroundProcess?: ToolResult["backgroundProcess"];
|
|
3827
|
+
media?: ToolResult["media"];
|
|
3828
|
+
computer?: ToolResult["computer"];
|
|
3829
|
+
lspDiagnostics?: ToolResult["lspDiagnostics"];
|
|
3830
|
+
};
|
|
3831
|
+
return {
|
|
3832
|
+
success: r.success,
|
|
3833
|
+
output: r.output,
|
|
3834
|
+
error: r.error ?? (r.success ? undefined : r.output),
|
|
3835
|
+
diff: r.diff,
|
|
3836
|
+
plan: r.plan,
|
|
3837
|
+
task: r.task,
|
|
3838
|
+
delegation: r.delegation,
|
|
3839
|
+
backgroundProcess: r.backgroundProcess,
|
|
3840
|
+
media: r.media,
|
|
3841
|
+
computer: r.computer,
|
|
3842
|
+
lspDiagnostics: r.lspDiagnostics,
|
|
3843
|
+
};
|
|
3844
|
+
}
|
|
3845
|
+
return { success: true, output: String(output) };
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
function formatSubagentActivity(toolName: string, args?: unknown): string {
|
|
3849
|
+
const parsed = parseToolArgs(args);
|
|
3850
|
+
if (toolName === "read_file") return `Read ${parsed.path || "file"}`;
|
|
3851
|
+
if (toolName === "lsp") return `LSP ${parsed.operation || "query"} ${parsed.filePath || ""}`.trim();
|
|
3852
|
+
if (toolName === "write_file") return `Write ${parsed.path || "file"}`;
|
|
3853
|
+
if (toolName === "edit_file") return `Edit ${parsed.path || "file"}`;
|
|
3854
|
+
if (toolName === "search_web") return `Web search "${truncate(parsed.query || "", 50)}"`;
|
|
3855
|
+
if (toolName === "search_x") return `X search "${truncate(parsed.query || "", 50)}"`;
|
|
3856
|
+
if (toolName === "generate_image") return `Generate image "${truncate(parsed.prompt || "", 50)}"`;
|
|
3857
|
+
if (toolName === "generate_video") return `Generate video "${truncate(parsed.prompt || "", 50)}"`;
|
|
3858
|
+
if (toolName === "computer_snapshot") return `Snapshot ${parsed.app || "desktop"}`;
|
|
3859
|
+
if (toolName === "computer_screenshot") return "Capture desktop screenshot";
|
|
3860
|
+
if (toolName === "computer_click")
|
|
3861
|
+
return parsed.ref ? `Click ${parsed.ref}` : `Click at ${parsed.x || "?"},${parsed.y || "?"}`;
|
|
3862
|
+
if (toolName === "computer_mouse_move")
|
|
3863
|
+
return parsed.ref ? `Hover ${parsed.ref}` : `Move mouse to ${parsed.x || "?"},${parsed.y || "?"}`;
|
|
3864
|
+
if (toolName === "computer_type") return `Type into ${parsed.ref || "element"}`;
|
|
3865
|
+
if (toolName === "computer_press") return `Press ${parsed.key || "key"}`;
|
|
3866
|
+
if (toolName === "computer_scroll") return `Scroll ${parsed.ref || "element"} ${parsed.direction || "down"}`;
|
|
3867
|
+
if (toolName === "computer_launch") return `Launch ${parsed.app || "app"}`;
|
|
3868
|
+
if (toolName === "computer_list_windows") return `List windows${parsed.app ? ` for ${parsed.app}` : ""}`;
|
|
3869
|
+
if (toolName === "computer_focus_window")
|
|
3870
|
+
return `Focus window ${parsed.window_id || parsed.title || parsed.app || ""}`.trim();
|
|
3871
|
+
if (toolName === "computer_wait") return "Wait for desktop state";
|
|
3872
|
+
if (toolName === "computer_get") return `Read ${parsed.property || "text"} from ${parsed.ref || "element"}`;
|
|
3873
|
+
if (toolName === "bash") return truncate(parsed.command || "Run command", 70);
|
|
3874
|
+
return truncate(`${toolName}`, 70);
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
function parseToolArgs(args: unknown): Record<string, string> {
|
|
3878
|
+
if (!args || typeof args !== "object") return {};
|
|
3879
|
+
const result: Record<string, string> = {};
|
|
3880
|
+
for (const [key, value] of Object.entries(args)) {
|
|
3881
|
+
result[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
3882
|
+
}
|
|
3883
|
+
return result;
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
function firstLine(text: string): string {
|
|
3887
|
+
return text.trim().split("\n").find(Boolean)?.trim() || "Task completed.";
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
function truncate(text: string, max: number): string {
|
|
3891
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
function combineAbortSignals(...signals: Array<AbortSignal | undefined>): AbortSignal | undefined {
|
|
3895
|
+
const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal));
|
|
3896
|
+
if (activeSignals.length === 0) return undefined;
|
|
3897
|
+
if (activeSignals.length === 1) return activeSignals[0];
|
|
3898
|
+
|
|
3899
|
+
if (typeof AbortSignal.any === "function") {
|
|
3900
|
+
return AbortSignal.any(activeSignals);
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
const controller = new AbortController();
|
|
3904
|
+
for (const signal of activeSignals) {
|
|
3905
|
+
if (signal.aborted) {
|
|
3906
|
+
controller.abort();
|
|
3907
|
+
break;
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
return controller.signal;
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
function isContextLimitError(error: unknown): boolean {
|
|
3917
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3918
|
+
return /(context|token|prompt).*(limit|length|large|window|overflow)|too many tokens|maximum context/i.test(message);
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
function isAuthenticationError(error: unknown): boolean {
|
|
3922
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3923
|
+
return /\b(401|403)\b|unauthori[sz]ed|invalid.*(api[_ ]?key|token|credential)|authentication failed|forbidden|access denied/i.test(
|
|
3924
|
+
message,
|
|
3925
|
+
);
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
const STATUS_MESSAGES: Record<number, string> = {
|
|
3929
|
+
400: "The request was invalid. This may be caused by an unsupported parameter or model.",
|
|
3930
|
+
401: "Authentication failed. Your API key may be invalid or expired.",
|
|
3931
|
+
403: "Access denied. Your API key does not have permission for this request.",
|
|
3932
|
+
404: "The requested model or endpoint was not found. Check your model name and base URL.",
|
|
3933
|
+
408: "The request timed out. Please try again.",
|
|
3934
|
+
422: "The request could not be processed. Check your message format or parameters.",
|
|
3935
|
+
429: "Rate limit exceeded. Please wait a moment and try again.",
|
|
3936
|
+
500: "The API server encountered an internal error. Please try again later.",
|
|
3937
|
+
502: "The API server is temporarily unavailable. Please try again later.",
|
|
3938
|
+
503: "The API service is temporarily overloaded. Please try again later.",
|
|
3939
|
+
529: "The API service is overloaded. Please try again later.",
|
|
3940
|
+
};
|
|
3941
|
+
|
|
3942
|
+
function humanizeApiError(error: unknown): string {
|
|
3943
|
+
if (APICallError.isInstance(error)) {
|
|
3944
|
+
const detail = extractResponseDetail(error.responseBody);
|
|
3945
|
+
if (detail) return detail;
|
|
3946
|
+
if (error.statusCode && STATUS_MESSAGES[error.statusCode]) {
|
|
3947
|
+
return STATUS_MESSAGES[error.statusCode];
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
3952
|
+
return raw.replace(/^AI_\w+Error:\s*/i, "").trim() || raw;
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
function extractResponseDetail(body: string | undefined): string | null {
|
|
3956
|
+
if (!body) return null;
|
|
3957
|
+
try {
|
|
3958
|
+
const parsed = JSON.parse(body);
|
|
3959
|
+
const msg = parsed?.error?.message ?? parsed?.message ?? parsed?.detail;
|
|
3960
|
+
if (typeof msg === "string" && msg.trim()) return msg.trim();
|
|
3961
|
+
} catch {
|
|
3962
|
+
/* not JSON */
|
|
3963
|
+
}
|
|
3964
|
+
return null;
|
|
3965
|
+
}
|