@cjhyy/code-shell-core 0.5.0-rc.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 +162 -0
- package/dist/agent/coordinator.d.ts +49 -0
- package/dist/agent/coordinator.js +77 -0
- package/dist/arena/arena.d.ts +43 -0
- package/dist/arena/arena.js +334 -0
- package/dist/arena/context/context-tools.d.ts +16 -0
- package/dist/arena/context/context-tools.js +231 -0
- package/dist/arena/detect-mode.d.ts +20 -0
- package/dist/arena/detect-mode.js +78 -0
- package/dist/arena/digest-builder.d.ts +25 -0
- package/dist/arena/digest-builder.js +120 -0
- package/dist/arena/index.d.ts +29 -0
- package/dist/arena/index.js +29 -0
- package/dist/arena/iterate/convergence.d.ts +25 -0
- package/dist/arena/iterate/convergence.js +103 -0
- package/dist/arena/iterate/formats/index.d.ts +22 -0
- package/dist/arena/iterate/formats/index.js +283 -0
- package/dist/arena/iterate/index.d.ts +11 -0
- package/dist/arena/iterate/index.js +9 -0
- package/dist/arena/iterate/iterative-arena.d.ts +23 -0
- package/dist/arena/iterate/iterative-arena.js +237 -0
- package/dist/arena/iterate/parse.d.ts +42 -0
- package/dist/arena/iterate/parse.js +123 -0
- package/dist/arena/iterate/phases/argue.d.ts +22 -0
- package/dist/arena/iterate/phases/argue.js +159 -0
- package/dist/arena/iterate/phases/revise.d.ts +16 -0
- package/dist/arena/iterate/phases/revise.js +62 -0
- package/dist/arena/iterate/phases/tournament.d.ts +34 -0
- package/dist/arena/iterate/phases/tournament.js +113 -0
- package/dist/arena/iterate/tools/web-tools.d.ts +13 -0
- package/dist/arena/iterate/tools/web-tools.js +54 -0
- package/dist/arena/iterate/types.d.ts +152 -0
- package/dist/arena/iterate/types.js +8 -0
- package/dist/arena/ledger.d.ts +47 -0
- package/dist/arena/ledger.js +151 -0
- package/dist/arena/lenses/architecture.d.ts +5 -0
- package/dist/arena/lenses/architecture.js +22 -0
- package/dist/arena/lenses/engineering.d.ts +5 -0
- package/dist/arena/lenses/engineering.js +22 -0
- package/dist/arena/lenses/general.d.ts +5 -0
- package/dist/arena/lenses/general.js +20 -0
- package/dist/arena/lenses/index.d.ts +16 -0
- package/dist/arena/lenses/index.js +47 -0
- package/dist/arena/lenses/product.d.ts +5 -0
- package/dist/arena/lenses/product.js +22 -0
- package/dist/arena/model-presets.d.ts +23 -0
- package/dist/arena/model-presets.js +44 -0
- package/dist/arena/phases/adjudication.d.ts +24 -0
- package/dist/arena/phases/adjudication.js +144 -0
- package/dist/arena/phases/build-consensus.d.ts +29 -0
- package/dist/arena/phases/build-consensus.js +86 -0
- package/dist/arena/phases/claim-registry.d.ts +26 -0
- package/dist/arena/phases/claim-registry.js +60 -0
- package/dist/arena/phases/cross-review.d.ts +45 -0
- package/dist/arena/phases/cross-review.js +224 -0
- package/dist/arena/phases/debate-rounds.d.ts +27 -0
- package/dist/arena/phases/debate-rounds.js +162 -0
- package/dist/arena/phases/participant-research.d.ts +38 -0
- package/dist/arena/phases/participant-research.js +317 -0
- package/dist/arena/phases/planning-detail-expansion.d.ts +38 -0
- package/dist/arena/phases/planning-detail-expansion.js +121 -0
- package/dist/arena/planner.d.ts +27 -0
- package/dist/arena/planner.js +312 -0
- package/dist/arena/providers/docs.d.ts +6 -0
- package/dist/arena/providers/docs.js +108 -0
- package/dist/arena/providers/git.d.ts +8 -0
- package/dist/arena/providers/git.js +174 -0
- package/dist/arena/providers/index.d.ts +32 -0
- package/dist/arena/providers/index.js +132 -0
- package/dist/arena/providers/none.d.ts +7 -0
- package/dist/arena/providers/none.js +11 -0
- package/dist/arena/providers/repo.d.ts +6 -0
- package/dist/arena/providers/repo.js +255 -0
- package/dist/arena/render/session.d.ts +17 -0
- package/dist/arena/render/session.js +190 -0
- package/dist/arena/render/terminal.d.ts +34 -0
- package/dist/arena/render/terminal.js +286 -0
- package/dist/arena/strategies/discussion.d.ts +25 -0
- package/dist/arena/strategies/discussion.js +143 -0
- package/dist/arena/strategies/index.d.ts +15 -0
- package/dist/arena/strategies/index.js +28 -0
- package/dist/arena/strategies/language-wrapper.d.ts +17 -0
- package/dist/arena/strategies/language-wrapper.js +102 -0
- package/dist/arena/strategies/lens-wrapper.d.ts +16 -0
- package/dist/arena/strategies/lens-wrapper.js +236 -0
- package/dist/arena/strategies/planning.d.ts +30 -0
- package/dist/arena/strategies/planning.js +225 -0
- package/dist/arena/strategies/review.d.ts +26 -0
- package/dist/arena/strategies/review.js +168 -0
- package/dist/arena/strategies/utils.d.ts +39 -0
- package/dist/arena/strategies/utils.js +647 -0
- package/dist/arena/tools/selector.d.ts +17 -0
- package/dist/arena/tools/selector.js +61 -0
- package/dist/arena/transitions.d.ts +48 -0
- package/dist/arena/transitions.js +92 -0
- package/dist/arena/types.d.ts +508 -0
- package/dist/arena/types.js +27 -0
- package/dist/cli/agent-server-stdio.d.ts +32 -0
- package/dist/cli/agent-server-stdio.js +116 -0
- package/dist/colorizer.d.ts +23 -0
- package/dist/colorizer.js +12 -0
- package/dist/context/compaction.d.ts +116 -0
- package/dist/context/compaction.js +534 -0
- package/dist/context/manager.d.ts +145 -0
- package/dist/context/manager.js +423 -0
- package/dist/context/token-counter.d.ts +27 -0
- package/dist/context/token-counter.js +91 -0
- package/dist/context/tool-result-storage.d.ts +96 -0
- package/dist/context/tool-result-storage.js +296 -0
- package/dist/cost-tracker.d.ts +64 -0
- package/dist/cost-tracker.js +314 -0
- package/dist/cron/scheduler.d.ts +34 -0
- package/dist/cron/scheduler.js +115 -0
- package/dist/data/deepseek-models.json +46 -0
- package/dist/data/gemini-models.json +67 -0
- package/dist/data/openai-models.json +175 -0
- package/dist/data/openrouter-models.d.ts +35 -0
- package/dist/data/openrouter-models.js +36 -0
- package/dist/data/openrouter-models.json +4626 -0
- package/dist/data/openrouter-sync.d.ts +16 -0
- package/dist/data/openrouter-sync.js +53 -0
- package/dist/data/static-catalogs.d.ts +25 -0
- package/dist/data/static-catalogs.js +26 -0
- package/dist/data/zai-models.json +56 -0
- package/dist/engine/cost-store.d.ts +22 -0
- package/dist/engine/cost-store.js +15 -0
- package/dist/engine/engine.d.ts +295 -0
- package/dist/engine/engine.js +1467 -0
- package/dist/engine/model-facade.d.ts +24 -0
- package/dist/engine/model-facade.js +194 -0
- package/dist/engine/parse-task.d.ts +59 -0
- package/dist/engine/parse-task.js +135 -0
- package/dist/engine/patch-orphaned-tools.d.ts +52 -0
- package/dist/engine/patch-orphaned-tools.js +95 -0
- package/dist/engine/query.d.ts +50 -0
- package/dist/engine/query.js +107 -0
- package/dist/engine/runtime.d.ts +50 -0
- package/dist/engine/runtime.js +59 -0
- package/dist/engine/streaming-tool-queue.d.ts +34 -0
- package/dist/engine/streaming-tool-queue.js +61 -0
- package/dist/engine/token-budget.d.ts +22 -0
- package/dist/engine/token-budget.js +40 -0
- package/dist/engine/tool-summary.d.ts +13 -0
- package/dist/engine/tool-summary.js +35 -0
- package/dist/engine/turn-loop.d.ts +122 -0
- package/dist/engine/turn-loop.js +602 -0
- package/dist/engine/turn-state.d.ts +20 -0
- package/dist/engine/turn-state.js +17 -0
- package/dist/exceptions.d.ts +61 -0
- package/dist/exceptions.js +116 -0
- package/dist/git/utils.d.ts +35 -0
- package/dist/git/utils.js +98 -0
- package/dist/git/worktree.d.ts +36 -0
- package/dist/git/worktree.js +139 -0
- package/dist/hooks/events.d.ts +114 -0
- package/dist/hooks/events.js +4 -0
- package/dist/hooks/inject.d.ts +19 -0
- package/dist/hooks/inject.js +29 -0
- package/dist/hooks/registry.d.ts +15 -0
- package/dist/hooks/registry.js +84 -0
- package/dist/hooks/shell-runner.d.ts +45 -0
- package/dist/hooks/shell-runner.js +173 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +145 -0
- package/dist/llm/api-key-sanitize.d.ts +22 -0
- package/dist/llm/api-key-sanitize.js +81 -0
- package/dist/llm/capabilities/index.d.ts +20 -0
- package/dist/llm/capabilities/index.js +39 -0
- package/dist/llm/capabilities/rules.d.ts +26 -0
- package/dist/llm/capabilities/rules.js +215 -0
- package/dist/llm/capabilities/types.d.ts +114 -0
- package/dist/llm/capabilities/types.js +24 -0
- package/dist/llm/client-base.d.ts +29 -0
- package/dist/llm/client-base.js +137 -0
- package/dist/llm/client-factory.d.ts +10 -0
- package/dist/llm/client-factory.js +29 -0
- package/dist/llm/model-cache.d.ts +21 -0
- package/dist/llm/model-cache.js +46 -0
- package/dist/llm/model-fetcher.d.ts +29 -0
- package/dist/llm/model-fetcher.js +282 -0
- package/dist/llm/model-pool.d.ts +95 -0
- package/dist/llm/model-pool.js +246 -0
- package/dist/llm/provider-catalog.d.ts +32 -0
- package/dist/llm/provider-catalog.js +50 -0
- package/dist/llm/provider-kinds.d.ts +21 -0
- package/dist/llm/provider-kinds.js +132 -0
- package/dist/llm/providers/anthropic.d.ts +19 -0
- package/dist/llm/providers/anthropic.js +267 -0
- package/dist/llm/providers/openai.d.ts +64 -0
- package/dist/llm/providers/openai.js +606 -0
- package/dist/llm/retry.d.ts +12 -0
- package/dist/llm/retry.js +17 -0
- package/dist/llm/stream-watchdog.d.ts +42 -0
- package/dist/llm/stream-watchdog.js +70 -0
- package/dist/llm/token-counter.d.ts +17 -0
- package/dist/llm/token-counter.js +36 -0
- package/dist/llm/types.d.ts +37 -0
- package/dist/llm/types.js +4 -0
- package/dist/logging/logger.d.ts +109 -0
- package/dist/logging/logger.js +390 -0
- package/dist/logging/sanitize-messages.d.ts +42 -0
- package/dist/logging/sanitize-messages.js +150 -0
- package/dist/logging/session-recorder.d.ts +90 -0
- package/dist/logging/session-recorder.js +304 -0
- package/dist/lsp/client.d.ts +41 -0
- package/dist/lsp/client.js +166 -0
- package/dist/lsp/manager.d.ts +40 -0
- package/dist/lsp/manager.js +124 -0
- package/dist/lsp/servers.d.ts +16 -0
- package/dist/lsp/servers.js +60 -0
- package/dist/migrate-models.d.ts +45 -0
- package/dist/migrate-models.js +179 -0
- package/dist/onboarding.d.ts +189 -0
- package/dist/onboarding.js +625 -0
- package/dist/plugins/gitOps.d.ts +32 -0
- package/dist/plugins/gitOps.js +62 -0
- package/dist/plugins/installedPlugins.d.ts +15 -0
- package/dist/plugins/installedPlugins.js +56 -0
- package/dist/plugins/knownMarketplaces.d.ts +10 -0
- package/dist/plugins/knownMarketplaces.js +46 -0
- package/dist/plugins/loadPluginHooks.d.ts +52 -0
- package/dist/plugins/loadPluginHooks.js +162 -0
- package/dist/plugins/marketplaceManager.d.ts +49 -0
- package/dist/plugins/marketplaceManager.js +142 -0
- package/dist/plugins/parseMarketplaceInput.d.ts +16 -0
- package/dist/plugins/parseMarketplaceInput.js +73 -0
- package/dist/plugins/pluginCommandHook.d.ts +54 -0
- package/dist/plugins/pluginCommandHook.js +180 -0
- package/dist/plugins/pluginCommandsLoader.d.ts +25 -0
- package/dist/plugins/pluginCommandsLoader.js +114 -0
- package/dist/plugins/pluginInstaller.d.ts +36 -0
- package/dist/plugins/pluginInstaller.js +228 -0
- package/dist/plugins/schemas.d.ts +9 -0
- package/dist/plugins/schemas.js +136 -0
- package/dist/plugins/types.d.ts +75 -0
- package/dist/plugins/types.js +7 -0
- package/dist/plugins/varRewrite.d.ts +42 -0
- package/dist/plugins/varRewrite.js +129 -0
- package/dist/preset/index.d.ts +60 -0
- package/dist/preset/index.js +159 -0
- package/dist/product/define.d.ts +60 -0
- package/dist/product/define.js +123 -0
- package/dist/product/index.d.ts +5 -0
- package/dist/product/index.js +4 -0
- package/dist/product/types.d.ts +111 -0
- package/dist/product/types.js +21 -0
- package/dist/prompt/composer.d.ts +49 -0
- package/dist/prompt/composer.js +168 -0
- package/dist/prompt/instruction-scanner.d.ts +54 -0
- package/dist/prompt/instruction-scanner.js +187 -0
- package/dist/prompt/section-cache.d.ts +15 -0
- package/dist/prompt/section-cache.js +31 -0
- package/dist/prompt/section-loader.d.ts +26 -0
- package/dist/prompt/section-loader.js +53 -0
- package/dist/prompt/sections/base.md +35 -0
- package/dist/prompt/sections/coding.md +31 -0
- package/dist/prompt/sections/orchestration.md +16 -0
- package/dist/prompt/sections/tone.md +9 -0
- package/dist/protocol/chat-session-manager.d.ts +33 -0
- package/dist/protocol/chat-session-manager.js +65 -0
- package/dist/protocol/chat-session.d.ts +49 -0
- package/dist/protocol/chat-session.js +86 -0
- package/dist/protocol/client.d.ts +104 -0
- package/dist/protocol/client.js +260 -0
- package/dist/protocol/factories.d.ts +93 -0
- package/dist/protocol/factories.js +64 -0
- package/dist/protocol/helpers.d.ts +42 -0
- package/dist/protocol/helpers.js +55 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.js +4 -0
- package/dist/protocol/server.d.ts +80 -0
- package/dist/protocol/server.js +855 -0
- package/dist/protocol/transport.d.ts +37 -0
- package/dist/protocol/transport.js +85 -0
- package/dist/protocol/types.d.ts +221 -0
- package/dist/protocol/types.js +61 -0
- package/dist/remote/bridge.d.ts +37 -0
- package/dist/remote/bridge.js +103 -0
- package/dist/run/ArtifactTracker.d.ts +38 -0
- package/dist/run/ArtifactTracker.js +132 -0
- package/dist/run/CheckpointWriter.d.ts +53 -0
- package/dist/run/CheckpointWriter.js +135 -0
- package/dist/run/EngineRunner.d.ts +72 -0
- package/dist/run/EngineRunner.js +119 -0
- package/dist/run/Evaluator.d.ts +56 -0
- package/dist/run/Evaluator.js +62 -0
- package/dist/run/FileRunStore.d.ts +39 -0
- package/dist/run/FileRunStore.js +176 -0
- package/dist/run/Heartbeat.d.ts +54 -0
- package/dist/run/Heartbeat.js +123 -0
- package/dist/run/RunApprovalBackend.d.ts +53 -0
- package/dist/run/RunApprovalBackend.js +85 -0
- package/dist/run/RunLock.d.ts +40 -0
- package/dist/run/RunLock.js +98 -0
- package/dist/run/RunManager.d.ts +82 -0
- package/dist/run/RunManager.js +587 -0
- package/dist/run/RunQueue.d.ts +27 -0
- package/dist/run/RunQueue.js +73 -0
- package/dist/run/RunStore.d.ts +25 -0
- package/dist/run/RunStore.js +9 -0
- package/dist/run/factory.d.ts +63 -0
- package/dist/run/factory.js +51 -0
- package/dist/run/index.d.ts +17 -0
- package/dist/run/index.js +23 -0
- package/dist/run/types.d.ts +136 -0
- package/dist/run/types.js +28 -0
- package/dist/runtime/safe-spawn.d.ts +118 -0
- package/dist/runtime/safe-spawn.js +268 -0
- package/dist/services/analytics.d.ts +29 -0
- package/dist/services/analytics.js +84 -0
- package/dist/services/auto-dream.d.ts +49 -0
- package/dist/services/auto-dream.js +124 -0
- package/dist/services/diagnostics.d.ts +41 -0
- package/dist/services/diagnostics.js +99 -0
- package/dist/services/extract-memories.d.ts +31 -0
- package/dist/services/extract-memories.js +71 -0
- package/dist/services/index.d.ts +11 -0
- package/dist/services/index.js +17 -0
- package/dist/services/memory-orchestrator.d.ts +60 -0
- package/dist/services/memory-orchestrator.js +133 -0
- package/dist/services/notifier.d.ts +25 -0
- package/dist/services/notifier.js +69 -0
- package/dist/services/oauth.d.ts +27 -0
- package/dist/services/oauth.js +153 -0
- package/dist/services/session-memory.d.ts +37 -0
- package/dist/services/session-memory.js +81 -0
- package/dist/session/file-history.d.ts +41 -0
- package/dist/session/file-history.js +115 -0
- package/dist/session/memory.d.ts +104 -0
- package/dist/session/memory.js +310 -0
- package/dist/session/session-manager.d.ts +41 -0
- package/dist/session/session-manager.js +261 -0
- package/dist/session/transcript.d.ts +39 -0
- package/dist/session/transcript.js +186 -0
- package/dist/settings/manager.d.ts +36 -0
- package/dist/settings/manager.js +187 -0
- package/dist/settings/schema.d.ts +1219 -0
- package/dist/settings/schema.js +246 -0
- package/dist/skills/frontmatter.d.ts +13 -0
- package/dist/skills/frontmatter.js +70 -0
- package/dist/skills/index.d.ts +7 -0
- package/dist/skills/index.js +6 -0
- package/dist/skills/scanner.d.ts +31 -0
- package/dist/skills/scanner.js +171 -0
- package/dist/state.d.ts +160 -0
- package/dist/state.js +267 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +84 -0
- package/dist/tool-system/builtin/agent-notifications.js +197 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +67 -0
- package/dist/tool-system/builtin/agent-registry.js +126 -0
- package/dist/tool-system/builtin/agent-transcript-translator.d.ts +22 -0
- package/dist/tool-system/builtin/agent-transcript-translator.js +160 -0
- package/dist/tool-system/builtin/agent.d.ts +16 -0
- package/dist/tool-system/builtin/agent.js +364 -0
- package/dist/tool-system/builtin/apply-patch/applier.d.ts +26 -0
- package/dist/tool-system/builtin/apply-patch/applier.js +250 -0
- package/dist/tool-system/builtin/apply-patch/index.d.ts +20 -0
- package/dist/tool-system/builtin/apply-patch/index.js +101 -0
- package/dist/tool-system/builtin/apply-patch/parser.d.ts +17 -0
- package/dist/tool-system/builtin/apply-patch/parser.js +212 -0
- package/dist/tool-system/builtin/apply-patch/seek-sequence.d.ts +18 -0
- package/dist/tool-system/builtin/apply-patch/seek-sequence.js +123 -0
- package/dist/tool-system/builtin/apply-patch/types.d.ts +49 -0
- package/dist/tool-system/builtin/apply-patch/types.js +13 -0
- package/dist/tool-system/builtin/arena.d.ts +31 -0
- package/dist/tool-system/builtin/arena.js +416 -0
- package/dist/tool-system/builtin/ask-user.d.ts +20 -0
- package/dist/tool-system/builtin/ask-user.js +99 -0
- package/dist/tool-system/builtin/bash.d.ts +21 -0
- package/dist/tool-system/builtin/bash.js +149 -0
- package/dist/tool-system/builtin/brief.d.ts +6 -0
- package/dist/tool-system/builtin/brief.js +41 -0
- package/dist/tool-system/builtin/config.d.ts +6 -0
- package/dist/tool-system/builtin/config.js +67 -0
- package/dist/tool-system/builtin/cron.d.ts +10 -0
- package/dist/tool-system/builtin/cron.js +61 -0
- package/dist/tool-system/builtin/edit.d.ts +6 -0
- package/dist/tool-system/builtin/edit.js +100 -0
- package/dist/tool-system/builtin/file-cache.d.ts +11 -0
- package/dist/tool-system/builtin/file-cache.js +39 -0
- package/dist/tool-system/builtin/glob.d.ts +7 -0
- package/dist/tool-system/builtin/glob.js +76 -0
- package/dist/tool-system/builtin/grep.d.ts +7 -0
- package/dist/tool-system/builtin/grep.js +150 -0
- package/dist/tool-system/builtin/index.d.ts +20 -0
- package/dist/tool-system/builtin/index.js +449 -0
- package/dist/tool-system/builtin/lsp.d.ts +6 -0
- package/dist/tool-system/builtin/lsp.js +141 -0
- package/dist/tool-system/builtin/mcp-tools.d.ts +10 -0
- package/dist/tool-system/builtin/mcp-tools.js +102 -0
- package/dist/tool-system/builtin/memory.d.ts +26 -0
- package/dist/tool-system/builtin/memory.js +226 -0
- package/dist/tool-system/builtin/notebook-edit.d.ts +6 -0
- package/dist/tool-system/builtin/notebook-edit.js +124 -0
- package/dist/tool-system/builtin/plan.d.ts +15 -0
- package/dist/tool-system/builtin/plan.js +55 -0
- package/dist/tool-system/builtin/powershell.d.ts +11 -0
- package/dist/tool-system/builtin/powershell.js +64 -0
- package/dist/tool-system/builtin/read.d.ts +6 -0
- package/dist/tool-system/builtin/read.js +70 -0
- package/dist/tool-system/builtin/remote-trigger.d.ts +6 -0
- package/dist/tool-system/builtin/remote-trigger.js +54 -0
- package/dist/tool-system/builtin/repl.d.ts +11 -0
- package/dist/tool-system/builtin/repl.js +82 -0
- package/dist/tool-system/builtin/send-message.d.ts +6 -0
- package/dist/tool-system/builtin/send-message.js +47 -0
- package/dist/tool-system/builtin/skill-prompt.d.ts +20 -0
- package/dist/tool-system/builtin/skill-prompt.js +52 -0
- package/dist/tool-system/builtin/skill.d.ts +9 -0
- package/dist/tool-system/builtin/skill.js +55 -0
- package/dist/tool-system/builtin/sleep.d.ts +6 -0
- package/dist/tool-system/builtin/sleep.js +33 -0
- package/dist/tool-system/builtin/task.d.ts +48 -0
- package/dist/tool-system/builtin/task.js +139 -0
- package/dist/tool-system/builtin/tool-search.d.ts +11 -0
- package/dist/tool-system/builtin/tool-search.js +90 -0
- package/dist/tool-system/builtin/web-fetch.d.ts +10 -0
- package/dist/tool-system/builtin/web-fetch.js +328 -0
- package/dist/tool-system/builtin/web-search.d.ts +25 -0
- package/dist/tool-system/builtin/web-search.js +174 -0
- package/dist/tool-system/builtin/worktree.d.ts +10 -0
- package/dist/tool-system/builtin/worktree.js +95 -0
- package/dist/tool-system/builtin/write.d.ts +6 -0
- package/dist/tool-system/builtin/write.js +36 -0
- package/dist/tool-system/context.d.ts +165 -0
- package/dist/tool-system/context.js +18 -0
- package/dist/tool-system/executor.d.ts +49 -0
- package/dist/tool-system/executor.js +495 -0
- package/dist/tool-system/investigation-guard.d.ts +46 -0
- package/dist/tool-system/investigation-guard.js +155 -0
- package/dist/tool-system/mcp-manager.d.ts +67 -0
- package/dist/tool-system/mcp-manager.js +249 -0
- package/dist/tool-system/permission.d.ts +111 -0
- package/dist/tool-system/permission.js +774 -0
- package/dist/tool-system/registry.d.ts +32 -0
- package/dist/tool-system/registry.js +131 -0
- package/dist/tool-system/sandbox/bwrap.d.ts +13 -0
- package/dist/tool-system/sandbox/bwrap.js +63 -0
- package/dist/tool-system/sandbox/index.d.ts +78 -0
- package/dist/tool-system/sandbox/index.js +222 -0
- package/dist/tool-system/sandbox/off.d.ts +2 -0
- package/dist/tool-system/sandbox/off.js +8 -0
- package/dist/tool-system/sandbox/seatbelt.d.ts +18 -0
- package/dist/tool-system/sandbox/seatbelt.js +109 -0
- package/dist/tool-system/task-guard.d.ts +29 -0
- package/dist/tool-system/task-guard.js +70 -0
- package/dist/tool-system/validation.d.ts +5 -0
- package/dist/tool-system/validation.js +39 -0
- package/dist/types.d.ts +376 -0
- package/dist/types.js +4 -0
- package/dist/updater.d.ts +65 -0
- package/dist/updater.js +405 -0
- package/dist/utils/debug.d.ts +41 -0
- package/dist/utils/debug.js +110 -0
- package/dist/utils/earlyInput.d.ts +43 -0
- package/dist/utils/earlyInput.js +166 -0
- package/dist/utils/env.d.ts +24 -0
- package/dist/utils/env.js +104 -0
- package/dist/utils/envUtils.d.ts +51 -0
- package/dist/utils/envUtils.js +135 -0
- package/dist/utils/execFileNoThrow.d.ts +39 -0
- package/dist/utils/execFileNoThrow.js +74 -0
- package/dist/utils/format.d.ts +44 -0
- package/dist/utils/format.js +236 -0
- package/dist/utils/intl.d.ts +22 -0
- package/dist/utils/intl.js +83 -0
- package/dist/utils/lockfile.d.ts +15 -0
- package/dist/utils/lockfile.js +30 -0
- package/dist/utils/memoize.d.ts +19 -0
- package/dist/utils/memoize.js +14 -0
- package/dist/utils/semver.d.ts +6 -0
- package/dist/utils/semver.js +13 -0
- package/dist/utils/sliceAnsi.d.ts +12 -0
- package/dist/utils/sliceAnsi.js +62 -0
- package/dist/utils/systemTheme.d.ts +40 -0
- package/dist/utils/systemTheme.js +108 -0
- package/dist/utils/task-sanitizer.d.ts +6 -0
- package/dist/utils/task-sanitizer.js +24 -0
- package/dist/utils/theme.d.ts +93 -0
- package/dist/utils/theme.js +544 -0
- package/dist/utils/toolDisplay.d.ts +31 -0
- package/dist/utils/toolDisplay.js +188 -0
- package/package.json +72 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn loop — state machine implementation.
|
|
3
|
+
*
|
|
4
|
+
* Following Claude Code's po_() pattern:
|
|
5
|
+
* pre_check → model_call → post_check → tool_exec → context_mgmt → hook_notify → next turn
|
|
6
|
+
*/
|
|
7
|
+
import { initialTurnState, newTurnId } from "./turn-state.js";
|
|
8
|
+
import { wrapHookMessages } from "../hooks/inject.js";
|
|
9
|
+
import { ContextLimitError } from "../exceptions.js";
|
|
10
|
+
import { logger } from "../logging/logger.js";
|
|
11
|
+
import { checkTokenBudget, createBudgetTracker } from "./token-budget.js";
|
|
12
|
+
import { StreamingToolQueue } from "./streaming-tool-queue.js";
|
|
13
|
+
import { estimateTokens } from "../context/compaction.js";
|
|
14
|
+
export class TurnLoop {
|
|
15
|
+
deps;
|
|
16
|
+
config;
|
|
17
|
+
turnCount = 0;
|
|
18
|
+
/** Tool IDs already emitted as tool_use_start during streaming (to avoid duplicates). */
|
|
19
|
+
streamedToolIds = new Set();
|
|
20
|
+
/**
|
|
21
|
+
* Turn-scoped child logger set at the top of each loop iteration. Class
|
|
22
|
+
* methods invoked inside the iteration (callModelWithFallback,
|
|
23
|
+
* executeToolCall, ...) read it instead of using the root `logger`, so
|
|
24
|
+
* every line they write is tagged with the current turn/turnId.
|
|
25
|
+
*/
|
|
26
|
+
currentTurnLog = logger;
|
|
27
|
+
/** Last emitted ctx token estimate; used to skip no-op usage_update events. */
|
|
28
|
+
lastCtxEmit = -1;
|
|
29
|
+
constructor(deps, config) {
|
|
30
|
+
this.deps = deps;
|
|
31
|
+
this.config = config;
|
|
32
|
+
// Wrap onStream so a single throwing handler can't silently break
|
|
33
|
+
// the channel for the rest of the run. A 2026-05-25 incident saw a
|
|
34
|
+
// sub-agent's events stop reaching the renderer ~23s into its run —
|
|
35
|
+
// the engine kept executing tools, but every subsequent stream emit
|
|
36
|
+
// was happening into a dead pipe. With this guard the failure shows
|
|
37
|
+
// up in ~/.code-shell/logs/engine-*.log with the offending event
|
|
38
|
+
// type, instead of presenting as a frozen UI.
|
|
39
|
+
//
|
|
40
|
+
// We replace `config.onStream` so the wrap covers BOTH direct calls
|
|
41
|
+
// (`this.config.onStream?.({...})` inside this class) AND places
|
|
42
|
+
// where the reference is passed onward (ModelFacade, StreamingToolQueue).
|
|
43
|
+
if (this.config.onStream) {
|
|
44
|
+
const inner = this.config.onStream;
|
|
45
|
+
this.config = {
|
|
46
|
+
...this.config,
|
|
47
|
+
onStream: (event) => {
|
|
48
|
+
try {
|
|
49
|
+
inner(event);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// Use root logger, not currentTurnLog — this can fire from
|
|
53
|
+
// outside a turn iteration (e.g. ModelFacade after run() resolves).
|
|
54
|
+
logger.warn("stream.handler_threw", {
|
|
55
|
+
eventType: event.type,
|
|
56
|
+
error: err.message,
|
|
57
|
+
stack: err.stack?.split("\n").slice(0, 4).join("\n"),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Emit a lifecycle hook with isSubAgent + sessionId auto-merged into data.
|
|
66
|
+
* Returns the aggregated HookResult so callers can consume `messages` /
|
|
67
|
+
* `decision` / `stop`. Use this instead of `deps.hooks.emit` directly so
|
|
68
|
+
* every emit carries the same context envelope.
|
|
69
|
+
*/
|
|
70
|
+
async emitHook(event, data = {}) {
|
|
71
|
+
return this.deps.hooks.emit(event, {
|
|
72
|
+
...data,
|
|
73
|
+
isSubAgent: this.deps.isSubAgent === true,
|
|
74
|
+
sessionId: this.deps.sessionId,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Emit a usage_update so the UI ctx bar reflects current message-array
|
|
79
|
+
* size. Called at every point where messages mutate: after a tool_result
|
|
80
|
+
* is appended, after context management (which may shrink), and after an
|
|
81
|
+
* LLM response (which we also feed through with the provider's authoritative
|
|
82
|
+
* promptTokens to override our estimate).
|
|
83
|
+
*/
|
|
84
|
+
emitCtxFromMessages(messages) {
|
|
85
|
+
if (!this.config.onStream)
|
|
86
|
+
return;
|
|
87
|
+
// Messages-only estimate is missing system prompt + tool defs (typically
|
|
88
|
+
// ~16k tokens). Pull the cached overhead derived from the previous LLM
|
|
89
|
+
// response to keep the ctx bar in the same ballpark as the real prompt.
|
|
90
|
+
// Without this, the bar drops to ~1k every time tool results land.
|
|
91
|
+
const msgsEstimate = estimateTokens(messages);
|
|
92
|
+
const overhead = this.deps.ctxOverheadStore.get(this.deps.sessionId);
|
|
93
|
+
const ctx = msgsEstimate + overhead;
|
|
94
|
+
this.currentTurnLog.info("debug.ctx.emit", {
|
|
95
|
+
src: "messages",
|
|
96
|
+
value: ctx,
|
|
97
|
+
msgsEstimate,
|
|
98
|
+
overhead,
|
|
99
|
+
msgCount: messages.length,
|
|
100
|
+
prev: this.lastCtxEmit,
|
|
101
|
+
});
|
|
102
|
+
if (ctx === this.lastCtxEmit)
|
|
103
|
+
return;
|
|
104
|
+
this.lastCtxEmit = ctx;
|
|
105
|
+
this.config.onStream({ type: "usage_update", promptTokens: ctx });
|
|
106
|
+
}
|
|
107
|
+
emitCtxFromUsage(promptTokens, messages) {
|
|
108
|
+
if (!this.config.onStream)
|
|
109
|
+
return;
|
|
110
|
+
// Reverse-derive overhead from this authoritative reading so the next
|
|
111
|
+
// estimate-based emit (post-tool-result) is calibrated.
|
|
112
|
+
const msgsEstimate = estimateTokens(messages);
|
|
113
|
+
const overhead = Math.max(0, promptTokens - msgsEstimate);
|
|
114
|
+
this.deps.ctxOverheadStore.set(this.deps.sessionId, overhead);
|
|
115
|
+
this.currentTurnLog.info("debug.ctx.emit", {
|
|
116
|
+
src: "usage",
|
|
117
|
+
value: promptTokens,
|
|
118
|
+
msgsEstimate,
|
|
119
|
+
derivedOverhead: overhead,
|
|
120
|
+
prev: this.lastCtxEmit,
|
|
121
|
+
});
|
|
122
|
+
if (promptTokens === this.lastCtxEmit)
|
|
123
|
+
return;
|
|
124
|
+
this.lastCtxEmit = promptTokens;
|
|
125
|
+
this.config.onStream({ type: "usage_update", promptTokens });
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Run the multi-turn agent loop until completion.
|
|
129
|
+
*/
|
|
130
|
+
async run(initialMessages) {
|
|
131
|
+
let messages = [...initialMessages];
|
|
132
|
+
let finalText = "";
|
|
133
|
+
const budgetTracker = createBudgetTracker();
|
|
134
|
+
while (this.turnCount < this.config.maxTurns) {
|
|
135
|
+
this.turnCount++;
|
|
136
|
+
const state = initialTurnState(this.turnCount);
|
|
137
|
+
// Per-turn correlation ID. Every log written through `tlog` (or any
|
|
138
|
+
// child derived from it) is stamped with `turn` + `turnId`, so
|
|
139
|
+
// `jq 'select(.turnId == "...")'` reconstructs one turn's timeline.
|
|
140
|
+
// Span is *not* used for the loop itself because there are 6+ early
|
|
141
|
+
// returns; instead, each return-causing branch logs its own terminal
|
|
142
|
+
// event (model_error, completed, etc.).
|
|
143
|
+
const turnId = newTurnId();
|
|
144
|
+
const tlog = logger.child({ turn: this.turnCount, turnId });
|
|
145
|
+
this.currentTurnLog = tlog;
|
|
146
|
+
const turnStartedAt = Date.now();
|
|
147
|
+
tlog.info("turn.start", { cat: "turn", messageCount: messages.length });
|
|
148
|
+
// Tag downstream tool-exec / permission lines with this turn's IDs.
|
|
149
|
+
this.deps.toolExecutor.setLogger(tlog);
|
|
150
|
+
this.config.onStream?.({ type: "stream_request_start", turnNumber: this.turnCount });
|
|
151
|
+
const turnStartHook = await this.emitHook("on_turn_start", {
|
|
152
|
+
turnNumber: this.turnCount,
|
|
153
|
+
});
|
|
154
|
+
const turnStartInjection = wrapHookMessages(turnStartHook.messages);
|
|
155
|
+
if (turnStartInjection) {
|
|
156
|
+
messages.push(turnStartInjection);
|
|
157
|
+
}
|
|
158
|
+
// Approaching max turns: inject a warning so the model can wrap up
|
|
159
|
+
const turnsRemaining = this.config.maxTurns - this.turnCount;
|
|
160
|
+
if (turnsRemaining === 2) {
|
|
161
|
+
messages.push({
|
|
162
|
+
role: "user",
|
|
163
|
+
content: "<system-reminder>Warning: you have only 2 turns remaining before the turn limit is reached. " +
|
|
164
|
+
"Start wrapping up your work and prepare a summary of what you've accomplished and what remains to be done.</system-reminder>",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (turnsRemaining === 0) {
|
|
168
|
+
messages.push({
|
|
169
|
+
role: "user",
|
|
170
|
+
content: "<system-reminder>This is your LAST turn. You MUST respond with a final text summary now. " +
|
|
171
|
+
"Do NOT call any tools. Summarize what you have accomplished and list any remaining work.</system-reminder>",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// Pre-check: context management (async — may trigger LLM summarization)
|
|
175
|
+
messages = await this.deps.contextManager.manageAsync(messages);
|
|
176
|
+
// No pre-llm ctx emit here: the messages-only estimate would be ~16k
|
|
177
|
+
// smaller than the real prompt (system + tools not included), making
|
|
178
|
+
// the bar visibly drop on every submit. Post-llm/post-tool-result
|
|
179
|
+
// events carry an accurate value; if compaction shrank the array, the
|
|
180
|
+
// dedicated context_compact event has already informed the UI.
|
|
181
|
+
// post_compact hook: ContextManager just finished a manage() pass.
|
|
182
|
+
// If any non-micro tier fired, give handlers a chance to inject a
|
|
183
|
+
// <system-reminder> ("context was compacted — recall earlier
|
|
184
|
+
// decisions from the transcript") into THIS turn before the model
|
|
185
|
+
// call. Microcompact is lossless (just clearing redundant
|
|
186
|
+
// tool_results) so we suppress hook emits for it to keep token
|
|
187
|
+
// overhead down.
|
|
188
|
+
const pending = this.deps.consumePendingCompactInfo?.();
|
|
189
|
+
if (pending && pending.strategy !== "micro") {
|
|
190
|
+
const compactHook = await this.emitHook("post_compact", {
|
|
191
|
+
strategy: pending.strategy,
|
|
192
|
+
beforeTokens: pending.before,
|
|
193
|
+
afterTokens: pending.after,
|
|
194
|
+
});
|
|
195
|
+
const compactInjection = wrapHookMessages(compactHook.messages);
|
|
196
|
+
if (compactInjection) {
|
|
197
|
+
messages.push(compactInjection);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Model call (with streaming fallback and max_output_tokens continuation)
|
|
201
|
+
// Track tool IDs streamed during this turn to avoid duplicate UI events
|
|
202
|
+
this.streamedToolIds.clear();
|
|
203
|
+
// Streaming tool queue: start concurrency-safe tools during streaming
|
|
204
|
+
const streamingQueue = new StreamingToolQueue(this.deps.toolExecutor);
|
|
205
|
+
let response;
|
|
206
|
+
try {
|
|
207
|
+
response = await this.callModelWithFallback(messages);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
if (err instanceof ContextLimitError) {
|
|
211
|
+
// Progressive recovery: drop oldest API rounds, up to 3 retries
|
|
212
|
+
const { dropOldestRounds } = await import("../context/compaction.js");
|
|
213
|
+
let recovered = false;
|
|
214
|
+
for (let retry = 1; retry <= 3; retry++) {
|
|
215
|
+
tlog.warn("turn.ptl_recovery", { cat: "turn", retry, roundsToDrop: retry });
|
|
216
|
+
messages = dropOldestRounds(messages, retry);
|
|
217
|
+
try {
|
|
218
|
+
response = await this.callModelWithFallback(messages);
|
|
219
|
+
recovered = true;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
catch (retryErr) {
|
|
223
|
+
if (!(retryErr instanceof ContextLimitError)) {
|
|
224
|
+
this.config.onStream?.({ type: "error", error: retryErr.message });
|
|
225
|
+
return { text: finalText, reason: "model_error", messages };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!recovered) {
|
|
230
|
+
this.patchOrphanedToolUses(messages);
|
|
231
|
+
this.config.onStream?.({
|
|
232
|
+
type: "error",
|
|
233
|
+
error: "Context limit exceeded after 3 recovery attempts",
|
|
234
|
+
});
|
|
235
|
+
return { text: finalText, reason: "prompt_too_long", messages };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.patchOrphanedToolUses(messages);
|
|
240
|
+
this.config.onStream?.({ type: "error", error: err.message });
|
|
241
|
+
return { text: finalText, reason: "model_error", messages };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// UI ctx bar: prefer the provider's authoritative promptTokens.
|
|
245
|
+
if (response.usage?.promptTokens !== undefined) {
|
|
246
|
+
this.emitCtxFromUsage(response.usage.promptTokens, messages);
|
|
247
|
+
}
|
|
248
|
+
// Feed actual token usage back to the context manager so subsequent
|
|
249
|
+
// compaction decisions use hybrid (actual + delta) estimation rather than
|
|
250
|
+
// pure heuristics. Without this the manager falls back to char/4 estimates.
|
|
251
|
+
if (response.usage?.promptTokens !== undefined) {
|
|
252
|
+
this.deps.contextManager.recordActualUsage(response.usage.promptTokens, messages.length);
|
|
253
|
+
}
|
|
254
|
+
// Handle max_output_tokens: if response was truncated, do continuation (up to 3 times)
|
|
255
|
+
if (response.stopReason === "max_tokens" &&
|
|
256
|
+
response.toolCalls.length === 0 &&
|
|
257
|
+
response.text) {
|
|
258
|
+
let combinedText = response.text;
|
|
259
|
+
for (let retry = 0; retry < 3; retry++) {
|
|
260
|
+
tlog.info("turn.max_tokens_continuation", { cat: "turn", retry: retry + 1 });
|
|
261
|
+
const contMessages = [
|
|
262
|
+
...messages,
|
|
263
|
+
{ role: "assistant", content: combinedText },
|
|
264
|
+
{
|
|
265
|
+
role: "user",
|
|
266
|
+
content: "<system-reminder>Your previous response was truncated due to length. Please continue from where you left off.</system-reminder>",
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
try {
|
|
270
|
+
const contResponse = await this.deps.model.call(this.deps.systemPrompt, contMessages, this.deps.tools, this.config.onStream, this.config.signal);
|
|
271
|
+
combinedText += contResponse.text;
|
|
272
|
+
if (contResponse.stopReason !== "max_tokens" || contResponse.toolCalls.length > 0) {
|
|
273
|
+
response = { ...contResponse, text: combinedText };
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
response = { ...response, text: combinedText };
|
|
282
|
+
}
|
|
283
|
+
// After any continuation, send latest usage so ctx bar reflects real context
|
|
284
|
+
if (response.usage?.promptTokens !== undefined) {
|
|
285
|
+
this.emitCtxFromUsage(response.usage.promptTokens, messages);
|
|
286
|
+
}
|
|
287
|
+
// Aborted?
|
|
288
|
+
if (this.config.signal?.aborted) {
|
|
289
|
+
return { text: finalText, reason: "aborted_streaming", messages };
|
|
290
|
+
}
|
|
291
|
+
// Accumulate text
|
|
292
|
+
if (response.text) {
|
|
293
|
+
finalText = response.text;
|
|
294
|
+
}
|
|
295
|
+
// Post-check: tool calls?
|
|
296
|
+
if (response.toolCalls.length === 0) {
|
|
297
|
+
// No tool use — final answer
|
|
298
|
+
this.config.onStream?.({
|
|
299
|
+
type: "assistant_message",
|
|
300
|
+
message: { role: "assistant", content: finalText },
|
|
301
|
+
});
|
|
302
|
+
await this.emitHook("on_turn_end", {
|
|
303
|
+
turnNumber: this.turnCount,
|
|
304
|
+
hasToolUse: false,
|
|
305
|
+
});
|
|
306
|
+
messages.push({ role: "assistant", content: finalText });
|
|
307
|
+
return { text: finalText, reason: "completed", messages };
|
|
308
|
+
}
|
|
309
|
+
// Tool execution phase
|
|
310
|
+
tlog.info("turn.tool_use", { cat: "turn", tools: response.toolCalls.map((t) => t.toolName) });
|
|
311
|
+
const toolCalls = response.toolCalls.slice(0, this.config.maxToolCallsPerTurn);
|
|
312
|
+
// Add assistant message with tool_use blocks to messages
|
|
313
|
+
const assistantBlocks = [];
|
|
314
|
+
if (response.text) {
|
|
315
|
+
assistantBlocks.push({ type: "text", text: response.text });
|
|
316
|
+
}
|
|
317
|
+
for (const tc of toolCalls) {
|
|
318
|
+
assistantBlocks.push({
|
|
319
|
+
type: "tool_use",
|
|
320
|
+
id: tc.id,
|
|
321
|
+
name: tc.toolName,
|
|
322
|
+
input: tc.args,
|
|
323
|
+
});
|
|
324
|
+
// Only emit tool_use_start if not already emitted during streaming
|
|
325
|
+
if (!this.streamedToolIds.has(tc.id)) {
|
|
326
|
+
this.config.onStream?.({ type: "tool_use_start", toolCall: tc });
|
|
327
|
+
}
|
|
328
|
+
// Record in transcript
|
|
329
|
+
this.deps.transcript.appendToolUse(tc.toolName, tc.id, tc.args);
|
|
330
|
+
}
|
|
331
|
+
messages.push({ role: "assistant", content: assistantBlocks });
|
|
332
|
+
// Execute tools — enqueue concurrency-safe tools for early start,
|
|
333
|
+
// drain remaining (unsafe) tools sequentially.
|
|
334
|
+
for (const tc of toolCalls) {
|
|
335
|
+
streamingQueue.enqueue(tc);
|
|
336
|
+
}
|
|
337
|
+
const results = await streamingQueue.drain();
|
|
338
|
+
// Record results in transcript and stream
|
|
339
|
+
const resultBlocks = [];
|
|
340
|
+
for (const result of results) {
|
|
341
|
+
const content = result.error ? `Error: ${result.error}` : (result.result ?? "(no output)");
|
|
342
|
+
resultBlocks.push({
|
|
343
|
+
type: "tool_result",
|
|
344
|
+
tool_use_id: result.id,
|
|
345
|
+
content,
|
|
346
|
+
});
|
|
347
|
+
this.deps.transcript.appendToolResult(result.id, result.toolName, result.result, result.error);
|
|
348
|
+
this.config.onStream?.({ type: "tool_result", result });
|
|
349
|
+
}
|
|
350
|
+
// Fire-and-forget tool use summary (non-blocking)
|
|
351
|
+
if (this.config.onStream) {
|
|
352
|
+
import("./tool-summary.js").then(({ generateToolUseSummary }) => {
|
|
353
|
+
if (!this.deps.model.summarize)
|
|
354
|
+
return;
|
|
355
|
+
generateToolUseSummary(toolCalls, results, this.deps.model.summarize).then((summary) => {
|
|
356
|
+
if (summary) {
|
|
357
|
+
this.config.onStream?.({ type: "tool_summary", summary });
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
messages.push({ role: "user", content: resultBlocks });
|
|
363
|
+
// Tool results just pushed; recompute ctx so the bar updates *before*
|
|
364
|
+
// the next model round-trip — large tool outputs can move it sharply.
|
|
365
|
+
this.emitCtxFromMessages(messages);
|
|
366
|
+
// Token budget check
|
|
367
|
+
const totalOutputTokens = this.deps.model.getOutputTokens?.() ?? 0;
|
|
368
|
+
const budgetDecision = checkTokenBudget(totalOutputTokens, this.config.tokenBudget ?? Infinity, budgetTracker);
|
|
369
|
+
if (budgetDecision === "stop") {
|
|
370
|
+
tlog.info("turn.budget_stop", {
|
|
371
|
+
cat: "turn",
|
|
372
|
+
outputTokens: totalOutputTokens,
|
|
373
|
+
budget: this.config.tokenBudget,
|
|
374
|
+
});
|
|
375
|
+
this.config.onStream?.({
|
|
376
|
+
type: "assistant_message",
|
|
377
|
+
message: { role: "assistant", content: finalText },
|
|
378
|
+
});
|
|
379
|
+
messages.push({ role: "assistant", content: finalText });
|
|
380
|
+
return { text: finalText, reason: "completed", messages };
|
|
381
|
+
}
|
|
382
|
+
if (budgetDecision === "nudge") {
|
|
383
|
+
messages.push({
|
|
384
|
+
role: "user",
|
|
385
|
+
content: "<system-reminder>You are approaching the token budget limit. Please start wrapping up your work and provide a summary.</system-reminder>",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
// Investigation guard: end-of-turn check. If too many consecutive
|
|
389
|
+
// read-only turns went by without any user-visible text or side-effecting
|
|
390
|
+
// tool, inject a reminder that will land at the top of the next turn.
|
|
391
|
+
const guard = this.deps.toolExecutor.getInvestigationGuard();
|
|
392
|
+
if (guard) {
|
|
393
|
+
guard.noteText(response.text);
|
|
394
|
+
const turnReminder = guard.turnEnded(this.turnCount);
|
|
395
|
+
if (turnReminder) {
|
|
396
|
+
messages.push({ role: "user", content: turnReminder });
|
|
397
|
+
tlog.info("guard.silent_turn", { cat: "guard", turn: this.turnCount });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Task guard: nudge the model if it has an in_progress task that
|
|
401
|
+
// hasn't moved in several turns. TaskCreate is sticky in working
|
|
402
|
+
// memory for the first few turns only; without this, the spinner
|
|
403
|
+
// runs forever on tasks the model has mentally finished.
|
|
404
|
+
const taskGuard = this.deps.toolExecutor.getTaskGuard();
|
|
405
|
+
if (taskGuard) {
|
|
406
|
+
const taskReminder = taskGuard.turnEnded(this.turnCount);
|
|
407
|
+
if (taskReminder) {
|
|
408
|
+
messages.push({ role: "user", content: taskReminder });
|
|
409
|
+
tlog.info("guard.stale_task", { cat: "guard", turn: this.turnCount });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Hook: turn end
|
|
413
|
+
await this.emitHook("on_turn_end", {
|
|
414
|
+
turnNumber: this.turnCount,
|
|
415
|
+
hasToolUse: true,
|
|
416
|
+
toolCallCount: toolCalls.length,
|
|
417
|
+
});
|
|
418
|
+
// Record turn boundary
|
|
419
|
+
this.deps.transcript.appendTurnBoundary();
|
|
420
|
+
this.config.onTurnBoundary?.(this.turnCount);
|
|
421
|
+
tlog.info("turn.end", {
|
|
422
|
+
cat: "turn",
|
|
423
|
+
duration_ms: Date.now() - turnStartedAt,
|
|
424
|
+
outcome: "continue",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
// Max turns reached — do one final summarization call (no tools)
|
|
428
|
+
logger.warn("turn.max_turns_reached", {
|
|
429
|
+
cat: "turn",
|
|
430
|
+
maxTurns: this.config.maxTurns,
|
|
431
|
+
turnCount: this.turnCount,
|
|
432
|
+
});
|
|
433
|
+
messages = this.deps.contextManager.manage(messages);
|
|
434
|
+
messages.push({
|
|
435
|
+
role: "user",
|
|
436
|
+
content: "<system-reminder>Turn limit reached. Provide a final summary of what you accomplished and what remains to be done. Do NOT call any tools.</system-reminder>",
|
|
437
|
+
});
|
|
438
|
+
this.emitCtxFromMessages(messages);
|
|
439
|
+
try {
|
|
440
|
+
const summaryResponse = await this.deps.model.call(this.deps.systemPrompt, messages, [], // No tools available for summary turn
|
|
441
|
+
this.config.onStream, this.config.signal);
|
|
442
|
+
if (summaryResponse.text) {
|
|
443
|
+
finalText = summaryResponse.text;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// If even summary fails, just return what we have
|
|
448
|
+
this.currentTurnLog.warn("turn.summary_failed", { cat: "turn" });
|
|
449
|
+
}
|
|
450
|
+
if (finalText) {
|
|
451
|
+
this.config.onStream?.({
|
|
452
|
+
type: "assistant_message",
|
|
453
|
+
message: { role: "assistant", content: finalText },
|
|
454
|
+
});
|
|
455
|
+
messages.push({ role: "assistant", content: finalText });
|
|
456
|
+
}
|
|
457
|
+
this.config.onStream?.({ type: "turn_complete", reason: "max_turns" });
|
|
458
|
+
return { text: finalText, reason: "max_turns", messages };
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Call model with streaming fallback.
|
|
462
|
+
* If streaming fails, emit tombstone and retry non-streaming.
|
|
463
|
+
*/
|
|
464
|
+
async callModelWithFallback(messages) {
|
|
465
|
+
// Wrap stream callback to track tool_use_start events and reactive compaction
|
|
466
|
+
let streamingResponseTokens = 0;
|
|
467
|
+
const wrappedStream = this.config.onStream
|
|
468
|
+
? (event) => {
|
|
469
|
+
if (event.type === "tool_use_start" && event.toolCall?.id) {
|
|
470
|
+
this.streamedToolIds.add(event.toolCall.id);
|
|
471
|
+
}
|
|
472
|
+
// Track accumulating response size for reactive compaction
|
|
473
|
+
if (event.type === "text_delta" && event.text) {
|
|
474
|
+
streamingResponseTokens += Math.ceil(event.text.length / 4);
|
|
475
|
+
}
|
|
476
|
+
// Reactive compaction warning: if nearing context limit mid-stream,
|
|
477
|
+
// log a warning (actual compaction happens between turns)
|
|
478
|
+
if (streamingResponseTokens > 0 && streamingResponseTokens % 2000 === 0) {
|
|
479
|
+
if (this.deps.contextManager.shouldReactiveCompact(messages, streamingResponseTokens)) {
|
|
480
|
+
this.currentTurnLog.warn("turn.reactive_compact_warning", {
|
|
481
|
+
cat: "turn",
|
|
482
|
+
responseTokens: streamingResponseTokens,
|
|
483
|
+
turn: this.turnCount,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return this.config.onStream(event);
|
|
488
|
+
}
|
|
489
|
+
: undefined;
|
|
490
|
+
try {
|
|
491
|
+
return await this.deps.model.call(this.deps.systemPrompt, messages, this.deps.tools, wrappedStream, this.config.signal);
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
// If it's a context or rate limit error, don't fallback — propagate
|
|
495
|
+
if (err instanceof ContextLimitError)
|
|
496
|
+
throw err;
|
|
497
|
+
// Streaming might have partially emitted — send tombstone to revoke
|
|
498
|
+
this.config.onStream?.({ type: "tombstone", messageId: `turn_${this.turnCount}` });
|
|
499
|
+
this.currentTurnLog.warn("turn.streaming_fallback", {
|
|
500
|
+
cat: "turn",
|
|
501
|
+
error: err.message,
|
|
502
|
+
});
|
|
503
|
+
// Retry without streaming
|
|
504
|
+
return await this.deps.model.callWithoutStreaming(this.deps.systemPrompt, messages, this.deps.tools, this.config.signal);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Execute tools with overlap: start concurrent-safe (read-only) tools
|
|
509
|
+
* immediately in parallel while sequential (write) tools run one-by-one.
|
|
510
|
+
* Both groups run simultaneously — we don't wait for safe tools to finish
|
|
511
|
+
* before starting unsafe ones.
|
|
512
|
+
*/
|
|
513
|
+
async executeToolsOverlapped(calls) {
|
|
514
|
+
if (calls.length <= 1) {
|
|
515
|
+
// Single tool — no overlap needed
|
|
516
|
+
return this.deps.toolExecutor.executeAll(calls);
|
|
517
|
+
}
|
|
518
|
+
const safe = [];
|
|
519
|
+
const unsafe = [];
|
|
520
|
+
for (const call of calls) {
|
|
521
|
+
if (this.deps.toolExecutor.isConcurrencySafe(call.toolName)) {
|
|
522
|
+
safe.push(call);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
unsafe.push(call);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// If all same type, delegate directly
|
|
529
|
+
if (safe.length === 0 || unsafe.length === 0) {
|
|
530
|
+
return this.deps.toolExecutor.executeAll(calls);
|
|
531
|
+
}
|
|
532
|
+
// Run both groups simultaneously:
|
|
533
|
+
// - safe tools all in parallel
|
|
534
|
+
// - unsafe tools sequentially (but started at the same time as safe group)
|
|
535
|
+
const resultMap = new Map();
|
|
536
|
+
const safePromise = Promise.all(safe.map((c) => this.deps.toolExecutor.executeSingle(c)));
|
|
537
|
+
const unsafePromise = (async () => {
|
|
538
|
+
const results = [];
|
|
539
|
+
for (const call of unsafe) {
|
|
540
|
+
results.push(await this.deps.toolExecutor.executeSingle(call));
|
|
541
|
+
}
|
|
542
|
+
return results;
|
|
543
|
+
})();
|
|
544
|
+
const [safeResults, unsafeResults] = await Promise.all([safePromise, unsafePromise]);
|
|
545
|
+
for (const r of safeResults)
|
|
546
|
+
resultMap.set(r.id, r);
|
|
547
|
+
for (const r of unsafeResults)
|
|
548
|
+
resultMap.set(r.id, r);
|
|
549
|
+
// Return results in original call order for deterministic transcript
|
|
550
|
+
return calls.map((c) => resultMap.get(c.id));
|
|
551
|
+
}
|
|
552
|
+
get currentTurn() {
|
|
553
|
+
return this.turnCount;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Generate synthetic error tool_results for any dangling tool_use blocks
|
|
557
|
+
* that never received results (e.g. because the API call failed).
|
|
558
|
+
* Prevents model confusion on the next turn.
|
|
559
|
+
*/
|
|
560
|
+
patchOrphanedToolUses(messages) {
|
|
561
|
+
// Find the last assistant message with tool_use blocks
|
|
562
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
563
|
+
const msg = messages[i];
|
|
564
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
565
|
+
continue;
|
|
566
|
+
const toolUseIds = [];
|
|
567
|
+
for (const block of msg.content) {
|
|
568
|
+
if (block.type === "tool_use" && block.id)
|
|
569
|
+
toolUseIds.push(block.id);
|
|
570
|
+
}
|
|
571
|
+
if (toolUseIds.length === 0)
|
|
572
|
+
continue;
|
|
573
|
+
// Check if all tool_use IDs have corresponding tool_results
|
|
574
|
+
const answeredIds = new Set();
|
|
575
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
576
|
+
const rm = messages[j];
|
|
577
|
+
if (!Array.isArray(rm.content))
|
|
578
|
+
continue;
|
|
579
|
+
for (const block of rm.content) {
|
|
580
|
+
if (block.type === "tool_result" && block.tool_use_id) {
|
|
581
|
+
answeredIds.add(block.tool_use_id);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const orphanedIds = toolUseIds.filter((id) => !answeredIds.has(id));
|
|
586
|
+
if (orphanedIds.length === 0)
|
|
587
|
+
return;
|
|
588
|
+
// Inject synthetic error results
|
|
589
|
+
const errorBlocks = orphanedIds.map((id) => ({
|
|
590
|
+
type: "tool_result",
|
|
591
|
+
tool_use_id: id,
|
|
592
|
+
content: "Error: Tool execution was cancelled because the previous API call failed.",
|
|
593
|
+
}));
|
|
594
|
+
messages.push({ role: "user", content: errorBlocks });
|
|
595
|
+
this.currentTurnLog.warn("turn.patched_orphaned_tool_uses", {
|
|
596
|
+
cat: "turn",
|
|
597
|
+
count: orphanedIds.length,
|
|
598
|
+
});
|
|
599
|
+
return; // Only patch the most recent orphaned set
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn loop state definitions.
|
|
3
|
+
*/
|
|
4
|
+
import type { TurnPhase, ToolCall, LLMResponse, TerminalReason } from "../types.js";
|
|
5
|
+
export interface TurnState {
|
|
6
|
+
phase: TurnPhase;
|
|
7
|
+
turnNumber: number;
|
|
8
|
+
modelResponse?: LLMResponse;
|
|
9
|
+
toolCalls?: ToolCall[];
|
|
10
|
+
finalText?: string;
|
|
11
|
+
error?: Error;
|
|
12
|
+
terminalReason?: TerminalReason;
|
|
13
|
+
}
|
|
14
|
+
export declare function initialTurnState(turnNumber: number): TurnState;
|
|
15
|
+
/**
|
|
16
|
+
* Short, human-readable turn correlation ID. Stamped on every log entry
|
|
17
|
+
* written for the duration of one turn so `jq 'select(.turnId == "abc123")'`
|
|
18
|
+
* pulls a single turn's whole timeline out of the daily log file.
|
|
19
|
+
*/
|
|
20
|
+
export declare function newTurnId(): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn loop state definitions.
|
|
3
|
+
*/
|
|
4
|
+
export function initialTurnState(turnNumber) {
|
|
5
|
+
return {
|
|
6
|
+
phase: "pre_check",
|
|
7
|
+
turnNumber,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Short, human-readable turn correlation ID. Stamped on every log entry
|
|
12
|
+
* written for the duration of one turn so `jq 'select(.turnId == "abc123")'`
|
|
13
|
+
* pulls a single turn's whole timeline out of the daily log file.
|
|
14
|
+
*/
|
|
15
|
+
export function newTurnId() {
|
|
16
|
+
return `t${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
17
|
+
}
|