@cjhyy/code-shell-core 0.5.0-rc.1 → 0.6.0-rc.1
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/README.md +13 -10
- package/dist/agent/agent-definition-registry.d.ts +21 -3
- package/dist/agent/agent-definition-registry.js +49 -18
- package/dist/agent/agent-definition.d.ts +32 -0
- package/dist/agent/agent-definition.js +49 -4
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +47 -6
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/ledger.js +9 -1
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.d.ts +1 -4
- package/dist/arena/strategies/utils.js +7 -51
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +41 -0
- package/dist/automation/cron-expr.js +169 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +84 -0
- package/dist/automation/runner.js +92 -0
- package/dist/automation/scheduler.d.ts +194 -0
- package/dist/automation/scheduler.js +623 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +122 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/disabled-lists.d.ts +25 -0
- package/dist/capability-control/disabled-lists.js +57 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +64 -0
- package/dist/capability-control/overlay.js +116 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
- package/dist/cc-orchestrator/agent-adapter.js +133 -0
- package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
- package/dist/cc-orchestrator/cc-capability.js +53 -0
- package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
- package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
- package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
- package/dist/cc-orchestrator/codex-session-history.js +187 -0
- package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
- package/dist/cc-orchestrator/external-agent-changes.js +214 -0
- package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
- package/dist/cc-orchestrator/external-agent-driver.js +69 -0
- package/dist/cc-orchestrator/index.d.ts +8 -0
- package/dist/cc-orchestrator/index.js +8 -0
- package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
- package/dist/cc-orchestrator/relevance-judge.js +29 -0
- package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
- package/dist/cc-orchestrator/session-discovery.js +125 -0
- package/dist/cc-orchestrator/session-history.d.ts +19 -0
- package/dist/cc-orchestrator/session-history.js +67 -0
- package/dist/cli/agent-server-stdio.d.ts +26 -1
- package/dist/cli/agent-server-stdio.js +221 -28
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +134 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.d.ts +56 -0
- package/dist/context/compaction.js +209 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +25 -41
- package/dist/cost-tracker.js +5 -61
- package/dist/credentials/cipher.d.ts +49 -0
- package/dist/credentials/cipher.js +45 -0
- package/dist/credentials/cookie-jar.d.ts +24 -0
- package/dist/credentials/cookie-jar.js +40 -0
- package/dist/credentials/index.d.ts +7 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/inject-credential-tool.d.ts +20 -0
- package/dist/credentials/inject-credential-tool.js +130 -0
- package/dist/credentials/store.d.ts +72 -0
- package/dist/credentials/store.js +184 -0
- package/dist/credentials/types.d.ts +56 -0
- package/dist/credentials/use-credential-tool.d.ts +29 -0
- package/dist/credentials/use-credential-tool.js +205 -0
- package/dist/credentials/use-gate.d.ts +56 -0
- package/dist/credentials/use-gate.js +52 -0
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/model-metadata.d.ts +77 -0
- package/dist/data/model-metadata.js +56 -0
- package/dist/data/model-metadata.json +216 -0
- package/dist/data/openrouter-models.d.ts +18 -7
- package/dist/data/openrouter-models.js +35 -8
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/aux-key.d.ts +10 -0
- package/dist/engine/aux-key.js +11 -0
- package/dist/engine/dynamic-tool-defs.d.ts +19 -0
- package/dist/engine/dynamic-tool-defs.js +36 -0
- package/dist/engine/engine.d.ts +431 -108
- package/dist/engine/engine.js +1630 -279
- package/dist/engine/friendly-error.d.ts +18 -0
- package/dist/engine/friendly-error.js +63 -0
- package/dist/engine/goal.d.ts +187 -0
- package/dist/engine/goal.js +196 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +165 -0
- package/dist/engine/image-policy.js +189 -0
- package/dist/engine/model-connections-pool.d.ts +17 -0
- package/dist/engine/model-connections-pool.js +66 -0
- package/dist/engine/model-facade.d.ts +10 -0
- package/dist/engine/model-facade.js +25 -0
- package/dist/engine/patch-orphaned-tools.js +3 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/resolve-llm-config.d.ts +16 -0
- package/dist/engine/resolve-llm-config.js +44 -0
- package/dist/engine/runtime.js +9 -1
- package/dist/engine/sandbox-cache-key.d.ts +10 -0
- package/dist/engine/sandbox-cache-key.js +9 -0
- package/dist/engine/sandbox-config.d.ts +31 -0
- package/dist/engine/sandbox-config.js +38 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/session-usage.d.ts +19 -0
- package/dist/engine/session-usage.js +25 -0
- package/dist/engine/steer-queue.d.ts +32 -0
- package/dist/engine/steer-queue.js +26 -0
- package/dist/engine/streaming-tool-queue.d.ts +12 -0
- package/dist/engine/streaming-tool-queue.js +50 -10
- package/dist/engine/turn-loop.d.ts +94 -9
- package/dist/engine/turn-loop.js +775 -342
- package/dist/engine/types.d.ts +175 -0
- package/dist/engine/types.js +13 -0
- package/dist/external-agents/config.d.ts +2 -0
- package/dist/external-agents/config.js +15 -0
- package/dist/external-agents/types.d.ts +31 -0
- package/dist/external-agents/types.js +1 -0
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +62 -26
- package/dist/git/worktree.d.ts +48 -0
- package/dist/git/worktree.js +111 -20
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +58 -0
- package/dist/hooks/goal-stop-hook.js +279 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +15 -0
- package/dist/hooks/registry.js +57 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +48 -18
- package/dist/index.d.ts +79 -11
- package/dist/index.js +89 -6
- package/dist/llm/capabilities/param-specs.d.ts +14 -0
- package/dist/llm/capabilities/param-specs.js +62 -0
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +37 -3
- package/dist/llm/capabilities/types.d.ts +32 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +69 -5
- package/dist/llm/client-base.js +185 -25
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-cache.js +4 -2
- package/dist/llm/model-pool.d.ts +29 -11
- package/dist/llm/model-pool.js +36 -20
- package/dist/llm/provider-auth.d.ts +41 -0
- package/dist/llm/provider-auth.js +76 -0
- package/dist/llm/provider-catalog.d.ts +6 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +207 -18
- package/dist/llm/providers/openai.d.ts +47 -2
- package/dist/llm/providers/openai.js +478 -71
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +40 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/stream-watchdog.js +5 -1
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/token-counter.js +9 -2
- package/dist/llm/types.d.ts +9 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +112 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.d.ts +1 -1
- package/dist/lsp/manager.js +42 -11
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/model-catalog/builtin.d.ts +12 -0
- package/dist/model-catalog/builtin.js +407 -0
- package/dist/model-catalog/gen-connections.d.ts +20 -0
- package/dist/model-catalog/gen-connections.js +28 -0
- package/dist/model-catalog/index.d.ts +41 -0
- package/dist/model-catalog/index.js +90 -0
- package/dist/model-catalog/params.d.ts +20 -0
- package/dist/model-catalog/params.js +45 -0
- package/dist/model-catalog/resolve.d.ts +48 -0
- package/dist/model-catalog/resolve.js +33 -0
- package/dist/model-catalog/save-entry.d.ts +32 -0
- package/dist/model-catalog/save-entry.js +104 -0
- package/dist/model-catalog/types.d.ts +561 -0
- package/dist/model-catalog/types.js +93 -0
- package/dist/model-catalog/upsert.d.ts +9 -0
- package/dist/model-catalog/upsert.js +8 -0
- package/dist/onboarding.d.ts +12 -82
- package/dist/onboarding.js +75 -333
- package/dist/plugins/gitOps.d.ts +53 -0
- package/dist/plugins/gitOps.js +130 -5
- package/dist/plugins/installer/checkUpdate.d.ts +16 -0
- package/dist/plugins/installer/checkUpdate.js +32 -0
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
- package/dist/plugins/installer/codex/convertCommands.js +46 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +11 -0
- package/dist/plugins/installer/codex/convertMcp.js +85 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +123 -0
- package/dist/plugins/installer/installFromArchive.d.ts +43 -0
- package/dist/plugins/installer/installFromArchive.js +134 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +51 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +27 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +14 -0
- package/dist/plugins/installer/loadPluginMcp.js +107 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
- package/dist/plugins/installer/pruneDisabled.js +73 -0
- package/dist/plugins/installer/types.d.ts +52 -0
- package/dist/plugins/installer/types.js +28 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +18 -0
- package/dist/plugins/installer/unzip.d.ts +14 -0
- package/dist/plugins/installer/unzip.js +82 -0
- package/dist/plugins/installer/update.d.ts +28 -0
- package/dist/plugins/installer/update.js +97 -0
- package/dist/plugins/loadPluginHooks.d.ts +54 -1
- package/dist/plugins/loadPluginHooks.js +83 -1
- package/dist/plugins/marketplaceManager.d.ts +15 -1
- package/dist/plugins/marketplaceManager.js +58 -4
- package/dist/plugins/pluginCommandHook.js +34 -14
- package/dist/plugins/pluginContent.d.ts +30 -0
- package/dist/plugins/pluginContent.js +83 -0
- package/dist/plugins/pluginInstaller.d.ts +34 -0
- package/dist/plugins/pluginInstaller.js +168 -35
- package/dist/plugins/schemas.js +41 -6
- package/dist/plugins/types.d.ts +10 -0
- package/dist/preset/index.d.ts +7 -1
- package/dist/preset/index.js +112 -6
- package/dist/product/types.d.ts +1 -1
- package/dist/prompt/composer.d.ts +31 -0
- package/dist/prompt/composer.js +95 -25
- package/dist/prompt/instruction-scanner.js +5 -3
- package/dist/prompt/section-loader.js +1 -0
- package/dist/prompt/sections/base.md +1 -0
- package/dist/prompt/sections/browser.md +10 -0
- package/dist/prompt/sections/coding.md +4 -0
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +15 -2
- package/dist/protocol/chat-session-manager.js +46 -0
- package/dist/protocol/chat-session.d.ts +82 -0
- package/dist/protocol/chat-session.js +121 -1
- package/dist/protocol/client.d.ts +27 -0
- package/dist/protocol/client.js +38 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +153 -2
- package/dist/protocol/server.js +807 -39
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/transport.js +3 -2
- package/dist/protocol/types.d.ts +69 -0
- package/dist/protocol/types.js +22 -0
- package/dist/quota/credentials.d.ts +3 -0
- package/dist/quota/credentials.js +80 -0
- package/dist/quota/index.d.ts +36 -0
- package/dist/quota/index.js +155 -0
- package/dist/quota/types.d.ts +48 -0
- package/dist/quota/types.js +13 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/review/review-prompt.d.ts +28 -0
- package/dist/review/review-prompt.js +81 -0
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +40 -11
- package/dist/run/RunApprovalBackend.js +25 -5
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.d.ts +12 -0
- package/dist/run/RunManager.js +86 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +8 -1
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/runtime/background-shell.d.ts +139 -0
- package/dist/runtime/background-shell.js +495 -0
- package/dist/runtime/output-clean.d.ts +24 -0
- package/dist/runtime/output-clean.js +41 -0
- package/dist/runtime/ring-file.d.ts +64 -0
- package/dist/runtime/ring-file.js +174 -0
- package/dist/runtime/safe-spawn.js +15 -34
- package/dist/runtime/spawn-common.d.ts +148 -0
- package/dist/runtime/spawn-common.js +303 -0
- package/dist/runtime/truncate-output.d.ts +22 -0
- package/dist/runtime/truncate-output.js +49 -0
- package/dist/runtime/utf8-cut.d.ts +11 -0
- package/dist/runtime/utf8-cut.js +23 -0
- package/dist/services/auto-dream.d.ts +4 -0
- package/dist/services/auto-dream.js +26 -26
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/diagnostics.d.ts +1 -2
- package/dist/services/diagnostics.js +12 -7
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/extract-memories.d.ts +14 -1
- package/dist/services/extract-memories.js +45 -6
- package/dist/services/memory-orchestrator.d.ts +21 -0
- package/dist/services/memory-orchestrator.js +125 -32
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +30 -19
- package/dist/session/file-history.d.ts +124 -1
- package/dist/session/file-history.js +232 -9
- package/dist/session/memory.d.ts +116 -2
- package/dist/session/memory.js +250 -28
- package/dist/session/session-manager.d.ts +63 -1
- package/dist/session/session-manager.js +186 -5
- package/dist/session/simple-diff.d.ts +23 -0
- package/dist/session/simple-diff.js +84 -0
- package/dist/session/transcript.d.ts +25 -1
- package/dist/session/transcript.js +35 -2
- package/dist/session/undo-target.d.ts +67 -0
- package/dist/session/undo-target.js +144 -0
- package/dist/settings/disk-defaults.d.ts +42 -0
- package/dist/settings/disk-defaults.js +33 -0
- package/dist/settings/feature-flags.d.ts +64 -0
- package/dist/settings/feature-flags.js +61 -0
- package/dist/settings/manager.d.ts +101 -1
- package/dist/settings/manager.js +345 -15
- package/dist/settings/migrate-config.d.ts +45 -0
- package/dist/settings/migrate-config.js +125 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema-export.d.ts +25 -0
- package/dist/settings/schema-export.js +38 -0
- package/dist/settings/schema.d.ts +1827 -396
- package/dist/settings/schema.js +361 -81
- package/dist/skills/scanner.d.ts +9 -0
- package/dist/skills/scanner.js +30 -2
- package/dist/stt/resolve-transcribe.d.ts +31 -0
- package/dist/stt/resolve-transcribe.js +108 -0
- package/dist/stt/transcribe.d.ts +51 -0
- package/dist/stt/transcribe.js +65 -0
- package/dist/tool-system/browser-bridge.d.ts +226 -0
- package/dist/tool-system/browser-bridge.js +163 -0
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
- package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
- package/dist/tool-system/builtin/agent-notifications.js +9 -3
- package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
- package/dist/tool-system/builtin/agent-output-file.js +72 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +23 -0
- package/dist/tool-system/builtin/agent-registry.js +16 -4
- package/dist/tool-system/builtin/agent.d.ts +50 -11
- package/dist/tool-system/builtin/agent.js +576 -73
- package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
- package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
- package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
- package/dist/tool-system/builtin/apply-patch/index.js +7 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
- package/dist/tool-system/builtin/background-jobs.js +124 -0
- package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
- package/dist/tool-system/builtin/background-shell-tools.js +108 -0
- package/dist/tool-system/builtin/background-work.d.ts +67 -0
- package/dist/tool-system/builtin/background-work.js +86 -0
- package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
- package/dist/tool-system/builtin/bash-output-style.js +40 -0
- package/dist/tool-system/builtin/bash.d.ts +5 -2
- package/dist/tool-system/builtin/bash.js +98 -62
- package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
- package/dist/tool-system/builtin/browser-tools.js +312 -0
- package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
- package/dist/tool-system/builtin/cancel-goal.js +64 -0
- package/dist/tool-system/builtin/check-quota.d.ts +15 -0
- package/dist/tool-system/builtin/check-quota.js +34 -0
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/config.js +7 -0
- package/dist/tool-system/builtin/cron.d.ts +7 -0
- package/dist/tool-system/builtin/cron.js +105 -9
- package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
- package/dist/tool-system/builtin/drive-claude-code.js +157 -0
- package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
- package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +21 -12
- package/dist/tool-system/builtin/eol.d.ts +29 -0
- package/dist/tool-system/builtin/eol.js +37 -0
- package/dist/tool-system/builtin/file-cache.d.ts +6 -0
- package/dist/tool-system/builtin/file-cache.js +8 -0
- package/dist/tool-system/builtin/generate-image.d.ts +60 -0
- package/dist/tool-system/builtin/generate-image.js +374 -0
- package/dist/tool-system/builtin/generate-video.d.ts +55 -0
- package/dist/tool-system/builtin/generate-video.js +361 -0
- package/dist/tool-system/builtin/grep.js +6 -2
- package/dist/tool-system/builtin/image-providers.d.ts +86 -0
- package/dist/tool-system/builtin/image-providers.js +190 -0
- package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
- package/dist/tool-system/builtin/image-uploader.js +74 -0
- package/dist/tool-system/builtin/index.d.ts +24 -1
- package/dist/tool-system/builtin/index.js +310 -25
- package/dist/tool-system/builtin/mcp-tools.js +23 -3
- package/dist/tool-system/builtin/memory.js +45 -7
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +1 -1
- package/dist/tool-system/builtin/powershell.js +4 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +11 -3
- package/dist/tool-system/builtin/repl.js +4 -1
- package/dist/tool-system/builtin/skill.js +9 -0
- package/dist/tool-system/builtin/sleep.js +12 -5
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/tool-search.js +25 -7
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/video-providers.d.ts +154 -0
- package/dist/tool-system/builtin/video-providers.js +235 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-fetch.js +12 -2
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +57 -10
- package/dist/tool-system/builtin/worktree.d.ts +2 -1
- package/dist/tool-system/builtin/worktree.js +31 -4
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +1 -1
- package/dist/tool-system/context.d.ts +166 -6
- package/dist/tool-system/executor.d.ts +3 -2
- package/dist/tool-system/executor.js +197 -119
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +20 -4
- package/dist/tool-system/mcp-manager.d.ts +78 -16
- package/dist/tool-system/mcp-manager.js +357 -28
- package/dist/tool-system/path-policy.d.ts +99 -0
- package/dist/tool-system/path-policy.js +578 -0
- package/dist/tool-system/permission.d.ts +36 -1
- package/dist/tool-system/permission.js +322 -117
- package/dist/tool-system/plan-mode-allowlist.d.ts +32 -0
- package/dist/tool-system/plan-mode-allowlist.js +65 -0
- package/dist/tool-system/registry.d.ts +1 -0
- package/dist/tool-system/registry.js +26 -1
- package/dist/tool-system/sandbox/index.d.ts +8 -0
- package/dist/tool-system/sandbox/index.js +7 -2
- package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
- package/dist/tool-system/validate-tool-metadata.js +63 -0
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +301 -15
- package/dist/updater.js +20 -9
- package/dist/utils/envUtils.d.ts +0 -9
- package/dist/utils/envUtils.js +3 -28
- package/dist/utils/exec.d.ts +40 -0
- package/dist/utils/exec.js +144 -0
- package/dist/utils/format.js +4 -2
- package/dist/utils/json.d.ts +12 -0
- package/dist/utils/json.js +92 -0
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +7 -1
- package/dist/utils/theme.js +5 -3
- package/dist/utils/toolDisplay.js +0 -1
- package/package.json +14 -7
- package/dist/agent/coordinator.d.ts +0 -49
- package/dist/agent/coordinator.js +0 -77
- package/dist/settings/manager.test.js +0 -73
- package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
- package/dist/tool-system/builtin/remote-trigger.js +0 -54
- package/dist/tool-system/builtin/send-message.d.ts +0 -6
- package/dist/tool-system/builtin/send-message.js +0 -47
- /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
package/dist/protocol/server.js
CHANGED
|
@@ -14,14 +14,43 @@
|
|
|
14
14
|
* falls back to borrowing any live session's engine for those reads.
|
|
15
15
|
*/
|
|
16
16
|
import { Methods, ErrorCodes, createResponse, createErrorResponse, createNotification, isRequest, } from "./types.js";
|
|
17
|
+
import { diskDefaultsFrom } from "../engine/engine.js";
|
|
18
|
+
import { isProtectedSettingKey } from "../settings/manager.js";
|
|
17
19
|
import { setInteractiveApprovalFn } from "../tool-system/permission.js";
|
|
18
20
|
import { getArenaStatus } from "../tool-system/builtin/arena.js";
|
|
19
|
-
import { agentNotificationBus } from "../tool-system/builtin/agent-notifications.js";
|
|
21
|
+
import { agentNotificationBus, notificationQueue, buildNotificationMessage, } from "../tool-system/builtin/agent-notifications.js";
|
|
22
|
+
import { backgroundShellManager } from "../runtime/background-shell.js";
|
|
23
|
+
import { backgroundJobRegistry } from "../tool-system/builtin/background-jobs.js";
|
|
24
|
+
import { listBackgroundWorkForUI } from "../tool-system/builtin/background-work.js";
|
|
25
|
+
import { logger } from "../logging/logger.js";
|
|
20
26
|
import { nanoid } from "nanoid";
|
|
27
|
+
import { redactLlmConfig, maskSecretValue } from "./redact.js";
|
|
28
|
+
import { redactSecrets } from "../logging/sanitize-messages.js";
|
|
21
29
|
export class AgentServer {
|
|
22
30
|
chatManager;
|
|
23
31
|
legacyEngine;
|
|
32
|
+
globalQueryEngine = null;
|
|
24
33
|
transport;
|
|
34
|
+
/** Fresh-disk settings reader for config hot-reload; null when not wired. */
|
|
35
|
+
settingsReader;
|
|
36
|
+
/** Disk-only active-goal reader for agent/goalGet on a non-live session. */
|
|
37
|
+
readActiveGoalFromDisk;
|
|
38
|
+
/**
|
|
39
|
+
* Monotonic config-reload version, bumped per reloadSettings request so each
|
|
40
|
+
* Engine.refreshRuntimeConfig can drop out-of-order (stale) deliveries (Q5).
|
|
41
|
+
*/
|
|
42
|
+
configVersion = 0;
|
|
43
|
+
/**
|
|
44
|
+
* JSON of the last disk-default patch broadcast to ALL live sessions (#6).
|
|
45
|
+
* When a new reloadSettings request produces a byte-identical patch we SKIP
|
|
46
|
+
* the entire forEachSession broadcast — every session's refreshRuntimeConfig
|
|
47
|
+
* would otherwise unconditionally reloadHooks() (invalidate + full disk
|
|
48
|
+
* re-read + hook teardown/re-register) per call, and the personalization UI
|
|
49
|
+
* auto-saves on a 600ms debounce, so identical re-saves caused K× hook churn
|
|
50
|
+
* per keystroke-pause. A genuine change still differs in JSON → propagates.
|
|
51
|
+
* null until the first broadcast.
|
|
52
|
+
*/
|
|
53
|
+
lastBroadcastPatch = null;
|
|
25
54
|
// ── Legacy single-engine state (used when chatManager is null) ──
|
|
26
55
|
running = false;
|
|
27
56
|
abortController = null;
|
|
@@ -41,6 +70,8 @@ export class AgentServer {
|
|
|
41
70
|
constructor(options) {
|
|
42
71
|
this.chatManager = options.chatManager ?? null;
|
|
43
72
|
this.legacyEngine = options.engine ?? null;
|
|
73
|
+
this.settingsReader = options.settingsReader ?? null;
|
|
74
|
+
this.readActiveGoalFromDisk = options.readActiveGoalFromDisk ?? null;
|
|
44
75
|
if (!this.chatManager && !this.legacyEngine) {
|
|
45
76
|
throw new Error("AgentServer: either chatManager or engine must be supplied");
|
|
46
77
|
}
|
|
@@ -53,29 +84,138 @@ export class AgentServer {
|
|
|
53
84
|
});
|
|
54
85
|
}
|
|
55
86
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
setInteractiveApprovalFn((request) => {
|
|
88
|
+
return this.requestApprovalFromClient(request);
|
|
89
|
+
});
|
|
59
90
|
if (this.legacyEngine) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
// Only wire an interactive askUser when a human is present. For
|
|
92
|
+
// unattended (headless) runs we leave askUser undefined so
|
|
93
|
+
// AskUserQuestion hits its headless-error branch and returns immediately
|
|
94
|
+
// instead of suspending until the tool-exec timeout (~300s).
|
|
95
|
+
if (!this.legacyEngine.isHeadless()) {
|
|
96
|
+
this.legacyEngine.setAskUser((question, opts) => {
|
|
97
|
+
return this.requestAskUserFromClient(question, opts);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
66
100
|
}
|
|
67
101
|
// B2.2 — forward background sub-agent completion events through the
|
|
68
102
|
// protocol so Desktop / SDK / remote AgentClients see them too. The
|
|
69
103
|
// bus is fed from `NotificationQueue.enqueue`, so this single
|
|
70
|
-
// subscription covers every enqueue site
|
|
71
|
-
//
|
|
72
|
-
//
|
|
104
|
+
// subscription covers every enqueue site — background sub-agents
|
|
105
|
+
// (agent.ts), background shells (background-shell.ts), and video/job
|
|
106
|
+
// polls (background-jobs.ts). The bus now guarantees a real sessionId
|
|
107
|
+
// (no more legacy-bucket coercion to ""), so we forward whatever sid it
|
|
108
|
+
// hands us.
|
|
73
109
|
this.bgAgentBusUnsubscribe = agentNotificationBus.subscribe((sessionId, event) => {
|
|
74
110
|
this.notify(Methods.StreamEvent, { sessionId, event });
|
|
111
|
+
// Background work that finishes while the session is IDLE (a
|
|
112
|
+
// run_in_background Bash like a download, a background sub-agent, or a
|
|
113
|
+
// video poll — the engine no longer parks on any of them) would otherwise
|
|
114
|
+
// leave its completion sitting in the queue until the user manually sends.
|
|
115
|
+
// Wake the session with one run carrying the notification so the model
|
|
116
|
+
// reads "download complete" and continues on its own (the persisted goal
|
|
117
|
+
// is judged that turn). If the work finishes while a run is still in
|
|
118
|
+
// flight, the idle guard below skips it and the run-boundary re-check
|
|
119
|
+
// (trigger B) drains it at end-of-turn instead. A never-exiting dev server
|
|
120
|
+
// emits no completion, so it never wakes anything (no task/service
|
|
121
|
+
// classification needed).
|
|
122
|
+
this.maybeWakeIdleSession(sessionId);
|
|
75
123
|
});
|
|
76
124
|
// Notify client we're ready
|
|
77
125
|
this.notify(Methods.Status, { status: "ready" });
|
|
78
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Wake an IDLE chatManager session that has pending background-completion
|
|
129
|
+
* notifications, by enqueueing one turn whose task is the drained
|
|
130
|
+
* notification(s). The model then sees the completion and continues; the
|
|
131
|
+
* session's persisted goal (if any) is judged on that turn.
|
|
132
|
+
*
|
|
133
|
+
* Guards:
|
|
134
|
+
* - chatManager path only (the legacy single-engine / headless path drives
|
|
135
|
+
* its own loop and has no idle-session-resume concept).
|
|
136
|
+
* - Session must exist AND be idle. If it's busy, we do nothing: the in-flight
|
|
137
|
+
* run's end-of-turn `drainAll` already collects every pending notification,
|
|
138
|
+
* so a second run would be redundant — and `enqueueTurn` while busy would
|
|
139
|
+
* queue a spurious extra turn.
|
|
140
|
+
* - We `drainAll` exactly here and feed the items into the woken turn. This
|
|
141
|
+
* also merges a burst of near-simultaneous completions into one wakeup:
|
|
142
|
+
* the first drains all currently-pending items; subsequent bus events for
|
|
143
|
+
* the same session find it busy (or find an empty queue) and no-op.
|
|
144
|
+
*
|
|
145
|
+
* INVARIANT (the burst-merge correctness depends on it): the merge only
|
|
146
|
+
* holds because (a) the bus fans out to subscribers SYNCHRONOUSLY, and (b)
|
|
147
|
+
* `enqueueTurn` sets the session's `active` (→ isBusy()===true) SYNCHRONOUSLY
|
|
148
|
+
* before its first await. So when bus events #2..N arrive — still on the same
|
|
149
|
+
* synchronous fan-out as #1 — they already see the session as busy and no-op.
|
|
150
|
+
* If either path is ever made async (e.g. an await is added inside
|
|
151
|
+
* enqueueTurn before `active` is set, or the bus starts deferring delivery),
|
|
152
|
+
* this degrades into N concurrent wakeups for N completions. Keep both
|
|
153
|
+
* synchronous, or replace this comment's assumption with an explicit
|
|
154
|
+
* "wakeup in flight" guard flag.
|
|
155
|
+
*/
|
|
156
|
+
maybeWakeIdleSession(sessionId) {
|
|
157
|
+
if (!this.chatManager)
|
|
158
|
+
return;
|
|
159
|
+
const session = this.chatManager.get(sessionId);
|
|
160
|
+
if (!session || session.isBusy())
|
|
161
|
+
return;
|
|
162
|
+
// Headless / automation runs are one-shot: the caller takes result.text and
|
|
163
|
+
// is gone, so there's no consumer for a woken continuation turn. Headless
|
|
164
|
+
// already drained its background sub-agents inside engine.run before
|
|
165
|
+
// returning; any remaining queued notification (video/shell) must NOT spin
|
|
166
|
+
// an orphan turn. Only the interactive path auto-continues.
|
|
167
|
+
if (session.engine.isHeadless())
|
|
168
|
+
return;
|
|
169
|
+
// Don't resurrect a session the user just Stopped: cancel() leaves it idle
|
|
170
|
+
// (active=null) so isBusy() reads false, but auto-running a fresh turn here
|
|
171
|
+
// would defeat the Stop. The flag clears the moment the user sends again.
|
|
172
|
+
if (session.wasCancelledSinceLastTurn())
|
|
173
|
+
return;
|
|
174
|
+
const pending = notificationQueue.drainAll(sessionId);
|
|
175
|
+
if (pending.length === 0)
|
|
176
|
+
return;
|
|
177
|
+
const task = `<system-reminder>\n${buildNotificationMessage(pending)}\n</system-reminder>`;
|
|
178
|
+
void session
|
|
179
|
+
.enqueueTurn(task, {
|
|
180
|
+
// Synthetic notification, not the user's own input: persisted with an
|
|
181
|
+
// `injected` flag so a disk rebuild doesn't render it as a phantom user
|
|
182
|
+
// bubble (the live UI shows only the woken assistant's reply).
|
|
183
|
+
injected: true,
|
|
184
|
+
onStream: (event) => this.notify(Methods.StreamEvent, { sessionId, event }),
|
|
185
|
+
})
|
|
186
|
+
.catch((err) => {
|
|
187
|
+
// A wakeup turn failing must not crash the bus fan-out. The drained
|
|
188
|
+
// notifications are already in the transcript via the run's messages;
|
|
189
|
+
// log and move on.
|
|
190
|
+
logger.warn("bg_wakeup.turn_failed", {
|
|
191
|
+
sessionId,
|
|
192
|
+
error: err.message,
|
|
193
|
+
});
|
|
194
|
+
// Belt-and-braces, mirroring the send() path's run().then(clear-busy):
|
|
195
|
+
// the renderer set the composer "working" spinner on this run's
|
|
196
|
+
// session_started, and clears it on turn_complete/error. A failure
|
|
197
|
+
// BEFORE the turn-loop runs (e.g. a setup error) emits neither, which
|
|
198
|
+
// would leave the spinner stuck. Emit a terminal `error` (NOT a
|
|
199
|
+
// turn_complete) so the renderer clears busy AND a woken automation
|
|
200
|
+
// session's runStatus flips to "failed" rather than being mislabeled
|
|
201
|
+
// "completed". If the turn-loop already emitted its own `error`, this is
|
|
202
|
+
// a harmless duplicate (busy already cleared, status already failed).
|
|
203
|
+
this.notify(Methods.StreamEvent, {
|
|
204
|
+
sessionId,
|
|
205
|
+
event: { type: "error", error: err?.message ?? "background wakeup failed" },
|
|
206
|
+
});
|
|
207
|
+
})
|
|
208
|
+
.finally(() => {
|
|
209
|
+
// Run-boundary re-check (trigger B): the woken summarize turn may have
|
|
210
|
+
// spawned NEW background work (e.g. goal "generate 2 videos" → after #1
|
|
211
|
+
// completes, this turn submits #2). When #2 finishes its notification
|
|
212
|
+
// arrives while we're idle again — but if it arrived DURING this turn
|
|
213
|
+
// (busy), trigger A skipped it. Re-checking at the run boundary drains
|
|
214
|
+
// anything that landed while busy, chaining wakeups until the queue is
|
|
215
|
+
// truly empty. Replaces the old engine for(;;) outer loop.
|
|
216
|
+
this.maybeWakeIdleSession(sessionId);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
79
219
|
// ─── Request Dispatch ───────────────────────────────────────────
|
|
80
220
|
async handleRequest(req) {
|
|
81
221
|
switch (req.method) {
|
|
@@ -97,9 +237,30 @@ export class AgentServer {
|
|
|
97
237
|
case Methods.Inject:
|
|
98
238
|
this.handleInject(req);
|
|
99
239
|
break;
|
|
240
|
+
case Methods.Steer:
|
|
241
|
+
this.handleSteer(req);
|
|
242
|
+
break;
|
|
243
|
+
case Methods.Unsteer:
|
|
244
|
+
this.handleUnsteer(req);
|
|
245
|
+
break;
|
|
100
246
|
case Methods.CloseSession:
|
|
101
247
|
this.handleCloseSession(req);
|
|
102
248
|
break;
|
|
249
|
+
case Methods.GoalExtend:
|
|
250
|
+
this.handleGoalExtend(req);
|
|
251
|
+
break;
|
|
252
|
+
case Methods.GoalClear:
|
|
253
|
+
this.handleGoalClear(req);
|
|
254
|
+
break;
|
|
255
|
+
case Methods.GoalGet:
|
|
256
|
+
this.handleGoalGet(req);
|
|
257
|
+
break;
|
|
258
|
+
case Methods.BackgroundShells:
|
|
259
|
+
this.handleBackgroundShells(req);
|
|
260
|
+
break;
|
|
261
|
+
case Methods.BackgroundWork:
|
|
262
|
+
this.handleBackgroundWork(req);
|
|
263
|
+
break;
|
|
103
264
|
default:
|
|
104
265
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.MethodNotFound, `Unknown method: ${req.method}`));
|
|
105
266
|
}
|
|
@@ -129,6 +290,7 @@ export class AgentServer {
|
|
|
129
290
|
session = cm.getOrCreate(params.sessionId, {
|
|
130
291
|
permissionMode: params.permissionMode,
|
|
131
292
|
cwd: params.cwd,
|
|
293
|
+
projectTrusted: params.projectTrusted,
|
|
132
294
|
});
|
|
133
295
|
}
|
|
134
296
|
catch (err) {
|
|
@@ -136,13 +298,46 @@ export class AgentServer {
|
|
|
136
298
|
this.transport.send(createErrorResponse(req.id, code, err.message));
|
|
137
299
|
return;
|
|
138
300
|
}
|
|
301
|
+
// `requireExisting`: reject if the target session isn't on disk rather than
|
|
302
|
+
// running the prompt against a freshly-created blank session. A cron
|
|
303
|
+
// "continue this conversation" job whose session the user deleted must fail
|
|
304
|
+
// loudly here (SessionNotFound) so the scheduler can auto-disable it,
|
|
305
|
+
// instead of silently executing with no transcript/goal/context.
|
|
306
|
+
if (params.requireExisting === true && !session.engine.sessionExistsOnDisk(params.sessionId)) {
|
|
307
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionNotFound, `session ${params.sessionId} does not exist`));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
139
310
|
if (typeof params.planMode === "boolean") {
|
|
140
311
|
session.engine.setPlanMode(params.planMode);
|
|
141
312
|
}
|
|
142
313
|
const sid = params.sessionId;
|
|
314
|
+
// Wire AskUserQuestion for this interactive session. The chatManager path
|
|
315
|
+
// builds a fresh per-session Engine via engineFactory, which (unlike the
|
|
316
|
+
// legacy single-engine path) never had askUser wired — so AskUserQuestion
|
|
317
|
+
// always fell into its "not available in headless mode" branch in normal
|
|
318
|
+
// chat (and in a resumed automation session). Route it to the client with
|
|
319
|
+
// this session's id so the renderer attributes the question to the right
|
|
320
|
+
// tab, resolving via the session's own pendingApprovals. Skip genuinely
|
|
321
|
+
// headless engines (no human to answer).
|
|
322
|
+
if (!session.engine.isHeadless()) {
|
|
323
|
+
session.engine.setAskUser((question, opts) => this.requestAskUserForSession(session, sid, question, opts));
|
|
324
|
+
// Browser automation bridge: each method routes a browser action to the
|
|
325
|
+
// client (Electron main drives the webview via CDP) over the SAME
|
|
326
|
+
// request/response channel as askUser (pendingApprovals + requestId),
|
|
327
|
+
// reusing its proven resolve/timeout/cleanup. The main-side handler
|
|
328
|
+
// replies with the action's JSON result.
|
|
329
|
+
session.engine.setBrowserBridge(this.makeBrowserBridge(session, sid));
|
|
330
|
+
// Cookie→browser injection (InjectCredential tool): same cross-process
|
|
331
|
+
// channel; main restores the cookie jar into the built-in browser.
|
|
332
|
+
session.engine.setInjectCredential((credentialId) => this.requestCredentialInjectForSession(session, sid, credentialId));
|
|
333
|
+
}
|
|
143
334
|
try {
|
|
144
335
|
const result = await session.enqueueTurn(params.task, {
|
|
145
336
|
cwd: params.cwd,
|
|
337
|
+
goal: typeof params.goal === "string" ||
|
|
338
|
+
(params.goal != null && typeof params.goal === "object")
|
|
339
|
+
? params.goal
|
|
340
|
+
: undefined,
|
|
146
341
|
onStream: (event) => this.notify(Methods.StreamEvent, { sessionId: sid, event }),
|
|
147
342
|
});
|
|
148
343
|
const runResult = {
|
|
@@ -153,9 +348,18 @@ export class AgentServer {
|
|
|
153
348
|
usage: result.usage,
|
|
154
349
|
};
|
|
155
350
|
this.transport.send(createResponse(req.id, runResult));
|
|
351
|
+
// Run-boundary re-check (trigger B): the session is idle now. If a
|
|
352
|
+
// background task (shell/video) completed DURING this run, its bus event
|
|
353
|
+
// fired while we were busy and trigger A skipped it — drain it now and
|
|
354
|
+
// wake a continuation turn. Interactive path only; headless already
|
|
355
|
+
// drained its sub-agents inside engine.run before returning.
|
|
356
|
+
this.maybeWakeIdleSession(sid);
|
|
156
357
|
}
|
|
157
358
|
catch (err) {
|
|
158
359
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
360
|
+
// Even on a failed run the session goes idle — re-check so a background
|
|
361
|
+
// completion that landed during the failed run isn't orphaned.
|
|
362
|
+
this.maybeWakeIdleSession(sid);
|
|
159
363
|
}
|
|
160
364
|
}
|
|
161
365
|
async handleRunLegacy(req) {
|
|
@@ -191,12 +395,20 @@ export class AgentServer {
|
|
|
191
395
|
};
|
|
192
396
|
// TodoWrite emits task_update directly through ToolContext.streamCallback;
|
|
193
397
|
// no module singleton wiring needed anymore.
|
|
398
|
+
// Snapshot the controller for the catch block — `this.abortController`
|
|
399
|
+
// is nulled in the `finally`, so by the time we'd want to check
|
|
400
|
+
// `signal.aborted` below it's already gone.
|
|
401
|
+
const runController = this.abortController;
|
|
194
402
|
try {
|
|
195
403
|
const result = await this.legacyEngine.run(params.task, {
|
|
196
404
|
cwd: params.cwd,
|
|
197
405
|
sessionId: params.sessionId,
|
|
198
|
-
signal:
|
|
406
|
+
signal: runController.signal,
|
|
199
407
|
onStream: streamToClient,
|
|
408
|
+
goal: typeof params.goal === "string" ||
|
|
409
|
+
(params.goal != null && typeof params.goal === "object")
|
|
410
|
+
? params.goal
|
|
411
|
+
: undefined,
|
|
200
412
|
});
|
|
201
413
|
const runResult = {
|
|
202
414
|
text: result.text,
|
|
@@ -208,7 +420,28 @@ export class AgentServer {
|
|
|
208
420
|
this.transport.send(createResponse(req.id, runResult));
|
|
209
421
|
}
|
|
210
422
|
catch (err) {
|
|
211
|
-
|
|
423
|
+
// ESC / Stop path. If the user cancelled, the engine's
|
|
424
|
+
// in-flight LLM call or Bash spawn rejects with an AbortError
|
|
425
|
+
// that bubbles up here. Don't surface it as InternalError —
|
|
426
|
+
// the user already knows they pressed stop; clients should
|
|
427
|
+
// just clear busy.
|
|
428
|
+
const errName = err.name;
|
|
429
|
+
const aborted = runController.signal.aborted ||
|
|
430
|
+
errName === "AbortError" ||
|
|
431
|
+
errName === "APIUserAbortError";
|
|
432
|
+
if (aborted) {
|
|
433
|
+
const cancelledResult = {
|
|
434
|
+
text: "",
|
|
435
|
+
reason: "aborted_streaming",
|
|
436
|
+
sessionId: params.sessionId ?? "",
|
|
437
|
+
turnCount: 0,
|
|
438
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
439
|
+
};
|
|
440
|
+
this.transport.send(createResponse(req.id, cancelledResult));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
444
|
+
}
|
|
212
445
|
}
|
|
213
446
|
finally {
|
|
214
447
|
this.running = false;
|
|
@@ -232,6 +465,11 @@ export class AgentServer {
|
|
|
232
465
|
return;
|
|
233
466
|
}
|
|
234
467
|
s.pendingApprovals.delete(params.requestId);
|
|
468
|
+
// Cancel the pending timeout for THIS request (the ask path armed one).
|
|
469
|
+
// The legacy single-engine branch below already does this; without it
|
|
470
|
+
// here a decided request leaves a live timer that fires at
|
|
471
|
+
// APPROVAL_TIMEOUT_MS and churns a dead map entry.
|
|
472
|
+
this.clearApprovalTimer(params.requestId);
|
|
235
473
|
resolve(params.decision);
|
|
236
474
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
237
475
|
return;
|
|
@@ -262,6 +500,14 @@ export class AgentServer {
|
|
|
262
500
|
return;
|
|
263
501
|
}
|
|
264
502
|
s.cancel();
|
|
503
|
+
// s.cancel() only aborts the engine controller + drains queued turns. The
|
|
504
|
+
// session's pendingApprovals (askUser / browser_action / tool approvals)
|
|
505
|
+
// are NOT driven by the abort signal — they only settle on a client reply
|
|
506
|
+
// or the 5-minute APPROVAL_TIMEOUT_MS. Left alone, the awaiting tool call
|
|
507
|
+
// hangs until that timeout (delaying run wind-down) and the server-side
|
|
508
|
+
// approvalTimers entry leaks. Resolve them as cancelled now and clear the
|
|
509
|
+
// matching timers, mirroring the legacy path below.
|
|
510
|
+
this.cancelSessionApprovals(s);
|
|
265
511
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
266
512
|
return;
|
|
267
513
|
}
|
|
@@ -278,6 +524,163 @@ export class AgentServer {
|
|
|
278
524
|
this.clearAllApprovalTimers();
|
|
279
525
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
280
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Extend a running goal's turn/budget ceilings mid-run (TODO 3.1). Routed to
|
|
529
|
+
* a specific session (multi-session path). Returns the resulting effective
|
|
530
|
+
* limits, or an error if no session / no active run.
|
|
531
|
+
*/
|
|
532
|
+
handleGoalExtend(req) {
|
|
533
|
+
const params = (req.params ?? {});
|
|
534
|
+
const ext = {
|
|
535
|
+
addTurns: params.addTurns,
|
|
536
|
+
addTokenBudget: params.addTokenBudget,
|
|
537
|
+
addTimeBudgetMs: params.addTimeBudgetMs,
|
|
538
|
+
addStopBlocks: params.addStopBlocks,
|
|
539
|
+
};
|
|
540
|
+
// Multi-session path: route to the named session.
|
|
541
|
+
const session = this.chatManager && typeof params.sessionId === "string"
|
|
542
|
+
? this.chatManager.get(params.sessionId)
|
|
543
|
+
: undefined;
|
|
544
|
+
// Legacy single-engine path (no chatManager): extend the one engine's run,
|
|
545
|
+
// mirroring handleCancel which also supports both paths. Without this a
|
|
546
|
+
// legacy host can cancel but never extend a goal.
|
|
547
|
+
const result = session
|
|
548
|
+
? session.extendGoalRun(ext)
|
|
549
|
+
: this.legacyEngine
|
|
550
|
+
? this.legacyEngine.extendGoalRun(ext)
|
|
551
|
+
: undefined;
|
|
552
|
+
if (result === undefined) {
|
|
553
|
+
// Couldn't even resolve a target (no session and no legacy engine).
|
|
554
|
+
if (!session && !this.legacyEngine) {
|
|
555
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, params.sessionId ? `No such session: ${params.sessionId}` : "sessionId is required"));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (!result) {
|
|
560
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "No active run to extend"));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
this.transport.send(createResponse(req.id, { ok: true, limits: result }));
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Clear a session's persisted active goal (CC /goal clear). Routes to the
|
|
567
|
+
* named session, falling back to the legacy single engine (mirrors
|
|
568
|
+
* handleGoalExtend / handleCancel's dual path). Returns { ok, cleared }.
|
|
569
|
+
*/
|
|
570
|
+
handleGoalClear(req) {
|
|
571
|
+
const params = (req.params ?? {});
|
|
572
|
+
const session = this.chatManager && typeof params.sessionId === "string"
|
|
573
|
+
? this.chatManager.get(params.sessionId)
|
|
574
|
+
: undefined;
|
|
575
|
+
if (!session && !this.legacyEngine) {
|
|
576
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, params.sessionId ? `No such session: ${params.sessionId}` : "sessionId is required"));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const cleared = session
|
|
580
|
+
? session.clearGoal()
|
|
581
|
+
: params.sessionId
|
|
582
|
+
? (this.legacyEngine.clearGoal(params.sessionId) ?? false)
|
|
583
|
+
: false;
|
|
584
|
+
this.transport.send(createResponse(req.id, { ok: true, cleared }));
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Read a session's persisted active goal so the host can re-surface the goal
|
|
588
|
+
* block + Cancel button on session load. A persistent goal lives only in
|
|
589
|
+
* state.activeGoal and is never replayed from the transcript, so a reloaded
|
|
590
|
+
* (or disk-rebuilt) session has no other way to learn it. Prefers a live
|
|
591
|
+
* chatManager session, else falls through to a disk read (readActiveGoalFromDisk
|
|
592
|
+
* in worker mode, or the legacy engine's disk read in single-engine mode) — the
|
|
593
|
+
* bug case is an aborted/reloaded session that is NOT live. Returns { ok, goal } where goal
|
|
594
|
+
* is the objective string, or null when there's no goal / unknown session.
|
|
595
|
+
* Never errors on "no goal" — null is the normal "nothing to show" answer.
|
|
596
|
+
*/
|
|
597
|
+
handleGoalGet(req) {
|
|
598
|
+
const params = (req.params ?? {});
|
|
599
|
+
if (typeof params.sessionId !== "string" || !params.sessionId) {
|
|
600
|
+
// Missing/empty param is InvalidParams, not SessionClosed (no session was
|
|
601
|
+
// ever named) — matches handleSteer/handleUnsteer/handleBackgroundShells.
|
|
602
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
// Prefer the live session; otherwise read straight off disk. The goal we
|
|
606
|
+
// want to recover belongs to a session that is, by definition of this bug,
|
|
607
|
+
// usually NOT currently live: reopening after restart doesn't create a
|
|
608
|
+
// ChatSession (that only happens on a send), so chatManager.get() misses.
|
|
609
|
+
// In worker (chatManager) mode there is no legacyEngine, so the disk read
|
|
610
|
+
// MUST come from readActiveGoalFromDisk — relying on legacyEngine there
|
|
611
|
+
// silently returned null and the UI goal block never re-surfaced.
|
|
612
|
+
const live = this.chatManager ? this.chatManager.get(params.sessionId) : undefined;
|
|
613
|
+
const goal = live
|
|
614
|
+
? live.getGoal()
|
|
615
|
+
: (this.readActiveGoalFromDisk?.(params.sessionId) ??
|
|
616
|
+
this.legacyEngine?.getGoal(params.sessionId) ??
|
|
617
|
+
undefined);
|
|
618
|
+
this.transport.send(createResponse(req.id, { ok: true, goal: goal ? goal.objective : null }));
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Query/control a session's background shells for the desktop UI panel (TODO
|
|
622
|
+
* 3.2). Reads the singleton BackgroundShellManager directly (shells outlive
|
|
623
|
+
* the run that spawned them, so we don't need a live ChatSession). Actions:
|
|
624
|
+
* - (default) list → the session's shells
|
|
625
|
+
* - output → full retained output of one shell
|
|
626
|
+
* - kill → terminate one shell's process group
|
|
627
|
+
* All scoped by sessionId (manager enforces ownership on output/kill).
|
|
628
|
+
*/
|
|
629
|
+
handleBackgroundShells(req) {
|
|
630
|
+
const params = (req.params ?? {});
|
|
631
|
+
const sessionId = params.sessionId;
|
|
632
|
+
if (typeof sessionId !== "string" || !sessionId) {
|
|
633
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const action = params.action ?? "list";
|
|
637
|
+
if (action === "list") {
|
|
638
|
+
const shells = backgroundShellManager.listForSession(sessionId);
|
|
639
|
+
this.transport.send(createResponse(req.id, { shells }));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (!params.shellId) {
|
|
643
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "shellId is required"));
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (action === "output") {
|
|
647
|
+
const res = backgroundShellManager.readOutput(params.shellId, "all", sessionId);
|
|
648
|
+
if (!res.ok) {
|
|
649
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, res.error));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
this.transport.send(createResponse(req.id, { header: res.header, text: res.text }));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (action === "kill") {
|
|
656
|
+
void backgroundShellManager.kill(params.shellId, sessionId).then((res) => {
|
|
657
|
+
if (!res.ok) {
|
|
658
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, res.error));
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
this.transport.send(createResponse(req.id, { ok: true }));
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, `unknown action: ${action}`));
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* BackgroundWork — unified, list-only view of a session's background work
|
|
670
|
+
* across all three registries (shells + sub-agents + jobs) for the desktop
|
|
671
|
+
* background panel. Per-shell output/kill still flow through BackgroundShells
|
|
672
|
+
* (by shellId); this just answers "what's running in the background right now".
|
|
673
|
+
*/
|
|
674
|
+
handleBackgroundWork(req) {
|
|
675
|
+
const params = (req.params ?? {});
|
|
676
|
+
const sessionId = params.sessionId;
|
|
677
|
+
if (typeof sessionId !== "string" || !sessionId) {
|
|
678
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const items = listBackgroundWorkForUI(sessionId);
|
|
682
|
+
this.transport.send(createResponse(req.id, { items }));
|
|
683
|
+
}
|
|
281
684
|
// ─── CloseSession ───────────────────────────────────────────────
|
|
282
685
|
handleCloseSession(req) {
|
|
283
686
|
const params = (req.params ?? {});
|
|
@@ -288,6 +691,14 @@ export class AgentServer {
|
|
|
288
691
|
if (this.chatManager) {
|
|
289
692
|
this.chatManager.close(params.sessionId);
|
|
290
693
|
}
|
|
694
|
+
// Explicit session teardown — reap that session's background shells
|
|
695
|
+
// (design §6 "session 被显式删除 → killSession"). This is the RPC path
|
|
696
|
+
// (agent/closeSession from the host on delete), distinct from the idle
|
|
697
|
+
// sweeper's chatManager.close() which must NOT kill (§6). Fire-and-forget.
|
|
698
|
+
void backgroundShellManager.killSession(params.sessionId);
|
|
699
|
+
// Drop this session's retained background jobs too (#2/#5): finished jobs
|
|
700
|
+
// are kept for the panel, so explicit teardown is where they're released.
|
|
701
|
+
backgroundJobRegistry.dropForSession(params.sessionId);
|
|
291
702
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
292
703
|
}
|
|
293
704
|
// ─── Configure ──────────────────────────────────────────────────
|
|
@@ -306,15 +717,51 @@ export class AgentServer {
|
|
|
306
717
|
if (typeof params.permissionMode === "string") {
|
|
307
718
|
s.engine.setPermissionMode(params.permissionMode);
|
|
308
719
|
}
|
|
720
|
+
// Per-session model switch — the missing piece that made model changes
|
|
721
|
+
// worker-global (session-isolation research §3). requestModelSwitch
|
|
722
|
+
// applies immediately when idle, defers to the run boundary when busy
|
|
723
|
+
// so it never swaps the model under a running LLM client.
|
|
724
|
+
if (typeof params.model === "string") {
|
|
725
|
+
try {
|
|
726
|
+
s.requestModelSwitch(params.model);
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, err.message));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
// Hot-reload disk-default config onto this one session (layer 2).
|
|
734
|
+
if (params.reloadSettings === true) {
|
|
735
|
+
if (!this.settingsReader) {
|
|
736
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "reloadSettings is not supported by this AgentServer: no settingsReader is wired"));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const settings = this.settingsReader();
|
|
740
|
+
const version = ++this.configVersion;
|
|
741
|
+
s.engine.refreshRuntimeConfig(diskDefaultsFrom(settings, s.engine.getEffectiveDisabledLists().disabledPlugins), version);
|
|
742
|
+
}
|
|
309
743
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
310
744
|
return;
|
|
311
745
|
}
|
|
312
746
|
// Global configure — delegate to legacyEngine if available,
|
|
313
747
|
// or to any session's engine from chatManager for settings ops
|
|
314
748
|
const engine = this.legacyEngine ?? this.anyEngine();
|
|
315
|
-
if (params.reloadModels
|
|
749
|
+
if (params.reloadModels) {
|
|
316
750
|
try {
|
|
317
|
-
|
|
751
|
+
const seen = new Set();
|
|
752
|
+
const reload = (target) => {
|
|
753
|
+
if (!target || seen.has(target))
|
|
754
|
+
return;
|
|
755
|
+
seen.add(target);
|
|
756
|
+
target.reloadModelPool();
|
|
757
|
+
};
|
|
758
|
+
reload(this.legacyEngine);
|
|
759
|
+
reload(this.globalQueryEngine);
|
|
760
|
+
if (this.chatManager) {
|
|
761
|
+
this.chatManager.forEachSession((s) => reload(s.engine));
|
|
762
|
+
}
|
|
763
|
+
if (seen.size === 0)
|
|
764
|
+
reload(engine);
|
|
318
765
|
}
|
|
319
766
|
catch (err) {
|
|
320
767
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
@@ -335,6 +782,48 @@ export class AgentServer {
|
|
|
335
782
|
if (params.planMode !== undefined && engine) {
|
|
336
783
|
engine.setPlanMode(params.planMode);
|
|
337
784
|
}
|
|
785
|
+
// Config hot-reload layer 2: re-read disk settings once and push the
|
|
786
|
+
// disk-default config patch (+ settings-hook reload + incremental MCP
|
|
787
|
+
// connect) onto every live session. Parallel to reloadModels; no new
|
|
788
|
+
// protocol method. In-flight turns are untouched — refreshRuntimeConfig
|
|
789
|
+
// only mutates this.config, picked up at the next turn boundary.
|
|
790
|
+
if (params.reloadSettings === true) {
|
|
791
|
+
if (!this.settingsReader) {
|
|
792
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "reloadSettings is not supported by this AgentServer: no settingsReader is wired"));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (!this.chatManager) {
|
|
796
|
+
// Single-engine host (legacyEngine / anyEngine, no chatManager): there
|
|
797
|
+
// are no sessions to fan out to, but the one engine must still pick up
|
|
798
|
+
// the reload — otherwise we'd report ok:true while silently dropping it.
|
|
799
|
+
// Mirrors how reloadModels/model/planMode above act on `engine`.
|
|
800
|
+
if (engine) {
|
|
801
|
+
const settings = this.settingsReader();
|
|
802
|
+
engine.refreshRuntimeConfig(diskDefaultsFrom(settings, engine.getEffectiveDisabledLists().disabledPlugins), ++this.configVersion);
|
|
803
|
+
}
|
|
804
|
+
this.transport.send(createResponse(req.id, { ok: true }));
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const settings = this.settingsReader();
|
|
808
|
+
// The MCP merge folds each session's PROJECT capabilityOverrides (a
|
|
809
|
+
// project-level "on" must override the global disabledPlugins), so the
|
|
810
|
+
// patch is cwd-dependent — compute it per session, not once.
|
|
811
|
+
const perSession = [];
|
|
812
|
+
this.chatManager.forEachSession((s) => {
|
|
813
|
+
const patch = diskDefaultsFrom(settings, s.engine.getEffectiveDisabledLists().disabledPlugins);
|
|
814
|
+
perSession.push({ apply: (v) => s.engine.refreshRuntimeConfig(patch, v), patch });
|
|
815
|
+
});
|
|
816
|
+
// #6: content short-circuit — now keyed on ALL per-session patches, so a
|
|
817
|
+
// change visible to only one session (e.g. its project's overrides)
|
|
818
|
+
// still propagates while a true no-op skips the reloadHooks churn.
|
|
819
|
+
const patchJson = JSON.stringify(perSession.map((p) => p.patch));
|
|
820
|
+
if (patchJson !== this.lastBroadcastPatch) {
|
|
821
|
+
this.lastBroadcastPatch = patchJson;
|
|
822
|
+
const version = ++this.configVersion;
|
|
823
|
+
for (const p of perSession)
|
|
824
|
+
p.apply(version);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
338
827
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
339
828
|
}
|
|
340
829
|
// ─── Query ──────────────────────────────────────────────────────
|
|
@@ -361,12 +850,15 @@ export class AgentServer {
|
|
|
361
850
|
case "sessions": {
|
|
362
851
|
if (this.chatManager) {
|
|
363
852
|
// Return the ChatSessionManager's live sessions
|
|
364
|
-
const sessions = [
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
853
|
+
const sessions = [];
|
|
854
|
+
this.chatManager.forEachSession((s) => {
|
|
855
|
+
sessions.push({
|
|
856
|
+
sessionId: s.id,
|
|
857
|
+
busy: s.isBusy(),
|
|
858
|
+
queueDepth: s.queueDepth(),
|
|
859
|
+
lastActivityAt: s.lastActivityAt,
|
|
860
|
+
});
|
|
861
|
+
});
|
|
370
862
|
this.transport.send(createResponse(req.id, { type: "sessions", data: sessions }));
|
|
371
863
|
}
|
|
372
864
|
else if (engine) {
|
|
@@ -393,15 +885,14 @@ export class AgentServer {
|
|
|
393
885
|
model: config.llm.model,
|
|
394
886
|
cwd: config.cwd,
|
|
395
887
|
maxContextTokens: config.maxContextTokens,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
},
|
|
888
|
+
// apiKey is redacted at this boundary: clients only get
|
|
889
|
+
// hasApiKey + optional apiKeyPreview. See protocol/redact.ts.
|
|
890
|
+
llm: redactLlmConfig(config.llm),
|
|
891
|
+
// Resolved feature flags (defaults merged with settings overlay)
|
|
892
|
+
// so the /features command can list current state.
|
|
893
|
+
featureFlags: engine.getFeatureFlags(),
|
|
894
|
+
// Effective permission rules so /permissions can list them (TODO 5.1).
|
|
895
|
+
permissionRules: engine.getPermissionRules(),
|
|
405
896
|
},
|
|
406
897
|
}));
|
|
407
898
|
break;
|
|
@@ -499,7 +990,15 @@ export class AgentServer {
|
|
|
499
990
|
cachedAt: cache ? cache.fetchedAt : undefined,
|
|
500
991
|
};
|
|
501
992
|
});
|
|
502
|
-
|
|
993
|
+
// Strip apiKey / other secret-shaped fields from each provider
|
|
994
|
+
// before sending. `...p` above spreads the whole provider record,
|
|
995
|
+
// which includes apiKey verbatim — without this redact pass, any
|
|
996
|
+
// protocol client could call query("providers") and harvest
|
|
997
|
+
// credentials. The shared redactor preserves
|
|
998
|
+
// presence-without-value (hasApiKey-style consumers still see the
|
|
999
|
+
// field shape, just with [redacted] in place of the secret).
|
|
1000
|
+
const safe = redactSecrets(enriched);
|
|
1001
|
+
this.transport.send(createResponse(req.id, { type: "providers", data: safe }));
|
|
503
1002
|
}
|
|
504
1003
|
catch (err) {
|
|
505
1004
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
@@ -520,8 +1019,21 @@ export class AgentServer {
|
|
|
520
1019
|
const { key, value } = params;
|
|
521
1020
|
if (!key)
|
|
522
1021
|
throw new Error("key is required for config_set");
|
|
1022
|
+
// Refuse writes to trust-root fields via the generic config path. A
|
|
1023
|
+
// protocol peer (external driver, paired phone, compromised renderer)
|
|
1024
|
+
// could otherwise self-authorize tools or inject env/hooks/MCP servers,
|
|
1025
|
+
// which is equivalent to remotely bypassing workspace trust. Legit
|
|
1026
|
+
// writes to these go through the dedicated settings UI, not config_set;
|
|
1027
|
+
// provider/model edits use provider_add / models handlers below.
|
|
1028
|
+
if (isProtectedSettingKey(key)) {
|
|
1029
|
+
throw new Error(`config_set refused: "${key}" is a protected trust/permission field and cannot be written through this path.`);
|
|
1030
|
+
}
|
|
523
1031
|
engine.updateConfig(key, value);
|
|
524
|
-
|
|
1032
|
+
// Echo back through the same secret-aware masker as config_get so
|
|
1033
|
+
// a `config_set("llm.apiKey", "...")` confirmation doesn't ship
|
|
1034
|
+
// the new secret through the response/log path.
|
|
1035
|
+
const safeValue = maskSecretValue(key, value);
|
|
1036
|
+
this.transport.send(createResponse(req.id, { type: "config_set", data: { key, value: safeValue } }));
|
|
525
1037
|
}
|
|
526
1038
|
catch (err) {
|
|
527
1039
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
@@ -538,7 +1050,9 @@ export class AgentServer {
|
|
|
538
1050
|
if (!key)
|
|
539
1051
|
throw new Error("key is required for config_get");
|
|
540
1052
|
const value = engine.readSetting(key);
|
|
541
|
-
|
|
1053
|
+
// Mask secret-looking keys (apiKey/token/secret/…) at the boundary.
|
|
1054
|
+
const safeValue = maskSecretValue(key, value);
|
|
1055
|
+
this.transport.send(createResponse(req.id, { type: "config_get", data: { key, value: safeValue } }));
|
|
542
1056
|
}
|
|
543
1057
|
catch (err) {
|
|
544
1058
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
@@ -721,6 +1235,55 @@ export class AgentServer {
|
|
|
721
1235
|
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
722
1236
|
}
|
|
723
1237
|
}
|
|
1238
|
+
// ─── Steer ──────────────────────────────────────────────────────
|
|
1239
|
+
// Queue a user message into the in-flight run's turn loop (不打断). Unlike
|
|
1240
|
+
// Inject (appends an assistant context msg for the NEXT run), Steer feeds a
|
|
1241
|
+
// user message to the CURRENT run's next step. No abort, no LLM trigger by
|
|
1242
|
+
// itself — the running loop picks it up at its next step boundary.
|
|
1243
|
+
handleSteer(req) {
|
|
1244
|
+
const params = (req.params ?? {});
|
|
1245
|
+
if (!params.text || !params.sessionId) {
|
|
1246
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "text and sessionId required"));
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
const engine = this.chatManager
|
|
1250
|
+
? this.chatManager.get(params.sessionId)?.engine
|
|
1251
|
+
: this.legacyEngine;
|
|
1252
|
+
if (!engine) {
|
|
1253
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, `No such session: ${params.sessionId}`));
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
try {
|
|
1257
|
+
engine.enqueueSteer(params.sessionId, params.text, params.id);
|
|
1258
|
+
this.transport.send(createResponse(req.id, { ok: true }));
|
|
1259
|
+
}
|
|
1260
|
+
catch (err) {
|
|
1261
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
handleUnsteer(req) {
|
|
1265
|
+
const params = (req.params ?? {});
|
|
1266
|
+
if (!params.id || !params.sessionId) {
|
|
1267
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "id and sessionId required"));
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const engine = this.chatManager
|
|
1271
|
+
? this.chatManager.get(params.sessionId)?.engine
|
|
1272
|
+
: this.legacyEngine;
|
|
1273
|
+
if (!engine) {
|
|
1274
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, `No such session: ${params.sessionId}`));
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
try {
|
|
1278
|
+
// removed=false means the loop already consumed it — not an error, the
|
|
1279
|
+
// host just learns it couldn't be revoked (it will arrive as a bubble).
|
|
1280
|
+
const removed = engine.unsteer(params.sessionId, params.id);
|
|
1281
|
+
this.transport.send(createResponse(req.id, { ok: true, removed }));
|
|
1282
|
+
}
|
|
1283
|
+
catch (err) {
|
|
1284
|
+
this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
724
1287
|
// ─── Client Communication ───────────────────────────────────────
|
|
725
1288
|
/**
|
|
726
1289
|
* Ask the client to approve a tool operation (legacy single-engine path).
|
|
@@ -743,6 +1306,179 @@ export class AgentServer {
|
|
|
743
1306
|
/**
|
|
744
1307
|
* Ask the client to answer a question from the agent (legacy single-engine path).
|
|
745
1308
|
*/
|
|
1309
|
+
/**
|
|
1310
|
+
* Per-session AskUserQuestion for the chatManager path. Mirrors
|
|
1311
|
+
* requestAskUserFromClient but resolves via the SESSION's pendingApprovals
|
|
1312
|
+
* (the chatManager approve handler looks there, keyed by sessionId+requestId)
|
|
1313
|
+
* and tags the notify with sessionId so the renderer routes the question to
|
|
1314
|
+
* the right chat tab.
|
|
1315
|
+
*/
|
|
1316
|
+
requestAskUserForSession(session, sessionId, question, opts) {
|
|
1317
|
+
return new Promise((resolve) => {
|
|
1318
|
+
const requestId = nanoid(12);
|
|
1319
|
+
session.pendingApprovals.set(requestId, (decision) => {
|
|
1320
|
+
this.clearApprovalTimer(requestId);
|
|
1321
|
+
const result = decision;
|
|
1322
|
+
if (result && typeof result === "object" && "approved" in result) {
|
|
1323
|
+
resolve(result.approved ? (result.answer ?? "") : (result.reason ?? "(user declined to answer)"));
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
// chatManager approve handler resolves with the raw decision value.
|
|
1327
|
+
resolve(typeof decision === "string" ? decision : "");
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
const timer = setTimeout(() => {
|
|
1331
|
+
if (session.pendingApprovals.has(requestId)) {
|
|
1332
|
+
session.pendingApprovals.delete(requestId);
|
|
1333
|
+
this.approvalTimers.delete(requestId);
|
|
1334
|
+
resolve("(approval timed out)");
|
|
1335
|
+
}
|
|
1336
|
+
}, AgentServer.APPROVAL_TIMEOUT_MS);
|
|
1337
|
+
this.approvalTimers.set(requestId, timer);
|
|
1338
|
+
const args = { question };
|
|
1339
|
+
if (opts?.header !== undefined)
|
|
1340
|
+
args.header = opts.header;
|
|
1341
|
+
if (opts?.options !== undefined)
|
|
1342
|
+
args.options = opts.options;
|
|
1343
|
+
if (opts?.multiSelect !== undefined)
|
|
1344
|
+
args.multiSelect = opts.multiSelect;
|
|
1345
|
+
if (opts?.optionsOnly !== undefined)
|
|
1346
|
+
args.optionsOnly = opts.optionsOnly;
|
|
1347
|
+
this.notify(Methods.ApprovalRequest, {
|
|
1348
|
+
sessionId,
|
|
1349
|
+
requestId,
|
|
1350
|
+
request: {
|
|
1351
|
+
toolName: "__ask_user__",
|
|
1352
|
+
args,
|
|
1353
|
+
description: question,
|
|
1354
|
+
riskLevel: "low",
|
|
1355
|
+
},
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Build a BrowserBridge whose every method routes a browser action to the
|
|
1361
|
+
* client over the session's pendingApprovals channel (same as askUser). The
|
|
1362
|
+
* client (Electron main) drives the webview via CDP and replies with a JSON
|
|
1363
|
+
* result string we parse back into the typed result. On timeout / malformed
|
|
1364
|
+
* reply we degrade to a safe error result rather than throwing.
|
|
1365
|
+
*/
|
|
1366
|
+
makeBrowserBridge(session, sessionId) {
|
|
1367
|
+
const call = (action, payload) => this.requestBrowserActionForSession(session, sessionId, action, payload);
|
|
1368
|
+
return {
|
|
1369
|
+
snapshot: () => call("snapshot", {}),
|
|
1370
|
+
click: (ref) => call("click", { ref }),
|
|
1371
|
+
type: (ref, text) => call("type", { ref, text }),
|
|
1372
|
+
navigate: (url) => call("navigate", { url }),
|
|
1373
|
+
scroll: (dir, amount) => call("scroll", { dir, amount }),
|
|
1374
|
+
readContent: () => call("readContent", {}),
|
|
1375
|
+
extractLinks: () => call("extractLinks", {}),
|
|
1376
|
+
waitForLoad: (timeoutMs) => call("waitForLoad", { timeoutMs }),
|
|
1377
|
+
hover: (ref) => call("hover", { ref }),
|
|
1378
|
+
selectOption: (ref, value) => call("selectOption", { ref, value }),
|
|
1379
|
+
pressKey: (key, ref) => call("pressKey", { key, ref }),
|
|
1380
|
+
fetchImages: (refs) => call("fetchImages", { refs }),
|
|
1381
|
+
screenshot: (ref) => call("screenshot", { ref }),
|
|
1382
|
+
listTabs: () => call("listTabs", {}),
|
|
1383
|
+
switchTab: (tabId) => call("switchTab", { tabId }),
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
requestBrowserActionForSession(session, sessionId, action, payload) {
|
|
1387
|
+
return new Promise((resolve) => {
|
|
1388
|
+
const requestId = nanoid(12);
|
|
1389
|
+
session.pendingApprovals.set(requestId, (decision) => {
|
|
1390
|
+
this.clearApprovalTimer(requestId);
|
|
1391
|
+
// Main replies with { approved:true, answer:<json string> } (reusing the
|
|
1392
|
+
// ApprovalResult shape) or a raw json string. Parse → typed result.
|
|
1393
|
+
let raw;
|
|
1394
|
+
if (decision && typeof decision === "object" && "approved" in decision) {
|
|
1395
|
+
const r = decision;
|
|
1396
|
+
raw = r.approved ? r.answer : undefined;
|
|
1397
|
+
}
|
|
1398
|
+
else if (typeof decision === "string") {
|
|
1399
|
+
raw = decision;
|
|
1400
|
+
}
|
|
1401
|
+
if (raw === undefined) {
|
|
1402
|
+
resolve({ ok: false, detail: "browser action declined or unavailable" });
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
resolve(JSON.parse(raw));
|
|
1407
|
+
}
|
|
1408
|
+
catch {
|
|
1409
|
+
resolve({ ok: false, detail: "malformed browser action result" });
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
const timer = setTimeout(() => {
|
|
1413
|
+
if (session.pendingApprovals.has(requestId)) {
|
|
1414
|
+
session.pendingApprovals.delete(requestId);
|
|
1415
|
+
this.approvalTimers.delete(requestId);
|
|
1416
|
+
resolve({ ok: false, detail: "browser action timed out" });
|
|
1417
|
+
}
|
|
1418
|
+
}, AgentServer.APPROVAL_TIMEOUT_MS);
|
|
1419
|
+
this.approvalTimers.set(requestId, timer);
|
|
1420
|
+
this.notify(Methods.ApprovalRequest, {
|
|
1421
|
+
sessionId,
|
|
1422
|
+
requestId,
|
|
1423
|
+
request: {
|
|
1424
|
+
toolName: "__browser_action__",
|
|
1425
|
+
args: { action, ...payload },
|
|
1426
|
+
description: `browser:${action}`,
|
|
1427
|
+
riskLevel: "low",
|
|
1428
|
+
},
|
|
1429
|
+
});
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Ask the client (Electron main) to inject a cookie credential into the
|
|
1434
|
+
* built-in browser. Same pendingApprovals channel as browser actions; main
|
|
1435
|
+
* calls restoreCookiesToBrowser and replies with a JSON result string we
|
|
1436
|
+
* parse into { ok, count?, error? }. Degrades to ok:false on timeout/malformed.
|
|
1437
|
+
*/
|
|
1438
|
+
requestCredentialInjectForSession(session, sessionId, credentialId) {
|
|
1439
|
+
return new Promise((resolve) => {
|
|
1440
|
+
const requestId = nanoid(12);
|
|
1441
|
+
session.pendingApprovals.set(requestId, (decision) => {
|
|
1442
|
+
this.clearApprovalTimer(requestId);
|
|
1443
|
+
let raw;
|
|
1444
|
+
if (decision && typeof decision === "object" && "approved" in decision) {
|
|
1445
|
+
const r = decision;
|
|
1446
|
+
raw = r.approved ? r.answer : undefined;
|
|
1447
|
+
}
|
|
1448
|
+
else if (typeof decision === "string") {
|
|
1449
|
+
raw = decision;
|
|
1450
|
+
}
|
|
1451
|
+
if (raw === undefined) {
|
|
1452
|
+
resolve({ ok: false, error: "credential inject declined or unavailable" });
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
try {
|
|
1456
|
+
resolve(JSON.parse(raw));
|
|
1457
|
+
}
|
|
1458
|
+
catch {
|
|
1459
|
+
resolve({ ok: false, error: "malformed credential inject result" });
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
const timer = setTimeout(() => {
|
|
1463
|
+
if (session.pendingApprovals.has(requestId)) {
|
|
1464
|
+
session.pendingApprovals.delete(requestId);
|
|
1465
|
+
this.approvalTimers.delete(requestId);
|
|
1466
|
+
resolve({ ok: false, error: "credential inject timed out" });
|
|
1467
|
+
}
|
|
1468
|
+
}, AgentServer.APPROVAL_TIMEOUT_MS);
|
|
1469
|
+
this.approvalTimers.set(requestId, timer);
|
|
1470
|
+
this.notify(Methods.ApprovalRequest, {
|
|
1471
|
+
sessionId,
|
|
1472
|
+
requestId,
|
|
1473
|
+
request: {
|
|
1474
|
+
toolName: "__credential_action__",
|
|
1475
|
+
args: { action: "injectCookie", credentialId },
|
|
1476
|
+
description: `credential:inject:${credentialId}`,
|
|
1477
|
+
riskLevel: "low",
|
|
1478
|
+
},
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
746
1482
|
requestAskUserFromClient(question, opts) {
|
|
747
1483
|
return new Promise((resolve) => {
|
|
748
1484
|
const requestId = nanoid(12);
|
|
@@ -770,6 +1506,8 @@ export class AgentServer {
|
|
|
770
1506
|
args.options = opts.options;
|
|
771
1507
|
if (opts?.multiSelect !== undefined)
|
|
772
1508
|
args.multiSelect = opts.multiSelect;
|
|
1509
|
+
if (opts?.optionsOnly !== undefined)
|
|
1510
|
+
args.optionsOnly = opts.optionsOnly;
|
|
773
1511
|
this.notify(Methods.ApprovalRequest, {
|
|
774
1512
|
requestId,
|
|
775
1513
|
request: {
|
|
@@ -785,16 +1523,26 @@ export class AgentServer {
|
|
|
785
1523
|
this.transport.send(createNotification(method, params));
|
|
786
1524
|
}
|
|
787
1525
|
/**
|
|
788
|
-
* Get any available engine — used for global query ops when
|
|
789
|
-
* present but no legacyEngine was supplied.
|
|
1526
|
+
* Get any available engine — used for global query/configure ops when
|
|
1527
|
+
* chatManager is present but no legacyEngine was supplied. Prefer a live chat
|
|
1528
|
+
* session; before the first user turn, lazily build a detached engine through
|
|
1529
|
+
* the manager's factory so global UI surfaces (/login, model manager, model
|
|
1530
|
+
* selector) can query models/providers without requiring a chat session first.
|
|
790
1531
|
*/
|
|
791
1532
|
anyEngine() {
|
|
792
1533
|
if (!this.chatManager)
|
|
793
1534
|
return null;
|
|
794
|
-
// Access private `sessions` map — acceptable for same-package access.
|
|
795
1535
|
const sessions = this.chatManager.sessions;
|
|
796
1536
|
const first = sessions.values().next().value;
|
|
797
|
-
|
|
1537
|
+
if (first)
|
|
1538
|
+
return first.engine;
|
|
1539
|
+
if (this.globalQueryEngine)
|
|
1540
|
+
return this.globalQueryEngine;
|
|
1541
|
+
const factory = this.chatManager.factory;
|
|
1542
|
+
if (typeof factory !== "function")
|
|
1543
|
+
return null;
|
|
1544
|
+
this.globalQueryEngine = factory({});
|
|
1545
|
+
return this.globalQueryEngine;
|
|
798
1546
|
}
|
|
799
1547
|
// ─── Lifecycle ──────────────────────────────────────────────────
|
|
800
1548
|
close() {
|
|
@@ -838,6 +1586,26 @@ export class AgentServer {
|
|
|
838
1586
|
this.approvalTimers.delete(requestId);
|
|
839
1587
|
}
|
|
840
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Resolve all of a chat session's pending approvals as cancelled and clear
|
|
1591
|
+
* the matching server-side approval timers. Used by handleCancel's
|
|
1592
|
+
* per-session path so a Stop while a tool is awaiting approval doesn't leave
|
|
1593
|
+
* the tool hanging until APPROVAL_TIMEOUT_MS (and leak the timer). Each
|
|
1594
|
+
* requestId in the session map has a same-keyed entry in this.approvalTimers
|
|
1595
|
+
* (see requestAskUserForSession / makeBrowserBridge).
|
|
1596
|
+
*/
|
|
1597
|
+
cancelSessionApprovals(session) {
|
|
1598
|
+
for (const [requestId, resolve] of session.pendingApprovals) {
|
|
1599
|
+
this.clearApprovalTimer(requestId);
|
|
1600
|
+
try {
|
|
1601
|
+
resolve({ approved: false, reason: "cancelled" });
|
|
1602
|
+
}
|
|
1603
|
+
catch {
|
|
1604
|
+
/* a resolver must never break cancel cleanup */
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
session.pendingApprovals.clear();
|
|
1608
|
+
}
|
|
841
1609
|
clearAllApprovalTimers() {
|
|
842
1610
|
for (const timer of this.approvalTimers.values()) {
|
|
843
1611
|
clearTimeout(timer);
|