@sayknow-cli/coding-agent 0.3.15 → 0.4.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/CHANGELOG.md +52 -0
- package/package.json +133 -116
- package/scripts/build-sdk-package-smoke.ts +132 -0
- package/scripts/capture-notifications-settings-showcase.ts +435 -0
- package/scripts/compile-args.ts +5 -2
- package/scripts/g005-daemon-smoke.ts +4 -5
- package/scripts/g011-daemon-path-smoke.ts +5 -5
- package/scripts/generate-hotkeys-docs.ts +74 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
- package/scripts/generate-sdk-operation-inventory.ts +914 -0
- package/scripts/generate-telegram-baseline-manifest.ts +128 -0
- package/scripts/manifest-schema.ts +124 -0
- package/scripts/run-test-manifest.ts +153 -0
- package/scripts/run-threat-model-check.ts +217 -0
- package/scripts/verify-skc-sdk-canonicalization.ts +3647 -0
- package/scripts/verify-skc-sdk-rename.ts +90 -0
- package/src/append-only-mode.ts +1 -1
- package/src/async/job-manager.ts +455 -69
- package/src/cli/args.ts +43 -12
- package/src/cli/auth-gateway-cli.ts +2 -2
- package/src/cli/config-cli.ts +77 -3
- package/src/cli/daemon-cli.ts +63 -14
- package/src/cli/launch-disposition.ts +42 -0
- package/src/cli/list-models.ts +1 -1
- package/src/cli/notify-cli.ts +373 -283
- package/src/cli/session-picker.ts +20 -3
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +31 -11
- package/src/commands/config.ts +1 -1
- package/src/commands/daemon.ts +50 -6
- package/src/commands/gc.ts +11 -1
- package/src/commands/harness.ts +127 -142
- package/src/commands/launch.ts +11 -6
- package/src/commands/mcp-serve.ts +97 -13
- package/src/commands/mcp.ts +2 -2
- package/src/commands/notify.ts +44 -7
- package/src/commands/sdk.ts +453 -0
- package/src/commands/ultragoal.ts +4 -1
- package/src/commands/worktree.ts +10 -53
- package/src/commit/agentic/agent.ts +1 -0
- package/src/config/atomic-yaml-patch.ts +490 -0
- package/src/config/file-lock.ts +105 -43
- package/src/config/keybindings.ts +64 -20
- package/src/config/model-auth.ts +9 -0
- package/src/config/model-bindings-applier.ts +139 -0
- package/src/config/model-discovery-manager.ts +224 -0
- package/src/config/model-equivalence.ts +66 -0
- package/src/config/model-profile-activation.ts +233 -86
- package/src/config/model-profiles.ts +52 -49
- package/src/config/model-registry.ts +5 -4
- package/src/config/model-resolver.ts +487 -410
- package/src/config/model-selector-value.ts +50 -0
- package/src/config/models-config-schema.ts +17 -23
- package/src/config/settings-schema.ts +372 -21
- package/src/config/settings.ts +1120 -182
- package/src/coordinator-mcp/model-preset.ts +74 -20
- package/src/coordinator-mcp/policy.ts +76 -11
- package/src/coordinator-mcp/question-gate-codec.ts +274 -0
- package/src/coordinator-mcp/question-state.ts +778 -0
- package/src/coordinator-mcp/server.ts +3120 -3111
- package/src/daemon/builtin.ts +8 -5
- package/src/daemon/control-types.ts +1 -1
- package/src/daemon/runtime.ts +2 -2
- package/src/debug/index.ts +1 -1
- package/src/defaults/skc/skills/team/SKILL.md +27 -13
- package/src/defaults/skc/skills/ultragoal/SKILL.md +0 -2
- package/src/discovery/helpers.ts +1 -50
- package/src/discovery/mcp-json.ts +327 -20
- package/src/edit/index.ts +64 -0
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/env.ts +26 -0
- package/src/eval/py/index.ts +2 -2
- package/src/eval/py/kernel.ts +5 -3
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +52 -18
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/compact-handler.ts +3 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -2
- package/src/extensibility/extensions/loader.ts +77 -5
- package/src/extensibility/extensions/runner.ts +124 -7
- package/src/extensibility/extensions/types.ts +215 -18
- package/src/extensibility/hooks/runner.ts +2 -2
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/marketplace/manager.ts +94 -10
- package/src/extensibility/runtime-skill-discovery.ts +33 -13
- package/src/extensibility/shared-events.ts +2 -0
- package/src/extensibility/skc-plugins/installer.ts +16 -16
- package/src/extensibility/slash-commands.ts +3 -3
- package/src/goals/continuation-timeout-guard.ts +79 -0
- package/src/goals/runtime.ts +7 -5
- package/src/goals/state.ts +18 -0
- package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
- package/src/harness-control-plane/operate.ts +9 -9
- package/src/harness-control-plane/owner.ts +341 -41
- package/src/harness-control-plane/phase-rollup.ts +4 -7
- package/src/harness-control-plane/receipt-ingest.ts +5 -0
- package/src/harness-control-plane/receipt-spool.ts +51 -7
- package/src/harness-control-plane/receipts.ts +0 -1
- package/src/harness-control-plane/sdk-transport.ts +395 -0
- package/src/harness-control-plane/session-lease.ts +12 -130
- package/src/harness-control-plane/session-transport.ts +94 -0
- package/src/harness-control-plane/types.ts +1 -1
- package/src/hindsight/backend.ts +26 -27
- package/src/hindsight/client.ts +65 -2
- package/src/hindsight/state.ts +101 -32
- package/src/hooks/skill-state.ts +17 -11
- package/src/i18n/messages/en.ts +1 -0
- package/src/internal-urls/agent-protocol.ts +76 -5
- package/src/internal-urls/docs-index.generated.ts +44 -37
- package/src/internal-urls/local-protocol.ts +420 -18
- package/src/lsp/client.ts +43 -7
- package/src/lsp/config.ts +145 -26
- package/src/lsp/index.ts +7 -91
- package/src/lsp/lspmux.ts +39 -10
- package/src/lsp/path-trust.ts +81 -0
- package/src/main.ts +451 -236
- package/src/memories/index.ts +1 -0
- package/src/memories/storage.ts +6 -2
- package/src/modes/DESIGN.md +258 -0
- package/src/modes/acp/acp-agent.ts +1236 -2612
- package/src/modes/acp/acp-client-bridge.ts +2 -20
- package/src/modes/acp/acp-event-mapper.ts +18 -2
- package/src/modes/acp/acp-mode.ts +8 -36
- package/src/modes/acp/permission-mode.ts +25 -0
- package/src/modes/acp/startup-options.ts +6 -0
- package/src/modes/action-registry.ts +165 -0
- package/src/modes/bridge/bridge-mode.ts +6 -4
- package/src/modes/components/agent-dashboard.ts +37 -17
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/chrome.ts +40 -0
- package/src/modes/components/command-palette.ts +168 -0
- package/src/modes/components/custom-editor.ts +105 -33
- package/src/modes/components/custom-provider-wizard.ts +77 -15
- package/src/modes/components/index.ts +2 -1
- package/src/modes/components/model-selector.ts +93 -35
- package/src/modes/components/notifications-settings-editor.ts +1403 -0
- package/src/modes/components/pet-capability.ts +115 -0
- package/src/modes/components/pet-selector.ts +15 -23
- package/src/modes/components/plan-preview-overlay.ts +295 -0
- package/src/modes/components/queue-mode-selector.ts +10 -27
- package/src/modes/components/queue-pane.ts +79 -0
- package/src/modes/components/read-tool-group.ts +13 -0
- package/src/modes/components/sayknow-pet-widget.ts +169 -20
- package/src/modes/components/session-observer-overlay.ts +213 -775
- package/src/modes/components/sessions-dashboard.ts +181 -0
- package/src/modes/components/settings-selector.ts +89 -12
- package/src/modes/components/show-images-selector.ts +10 -24
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +1 -969
- package/src/modes/components/tasks-pane.ts +73 -0
- package/src/modes/components/theme-selector.ts +11 -31
- package/src/modes/components/tool-execution.ts +22 -0
- package/src/modes/components/tool-status-header.ts +1056 -0
- package/src/modes/components/tool-transcript-format.ts +393 -0
- package/src/modes/components/transcript-viewer-overlay.ts +446 -0
- package/src/modes/components/welcome.ts +4 -4
- package/src/modes/controllers/btw-controller.ts +1 -0
- package/src/modes/controllers/command-controller.ts +25 -17
- package/src/modes/controllers/event-controller.ts +14 -18
- package/src/modes/controllers/extension-ui-controller.ts +441 -37
- package/src/modes/controllers/goal-mode-controller.ts +493 -0
- package/src/modes/controllers/input-controller.ts +889 -263
- package/src/modes/controllers/mode-gate.ts +24 -0
- package/src/modes/controllers/plan-mode-controller.ts +519 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +957 -54
- package/src/modes/controllers/stt-controller.ts +125 -0
- package/src/modes/index.ts +0 -22
- package/src/modes/interactive-mode.ts +376 -1130
- package/src/modes/jobs-observer.ts +3 -0
- package/src/modes/prompt-action-autocomplete.ts +7 -0
- package/src/modes/rpc/rpc-mode.ts +3 -1
- package/src/modes/runtime-init.ts +320 -6
- package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
- package/src/modes/shared/agent-wire/command-dispatch.ts +3 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +65 -111
- package/src/modes/shared/agent-wire/event-contract.ts +7 -8
- package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
- package/src/modes/shared/agent-wire/event-observation.ts +16 -7
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1331 -89
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +19 -17
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +407 -0
- package/src/modes/tasks-aggregator.ts +199 -0
- package/src/modes/transcript-item-registry.ts +229 -0
- package/src/modes/types.ts +63 -21
- package/src/modes/utils/ui-helpers.ts +17 -9
- package/src/notifications/index.ts +8 -2
- package/src/plan-mode/approved-plan.ts +3 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -0
- package/src/prompts/agents/architect.md +3 -8
- package/src/prompts/agents/critic.md +3 -8
- package/src/prompts/agents/executor.md +3 -0
- package/src/prompts/agents/planner.md +4 -12
- package/src/prompts/system/custom-system-prompt.md +2 -24
- package/src/prompts/system/plan-mode-approved.md +8 -0
- package/src/prompts/system/system-prompt.md +18 -94
- package/src/prompts/tools/bash.md +4 -4
- package/src/prompts/tools/browser.md +15 -52
- package/src/prompts/tools/find.md +2 -2
- package/src/prompts/tools/hashline.md +13 -55
- package/src/prompts/tools/lsp.md +3 -3
- package/src/prompts/tools/read.md +3 -6
- package/src/prompts/tools/search-tool-bm25.md +0 -2
- package/src/prompts/tools/search.md +2 -4
- package/src/prompts/tools/skill-discovery.md +1 -2
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-mcp/client.ts +94 -19
- package/src/runtime-mcp/config.ts +27 -15
- package/src/runtime-mcp/discoverable-tool-metadata.ts +16 -13
- package/src/runtime-mcp/manager.ts +359 -106
- package/src/runtime-mcp/oauth-discovery.ts +116 -26
- package/src/runtime-mcp/oauth-flow.ts +42 -12
- package/src/runtime-mcp/transports/http.ts +49 -19
- package/src/runtime-mcp/transports/stdio.ts +47 -28
- package/src/runtime-mcp/types.ts +10 -0
- package/src/sdk/acp/adapter.ts +418 -0
- package/src/sdk/acp/index.ts +2 -0
- package/src/sdk/broker/broker.ts +877 -0
- package/src/sdk/broker/discovery.ts +238 -0
- package/src/sdk/broker/ensure.ts +393 -0
- package/src/sdk/broker/identity.ts +42 -0
- package/src/sdk/broker/index.ts +8 -0
- package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
- package/src/sdk/broker/internal-source.bunfig.toml +1 -0
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/lifecycle-codec.ts +11 -0
- package/src/sdk/broker/lifecycle-ledger.ts +583 -0
- package/src/sdk/broker/lifecycle.ts +3413 -0
- package/src/sdk/broker/process-incarnation.ts +151 -0
- package/src/sdk/broker/runtime.ts +164 -0
- package/src/sdk/broker/session-index.ts +678 -0
- package/src/sdk/broker/state-version.ts +47 -0
- package/src/sdk/broker/transport.ts +125 -0
- package/src/sdk/bus/attachment-registry.ts +23 -0
- package/src/sdk/bus/chat-adapters.ts +147 -0
- package/src/sdk/bus/chat-command-policy.ts +107 -0
- package/src/sdk/bus/chat-daemon-cli.ts +278 -0
- package/src/sdk/bus/chat-daemon-control.ts +1101 -0
- package/src/sdk/bus/chat-daemon-runtime.ts +527 -0
- package/src/sdk/bus/chat-effect-journal.ts +363 -0
- package/src/sdk/bus/config-commands.ts +192 -0
- package/src/sdk/bus/config.ts +568 -0
- package/src/sdk/bus/conversation-store.ts +367 -0
- package/src/sdk/bus/daemon-paths.ts +34 -0
- package/src/sdk/bus/discord-conversation.ts +85 -0
- package/src/sdk/bus/discord-daemon.ts +2292 -0
- package/src/sdk/bus/discord-live-provider.ts +521 -0
- package/src/sdk/bus/discord-provider.ts +56 -0
- package/src/sdk/bus/engine.ts +100 -0
- package/src/sdk/bus/helpers.ts +135 -0
- package/src/sdk/bus/html-format.ts +518 -0
- package/src/sdk/bus/index.ts +4961 -0
- package/src/sdk/bus/lifecycle-commands.ts +291 -0
- package/src/sdk/bus/lifecycle-control-runtime.ts +1409 -0
- package/src/sdk/bus/lifecycle-orchestrator.ts +458 -0
- package/src/sdk/bus/managed-daemon.ts +163 -0
- package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
- package/src/sdk/bus/natives-augment.d.ts +63 -0
- package/src/sdk/bus/notification-orchestration.ts +535 -0
- package/src/sdk/bus/notification-service.ts +1174 -0
- package/src/sdk/bus/operator-runtime.ts +171 -0
- package/src/sdk/bus/rate-limit-pool.ts +276 -0
- package/src/sdk/bus/recent-activity.ts +279 -0
- package/src/sdk/bus/reply-sent-store.ts +134 -0
- package/src/sdk/bus/rich-document.ts +258 -0
- package/src/sdk/bus/rich-draft.ts +107 -0
- package/src/sdk/bus/rich-render.ts +135 -0
- package/src/sdk/bus/session-control.ts +439 -0
- package/src/sdk/bus/slack-conversation.ts +74 -0
- package/src/sdk/bus/slack-daemon.ts +2144 -0
- package/src/sdk/bus/slack-live-provider.ts +380 -0
- package/src/sdk/bus/slack-provider.ts +76 -0
- package/src/sdk/bus/telegram-cli.ts +194 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +292 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +27 -0
- package/src/sdk/bus/telegram-daemon-control.ts +642 -0
- package/src/sdk/bus/telegram-daemon.ts +6675 -0
- package/src/sdk/bus/telegram-reference.ts +436 -0
- package/src/sdk/bus/telegram-setup.ts +663 -0
- package/src/sdk/bus/threaded-inbound.ts +141 -0
- package/src/sdk/bus/threaded-render.ts +294 -0
- package/src/sdk/bus/topic-registry.ts +364 -0
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/session-cli.ts +275 -0
- package/src/sdk/client/client.ts +1 -0
- package/src/sdk/client/discovery.ts +108 -0
- package/src/sdk/client/index.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +388 -0
- package/src/sdk/host/control/index.ts +10 -0
- package/src/sdk/host/control/operations.ts +68 -0
- package/src/sdk/host/events.ts +91 -0
- package/src/sdk/host/host.ts +338 -0
- package/src/sdk/host/index.ts +4 -0
- package/src/sdk/host/query/cursor.ts +189 -0
- package/src/sdk/host/query/handlers.ts +531 -0
- package/src/sdk/host/query/index.ts +3 -0
- package/src/sdk/host/query/revision-store.ts +1149 -0
- package/src/sdk/host/reverse-leases.ts +325 -0
- package/src/sdk/host/types.ts +29 -0
- package/src/sdk/index.ts +14 -0
- package/src/sdk/lifecycle-session.ts +39 -0
- package/src/sdk/mcp/index.ts +1 -0
- package/src/sdk/mcp/server.ts +332 -0
- package/src/sdk/models.ts +168 -0
- package/src/sdk/protocol/adapter-validation.ts +48 -0
- package/src/sdk/protocol/operation-inventory.generated.json +4719 -0
- package/src/sdk/protocol/operation-registry.ts +312 -0
- package/src/sdk/session-directory.ts +141 -0
- package/src/sdk/session.ts +2869 -0
- package/src/sdk/startup-capability.ts +189 -0
- package/src/secrets/index.ts +53 -10
- package/src/secrets/obfuscator.ts +21 -18
- package/src/secrets/regex.ts +2 -0
- package/src/session/agent-session.ts +5602 -2138
- package/src/session/agent-storage.ts +18 -0
- package/src/session/artifacts.ts +188 -23
- package/src/session/cache-economics.ts +25 -8
- package/src/session/context-estimation.ts +7 -2
- package/src/session/default-model-selection.ts +93 -0
- package/src/session/fallback-chain-controller.ts +186 -0
- package/src/session/internal/managed-session-scope.ts +2402 -0
- package/src/session/internal/managed-session-storage.ts +1559 -0
- package/src/session/messages.ts +91 -2
- package/src/session/session-manager-internal.ts +33 -0
- package/src/session/session-manager.ts +2578 -392
- package/src/session/session-storage.ts +550 -40
- package/src/session/streaming-output.ts +6 -0
- package/src/session/volatile-context-pruning.ts +78 -0
- package/src/session/yield-queue.ts +20 -0
- package/src/setup/credential-auto-import.ts +291 -35
- package/src/setup/hermes/templates/operator-instructions.v1.md +4 -6
- package/src/setup/provider-onboarding.ts +36 -61
- package/src/setup/provider-presets.json +5 -0
- package/src/skc-runtime/deep-interview-recorder.ts +70 -3
- package/src/skc-runtime/deep-interview-state.ts +441 -5
- package/src/skc-runtime/gc-render.ts +19 -1
- package/src/skc-runtime/gc-runtime.ts +68 -3
- package/src/skc-runtime/goal-mode-request.ts +55 -8
- package/src/skc-runtime/launch-worktree.ts +5 -5
- package/src/skc-runtime/linux-proc.ts +71 -0
- package/src/skc-runtime/managed-owner-admission.ts +352 -0
- package/src/skc-runtime/managed-owner-supervisor.ts +162 -0
- package/src/skc-runtime/psmux-detect.ts +82 -4
- package/src/skc-runtime/ralplan-runtime.ts +74 -90
- package/src/skc-runtime/session-resolution.ts +18 -3
- package/src/skc-runtime/session-state-sidecar.ts +47 -138
- package/src/skc-runtime/state-argv.ts +292 -0
- package/src/skc-runtime/state-migrations.ts +1 -1
- package/src/skc-runtime/state-runtime.ts +387 -451
- package/src/skc-runtime/state-writer.ts +36 -40
- package/src/skc-runtime/team-launch.ts +319 -0
- package/src/skc-runtime/team-notify.ts +326 -0
- package/src/skc-runtime/team-runtime.ts +8 -2
- package/src/skc-runtime/team-store.ts +828 -0
- package/src/skc-runtime/team-workers.ts +962 -0
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +55 -28
- package/src/skc-runtime/tmux-owner-isolation.ts +136 -11
- package/src/skc-runtime/tmux-sessions.ts +23 -34
- package/src/skc-runtime/ultragoal-change-set.ts +172 -0
- package/src/skc-runtime/ultragoal-evidence.ts +874 -0
- package/src/skc-runtime/ultragoal-guard.ts +136 -18
- package/src/skc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
- package/src/skc-runtime/ultragoal-pipeline.ts +306 -0
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +102 -1
- package/src/skc-runtime/ultragoal-runtime.ts +325 -1322
- package/src/skc-runtime/windows-powershell-command.ts +58 -0
- package/src/skc-runtime/workflow-cli-common.ts +30 -0
- package/src/skc-runtime/workflow-manifest.generated.json +58 -0
- package/src/skc-runtime/workflow-manifest.ts +18 -0
- package/src/skill-state/active-state.ts +3 -13
- package/src/skill-state/workflow-mutation-guard.ts +661 -0
- package/src/skill-state/workflow-state-contract.ts +6 -41
- package/src/slash-commands/builtin-registry.ts +115 -41
- package/src/system-prompt.ts +37 -44
- package/src/task/agents.ts +19 -1
- package/src/task/executor.ts +223 -45
- package/src/task/index.ts +124 -56
- package/src/thinking.ts +21 -6
- package/src/tool-discovery/tool-index.ts +20 -134
- package/src/tools/ask-answer-registry.ts +16 -2
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +40 -2
- package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
- package/src/tools/browser/benchmark/detector-report.ts +157 -0
- package/src/tools/browser/benchmark/run-suite.ts +39 -0
- package/src/tools/browser/dead-tab-recovery.ts +78 -0
- package/src/tools/browser/launch.ts +127 -20
- package/src/tools/browser/profile-discovery.ts +63 -0
- package/src/tools/browser/profile-posture.ts +59 -0
- package/src/tools/browser/profile-reuse.ts +102 -0
- package/src/tools/browser/profile-warmup.ts +109 -0
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/registry.ts +87 -11
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +392 -142
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +8 -6
- package/src/tools/browser.ts +6 -0
- package/src/tools/computer.ts +10 -3
- package/src/tools/cron.ts +30 -16
- package/src/tools/fetch.ts +11 -4
- package/src/tools/index.ts +88 -32
- package/src/tools/output-meta.ts +19 -7
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
- package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
- package/src/tools/read.ts +226 -65
- package/src/tools/resource-gc.ts +125 -36
- package/src/tools/search-tool-bm25.ts +11 -74
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent-render.ts +19 -8
- package/src/tools/subagent.ts +129 -34
- package/src/tools/telegram-send.ts +2 -2
- package/src/utils/event-bus.ts +9 -4
- package/src/utils/file-mentions.ts +4 -4
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/index.ts +3 -3
- package/src/web/search/providers/openai-compatible.ts +76 -23
- package/dist/types/append-only-mode.d.ts +0 -31
- package/dist/types/async/index.d.ts +0 -2
- package/dist/types/async/job-manager.d.ts +0 -328
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/autoresearch/dashboard.d.ts +0 -4
- package/dist/types/autoresearch/git.d.ts +0 -36
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/autoresearch/types.d.ts +0 -154
- package/dist/types/build-metadata.d.ts +0 -7
- package/dist/types/capability/context-file.d.ts +0 -17
- package/dist/types/capability/extension-module.d.ts +0 -15
- package/dist/types/capability/extension.d.ts +0 -28
- package/dist/types/capability/fs.d.ts +0 -20
- package/dist/types/capability/hook.d.ts +0 -19
- package/dist/types/capability/index.d.ts +0 -80
- package/dist/types/capability/instruction.d.ts +0 -17
- package/dist/types/capability/mcp.d.ts +0 -49
- package/dist/types/capability/prompt.d.ts +0 -15
- package/dist/types/capability/rule.d.ts +0 -69
- package/dist/types/capability/settings.d.ts +0 -15
- package/dist/types/capability/skill.d.ts +0 -36
- package/dist/types/capability/slash-command.d.ts +0 -17
- package/dist/types/capability/ssh.d.ts +0 -23
- package/dist/types/capability/system-prompt.d.ts +0 -15
- package/dist/types/capability/tool.d.ts +0 -19
- package/dist/types/capability/types.d.ts +0 -154
- package/dist/types/cli/agents-cli.d.ts +0 -12
- package/dist/types/cli/args.d.ts +0 -57
- package/dist/types/cli/auth-broker-cli.d.ts +0 -25
- package/dist/types/cli/auth-gateway-cli.d.ts +0 -18
- package/dist/types/cli/classify-install-target.d.ts +0 -8
- package/dist/types/cli/commands/init-xdg.d.ts +0 -1
- package/dist/types/cli/completion-cli.d.ts +0 -41
- package/dist/types/cli/config-cli.d.ts +0 -23
- package/dist/types/cli/daemon-cli.d.ts +0 -27
- package/dist/types/cli/fast-help.d.ts +0 -1
- package/dist/types/cli/file-processor.d.ts +0 -11
- package/dist/types/cli/fixture-report.d.ts +0 -20
- package/dist/types/cli/fixture-report.test.d.ts +0 -1
- package/dist/types/cli/grep-cli.d.ts +0 -17
- package/dist/types/cli/initial-message.d.ts +0 -17
- package/dist/types/cli/list-models.d.ts +0 -36
- package/dist/types/cli/local-provider-smoke.d.ts +0 -59
- package/dist/types/cli/malloc-env-guard.d.ts +0 -65
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/cli/mcp-cli.d.ts +0 -25
- package/dist/types/cli/migrate-cli.d.ts +0 -20
- package/dist/types/cli/nofile-limit.d.ts +0 -11
- package/dist/types/cli/notify-cli.d.ts +0 -39
- package/dist/types/cli/plugin-cli.d.ts +0 -34
- package/dist/types/cli/read-cli.d.ts +0 -4
- package/dist/types/cli/session-picker.d.ts +0 -4
- package/dist/types/cli/setup-cli.d.ts +0 -73
- package/dist/types/cli/shell-cli.d.ts +0 -8
- package/dist/types/cli/skills-cli.d.ts +0 -9
- package/dist/types/cli/ssh-cli.d.ts +0 -21
- package/dist/types/cli/stats-cli.d.ts +0 -17
- package/dist/types/cli/telegram-cli.d.ts +0 -13
- package/dist/types/cli/update-cli.d.ts +0 -108
- package/dist/types/cli/web-search-cli.d.ts +0 -32
- package/dist/types/cli/worktree-cli.d.ts +0 -26
- package/dist/types/cli.d.ts +0 -140
- package/dist/types/commands/acp.d.ts +0 -12
- package/dist/types/commands/agents.d.ts +0 -34
- package/dist/types/commands/auth-broker.d.ts +0 -54
- package/dist/types/commands/auth-gateway.d.ts +0 -32
- package/dist/types/commands/codex-native-hook.d.ts +0 -6
- package/dist/types/commands/commit.d.ts +0 -27
- package/dist/types/commands/completion.d.ts +0 -29
- package/dist/types/commands/config.d.ts +0 -33
- package/dist/types/commands/contribution-prep.d.ts +0 -18
- package/dist/types/commands/coordinator.d.ts +0 -19
- package/dist/types/commands/daemon.d.ts +0 -45
- package/dist/types/commands/deep-interview.d.ts +0 -54
- package/dist/types/commands/gc.d.ts +0 -26
- package/dist/types/commands/grep.d.ts +0 -42
- package/dist/types/commands/harness.d.ts +0 -43
- package/dist/types/commands/launch.d.ts +0 -131
- package/dist/types/commands/local-provider.d.ts +0 -34
- package/dist/types/commands/mcp-serve.d.ts +0 -24
- package/dist/types/commands/mcp.d.ts +0 -70
- package/dist/types/commands/migrate.d.ts +0 -33
- package/dist/types/commands/notify.d.ts +0 -47
- package/dist/types/commands/plugin.d.ts +0 -58
- package/dist/types/commands/ralplan.d.ts +0 -7
- package/dist/types/commands/read.d.ts +0 -15
- package/dist/types/commands/rlm.d.ts +0 -10
- package/dist/types/commands/session.d.ts +0 -30
- package/dist/types/commands/setup.d.ts +0 -105
- package/dist/types/commands/shell.d.ts +0 -21
- package/dist/types/commands/skills.d.ts +0 -26
- package/dist/types/commands/ssh.d.ts +0 -48
- package/dist/types/commands/state.d.ts +0 -7
- package/dist/types/commands/stats.d.ts +0 -25
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/commands/telegram.d.ts +0 -22
- package/dist/types/commands/ultragoal.d.ts +0 -8
- package/dist/types/commands/update.d.ts +0 -20
- package/dist/types/commands/web-search.d.ts +0 -87
- package/dist/types/commands/worktree.d.ts +0 -34
- package/dist/types/commit/agentic/agent.d.ts +0 -31
- package/dist/types/commit/agentic/fallback.d.ts +0 -5
- package/dist/types/commit/agentic/index.d.ts +0 -2
- package/dist/types/commit/agentic/state.d.ts +0 -58
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +0 -19
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +0 -10
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +0 -9
- package/dist/types/commit/agentic/tools/git-overview.d.ts +0 -9
- package/dist/types/commit/agentic/tools/index.d.ts +0 -16
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +0 -28
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +0 -36
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +0 -7
- package/dist/types/commit/agentic/tools/schemas.d.ts +0 -27
- package/dist/types/commit/agentic/tools/split-commit.d.ts +0 -53
- package/dist/types/commit/agentic/topo-sort.d.ts +0 -4
- package/dist/types/commit/agentic/trivial.d.ts +0 -7
- package/dist/types/commit/agentic/validation.d.ts +0 -20
- package/dist/types/commit/analysis/conventional.d.ts +0 -22
- package/dist/types/commit/analysis/index.d.ts +0 -4
- package/dist/types/commit/analysis/scope.d.ts +0 -6
- package/dist/types/commit/analysis/summary.d.ts +0 -19
- package/dist/types/commit/analysis/validation.d.ts +0 -8
- package/dist/types/commit/changelog/detect.d.ts +0 -2
- package/dist/types/commit/changelog/generate.d.ts +0 -30
- package/dist/types/commit/changelog/index.d.ts +0 -30
- package/dist/types/commit/changelog/parse.d.ts +0 -2
- package/dist/types/commit/cli.d.ts +0 -3
- package/dist/types/commit/git/diff.d.ts +0 -5
- package/dist/types/commit/index.d.ts +0 -4
- package/dist/types/commit/map-reduce/index.d.ts +0 -28
- package/dist/types/commit/map-reduce/map-phase.d.ts +0 -17
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +0 -13
- package/dist/types/commit/map-reduce/utils.d.ts +0 -2
- package/dist/types/commit/message.d.ts +0 -2
- package/dist/types/commit/model-selection.d.ts +0 -15
- package/dist/types/commit/pipeline.d.ts +0 -5
- package/dist/types/commit/shared-llm.d.ts +0 -54
- package/dist/types/commit/types.d.ts +0 -78
- package/dist/types/commit/utils/exclusions.d.ts +0 -4
- package/dist/types/commit/utils.d.ts +0 -20
- package/dist/types/config/config-file.d.ts +0 -58
- package/dist/types/config/file-lock-gc.d.ts +0 -5
- package/dist/types/config/file-lock.d.ts +0 -36
- package/dist/types/config/keybindings.d.ts +0 -364
- package/dist/types/config/model-equivalence.d.ts +0 -24
- package/dist/types/config/model-profile-activation.d.ts +0 -65
- package/dist/types/config/model-profiles.d.ts +0 -39
- package/dist/types/config/model-registry.d.ts +0 -473
- package/dist/types/config/model-resolver.d.ts +0 -217
- package/dist/types/config/models-config-schema.d.ts +0 -700
- package/dist/types/config/prompt-templates.d.ts +0 -32
- package/dist/types/config/resolve-config-value.d.ts +0 -17
- package/dist/types/config/settings-schema.d.ts +0 -4223
- package/dist/types/config/settings.d.ts +0 -158
- package/dist/types/config/skill-settings-defaults.d.ts +0 -14
- package/dist/types/config/telegram-autostart.d.ts +0 -14
- package/dist/types/config/telegram-env-bridge.d.ts +0 -18
- package/dist/types/config.d.ts +0 -66
- package/dist/types/coordinator/contract.d.ts +0 -4
- package/dist/types/coordinator-mcp/model-preset.d.ts +0 -45
- package/dist/types/coordinator-mcp/policy.d.ts +0 -27
- package/dist/types/coordinator-mcp/safety.d.ts +0 -26
- package/dist/types/coordinator-mcp/server.d.ts +0 -139
- package/dist/types/coordinator-mcp/session-reaper.d.ts +0 -53
- package/dist/types/cursor.d.ts +0 -24
- package/dist/types/daemon/builtin.d.ts +0 -20
- package/dist/types/daemon/control-types.d.ts +0 -67
- package/dist/types/daemon/operator-contract.d.ts +0 -47
- package/dist/types/daemon/runtime.d.ts +0 -25
- package/dist/types/dap/client.d.ts +0 -39
- package/dist/types/dap/config.d.ts +0 -6
- package/dist/types/dap/index.d.ts +0 -4
- package/dist/types/dap/session.d.ts +0 -108
- package/dist/types/dap/types.d.ts +0 -524
- package/dist/types/debug/crash-diagnostics.d.ts +0 -45
- package/dist/types/debug/index.d.ts +0 -15
- package/dist/types/debug/log-formatting.d.ts +0 -4
- package/dist/types/debug/log-viewer.d.ts +0 -68
- package/dist/types/debug/profiler.d.ts +0 -24
- package/dist/types/debug/raw-sse-buffer.d.ts +0 -44
- package/dist/types/debug/raw-sse.d.ts +0 -16
- package/dist/types/debug/report-bundle.d.ts +0 -55
- package/dist/types/debug/runtime-gauges.d.ts +0 -6
- package/dist/types/debug/system-info.d.ts +0 -26
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +0 -11
- package/dist/types/deep-interview/render-middleware.d.ts +0 -6
- package/dist/types/defaults/skc/extensions/grok-build/index.d.ts +0 -1
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/index.d.ts +0 -1
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +0 -29
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +0 -27
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/billing.d.ts +0 -8
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/register.d.ts +0 -5
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/stream.d.ts +0 -10
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/usage.d.ts +0 -2
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +0 -2
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +0 -38
- package/dist/types/defaults/skc-defaults.d.ts +0 -64
- package/dist/types/defaults/skc-grok-cli.d.ts +0 -5
- package/dist/types/discovery/agents-md.d.ts +0 -1
- package/dist/types/discovery/agents.d.ts +0 -12
- package/dist/types/discovery/builtin.d.ts +0 -1
- package/dist/types/discovery/claude-plugins.d.ts +0 -1
- package/dist/types/discovery/claude.d.ts +0 -1
- package/dist/types/discovery/cline.d.ts +0 -1
- package/dist/types/discovery/codex.d.ts +0 -1
- package/dist/types/discovery/cursor.d.ts +0 -19
- package/dist/types/discovery/gemini.d.ts +0 -1
- package/dist/types/discovery/github.d.ts +0 -1
- package/dist/types/discovery/helpers.d.ts +0 -271
- package/dist/types/discovery/index.d.ts +0 -48
- package/dist/types/discovery/mcp-json.d.ts +0 -1
- package/dist/types/discovery/opencode.d.ts +0 -1
- package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
- package/dist/types/discovery/ssh.d.ts +0 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -1
- package/dist/types/discovery/vscode.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +0 -15
- package/dist/types/edit/apply-patch/index.d.ts +0 -35
- package/dist/types/edit/apply-patch/parser.d.ts +0 -34
- package/dist/types/edit/diff.d.ts +0 -75
- package/dist/types/edit/file-read-cache.d.ts +0 -25
- package/dist/types/edit/index.d.ts +0 -58
- package/dist/types/edit/modes/apply-patch.d.ts +0 -24
- package/dist/types/edit/modes/patch.d.ts +0 -99
- package/dist/types/edit/modes/replace.d.ts +0 -149
- package/dist/types/edit/normalize.d.ts +0 -43
- package/dist/types/edit/notebook.d.ts +0 -26
- package/dist/types/edit/read-file.d.ts +0 -8
- package/dist/types/edit/renderer.d.ts +0 -110
- package/dist/types/edit/streaming.d.ts +0 -66
- package/dist/types/eval/backend.d.ts +0 -40
- package/dist/types/eval/index.d.ts +0 -4
- package/dist/types/eval/js/context-manager.d.ts +0 -27
- package/dist/types/eval/js/executor.d.ts +0 -29
- package/dist/types/eval/js/index.d.ts +0 -10
- package/dist/types/eval/js/shared/helpers.d.ts +0 -38
- package/dist/types/eval/js/shared/indirect-eval.d.ts +0 -14
- package/dist/types/eval/js/shared/prelude.d.ts +0 -1
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +0 -6
- package/dist/types/eval/js/shared/runtime.d.ts +0 -47
- package/dist/types/eval/js/shared/types.d.ts +0 -24
- package/dist/types/eval/js/tool-bridge.d.ts +0 -18
- package/dist/types/eval/js/worker-core.d.ts +0 -5
- package/dist/types/eval/js/worker-entry.d.ts +0 -1
- package/dist/types/eval/js/worker-protocol.d.ts +0 -77
- package/dist/types/eval/py/display.d.ts +0 -25
- package/dist/types/eval/py/executor.d.ts +0 -88
- package/dist/types/eval/py/index.d.ts +0 -10
- package/dist/types/eval/py/kernel.d.ts +0 -65
- package/dist/types/eval/py/prelude.d.ts +0 -1
- package/dist/types/eval/py/runtime.d.ts +0 -29
- package/dist/types/eval/py/tool-bridge.d.ts +0 -20
- package/dist/types/eval/types.d.ts +0 -52
- package/dist/types/exa/factory.d.ts +0 -13
- package/dist/types/exa/index.d.ts +0 -20
- package/dist/types/exa/mcp-client.d.ts +0 -45
- package/dist/types/exa/render.d.ts +0 -19
- package/dist/types/exa/researcher.d.ts +0 -9
- package/dist/types/exa/search.d.ts +0 -9
- package/dist/types/exa/types.d.ts +0 -155
- package/dist/types/exa/websets.d.ts +0 -9
- package/dist/types/exec/bash-executor.d.ts +0 -64
- package/dist/types/exec/exec.d.ts +0 -25
- package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
- package/dist/types/exec/non-interactive-env.d.ts +0 -1
- package/dist/types/export/custom-share.d.ts +0 -20
- package/dist/types/export/html/index.d.ts +0 -10
- package/dist/types/export/html/template.generated.d.ts +0 -1
- package/dist/types/export/html/template.macro.d.ts +0 -5
- package/dist/types/export/ttsr.d.ts +0 -55
- package/dist/types/extensibility/custom-commands/bundled/ci-green/index.d.ts +0 -9
- package/dist/types/extensibility/custom-commands/index.d.ts +0 -2
- package/dist/types/extensibility/custom-commands/loader.d.ts +0 -29
- package/dist/types/extensibility/custom-commands/types.d.ts +0 -106
- package/dist/types/extensibility/custom-tools/index.d.ts +0 -6
- package/dist/types/extensibility/custom-tools/loader.d.ts +0 -69
- package/dist/types/extensibility/custom-tools/types.d.ts +0 -229
- package/dist/types/extensibility/custom-tools/wrapper.d.ts +0 -24
- package/dist/types/extensibility/extensions/compact-handler.d.ts +0 -26
- package/dist/types/extensibility/extensions/get-commands-handler.d.ts +0 -29
- package/dist/types/extensibility/extensions/index.d.ts +0 -9
- package/dist/types/extensibility/extensions/loader.d.ts +0 -43
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -35
- package/dist/types/extensibility/extensions/runner.d.ts +0 -135
- package/dist/types/extensibility/extensions/types.d.ts +0 -920
- package/dist/types/extensibility/extensions/wrapper.d.ts +0 -55
- package/dist/types/extensibility/hooks/index.d.ts +0 -5
- package/dist/types/extensibility/hooks/loader.d.ts +0 -89
- package/dist/types/extensibility/hooks/runner.d.ts +0 -126
- package/dist/types/extensibility/hooks/tool-wrapper.d.ts +0 -25
- package/dist/types/extensibility/hooks/types.d.ts +0 -431
- package/dist/types/extensibility/plugins/doctor.d.ts +0 -3
- package/dist/types/extensibility/plugins/git-url.d.ts +0 -34
- package/dist/types/extensibility/plugins/index.d.ts +0 -8
- package/dist/types/extensibility/plugins/installer.d.ts +0 -5
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +0 -2
- package/dist/types/extensibility/plugins/loader.d.ts +0 -31
- package/dist/types/extensibility/plugins/manager.d.ts +0 -65
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +0 -41
- package/dist/types/extensibility/plugins/marketplace/fetcher.d.ts +0 -48
- package/dist/types/extensibility/plugins/marketplace/index.d.ts +0 -6
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +0 -57
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +0 -34
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +0 -28
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +0 -130
- package/dist/types/extensibility/plugins/parser.d.ts +0 -52
- package/dist/types/extensibility/plugins/security-scanner.d.ts +0 -31
- package/dist/types/extensibility/plugins/types.d.ts +0 -149
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +0 -20
- package/dist/types/extensibility/shared-events.d.ts +0 -281
- package/dist/types/extensibility/skc-plugins/activation.d.ts +0 -14
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +0 -19
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +0 -29
- package/dist/types/extensibility/skc-plugins/index.d.ts +0 -18
- package/dist/types/extensibility/skc-plugins/injection.d.ts +0 -40
- package/dist/types/extensibility/skc-plugins/installer.d.ts +0 -13
- package/dist/types/extensibility/skc-plugins/loader.d.ts +0 -3
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +0 -26
- package/dist/types/extensibility/skc-plugins/observability.d.ts +0 -27
- package/dist/types/extensibility/skc-plugins/paths.d.ts +0 -8
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +0 -16
- package/dist/types/extensibility/skc-plugins/registry.d.ts +0 -32
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +0 -64
- package/dist/types/extensibility/skc-plugins/schema.d.ts +0 -3
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +0 -42
- package/dist/types/extensibility/skc-plugins/state.d.ts +0 -9
- package/dist/types/extensibility/skc-plugins/tools.d.ts +0 -8
- package/dist/types/extensibility/skc-plugins/types.d.ts +0 -220
- package/dist/types/extensibility/skc-plugins/validation.d.ts +0 -11
- package/dist/types/extensibility/skills.d.ts +0 -82
- package/dist/types/extensibility/slash-commands.d.ts +0 -48
- package/dist/types/extensibility/tool-proxy.d.ts +0 -4
- package/dist/types/extensibility/typebox.d.ts +0 -155
- package/dist/types/extensibility/utils.d.ts +0 -12
- package/dist/types/goals/index.d.ts +0 -3
- package/dist/types/goals/runtime.d.ts +0 -63
- package/dist/types/goals/state.d.ts +0 -32
- package/dist/types/goals/tools/goal-tool.d.ts +0 -62
- package/dist/types/harness-control-plane/classifier.d.ts +0 -13
- package/dist/types/harness-control-plane/control-endpoint.d.ts +0 -31
- package/dist/types/harness-control-plane/finalize.d.ts +0 -60
- package/dist/types/harness-control-plane/gc-adapter.d.ts +0 -3
- package/dist/types/harness-control-plane/operate.d.ts +0 -35
- package/dist/types/harness-control-plane/owner.d.ts +0 -53
- package/dist/types/harness-control-plane/phase-rollup.d.ts +0 -23
- package/dist/types/harness-control-plane/preserve.d.ts +0 -19
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +0 -19
- package/dist/types/harness-control-plane/receipt-spool.d.ts +0 -19
- package/dist/types/harness-control-plane/receipts.d.ts +0 -149
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
- package/dist/types/harness-control-plane/seams.d.ts +0 -21
- package/dist/types/harness-control-plane/session-lease.d.ts +0 -65
- package/dist/types/harness-control-plane/state-machine.d.ts +0 -24
- package/dist/types/harness-control-plane/storage.d.ts +0 -81
- package/dist/types/harness-control-plane/types.d.ts +0 -187
- package/dist/types/hashline/anchors.d.ts +0 -20
- package/dist/types/hashline/apply.d.ts +0 -14
- package/dist/types/hashline/constants.d.ts +0 -19
- package/dist/types/hashline/diff-preview.d.ts +0 -2
- package/dist/types/hashline/diff.d.ts +0 -17
- package/dist/types/hashline/execute.d.ts +0 -4
- package/dist/types/hashline/hash.d.ts +0 -123
- package/dist/types/hashline/index.d.ts +0 -13
- package/dist/types/hashline/input.d.ts +0 -11
- package/dist/types/hashline/parser.d.ts +0 -7
- package/dist/types/hashline/prefixes.d.ts +0 -7
- package/dist/types/hashline/recovery.d.ts +0 -27
- package/dist/types/hashline/stream.d.ts +0 -2
- package/dist/types/hashline/types.d.ts +0 -77
- package/dist/types/hindsight/backend.d.ts +0 -13
- package/dist/types/hindsight/bank.d.ts +0 -54
- package/dist/types/hindsight/client.d.ts +0 -224
- package/dist/types/hindsight/config.d.ts +0 -51
- package/dist/types/hindsight/content.d.ts +0 -70
- package/dist/types/hindsight/index.d.ts +0 -8
- package/dist/types/hindsight/mental-models.d.ts +0 -125
- package/dist/types/hindsight/state.d.ts +0 -99
- package/dist/types/hindsight/transcript.d.ts +0 -28
- package/dist/types/hooks/codex-native-hooks-config.d.ts +0 -29
- package/dist/types/hooks/native-skill-hook.d.ts +0 -28
- package/dist/types/hooks/skill-keywords.d.ts +0 -18
- package/dist/types/hooks/skill-state.d.ts +0 -93
- package/dist/types/i18n/index.d.ts +0 -38
- package/dist/types/i18n/messages/de.d.ts +0 -2
- package/dist/types/i18n/messages/de.messages.d.ts +0 -1
- package/dist/types/i18n/messages/de.settings.d.ts +0 -1
- package/dist/types/i18n/messages/en.d.ts +0 -81
- package/dist/types/i18n/messages/es.d.ts +0 -2
- package/dist/types/i18n/messages/es.messages.d.ts +0 -1
- package/dist/types/i18n/messages/es.settings.d.ts +0 -1
- package/dist/types/i18n/messages/fr.d.ts +0 -2
- package/dist/types/i18n/messages/fr.messages.d.ts +0 -1
- package/dist/types/i18n/messages/fr.settings.d.ts +0 -1
- package/dist/types/i18n/messages/ja.d.ts +0 -2
- package/dist/types/i18n/messages/ja.messages.d.ts +0 -1
- package/dist/types/i18n/messages/ja.settings.d.ts +0 -1
- package/dist/types/i18n/messages/ko.d.ts +0 -2
- package/dist/types/i18n/messages/ko.messages.d.ts +0 -1
- package/dist/types/i18n/messages/ko.settings.d.ts +0 -1
- package/dist/types/i18n/messages/zh.d.ts +0 -2
- package/dist/types/i18n/messages/zh.messages.d.ts +0 -1
- package/dist/types/i18n/messages/zh.settings.d.ts +0 -1
- package/dist/types/index.d.ts +0 -33
- package/dist/types/internal-urls/agent-protocol.d.ts +0 -12
- package/dist/types/internal-urls/artifact-protocol.d.ts +0 -6
- package/dist/types/internal-urls/docs-index.generated.d.ts +0 -2
- package/dist/types/internal-urls/index.d.ts +0 -20
- package/dist/types/internal-urls/issue-pr-protocol.d.ts +0 -17
- package/dist/types/internal-urls/json-query.d.ts +0 -30
- package/dist/types/internal-urls/local-protocol.d.ts +0 -43
- package/dist/types/internal-urls/mcp-protocol.d.ts +0 -12
- package/dist/types/internal-urls/memory-protocol.d.ts +0 -23
- package/dist/types/internal-urls/parse.d.ts +0 -19
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -11
- package/dist/types/internal-urls/router.d.ts +0 -14
- package/dist/types/internal-urls/rule-protocol.d.ts +0 -6
- package/dist/types/internal-urls/skc-protocol.d.ts +0 -12
- package/dist/types/internal-urls/skill-protocol.d.ts +0 -13
- package/dist/types/internal-urls/types.d.ts +0 -109
- package/dist/types/lsp/client.d.ts +0 -66
- package/dist/types/lsp/clients/biome-client.d.ts +0 -16
- package/dist/types/lsp/clients/index.d.ts +0 -19
- package/dist/types/lsp/clients/lsp-linter-client.d.ts +0 -16
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -20
- package/dist/types/lsp/config.d.ts +0 -65
- package/dist/types/lsp/edits.d.ts +0 -23
- package/dist/types/lsp/index.d.ts +0 -130
- package/dist/types/lsp/lspmux.d.ts +0 -58
- package/dist/types/lsp/render.d.ts +0 -32
- package/dist/types/lsp/startup-events.d.ts +0 -12
- package/dist/types/lsp/types.d.ts +0 -306
- package/dist/types/lsp/utils.d.ts +0 -108
- package/dist/types/main.d.ts +0 -139
- package/dist/types/memories/index.d.ts +0 -28
- package/dist/types/memories/storage.d.ts +0 -110
- package/dist/types/memory-backend/index.d.ts +0 -4
- package/dist/types/memory-backend/local-backend.d.ts +0 -9
- package/dist/types/memory-backend/off-backend.d.ts +0 -7
- package/dist/types/memory-backend/resolve.d.ts +0 -15
- package/dist/types/memory-backend/types.d.ts +0 -61
- package/dist/types/migrate/action-planner.d.ts +0 -11
- package/dist/types/migrate/adapters/claude-code.d.ts +0 -2
- package/dist/types/migrate/adapters/codex.d.ts +0 -5
- package/dist/types/migrate/adapters/index.d.ts +0 -45
- package/dist/types/migrate/adapters/opencode.d.ts +0 -2
- package/dist/types/migrate/executor.d.ts +0 -2
- package/dist/types/migrate/mcp-mapper.d.ts +0 -20
- package/dist/types/migrate/report.d.ts +0 -18
- package/dist/types/migrate/skill-normalizer.d.ts +0 -27
- package/dist/types/migrate/types.d.ts +0 -126
- package/dist/types/modes/acp/acp-agent.d.ts +0 -78
- package/dist/types/modes/acp/acp-client-bridge.d.ts +0 -9
- package/dist/types/modes/acp/acp-event-mapper.d.ts +0 -36
- package/dist/types/modes/acp/acp-mode.d.ts +0 -17
- package/dist/types/modes/acp/index.d.ts +0 -2
- package/dist/types/modes/acp/terminal-auth.d.ts +0 -6
- package/dist/types/modes/bridge/auth.d.ts +0 -12
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
- package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
- package/dist/types/modes/bridge/event-stream.d.ts +0 -8
- package/dist/types/modes/components/agent-dashboard.d.ts +0 -21
- package/dist/types/modes/components/assistant-message.d.ts +0 -20
- package/dist/types/modes/components/bash-execution.d.ts +0 -29
- package/dist/types/modes/components/bordered-loader.d.ts +0 -11
- package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
- package/dist/types/modes/components/btw-panel.d.ts +0 -16
- package/dist/types/modes/components/compaction-summary-message.d.ts +0 -13
- package/dist/types/modes/components/countdown-timer.d.ts +0 -14
- package/dist/types/modes/components/custom-editor.d.ts +0 -69
- package/dist/types/modes/components/custom-message.d.ts +0 -15
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +0 -12
- package/dist/types/modes/components/custom-provider-wizard.d.ts +0 -10
- package/dist/types/modes/components/diff.d.ts +0 -11
- package/dist/types/modes/components/dynamic-border.d.ts +0 -14
- package/dist/types/modes/components/eval-execution.d.ts +0 -23
- package/dist/types/modes/components/execution-shared.d.ts +0 -46
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +0 -26
- package/dist/types/modes/components/extensions/extension-list.d.ts +0 -39
- package/dist/types/modes/components/extensions/index.d.ts +0 -8
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +0 -8
- package/dist/types/modes/components/extensions/state-manager.d.ts +0 -50
- package/dist/types/modes/components/extensions/types.d.ts +0 -151
- package/dist/types/modes/components/footer.d.ts +0 -30
- package/dist/types/modes/components/history-search.d.ts +0 -7
- package/dist/types/modes/components/hook-editor.d.ts +0 -18
- package/dist/types/modes/components/hook-input.d.ts +0 -15
- package/dist/types/modes/components/hook-message.d.ts +0 -15
- package/dist/types/modes/components/hook-selector.d.ts +0 -59
- package/dist/types/modes/components/index.d.ts +0 -35
- package/dist/types/modes/components/irc-sidebar.d.ts +0 -32
- package/dist/types/modes/components/jobs-overlay-model.d.ts +0 -31
- package/dist/types/modes/components/jobs-overlay.d.ts +0 -30
- package/dist/types/modes/components/keybinding-hints.d.ts +0 -40
- package/dist/types/modes/components/login-dialog.d.ts +0 -32
- package/dist/types/modes/components/message-frame.d.ts +0 -42
- package/dist/types/modes/components/model-selector.d.ts +0 -60
- package/dist/types/modes/components/oauth-selector.d.ts +0 -17
- package/dist/types/modes/components/pet-selector.d.ts +0 -11
- package/dist/types/modes/components/plugin-selector.d.ts +0 -26
- package/dist/types/modes/components/plugin-settings.d.ts +0 -66
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +0 -7
- package/dist/types/modes/components/queue-mode-selector.d.ts +0 -9
- package/dist/types/modes/components/queued-message-selector.d.ts +0 -12
- package/dist/types/modes/components/read-tool-group.d.ts +0 -30
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +0 -26
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +0 -59
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/modes/components/session-selector.d.ts +0 -43
- package/dist/types/modes/components/settings-defs.d.ts +0 -50
- package/dist/types/modes/components/settings-selector.d.ts +0 -64
- package/dist/types/modes/components/show-images-selector.d.ts +0 -9
- package/dist/types/modes/components/skill-hud/render.d.ts +0 -2
- package/dist/types/modes/components/skill-message.d.ts +0 -10
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +0 -4
- package/dist/types/modes/components/status-line/git-utils.d.ts +0 -28
- package/dist/types/modes/components/status-line/index.d.ts +0 -4
- package/dist/types/modes/components/status-line/presets.d.ts +0 -3
- package/dist/types/modes/components/status-line/segments.d.ts +0 -5
- package/dist/types/modes/components/status-line/separators.d.ts +0 -3
- package/dist/types/modes/components/status-line/token-rate.d.ts +0 -10
- package/dist/types/modes/components/status-line/types.d.ts +0 -83
- package/dist/types/modes/components/status-line.d.ts +0 -94
- package/dist/types/modes/components/theme-selector.d.ts +0 -10
- package/dist/types/modes/components/thinking-selector.d.ts +0 -15
- package/dist/types/modes/components/todo-reminder.d.ts +0 -13
- package/dist/types/modes/components/tool-execution.d.ts +0 -55
- package/dist/types/modes/components/tree-selector.d.ts +0 -31
- package/dist/types/modes/components/ttsr-notification.d.ts +0 -13
- package/dist/types/modes/components/two-column-body.d.ts +0 -9
- package/dist/types/modes/components/user-message-selector.d.ts +0 -28
- package/dist/types/modes/components/user-message.d.ts +0 -8
- package/dist/types/modes/components/visual-truncate.d.ts +0 -19
- package/dist/types/modes/components/welcome.d.ts +0 -46
- package/dist/types/modes/controllers/btw-controller.d.ts +0 -10
- package/dist/types/modes/controllers/command-controller-shared.d.ts +0 -47
- package/dist/types/modes/controllers/command-controller.d.ts +0 -40
- package/dist/types/modes/controllers/event-controller.d.ts +0 -21
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -80
- package/dist/types/modes/controllers/input-controller.d.ts +0 -42
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +0 -10
- package/dist/types/modes/controllers/selector-controller.d.ts +0 -68
- package/dist/types/modes/controllers/ssh-command-controller.d.ts +0 -10
- package/dist/types/modes/controllers/todo-command-controller.d.ts +0 -7
- package/dist/types/modes/emoji-autocomplete.d.ts +0 -16
- package/dist/types/modes/index.d.ts +0 -10
- package/dist/types/modes/interactive-mode.d.ts +0 -297
- package/dist/types/modes/irc-observation-ledger.d.ts +0 -22
- package/dist/types/modes/jobs-observer.d.ts +0 -57
- package/dist/types/modes/oauth-manual-input.d.ts +0 -8
- package/dist/types/modes/print-mode.d.ts +0 -44
- package/dist/types/modes/prompt-action-autocomplete.d.ts +0 -50
- package/dist/types/modes/rpc/host-tools.d.ts +0 -1
- package/dist/types/modes/rpc/host-uris.d.ts +0 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
- package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
- package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
- package/dist/types/modes/runtime-init.d.ts +0 -21
- package/dist/types/modes/session-observer-registry.d.ts +0 -26
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +0 -57
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +0 -102
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +0 -103
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +0 -60
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +0 -37
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +0 -154
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +0 -39
- package/dist/types/modes/shared.d.ts +0 -15
- package/dist/types/modes/slash-command-visibility.d.ts +0 -1
- package/dist/types/modes/theme/defaults/index.d.ts +0 -652
- package/dist/types/modes/theme/mermaid-cache.d.ts +0 -9
- package/dist/types/modes/theme/shimmer.d.ts +0 -38
- package/dist/types/modes/theme/theme.d.ts +0 -275
- package/dist/types/modes/tmux-scroll.d.ts +0 -18
- package/dist/types/modes/types.d.ts +0 -318
- package/dist/types/modes/utils/abort-message.d.ts +0 -4
- package/dist/types/modes/utils/context-usage.d.ts +0 -36
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +0 -5
- package/dist/types/modes/utils/injected-user-submission.d.ts +0 -52
- package/dist/types/modes/utils/irc-message.d.ts +0 -38
- package/dist/types/modes/utils/keybinding-matchers.d.ts +0 -10
- package/dist/types/modes/utils/terminal-bell.d.ts +0 -3
- package/dist/types/modes/utils/tools-markdown.d.ts +0 -5
- package/dist/types/modes/utils/ui-helpers.d.ts +0 -75
- package/dist/types/notifications/attachment-registry.d.ts +0 -17
- package/dist/types/notifications/chat-adapters.d.ts +0 -9
- package/dist/types/notifications/config-commands.d.ts +0 -64
- package/dist/types/notifications/config.d.ts +0 -132
- package/dist/types/notifications/daemon-paths.d.ts +0 -17
- package/dist/types/notifications/engine.d.ts +0 -59
- package/dist/types/notifications/helpers.d.ts +0 -55
- package/dist/types/notifications/html-format.d.ts +0 -75
- package/dist/types/notifications/index.d.ts +0 -219
- package/dist/types/notifications/lifecycle-commands.d.ts +0 -69
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +0 -107
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +0 -144
- package/dist/types/notifications/managed-daemon.d.ts +0 -48
- package/dist/types/notifications/notification-service.d.ts +0 -144
- package/dist/types/notifications/operator-runtime.d.ts +0 -52
- package/dist/types/notifications/rate-limit-pool.d.ts +0 -113
- package/dist/types/notifications/recent-activity.d.ts +0 -39
- package/dist/types/notifications/reply-sent-store.d.ts +0 -53
- package/dist/types/notifications/rich-draft.d.ts +0 -68
- package/dist/types/notifications/rich-render.d.ts +0 -90
- package/dist/types/notifications/telegram-cli.d.ts +0 -19
- package/dist/types/notifications/telegram-daemon-cli.d.ts +0 -28
- package/dist/types/notifications/telegram-daemon-control.d.ts +0 -70
- package/dist/types/notifications/telegram-daemon.d.ts +0 -558
- package/dist/types/notifications/telegram-reference.d.ts +0 -136
- package/dist/types/notifications/threaded-inbound.d.ts +0 -77
- package/dist/types/notifications/threaded-render.d.ts +0 -88
- package/dist/types/notifications/topic-registry.d.ts +0 -89
- package/dist/types/plan-mode/approved-plan.d.ts +0 -49
- package/dist/types/plan-mode/state.d.ts +0 -6
- package/dist/types/registry/agent-registry.d.ts +0 -64
- package/dist/types/reminders/star-reminder.d.ts +0 -115
- package/dist/types/research-plan/index.d.ts +0 -1
- package/dist/types/research-plan/ledger.d.ts +0 -33
- package/dist/types/rlm/artifacts.d.ts +0 -9
- package/dist/types/rlm/complete-research-tool.d.ts +0 -35
- package/dist/types/rlm/data-context.d.ts +0 -6
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/notebook.d.ts +0 -12
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/dist/types/rlm/report.d.ts +0 -14
- package/dist/types/rlm/types.d.ts +0 -37
- package/dist/types/runtime/process-lifecycle.d.ts +0 -108
- package/dist/types/runtime-api-key.d.ts +0 -7
- package/dist/types/runtime-credential-selector.d.ts +0 -7
- package/dist/types/runtime-mcp/client.d.ts +0 -74
- package/dist/types/runtime-mcp/config-writer.d.ts +0 -89
- package/dist/types/runtime-mcp/config.d.ts +0 -77
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
- package/dist/types/runtime-mcp/index.d.ts +0 -18
- package/dist/types/runtime-mcp/json-rpc.d.ts +0 -22
- package/dist/types/runtime-mcp/loader.d.ts +0 -45
- package/dist/types/runtime-mcp/manager.d.ts +0 -195
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +0 -40
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -59
- package/dist/types/runtime-mcp/render.d.ts +0 -25
- package/dist/types/runtime-mcp/smithery-auth.d.ts +0 -16
- package/dist/types/runtime-mcp/smithery-connect.d.ts +0 -38
- package/dist/types/runtime-mcp/smithery-registry.d.ts +0 -51
- package/dist/types/runtime-mcp/tool-bridge.d.ts +0 -86
- package/dist/types/runtime-mcp/tool-cache.d.ts +0 -8
- package/dist/types/runtime-mcp/transports/http.d.ts +0 -36
- package/dist/types/runtime-mcp/transports/index.d.ts +0 -5
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -29
- package/dist/types/runtime-mcp/types.d.ts +0 -349
- package/dist/types/sdk.d.ts +0 -256
- package/dist/types/secrets/index.d.ts +0 -9
- package/dist/types/secrets/obfuscator.d.ts +0 -23
- package/dist/types/secrets/regex.d.ts +0 -2
- package/dist/types/session/agent-session.d.ts +0 -1238
- package/dist/types/session/agent-storage.d.ts +0 -101
- package/dist/types/session/artifacts.d.ts +0 -64
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/auth-storage.d.ts +0 -6
- package/dist/types/session/blob-store.d.ts +0 -160
- package/dist/types/session/cache-economics.d.ts +0 -79
- package/dist/types/session/client-bridge.d.ts +0 -88
- package/dist/types/session/context-estimation.d.ts +0 -31
- package/dist/types/session/contribution-prep.d.ts +0 -47
- package/dist/types/session/file-mention-pruning.d.ts +0 -8
- package/dist/types/session/history-storage.d.ts +0 -16
- package/dist/types/session/messages.d.ts +0 -169
- package/dist/types/session/provider-safety-stop.d.ts +0 -5
- package/dist/types/session/session-dump-format.d.ts +0 -22
- package/dist/types/session/session-manager.d.ts +0 -781
- package/dist/types/session/session-storage.d.ts +0 -230
- package/dist/types/session/streaming-output.d.ts +0 -209
- package/dist/types/session/tool-choice-queue.d.ts +0 -84
- package/dist/types/session/yield-queue.d.ts +0 -24
- package/dist/types/setup/credential-auto-import.d.ts +0 -63
- package/dist/types/setup/credential-import.d.ts +0 -82
- package/dist/types/setup/hermes-setup.d.ts +0 -78
- package/dist/types/setup/host-plugin-setup.d.ts +0 -39
- package/dist/types/setup/model-onboarding-guidance.d.ts +0 -9
- package/dist/types/setup/provider-onboarding.d.ts +0 -52
- package/dist/types/skc-runtime/cli-write-receipt.d.ts +0 -24
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +0 -39
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +0 -114
- package/dist/types/skc-runtime/deep-interview-runtime.d.ts +0 -36
- package/dist/types/skc-runtime/deep-interview-state.d.ts +0 -127
- package/dist/types/skc-runtime/gc-render.d.ts +0 -6
- package/dist/types/skc-runtime/gc-runtime.d.ts +0 -134
- package/dist/types/skc-runtime/goal-mode-request.d.ts +0 -53
- package/dist/types/skc-runtime/launch-tmux.d.ts +0 -78
- package/dist/types/skc-runtime/launch-worktree.d.ts +0 -46
- package/dist/types/skc-runtime/ledger-event-renderer.d.ts +0 -68
- package/dist/types/skc-runtime/psmux-detect.d.ts +0 -78
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +0 -26
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +0 -3
- package/dist/types/skc-runtime/session-layout.d.ts +0 -59
- package/dist/types/skc-runtime/session-resolution.d.ts +0 -47
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +0 -70
- package/dist/types/skc-runtime/state-graph.d.ts +0 -4
- package/dist/types/skc-runtime/state-migrations.d.ts +0 -33
- package/dist/types/skc-runtime/state-renderer.d.ts +0 -70
- package/dist/types/skc-runtime/state-runtime.d.ts +0 -38
- package/dist/types/skc-runtime/state-schema.d.ts +0 -319
- package/dist/types/skc-runtime/state-validation.d.ts +0 -6
- package/dist/types/skc-runtime/state-writer.d.ts +0 -244
- package/dist/types/skc-runtime/team-gc.d.ts +0 -7
- package/dist/types/skc-runtime/team-runtime.d.ts +0 -355
- package/dist/types/skc-runtime/tmux-common.d.ts +0 -111
- package/dist/types/skc-runtime/tmux-gc.d.ts +0 -7
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +0 -5
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +0 -309
- package/dist/types/skc-runtime/tmux-sessions.d.ts +0 -94
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +0 -93
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +0 -42
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -410
- package/dist/types/skc-runtime/workflow-command-ref.d.ts +0 -43
- package/dist/types/skc-runtime/workflow-manifest.d.ts +0 -54
- package/dist/types/skill-state/active-state.d.ts +0 -126
- package/dist/types/skill-state/canonical-skills.d.ts +0 -3
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +0 -38
- package/dist/types/skill-state/initial-phase.d.ts +0 -12
- package/dist/types/skill-state/workflow-hud.d.ts +0 -83
- package/dist/types/skill-state/workflow-state-contract.d.ts +0 -57
- package/dist/types/skill-state/workflow-state-version.d.ts +0 -3
- package/dist/types/slash-commands/acp-builtins.d.ts +0 -18
- package/dist/types/slash-commands/builtin-registry.d.ts +0 -24
- package/dist/types/slash-commands/helpers/context-report.d.ts +0 -7
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +0 -82
- package/dist/types/slash-commands/helpers/format.d.ts +0 -10
- package/dist/types/slash-commands/helpers/mcp.d.ts +0 -3
- package/dist/types/slash-commands/helpers/parse.d.ts +0 -33
- package/dist/types/slash-commands/helpers/ssh.d.ts +0 -3
- package/dist/types/slash-commands/helpers/todo.d.ts +0 -3
- package/dist/types/slash-commands/helpers/usage-report.d.ts +0 -7
- package/dist/types/slash-commands/types.d.ts +0 -120
- package/dist/types/ssh/config-writer.d.ts +0 -49
- package/dist/types/ssh/connection-manager.d.ts +0 -33
- package/dist/types/ssh/ssh-executor.d.ts +0 -37
- package/dist/types/ssh/sshfs-mount.d.ts +0 -6
- package/dist/types/ssh/utils.d.ts +0 -2
- package/dist/types/stt/downloader.d.ts +0 -9
- package/dist/types/stt/index.d.ts +0 -3
- package/dist/types/stt/recorder.d.ts +0 -13
- package/dist/types/stt/setup.d.ts +0 -18
- package/dist/types/stt/stt-controller.d.ts +0 -16
- package/dist/types/stt/transcriber.d.ts +0 -16
- package/dist/types/system-prompt.d.ts +0 -118
- package/dist/types/task/agents.d.ts +0 -28
- package/dist/types/task/commands.d.ts +0 -35
- package/dist/types/task/discovery.d.ts +0 -19
- package/dist/types/task/executor.d.ts +0 -127
- package/dist/types/task/fork-context-advisory.d.ts +0 -13
- package/dist/types/task/fork-context-budget.d.ts +0 -7
- package/dist/types/task/id.d.ts +0 -7
- package/dist/types/task/index.d.ts +0 -43
- package/dist/types/task/name-generator.d.ts +0 -16
- package/dist/types/task/output-manager.d.ts +0 -36
- package/dist/types/task/parallel.d.ts +0 -34
- package/dist/types/task/receipt.d.ts +0 -89
- package/dist/types/task/render.d.ts +0 -28
- package/dist/types/task/roi-reconciliation.d.ts +0 -27
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/dist/types/task/skc-command.d.ts +0 -7
- package/dist/types/task/spawn-gate.d.ts +0 -29
- package/dist/types/task/subprocess-tool-registry.d.ts +0 -72
- package/dist/types/task/token-log.d.ts +0 -30
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/task/types.d.ts +0 -516
- package/dist/types/task/worktree.d.ts +0 -94
- package/dist/types/thinking-metadata.d.ts +0 -16
- package/dist/types/thinking.d.ts +0 -22
- package/dist/types/tool-discovery/tool-index.d.ts +0 -112
- package/dist/types/tools/archive-reader.d.ts +0 -40
- package/dist/types/tools/ask-answer-registry.d.ts +0 -18
- package/dist/types/tools/ask.d.ts +0 -123
- package/dist/types/tools/ast-edit.d.ts +0 -77
- package/dist/types/tools/ast-grep.d.ts +0 -69
- package/dist/types/tools/auto-generated-guard.d.ts +0 -17
- package/dist/types/tools/bash-allowed-prefixes.d.ts +0 -10
- package/dist/types/tools/bash-command-fixup.d.ts +0 -11
- package/dist/types/tools/bash-interactive.d.ts +0 -16
- package/dist/types/tools/bash-interceptor.d.ts +0 -24
- package/dist/types/tools/bash-pty-selection.d.ts +0 -7
- package/dist/types/tools/bash-skill-urls.d.ts +0 -31
- package/dist/types/tools/bash.d.ts +0 -157
- package/dist/types/tools/bisect.d.ts +0 -81
- package/dist/types/tools/browser/actions.d.ts +0 -54
- package/dist/types/tools/browser/attach.d.ts +0 -50
- package/dist/types/tools/browser/launch.d.ts +0 -62
- package/dist/types/tools/browser/readable.d.ts +0 -17
- package/dist/types/tools/browser/registry.d.ts +0 -57
- package/dist/types/tools/browser/render.d.ts +0 -53
- package/dist/types/tools/browser/tab-protocol.d.ts +0 -144
- package/dist/types/tools/browser/tab-supervisor.d.ts +0 -109
- package/dist/types/tools/browser/tab-worker-entry.d.ts +0 -1
- package/dist/types/tools/browser/tab-worker.d.ts +0 -19
- package/dist/types/tools/browser.d.ts +0 -211
- package/dist/types/tools/calculator.d.ts +0 -76
- package/dist/types/tools/checkpoint.d.ts +0 -63
- package/dist/types/tools/composer-bash-policy.d.ts +0 -14
- package/dist/types/tools/computer/render.d.ts +0 -17
- package/dist/types/tools/computer-gc.d.ts +0 -23
- package/dist/types/tools/computer.d.ts +0 -465
- package/dist/types/tools/conflict-detect.d.ts +0 -205
- package/dist/types/tools/context.d.ts +0 -19
- package/dist/types/tools/cron.d.ts +0 -91
- package/dist/types/tools/debug.d.ts +0 -209
- package/dist/types/tools/eval.d.ts +0 -108
- package/dist/types/tools/fetch.d.ts +0 -84
- package/dist/types/tools/file-recorder.d.ts +0 -13
- package/dist/types/tools/find.d.ts +0 -95
- package/dist/types/tools/fs-cache-invalidation.d.ts +0 -15
- package/dist/types/tools/gh-format.d.ts +0 -6
- package/dist/types/tools/gh-renderer.d.ts +0 -25
- package/dist/types/tools/gh.d.ts +0 -356
- package/dist/types/tools/github-cache.d.ts +0 -103
- package/dist/types/tools/grouped-file-output.d.ts +0 -36
- package/dist/types/tools/hindsight-recall.d.ts +0 -21
- package/dist/types/tools/hindsight-reflect.d.ts +0 -23
- package/dist/types/tools/hindsight-retain.d.ts +0 -27
- package/dist/types/tools/image-gen.d.ts +0 -79
- package/dist/types/tools/index.d.ts +0 -371
- package/dist/types/tools/irc.d.ts +0 -79
- package/dist/types/tools/job.d.ts +0 -78
- package/dist/types/tools/json-tree.d.ts +0 -23
- package/dist/types/tools/jtd-to-json-schema.d.ts +0 -29
- package/dist/types/tools/jtd-to-typescript.d.ts +0 -26
- package/dist/types/tools/jtd-utils.d.ts +0 -42
- package/dist/types/tools/list-limit.d.ts +0 -12
- package/dist/types/tools/match-line-format.d.ts +0 -11
- package/dist/types/tools/monitor.d.ts +0 -54
- package/dist/types/tools/output-meta.d.ts +0 -204
- package/dist/types/tools/path-utils.d.ts +0 -147
- package/dist/types/tools/plan-mode-guard.d.ts +0 -18
- package/dist/types/tools/read.d.ts +0 -85
- package/dist/types/tools/recipe/index.d.ts +0 -45
- package/dist/types/tools/recipe/render.d.ts +0 -36
- package/dist/types/tools/recipe/runner.d.ts +0 -60
- package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
- package/dist/types/tools/recipe/runners/index.d.ts +0 -2
- package/dist/types/tools/recipe/runners/just.d.ts +0 -2
- package/dist/types/tools/recipe/runners/make.d.ts +0 -2
- package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
- package/dist/types/tools/recipe/runners/task.d.ts +0 -2
- package/dist/types/tools/render-mermaid.d.ts +0 -37
- package/dist/types/tools/render-utils.d.ts +0 -158
- package/dist/types/tools/renderers.d.ts +0 -26
- package/dist/types/tools/resolve.d.ts +0 -80
- package/dist/types/tools/resource-gc.d.ts +0 -54
- package/dist/types/tools/review.d.ts +0 -63
- package/dist/types/tools/search-tool-bm25.d.ts +0 -65
- package/dist/types/tools/search.d.ts +0 -93
- package/dist/types/tools/skill-discovery.d.ts +0 -40
- package/dist/types/tools/skill.d.ts +0 -48
- package/dist/types/tools/sqlite-reader.d.ts +0 -94
- package/dist/types/tools/ssh.d.ts +0 -67
- package/dist/types/tools/subagent-render.d.ts +0 -31
- package/dist/types/tools/subagent.d.ts +0 -117
- package/dist/types/tools/telegram-send.d.ts +0 -39
- package/dist/types/tools/todo-write.d.ts +0 -120
- package/dist/types/tools/tool-errors.d.ts +0 -33
- package/dist/types/tools/tool-result.d.ts +0 -30
- package/dist/types/tools/tool-timeouts.d.ts +0 -56
- package/dist/types/tools/ultragoal-ask-guard.d.ts +0 -12
- package/dist/types/tools/vim.d.ts +0 -58
- package/dist/types/tools/write.d.ts +0 -59
- package/dist/types/tools/yield.d.ts +0 -25
- package/dist/types/tui/code-cell.d.ts +0 -32
- package/dist/types/tui/file-list.d.ts +0 -22
- package/dist/types/tui/hyperlink.d.ts +0 -42
- package/dist/types/tui/index.d.ts +0 -11
- package/dist/types/tui/output-block.d.ts +0 -28
- package/dist/types/tui/status-line.d.ts +0 -18
- package/dist/types/tui/tree-list.d.ts +0 -19
- package/dist/types/tui/types.d.ts +0 -13
- package/dist/types/tui/utils.d.ts +0 -36
- package/dist/types/utils/changelog.d.ts +0 -37
- package/dist/types/utils/clipboard.d.ts +0 -22
- package/dist/types/utils/cmux-workspace.d.ts +0 -52
- package/dist/types/utils/command-args.d.ts +0 -9
- package/dist/types/utils/commit-message-generator.d.ts +0 -7
- package/dist/types/utils/edit-mode.d.ts +0 -13
- package/dist/types/utils/event-bus.d.ts +0 -6
- package/dist/types/utils/external-editor.d.ts +0 -19
- package/dist/types/utils/file-display-mode.d.ts +0 -27
- package/dist/types/utils/file-mentions.d.ts +0 -25
- package/dist/types/utils/git.d.ts +0 -393
- package/dist/types/utils/image-loading.d.ts +0 -26
- package/dist/types/utils/image-resize.d.ts +0 -39
- package/dist/types/utils/lang-from-path.d.ts +0 -8
- package/dist/types/utils/linkedom.d.ts +0 -4
- package/dist/types/utils/markit.d.ts +0 -7
- package/dist/types/utils/open.d.ts +0 -2
- package/dist/types/utils/pasted-image-path.d.ts +0 -32
- package/dist/types/utils/session-color.d.ts +0 -10
- package/dist/types/utils/shell-snapshot.d.ts +0 -5
- package/dist/types/utils/sixel.d.ts +0 -21
- package/dist/types/utils/title-generator.d.ts +0 -31
- package/dist/types/utils/tool-choice.d.ts +0 -20
- package/dist/types/utils/tools-manager.d.ts +0 -9
- package/dist/types/vim/buffer.d.ts +0 -41
- package/dist/types/vim/commands.d.ts +0 -6
- package/dist/types/vim/engine.d.ts +0 -47
- package/dist/types/vim/parser.d.ts +0 -3
- package/dist/types/vim/render.d.ts +0 -25
- package/dist/types/vim/types.d.ts +0 -182
- package/dist/types/web/insane/bridge.d.ts +0 -103
- package/dist/types/web/insane/url-guard.d.ts +0 -25
- package/dist/types/web/kagi.d.ts +0 -23
- package/dist/types/web/parallel.d.ts +0 -58
- package/dist/types/web/scrapers/artifacthub.d.ts +0 -6
- package/dist/types/web/scrapers/arxiv.d.ts +0 -5
- package/dist/types/web/scrapers/aur.d.ts +0 -5
- package/dist/types/web/scrapers/biorxiv.d.ts +0 -5
- package/dist/types/web/scrapers/bluesky.d.ts +0 -5
- package/dist/types/web/scrapers/brew.d.ts +0 -5
- package/dist/types/web/scrapers/cheatsh.d.ts +0 -8
- package/dist/types/web/scrapers/chocolatey.d.ts +0 -5
- package/dist/types/web/scrapers/choosealicense.d.ts +0 -2
- package/dist/types/web/scrapers/cisa-kev.d.ts +0 -5
- package/dist/types/web/scrapers/clojars.d.ts +0 -5
- package/dist/types/web/scrapers/coingecko.d.ts +0 -5
- package/dist/types/web/scrapers/crates-io.d.ts +0 -5
- package/dist/types/web/scrapers/crossref.d.ts +0 -2
- package/dist/types/web/scrapers/devto.d.ts +0 -5
- package/dist/types/web/scrapers/discogs.d.ts +0 -8
- package/dist/types/web/scrapers/discourse.d.ts +0 -5
- package/dist/types/web/scrapers/dockerhub.d.ts +0 -5
- package/dist/types/web/scrapers/docs-rs.d.ts +0 -2
- package/dist/types/web/scrapers/fdroid.d.ts +0 -5
- package/dist/types/web/scrapers/firefox-addons.d.ts +0 -2
- package/dist/types/web/scrapers/flathub.d.ts +0 -2
- package/dist/types/web/scrapers/github-gist.d.ts +0 -5
- package/dist/types/web/scrapers/github.d.ts +0 -12
- package/dist/types/web/scrapers/gitlab.d.ts +0 -5
- package/dist/types/web/scrapers/go-pkg.d.ts +0 -5
- package/dist/types/web/scrapers/hackage.d.ts +0 -5
- package/dist/types/web/scrapers/hackernews.d.ts +0 -2
- package/dist/types/web/scrapers/hex.d.ts +0 -5
- package/dist/types/web/scrapers/huggingface.d.ts +0 -2
- package/dist/types/web/scrapers/iacr.d.ts +0 -5
- package/dist/types/web/scrapers/index.d.ts +0 -84
- package/dist/types/web/scrapers/jetbrains-marketplace.d.ts +0 -2
- package/dist/types/web/scrapers/lemmy.d.ts +0 -2
- package/dist/types/web/scrapers/lobsters.d.ts +0 -5
- package/dist/types/web/scrapers/mastodon.d.ts +0 -5
- package/dist/types/web/scrapers/maven.d.ts +0 -6
- package/dist/types/web/scrapers/mdn.d.ts +0 -2
- package/dist/types/web/scrapers/metacpan.d.ts +0 -5
- package/dist/types/web/scrapers/musicbrainz.d.ts +0 -5
- package/dist/types/web/scrapers/npm.d.ts +0 -5
- package/dist/types/web/scrapers/nuget.d.ts +0 -5
- package/dist/types/web/scrapers/nvd.d.ts +0 -5
- package/dist/types/web/scrapers/ollama.d.ts +0 -2
- package/dist/types/web/scrapers/open-vsx.d.ts +0 -5
- package/dist/types/web/scrapers/opencorporates.d.ts +0 -5
- package/dist/types/web/scrapers/openlibrary.d.ts +0 -5
- package/dist/types/web/scrapers/orcid.d.ts +0 -5
- package/dist/types/web/scrapers/osv.d.ts +0 -5
- package/dist/types/web/scrapers/packagist.d.ts +0 -5
- package/dist/types/web/scrapers/pub-dev.d.ts +0 -5
- package/dist/types/web/scrapers/pubmed.d.ts +0 -5
- package/dist/types/web/scrapers/pypi.d.ts +0 -5
- package/dist/types/web/scrapers/rawg.d.ts +0 -2
- package/dist/types/web/scrapers/readthedocs.d.ts +0 -2
- package/dist/types/web/scrapers/reddit.d.ts +0 -5
- package/dist/types/web/scrapers/repology.d.ts +0 -5
- package/dist/types/web/scrapers/rfc.d.ts +0 -5
- package/dist/types/web/scrapers/rubygems.d.ts +0 -5
- package/dist/types/web/scrapers/searchcode.d.ts +0 -2
- package/dist/types/web/scrapers/sec-edgar.d.ts +0 -5
- package/dist/types/web/scrapers/semantic-scholar.d.ts +0 -2
- package/dist/types/web/scrapers/snapcraft.d.ts +0 -2
- package/dist/types/web/scrapers/sourcegraph.d.ts +0 -2
- package/dist/types/web/scrapers/spdx.d.ts +0 -5
- package/dist/types/web/scrapers/spotify.d.ts +0 -8
- package/dist/types/web/scrapers/stackoverflow.d.ts +0 -6
- package/dist/types/web/scrapers/terraform.d.ts +0 -5
- package/dist/types/web/scrapers/tldr.d.ts +0 -7
- package/dist/types/web/scrapers/twitter.d.ts +0 -5
- package/dist/types/web/scrapers/types.d.ts +0 -83
- package/dist/types/web/scrapers/utils.d.ts +0 -32
- package/dist/types/web/scrapers/vimeo.d.ts +0 -5
- package/dist/types/web/scrapers/vscode-marketplace.d.ts +0 -5
- package/dist/types/web/scrapers/w3c.d.ts +0 -2
- package/dist/types/web/scrapers/wikidata.d.ts +0 -5
- package/dist/types/web/scrapers/wikipedia.d.ts +0 -5
- package/dist/types/web/scrapers/youtube.d.ts +0 -13
- package/dist/types/web/search/index.d.ts +0 -118
- package/dist/types/web/search/provider.d.ts +0 -55
- package/dist/types/web/search/providers/anthropic.d.ts +0 -32
- package/dist/types/web/search/providers/base.d.ts +0 -90
- package/dist/types/web/search/providers/brave.d.ts +0 -27
- package/dist/types/web/search/providers/codex.d.ts +0 -35
- package/dist/types/web/search/providers/duckduckgo.d.ts +0 -57
- package/dist/types/web/search/providers/exa.d.ts +0 -52
- package/dist/types/web/search/providers/gemini.d.ts +0 -57
- package/dist/types/web/search/providers/insane.d.ts +0 -53
- package/dist/types/web/search/providers/jina.d.ts +0 -26
- package/dist/types/web/search/providers/kagi.d.ts +0 -24
- package/dist/types/web/search/providers/kimi.d.ts +0 -27
- package/dist/types/web/search/providers/openai-compatible.d.ts +0 -9
- package/dist/types/web/search/providers/parallel.d.ts +0 -15
- package/dist/types/web/search/providers/perplexity.d.ts +0 -38
- package/dist/types/web/search/providers/searxng.d.ts +0 -44
- package/dist/types/web/search/providers/synthetic.d.ts +0 -21
- package/dist/types/web/search/providers/tavily.d.ts +0 -29
- package/dist/types/web/search/providers/text-citations.d.ts +0 -23
- package/dist/types/web/search/providers/utils.d.ts +0 -88
- package/dist/types/web/search/providers/xai.d.ts +0 -64
- package/dist/types/web/search/providers/zai.d.ts +0 -28
- package/dist/types/web/search/render.d.ts +0 -36
- package/dist/types/web/search/types.d.ts +0 -372
- package/dist/types/web/search/utils.d.ts +0 -4
- package/dist/types/workflow/workflow-intent-diff.d.ts +0 -25
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +0 -50
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +0 -2
- package/dist/types/workflow/workflow-intent-report.d.ts +0 -48
- package/dist/types/workspace-tree.d.ts +0 -42
- package/src/sdk.ts +0 -2623
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import {
|
|
3
4
|
type Agent,
|
|
@@ -5,15 +6,11 @@ import {
|
|
|
5
6
|
type AuthenticateRequest,
|
|
6
7
|
type AuthenticateResponse,
|
|
7
8
|
type AuthMethod,
|
|
8
|
-
type AvailableCommand,
|
|
9
9
|
type ClientCapabilities,
|
|
10
10
|
type CloseSessionRequest,
|
|
11
11
|
type CloseSessionResponse,
|
|
12
|
-
type CreateElicitationResponse,
|
|
13
12
|
type DeleteSessionRequest,
|
|
14
13
|
type DeleteSessionResponse,
|
|
15
|
-
type ElicitationContentValue,
|
|
16
|
-
type ElicitationPropertySchema,
|
|
17
14
|
type ForkSessionRequest,
|
|
18
15
|
type ForkSessionResponse,
|
|
19
16
|
type InitializeRequest,
|
|
@@ -22,7 +19,6 @@ import {
|
|
|
22
19
|
type ListSessionsResponse,
|
|
23
20
|
type LoadSessionRequest,
|
|
24
21
|
type LoadSessionResponse,
|
|
25
|
-
type McpServer,
|
|
26
22
|
type NewSessionRequest,
|
|
27
23
|
type NewSessionResponse,
|
|
28
24
|
PROTOCOL_VERSION,
|
|
@@ -30,471 +26,577 @@ import {
|
|
|
30
26
|
type PromptResponse,
|
|
31
27
|
type ResumeSessionRequest,
|
|
32
28
|
type ResumeSessionResponse,
|
|
33
|
-
type SessionConfigOption,
|
|
34
29
|
type SessionInfo,
|
|
35
|
-
type SessionModeState,
|
|
36
30
|
type SessionNotification,
|
|
37
|
-
type SessionUpdate,
|
|
38
31
|
type SetSessionConfigOptionRequest,
|
|
39
32
|
type SetSessionConfigOptionResponse,
|
|
40
33
|
type SetSessionModeRequest,
|
|
41
34
|
type SetSessionModeResponse,
|
|
42
|
-
type Usage,
|
|
43
35
|
} from "@agentclientprotocol/sdk";
|
|
44
|
-
import
|
|
45
|
-
import { logger } from "@sayknow-cli/utils";
|
|
36
|
+
import { getAgentDir } from "@sayknow-cli/utils";
|
|
46
37
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
47
|
-
import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
|
|
48
|
-
import { Settings } from "../../config/settings";
|
|
49
|
-
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
50
|
-
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../../extensibility/extensions";
|
|
51
|
-
import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
|
|
52
|
-
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
53
|
-
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/skc-plugins";
|
|
54
38
|
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} from "../../
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import type { MCPServerConfig } from "../../runtime-mcp/types";
|
|
66
|
-
import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
|
|
67
|
-
import { isSilentAbort, SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
|
|
68
|
-
import { isLegacyProviderSafetyStopMessage } from "../../session/provider-safety-stop";
|
|
69
|
-
import {
|
|
70
|
-
SessionManager,
|
|
71
|
-
type SessionInfo as StoredSessionInfo,
|
|
72
|
-
type StrictInventoryCandidate,
|
|
73
|
-
type UsageStatistics,
|
|
74
|
-
} from "../../session/session-manager";
|
|
75
|
-
import {
|
|
76
|
-
FileSessionStorage,
|
|
77
|
-
type SessionStorageFileIdentity,
|
|
78
|
-
type VerifiedSessionDeleteResult,
|
|
79
|
-
type VerifiedSessionDeleteTarget,
|
|
80
|
-
} from "../../session/session-storage";
|
|
81
|
-
import { ACP_BUILTIN_SLASH_COMMANDS, executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
|
|
82
|
-
import { parseThinkingLevel } from "../../thinking";
|
|
83
|
-
import { toAgentWireEventPayload } from "../shared/agent-wire/event-envelope";
|
|
84
|
-
import { createAcpClientBridge } from "./acp-client-bridge";
|
|
85
|
-
import {
|
|
86
|
-
buildToolCallStartUpdate,
|
|
87
|
-
mapAgentWireEventPayloadToAcpSessionUpdates,
|
|
88
|
-
normalizeReplayToolArguments,
|
|
89
|
-
} from "./acp-event-mapper";
|
|
39
|
+
type AcpProviderRegistration,
|
|
40
|
+
type AcpReverseConnection,
|
|
41
|
+
AcpSdkAdapter,
|
|
42
|
+
AcpSdkAdapterError,
|
|
43
|
+
} from "../../sdk/acp";
|
|
44
|
+
import { ensureBroker } from "../../sdk/broker/ensure";
|
|
45
|
+
import { readSdkBrokerDiscovery, SdkClient } from "../../sdk/client";
|
|
46
|
+
import { mapAgentWireEventPayloadToAcpSessionUpdates } from "./acp-event-mapper";
|
|
47
|
+
import { resolveAcpPermissionMode } from "./permission-mode";
|
|
48
|
+
import type { AcpStartupOptions } from "./startup-options";
|
|
90
49
|
import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
|
|
91
50
|
|
|
92
51
|
const ACP_DEFAULT_MODE_ID = "default";
|
|
93
52
|
const ACP_PLAN_MODE_ID = "plan";
|
|
94
|
-
const DEFAULT_PLAN_FILE_URL = "local://PLAN.md";
|
|
95
53
|
const MODE_CONFIG_ID = "mode";
|
|
96
54
|
const MODEL_CONFIG_ID = "model";
|
|
97
55
|
const THINKING_CONFIG_ID = "thinking";
|
|
98
|
-
const THINKING_OFF = "off";
|
|
99
56
|
const SESSION_PAGE_SIZE = 50;
|
|
57
|
+
export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
|
|
58
|
+
const MAX_ACP_REPLAY_PAGES = 10_000;
|
|
100
59
|
|
|
60
|
+
type JsonObject = Record<string, unknown>;
|
|
101
61
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* invalidate the snapshot.
|
|
62
|
+
* ACP prompt completion is tied to a post-acknowledgement lifecycle boundary.
|
|
63
|
+
* AgentSession events do not carry the command identity themselves, so the
|
|
64
|
+
* host stamps command/turn identities into its replay ring and ACP also keeps
|
|
65
|
+
* a per-endpoint ingress sequence. A frame observed before an acknowledgement
|
|
66
|
+
* can never settle the waiter that acknowledgement creates.
|
|
108
67
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
68
|
+
interface PromptWaiter {
|
|
69
|
+
cancelRequested: boolean;
|
|
70
|
+
acknowledged: boolean;
|
|
71
|
+
activityObserved: boolean;
|
|
72
|
+
/** The prompt was accepted while the host was already busy, so its next valid idle ends the steer. */
|
|
73
|
+
steeringAtAcknowledgement: boolean;
|
|
74
|
+
/** Highest inbound frame sequence already observed when the prompt was acknowledged. */
|
|
75
|
+
boundary: number;
|
|
76
|
+
correlation: PromptCorrelation;
|
|
77
|
+
messageProgress?: { textEmitted: boolean; thoughtEmitted: boolean };
|
|
78
|
+
pendingTerminal?: PromptCorrelation;
|
|
79
|
+
resolve: (response: PromptResponse) => void;
|
|
80
|
+
reject: (error: Error) => void;
|
|
81
|
+
}
|
|
122
82
|
|
|
123
|
-
type
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const ACP_CANCEL_CLEANUP_TIMEOUT_MS = 5_000;
|
|
141
|
-
const ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS = 250;
|
|
142
|
-
const ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES = 3;
|
|
143
|
-
|
|
144
|
-
type AgentImageContent = {
|
|
145
|
-
type: "image";
|
|
146
|
-
data: string;
|
|
147
|
-
mimeType: string;
|
|
83
|
+
type PromptCorrelation = { commandId?: string; turnId?: string };
|
|
84
|
+
|
|
85
|
+
type BrokerConnection = { adapter: AcpSdkAdapter; client: SdkClient };
|
|
86
|
+
type PendingAttachment = { epoch: number; task: Promise<void> };
|
|
87
|
+
|
|
88
|
+
type SessionRecord = {
|
|
89
|
+
cwd: string;
|
|
90
|
+
adapter: AcpSdkAdapter;
|
|
91
|
+
unsubscribe: () => void;
|
|
92
|
+
reconnectUnsubscribe: () => void;
|
|
93
|
+
/** Per-session frame work queue; callbacks never race prompt ownership. */
|
|
94
|
+
frameTail: Promise<void>;
|
|
95
|
+
/** Monotonic at WebSocket ingress, before queued work begins. */
|
|
96
|
+
inboundSequence: number;
|
|
97
|
+
/** Updated at ingress so a prompt acknowledgement can distinguish a steer from a fresh turn. */
|
|
98
|
+
busy: boolean;
|
|
99
|
+
activePrompt?: PromptWaiter;
|
|
148
100
|
};
|
|
101
|
+
type Endpoint = { url: string; token: string };
|
|
149
102
|
|
|
150
|
-
type
|
|
151
|
-
|
|
152
|
-
|
|
103
|
+
type BrokerSession = {
|
|
104
|
+
sessionId: string;
|
|
105
|
+
locator?: { repo?: string };
|
|
106
|
+
live?: boolean;
|
|
107
|
+
endpointGeneration?: number;
|
|
153
108
|
};
|
|
154
109
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
reject: (reason?: unknown) => void;
|
|
170
|
-
promise: Promise<PromptResponse>;
|
|
171
|
-
};
|
|
110
|
+
function parseAcpStartupOptions(value: unknown): AcpStartupOptions | undefined {
|
|
111
|
+
const candidate = object(value);
|
|
112
|
+
if (!candidate) return undefined;
|
|
113
|
+
const modelId = typeof candidate.modelId === "string" ? candidate.modelId : undefined;
|
|
114
|
+
const modelPreset = typeof candidate.modelPreset === "string" ? candidate.modelPreset : undefined;
|
|
115
|
+
const thinkingLevel = typeof candidate.thinkingLevel === "string" ? candidate.thinkingLevel : undefined;
|
|
116
|
+
return modelId || modelPreset || thinkingLevel
|
|
117
|
+
? {
|
|
118
|
+
...(modelId ? { modelId } : {}),
|
|
119
|
+
...(modelPreset ? { modelPreset } : {}),
|
|
120
|
+
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
121
|
+
}
|
|
122
|
+
: undefined;
|
|
123
|
+
}
|
|
172
124
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
125
|
+
function object(value: unknown): JsonObject | undefined {
|
|
126
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) : undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function aggregateAcpFailure(code: string, message: string, failures: unknown[]): AcpSdkAdapterError {
|
|
130
|
+
const aggregate = new AggregateError(failures, message);
|
|
131
|
+
return Object.assign(new AcpSdkAdapterError(code, aggregate.message), {
|
|
132
|
+
cause: aggregate,
|
|
133
|
+
errors: aggregate.errors,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Applies ACP's offset cursor after narrowing the broker listing to the requested cwd. */
|
|
138
|
+
export function paginateAcpSessions(listed: unknown[], cwd: string | undefined, offset: number): ListSessionsResponse {
|
|
139
|
+
const filtered = listed
|
|
140
|
+
.map(value => object(value) as BrokerSession | undefined)
|
|
141
|
+
.filter(
|
|
142
|
+
(value): value is BrokerSession & { locator: { repo: string } } =>
|
|
143
|
+
typeof value?.sessionId === "string" && typeof value.locator?.repo === "string",
|
|
144
|
+
)
|
|
145
|
+
.filter(value => !cwd || value.locator.repo === cwd);
|
|
146
|
+
const sessions = filtered
|
|
147
|
+
.slice(offset, offset + SESSION_PAGE_SIZE)
|
|
148
|
+
.map(
|
|
149
|
+
value =>
|
|
150
|
+
({ sessionId: value.sessionId, cwd: value.locator.repo, title: value.sessionId }) satisfies SessionInfo,
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
sessions,
|
|
154
|
+
nextCursor: offset + sessions.length < filtered.length ? String(offset + sessions.length) : undefined,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function endpoint(value: unknown): Endpoint {
|
|
159
|
+
const candidate = object(value);
|
|
160
|
+
const result = object(candidate?.result) ?? candidate;
|
|
161
|
+
const nested = object(result?.endpoint) ?? result;
|
|
162
|
+
if (typeof nested?.url !== "string" || typeof nested.token !== "string")
|
|
163
|
+
throw new AcpSdkAdapterError("unavailable", "SDK lifecycle response omitted a session endpoint.");
|
|
164
|
+
return { url: nested.url, token: nested.token };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function sessionId(value: unknown): string {
|
|
168
|
+
const candidate = object(value);
|
|
169
|
+
const result = object(candidate?.result) ?? candidate;
|
|
170
|
+
if (typeof result?.sessionId !== "string" || !result.sessionId)
|
|
171
|
+
throw new AcpSdkAdapterError("unavailable", "SDK lifecycle response omitted a session id.");
|
|
172
|
+
return result.sessionId;
|
|
181
173
|
}
|
|
182
174
|
|
|
175
|
+
function pageItems(value: unknown): unknown[] {
|
|
176
|
+
const response = object(value);
|
|
177
|
+
const result = object(response?.result) ?? response;
|
|
178
|
+
const page = object(result?.page);
|
|
179
|
+
return Array.isArray(page?.items) ? page.items : [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function correlationFrom(...values: unknown[]): PromptCorrelation {
|
|
183
|
+
const correlation: PromptCorrelation = {};
|
|
184
|
+
for (const value of values) {
|
|
185
|
+
const candidate = object(value);
|
|
186
|
+
for (const record of [candidate, object(candidate?.result)]) {
|
|
187
|
+
if (!record) continue;
|
|
188
|
+
if (!correlation.commandId) {
|
|
189
|
+
const commandId = record.commandId ?? record.command_id;
|
|
190
|
+
if (typeof commandId === "string" && commandId) correlation.commandId = commandId;
|
|
191
|
+
}
|
|
192
|
+
if (!correlation.turnId) {
|
|
193
|
+
const turnId = record.turnId ?? record.turn_id;
|
|
194
|
+
if (typeof turnId === "string" && turnId) correlation.turnId = turnId;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return correlation;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function correlationsConflict(expected: PromptCorrelation, actual: PromptCorrelation): boolean {
|
|
202
|
+
return (
|
|
203
|
+
(expected.commandId !== undefined && actual.commandId !== undefined && expected.commandId !== actual.commandId) ||
|
|
204
|
+
(expected.turnId !== undefined && actual.turnId !== undefined && expected.turnId !== actual.turnId)
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function correlationsMatch(expected: PromptCorrelation, actual: PromptCorrelation): boolean {
|
|
209
|
+
return (
|
|
210
|
+
(expected.commandId !== undefined && expected.commandId === actual.commandId) ||
|
|
211
|
+
(expected.turnId !== undefined && expected.turnId === actual.turnId)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isPromptActivity(eventType: string): boolean {
|
|
216
|
+
return [
|
|
217
|
+
"agent_start",
|
|
218
|
+
"turn_start",
|
|
219
|
+
"message_start",
|
|
220
|
+
"message_update",
|
|
221
|
+
"message_end",
|
|
222
|
+
"tool_execution_start",
|
|
223
|
+
"tool_execution_update",
|
|
224
|
+
"tool_execution_end",
|
|
225
|
+
].includes(eventType);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export type TranscriptReplayBlock = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
|
|
229
|
+
|
|
183
230
|
/**
|
|
184
|
-
*
|
|
185
|
-
* - `
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* - `pre_dispatch_retryable`: close_failed_retryable before verified-delete
|
|
189
|
-
* dispatch; normal ops rejected but a subsequent delete may retry.
|
|
190
|
-
* - `cleanup_pending`: verified delete returned cleanup_pending with identity
|
|
191
|
-
* evidence; normal ops rejected but a subsequent delete may retry.
|
|
192
|
-
* - `terminal_failure`: close_unknown or quiescence failure (or any unexpected
|
|
193
|
-
* failure); normal ops rejected and delete NEVER reopens — subsequent deletes
|
|
194
|
-
* share the settled terminal failure error.
|
|
195
|
-
* - `deleted`: verified delete succeeded; record removed from the active map.
|
|
231
|
+
* The production transcript query exposes durable `{ body, textSummary }`
|
|
232
|
+
* entries, not an ACP-shaped `content` array. Historical session JSONL has no
|
|
233
|
+
* recoverable image bytes, so replay exposes that boundary rather than
|
|
234
|
+
* pretending images were restored.
|
|
196
235
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
| "cleanup_pending"
|
|
202
|
-
| "terminal_failure"
|
|
203
|
-
| "deleted";
|
|
204
|
-
|
|
205
|
-
type ManagedSessionRecord = {
|
|
206
|
-
session: AgentSession;
|
|
207
|
-
mcpManager: MCPManager | undefined;
|
|
208
|
-
promptTurn: PromptTurnState | undefined;
|
|
209
|
-
promptQueue: PromptQueueState;
|
|
210
|
-
liveMessageId: string | undefined;
|
|
211
|
-
liveMessageProgress: { textEmitted: boolean; thoughtEmitted: boolean } | undefined;
|
|
212
|
-
toolArgsById: Map<string, unknown>;
|
|
213
|
-
extensionsConfigured: boolean;
|
|
214
|
-
// Installed after the bootstrap race guard or eagerly when the client starts a prompt;
|
|
215
|
-
// released in `#disposeSessionRecord`. Lives independent of any prompt turn.
|
|
216
|
-
lifetimeUnsubscribe: (() => void) | undefined;
|
|
217
|
-
// Per-record terminal state machine (see AcpSessionTerminalState). After ANY
|
|
218
|
-
// delete failure the record stays non-`active` so prompt/config/close/load/
|
|
219
|
-
// resume/fork remain rejected. Only `pre_dispatch_retryable` and
|
|
220
|
-
// `cleanup_pending` permit a retry delete; `terminal_failure` never reopens.
|
|
221
|
-
terminalState: AcpSessionTerminalState;
|
|
222
|
-
// The settled terminal failure error, shared with subsequent delete callers
|
|
223
|
-
// so a terminal_failure is observable without re-running the terminal op.
|
|
224
|
-
terminalFailure: Error | undefined;
|
|
225
|
-
// Resolves when an in-progress terminal delete settles. Concurrent delete
|
|
226
|
-
// callers and abort/shutdown join this promise.
|
|
227
|
-
terminalPromise: Promise<DeleteSessionResponse> | undefined;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
type ReplayableMessage = {
|
|
231
|
-
role: string;
|
|
232
|
-
content?: unknown;
|
|
233
|
-
errorMessage?: string;
|
|
234
|
-
toolCallId?: string;
|
|
235
|
-
toolName?: string;
|
|
236
|
-
details?: unknown;
|
|
237
|
-
isError?: boolean;
|
|
238
|
-
};
|
|
236
|
+
export interface TranscriptReplayContent {
|
|
237
|
+
blocks: TranscriptReplayBlock[];
|
|
238
|
+
images: { available: false; reason: "historical_transcript_images_unavailable" };
|
|
239
|
+
}
|
|
239
240
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
export function transcriptReplayContent(entry: unknown): TranscriptReplayContent {
|
|
242
|
+
const record = object(entry);
|
|
243
|
+
if (typeof record?.body !== "string")
|
|
244
|
+
throw new AcpSdkAdapterError(
|
|
245
|
+
"transcript_body_unavailable",
|
|
246
|
+
"ACP cannot replay a transcript entry without its production body.",
|
|
247
|
+
);
|
|
248
|
+
return {
|
|
249
|
+
blocks: record.body.length > 0 ? [{ type: "text", text: record.body }] : [],
|
|
250
|
+
images: { available: false, reason: "historical_transcript_images_unavailable" },
|
|
251
|
+
};
|
|
252
|
+
}
|
|
247
253
|
|
|
248
|
-
type
|
|
249
|
-
|
|
254
|
+
type ReceivedSdkEvent = {
|
|
255
|
+
event: JsonObject;
|
|
256
|
+
/** Event payload accepted by the ACP event mapper, when this is an agent-wire frame. */
|
|
257
|
+
wirePayload?: JsonObject;
|
|
250
258
|
};
|
|
251
259
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Native session hosts emit `activity` directly; test-only/legacy adapters may
|
|
262
|
+
* wrap agent-wire events in `{ type: "event", payload }`. Normalize both
|
|
263
|
+
* without treating notification-specific frames as agent lifecycle truth.
|
|
264
|
+
*/
|
|
265
|
+
function receivedSdkEvent(frame: JsonObject): ReceivedSdkEvent | undefined {
|
|
266
|
+
if (frame.type === "activity") {
|
|
267
|
+
const type = frame.state === "busy" ? "agent_start" : frame.state === "idle" ? "agent_end" : undefined;
|
|
268
|
+
return type ? { event: { type, ...correlationFrom(frame) } } : undefined;
|
|
269
|
+
}
|
|
270
|
+
if (frame.type !== "event") return undefined;
|
|
271
|
+
const payload = object(frame.payload);
|
|
272
|
+
if (!payload) return undefined;
|
|
273
|
+
const replayPayload = object(payload.payload);
|
|
274
|
+
const event = object(payload.event) ?? replayPayload ?? payload;
|
|
275
|
+
if (typeof event.type !== "string") return undefined;
|
|
276
|
+
return {
|
|
277
|
+
event,
|
|
278
|
+
...(object(payload.event) ? { wirePayload: payload } : {}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
258
281
|
|
|
259
|
-
|
|
260
|
-
|
|
282
|
+
const ACP_CONFIG_OPTIONS = [
|
|
283
|
+
{ id: MODEL_CONFIG_ID, name: "Model", options: [] },
|
|
284
|
+
{ id: THINKING_CONFIG_ID, name: "Thinking", options: [] },
|
|
285
|
+
{
|
|
286
|
+
id: "steeringMode",
|
|
287
|
+
name: "Steering queue",
|
|
288
|
+
options: [
|
|
289
|
+
{ value: "all", name: "All" },
|
|
290
|
+
{ value: "one-at-a-time", name: "One at a time" },
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "followUpMode",
|
|
295
|
+
name: "Follow-up queue",
|
|
296
|
+
options: [
|
|
297
|
+
{ value: "all", name: "All" },
|
|
298
|
+
{ value: "one-at-a-time", name: "One at a time" },
|
|
299
|
+
],
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "interruptMode",
|
|
303
|
+
name: "Interrupt mode",
|
|
304
|
+
options: [
|
|
305
|
+
{ value: "immediate", name: "Immediate" },
|
|
306
|
+
{ value: "wait", name: "Wait" },
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
] as const;
|
|
310
|
+
|
|
311
|
+
const ACP_CONFIG_CONTROL_OPERATIONS: Record<string, string> = {
|
|
312
|
+
steeringMode: "queue.steering_mode.set",
|
|
313
|
+
followUpMode: "queue.follow_up_mode.set",
|
|
314
|
+
interruptMode: "queue.interrupt_mode.set",
|
|
261
315
|
};
|
|
262
316
|
|
|
263
|
-
|
|
317
|
+
function configValues(query: unknown): Map<string, string> {
|
|
318
|
+
const values = new Map<string, string>();
|
|
319
|
+
for (const item of pageItems(query)) {
|
|
320
|
+
const record = object(item);
|
|
321
|
+
if (!record) continue;
|
|
322
|
+
if (typeof record.id === "string" && typeof record.value === "string") {
|
|
323
|
+
values.set(record.id, record.value);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
for (const [id, value] of Object.entries(record)) {
|
|
327
|
+
if (typeof value === "string") values.set(id, value);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return values;
|
|
331
|
+
}
|
|
264
332
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
* `dialogOptions.signal` short-circuits the elicitation if it is already
|
|
273
|
-
* aborted and races the in-flight request against the abort event. The SDK
|
|
274
|
-
* exposes no `cancel_elicitation` surface for form-mode elicitations
|
|
275
|
-
* (`unstable_completeElicitation` is URL-mode only), so the ACP request itself
|
|
276
|
-
* keeps running on the client side until the user dismisses it — but
|
|
277
|
-
* resolving the local promise unblocks the caller (matches the RPC mode
|
|
278
|
-
* pattern in `requestRpcEditor`). The abort listener is removed once the
|
|
279
|
-
* elicitation settles so that callers which reuse the same signal across many
|
|
280
|
-
* elicitations (e.g. `ask` multi-select loops) don't accumulate listeners and
|
|
281
|
-
* trip Node's `MaxListeners` warning.
|
|
282
|
-
*
|
|
283
|
-
* `dialogOptions.timeout` mirrors `RpcExtensionUIContext.#createDialogPromise`:
|
|
284
|
-
* when the timer fires before the client responds, `onTimeout` is invoked and
|
|
285
|
-
* the caller's promise resolves to the stub fallback. Late SDK responses that
|
|
286
|
-
* arrive after abort/timeout — both rejections and successful `accept`s —
|
|
287
|
-
* are dropped silently (no `logger.warn`) to keep operator logs clean.
|
|
288
|
-
*/
|
|
289
|
-
async function elicitFromAcpClient(
|
|
290
|
-
connection: AgentSideConnection,
|
|
291
|
-
sessionId: string,
|
|
292
|
-
method: "select" | "confirm" | "input",
|
|
293
|
-
message: string,
|
|
294
|
-
property: ElicitationPropertySchema,
|
|
295
|
-
dialogOptions: ExtensionUIDialogOptions | undefined,
|
|
296
|
-
): Promise<ElicitationContentValue | undefined> {
|
|
297
|
-
const signal = dialogOptions?.signal;
|
|
298
|
-
if (signal?.aborted) {
|
|
299
|
-
return undefined;
|
|
333
|
+
function modelConfigOptions(query: unknown, current: string | undefined): { value: string; name: string }[] {
|
|
334
|
+
const options = new Map<string, string>();
|
|
335
|
+
for (const item of pageItems(query)) {
|
|
336
|
+
const model = object(item);
|
|
337
|
+
if (!model || typeof model.provider !== "string" || typeof model.id !== "string") continue;
|
|
338
|
+
const value = `${model.provider}/${model.id}`;
|
|
339
|
+
options.set(value, typeof model.name === "string" ? model.name : value);
|
|
300
340
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
if (current && !options.has(current)) options.set(current, current);
|
|
342
|
+
return [...options].map(([value, name]) => ({ value, name }));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const THINKING_CONFIG_OPTIONS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map(value => ({
|
|
346
|
+
value,
|
|
347
|
+
name: value,
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
/** Maps live canonical SDK config and model queries into the ACP 1.2.1 session state surface. */
|
|
351
|
+
export function acpSessionStateFromConfig(query: unknown, modelsQuery?: unknown) {
|
|
352
|
+
const values = configValues(query);
|
|
353
|
+
const currentModeId = values.get(MODE_CONFIG_ID) === ACP_PLAN_MODE_ID ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
|
|
354
|
+
return {
|
|
355
|
+
configOptions: [
|
|
356
|
+
{
|
|
357
|
+
id: MODE_CONFIG_ID,
|
|
358
|
+
name: "Mode",
|
|
359
|
+
type: "select" as const,
|
|
360
|
+
currentValue: currentModeId,
|
|
361
|
+
options: [
|
|
362
|
+
{ value: ACP_DEFAULT_MODE_ID, name: "Default" },
|
|
363
|
+
{ value: ACP_PLAN_MODE_ID, name: "Plan" },
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
...ACP_CONFIG_OPTIONS.flatMap(option => {
|
|
367
|
+
const value = values.get(option.id);
|
|
368
|
+
if (value === undefined) return [];
|
|
369
|
+
const options =
|
|
370
|
+
option.id === MODEL_CONFIG_ID
|
|
371
|
+
? modelConfigOptions(modelsQuery, value)
|
|
372
|
+
: option.id === THINKING_CONFIG_ID
|
|
373
|
+
? THINKING_CONFIG_OPTIONS
|
|
374
|
+
: [...option.options];
|
|
375
|
+
return [{ ...option, type: "select" as const, currentValue: value, options }];
|
|
376
|
+
}),
|
|
377
|
+
],
|
|
378
|
+
modes: {
|
|
379
|
+
availableModes: [
|
|
380
|
+
{ id: ACP_DEFAULT_MODE_ID, name: "Default" },
|
|
381
|
+
{ id: ACP_PLAN_MODE_ID, name: "Plan" },
|
|
382
|
+
],
|
|
383
|
+
currentModeId,
|
|
384
|
+
},
|
|
310
385
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Convert every ACP prompt block the agent advertises without silently discarding context. */
|
|
389
|
+
export function acpPromptPayload(blocks: PromptRequest["prompt"]): {
|
|
390
|
+
text: string;
|
|
391
|
+
images: Array<{ data: string; mimeType: string }>;
|
|
392
|
+
} {
|
|
393
|
+
const text: string[] = [];
|
|
394
|
+
const images: Array<{ data: string; mimeType: string }> = [];
|
|
395
|
+
for (const block of blocks) {
|
|
396
|
+
switch (block.type) {
|
|
397
|
+
case "text":
|
|
398
|
+
text.push(block.text);
|
|
399
|
+
break;
|
|
400
|
+
case "image":
|
|
401
|
+
if (block.uri) text.push(`[Image URI: ${block.uri}]`);
|
|
402
|
+
images.push({ data: block.data, mimeType: block.mimeType });
|
|
403
|
+
break;
|
|
404
|
+
case "resource_link":
|
|
405
|
+
text.push(
|
|
406
|
+
[
|
|
407
|
+
`[Resource: ${block.name}]`,
|
|
408
|
+
`URI: ${block.uri}`,
|
|
409
|
+
...(block.title ? [`Title: ${block.title}`] : []),
|
|
410
|
+
...(block.description ? [block.description] : []),
|
|
411
|
+
...(block.mimeType ? [`MIME: ${block.mimeType}`] : []),
|
|
412
|
+
...(typeof block.size === "number" ? [`Size: ${block.size}`] : []),
|
|
413
|
+
].join("\n"),
|
|
414
|
+
);
|
|
415
|
+
break;
|
|
416
|
+
case "resource": {
|
|
417
|
+
const resource = block.resource;
|
|
418
|
+
if ("text" in resource) {
|
|
419
|
+
text.push(
|
|
420
|
+
[
|
|
421
|
+
`[Resource: ${resource.uri}]`,
|
|
422
|
+
...(resource.mimeType ? [`MIME: ${resource.mimeType}`] : []),
|
|
423
|
+
resource.text,
|
|
424
|
+
].join("\n"),
|
|
425
|
+
);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
const mimeType = resource.mimeType ?? "application/octet-stream";
|
|
429
|
+
if (!mimeType.startsWith("image/"))
|
|
430
|
+
throw new AcpSdkAdapterError(
|
|
431
|
+
"unsupported_content",
|
|
432
|
+
`Unsupported embedded resource MIME type: ${mimeType}`,
|
|
433
|
+
);
|
|
434
|
+
text.push(`[Resource: ${resource.uri}]\nMIME: ${mimeType}`);
|
|
435
|
+
images.push({ data: resource.blob, mimeType });
|
|
436
|
+
break;
|
|
322
437
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// loop alive on the happy path.
|
|
329
|
-
timeoutId.unref();
|
|
438
|
+
case "audio":
|
|
439
|
+
throw new AcpSdkAdapterError("unsupported_content", "ACP audio prompts are not supported.");
|
|
440
|
+
default:
|
|
441
|
+
throw new AcpSdkAdapterError("unsupported_content", "Unsupported ACP prompt content.");
|
|
442
|
+
}
|
|
330
443
|
}
|
|
331
|
-
|
|
332
|
-
.
|
|
333
|
-
|
|
334
|
-
|
|
444
|
+
if (text.length === 0 && images.length === 0)
|
|
445
|
+
throw new AcpSdkAdapterError("invalid_input", "ACP prompt must contain at least one supported content block.");
|
|
446
|
+
return { text: text.join("\n"), images };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Registers a permission provider only when the ACP client requires prompts. */
|
|
450
|
+
export function acpProviderRegistrations(
|
|
451
|
+
capabilities: ClientCapabilities | undefined,
|
|
452
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
453
|
+
): AcpProviderRegistration[] {
|
|
454
|
+
return [
|
|
455
|
+
...(capabilities?.fs?.readTextFile || capabilities?.fs?.writeTextFile
|
|
456
|
+
? [{ capability: "fs", definitions: [] }]
|
|
457
|
+
: []),
|
|
458
|
+
...(capabilities?.terminal ? [{ capability: "terminal", definitions: [] }] : []),
|
|
459
|
+
...(resolveAcpPermissionMode(capabilities, env) === "prompt"
|
|
460
|
+
? [{ capability: "permission", definitions: [] }]
|
|
461
|
+
: []),
|
|
462
|
+
{ capability: "ui", definitions: [] },
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Maps ACP permission handling to the session's canonical SDK policy. */
|
|
467
|
+
export async function applyAcpPermissionMode(
|
|
468
|
+
adapter: Pick<AcpSdkAdapter, "control">,
|
|
469
|
+
capabilities: ClientCapabilities | undefined,
|
|
470
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
471
|
+
): Promise<void> {
|
|
472
|
+
const mode = resolveAcpPermissionMode(capabilities, env);
|
|
473
|
+
await adapter.control("permission_mode.set", { mode: mode === "prompt" ? "prompt" : "allow" });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** Applies CLI-provided ACP startup settings through SDK controls before session exposure. */
|
|
477
|
+
export async function applyAcpStartupOptions(
|
|
478
|
+
adapter: Pick<AcpSdkAdapter, "setModel" | "control">,
|
|
479
|
+
options: AcpStartupOptions | undefined,
|
|
480
|
+
): Promise<void> {
|
|
481
|
+
if (options?.modelId) await adapter.setModel(options.modelId);
|
|
482
|
+
if (options?.thinkingLevel) await adapter.control("thinking.set", { level: options.thinkingLevel });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** ACP form elicitation uses the client-facing reverse surface without owning a session runtime. */
|
|
486
|
+
export function createAcpExtensionUiContext(
|
|
487
|
+
connection: AgentSideConnection,
|
|
488
|
+
getSessionId: () => string,
|
|
489
|
+
capabilities: ClientCapabilities | undefined,
|
|
490
|
+
): {
|
|
491
|
+
select: (
|
|
492
|
+
message: string,
|
|
493
|
+
options: string[],
|
|
494
|
+
dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
495
|
+
) => Promise<string | undefined>;
|
|
496
|
+
confirm: (
|
|
497
|
+
message: string,
|
|
498
|
+
detail?: string,
|
|
499
|
+
dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
500
|
+
) => Promise<boolean>;
|
|
501
|
+
input: (
|
|
502
|
+
message: string,
|
|
503
|
+
placeholder?: string,
|
|
504
|
+
dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
505
|
+
) => Promise<string | undefined>;
|
|
506
|
+
} {
|
|
507
|
+
const elicit = async (
|
|
508
|
+
kind: "select" | "confirm" | "input",
|
|
509
|
+
message: string,
|
|
510
|
+
options: string[] | undefined,
|
|
511
|
+
dialog: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void } | undefined,
|
|
512
|
+
): Promise<unknown> => {
|
|
513
|
+
if (!capabilities?.elicitation?.form || dialog?.signal?.aborted) return undefined;
|
|
514
|
+
const request = (
|
|
515
|
+
connection as unknown as { unstable_createElicitation(input: JsonObject): Promise<JsonObject> }
|
|
516
|
+
).unstable_createElicitation({
|
|
517
|
+
sessionId: getSessionId(),
|
|
335
518
|
message,
|
|
336
519
|
requestedSchema: {
|
|
337
520
|
type: "object",
|
|
338
|
-
properties: {
|
|
521
|
+
properties: {
|
|
522
|
+
value:
|
|
523
|
+
kind === "confirm" ? { type: "boolean" } : { type: "string", ...(options ? { enum: options } : {}) },
|
|
524
|
+
},
|
|
339
525
|
required: ["value"],
|
|
340
526
|
},
|
|
341
|
-
})
|
|
342
|
-
.then(finish, error => {
|
|
343
|
-
// Caller may already have moved on via abort/timeout; suppress noise.
|
|
344
|
-
if (settled) return;
|
|
345
|
-
logger.warn("ACP elicitation failed", { sessionId, method, error });
|
|
346
|
-
finish(undefined);
|
|
347
527
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Build an {@link ExtensionUIContext} that translates skill/extension UI
|
|
371
|
-
* requests into ACP elicitations against `connection` for the session
|
|
372
|
-
* returned by `getSessionId()`. The id is read lazily at each elicitation
|
|
373
|
-
* because `AgentSession.sessionId` is a getter over `sessionManager` state
|
|
374
|
-
* that mutates when an extension command calls `ctx.newSession` /
|
|
375
|
-
* `ctx.switchSession` — snapshotting it once at factory time would route
|
|
376
|
-
* later elicitations to the pre-switch id. Live reads keep the bridge
|
|
377
|
-
* symmetric with every other `sessionUpdate` call in this file
|
|
378
|
-
* (`record.session.sessionId` is always evaluated at emit time).
|
|
379
|
-
*
|
|
380
|
-
* The non-elicitation surface (custom components, editor, theming,
|
|
381
|
-
* terminal input) remains stubbed — ACP clients render those themselves
|
|
382
|
-
* or not at all. Capability gating respects the client's `initialize`
|
|
383
|
-
* advertisement.
|
|
384
|
-
*/
|
|
385
|
-
export function createAcpExtensionUiContext(
|
|
386
|
-
connection: AgentSideConnection,
|
|
387
|
-
getSessionId: () => string,
|
|
388
|
-
clientCapabilities: ClientCapabilities | undefined,
|
|
389
|
-
): ExtensionUIContext {
|
|
390
|
-
const supportsForm = clientCapabilities?.elicitation?.form != null;
|
|
528
|
+
let timer: NodeJS.Timeout | undefined;
|
|
529
|
+
const timeout =
|
|
530
|
+
dialog?.timeout === undefined
|
|
531
|
+
? undefined
|
|
532
|
+
: new Promise<undefined>(resolve => {
|
|
533
|
+
timer = setTimeout(() => {
|
|
534
|
+
dialog.onTimeout?.();
|
|
535
|
+
resolve(undefined);
|
|
536
|
+
}, dialog.timeout);
|
|
537
|
+
});
|
|
538
|
+
try {
|
|
539
|
+
const response = timeout ? await Promise.race([request, timeout]) : await request;
|
|
540
|
+
return object(object(response)?.content)?.value;
|
|
541
|
+
} catch {
|
|
542
|
+
return undefined;
|
|
543
|
+
} finally {
|
|
544
|
+
if (timer) clearTimeout(timer);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
391
547
|
return {
|
|
392
|
-
select: async (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
connection,
|
|
396
|
-
getSessionId(),
|
|
397
|
-
"select",
|
|
398
|
-
title,
|
|
399
|
-
{ type: "string", enum: options },
|
|
400
|
-
dialogOptions,
|
|
401
|
-
);
|
|
402
|
-
return typeof value === "string" ? value : undefined;
|
|
403
|
-
},
|
|
404
|
-
confirm: async (title, message, dialogOptions) => {
|
|
405
|
-
if (!supportsForm) return false;
|
|
406
|
-
const value = await elicitFromAcpClient(
|
|
407
|
-
connection,
|
|
408
|
-
getSessionId(),
|
|
409
|
-
"confirm",
|
|
410
|
-
message.trim().length > 0 ? `${title}\n\n${message}` : title,
|
|
411
|
-
{ type: "boolean" },
|
|
412
|
-
dialogOptions,
|
|
413
|
-
);
|
|
414
|
-
return typeof value === "boolean" ? value : false;
|
|
548
|
+
select: async (message, options, dialog) => {
|
|
549
|
+
const value = await elicit("select", message, options, dialog);
|
|
550
|
+
return typeof value === "string" && options.includes(value) ? value : undefined;
|
|
415
551
|
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
getSessionId(),
|
|
421
|
-
"input",
|
|
422
|
-
title,
|
|
423
|
-
// ACP's `StringPropertySchema` has no `placeholder` field, so we
|
|
424
|
-
// surface the placeholder text as `description` — the closest
|
|
425
|
-
// semantic field a client can render alongside the input.
|
|
426
|
-
// Empty / whitespace-only placeholders are treated as absent.
|
|
427
|
-
{ type: "string", ...(placeholder?.trim() ? { description: placeholder } : {}) },
|
|
428
|
-
dialogOptions,
|
|
429
|
-
);
|
|
552
|
+
confirm: async (message, detail, dialog) =>
|
|
553
|
+
(await elicit("confirm", detail ? `${message}\n\n${detail}` : message, undefined, dialog)) === true,
|
|
554
|
+
input: async (message, placeholder, dialog) => {
|
|
555
|
+
const value = await elicit("input", placeholder ? `${message}\n\n${placeholder}` : message, undefined, dialog);
|
|
430
556
|
return typeof value === "string" ? value : undefined;
|
|
431
557
|
},
|
|
432
|
-
notify: (message, type) => {
|
|
433
|
-
logger.debug("ACP extension notification", { message, type });
|
|
434
|
-
},
|
|
435
|
-
onTerminalInput: () => () => {},
|
|
436
|
-
setStatus: () => {},
|
|
437
|
-
setWorkingMessage: () => {},
|
|
438
|
-
setWidget: () => {},
|
|
439
|
-
setFooter: () => {},
|
|
440
|
-
setHeader: () => {},
|
|
441
|
-
setTitle: () => {},
|
|
442
|
-
custom: async () => undefined as never,
|
|
443
|
-
pasteToEditor: () => {},
|
|
444
|
-
setEditorText: () => {},
|
|
445
|
-
getEditorText: () => "",
|
|
446
|
-
editor: async () => undefined,
|
|
447
|
-
setEditorComponent: () => {},
|
|
448
|
-
get theme() {
|
|
449
|
-
return theme;
|
|
450
|
-
},
|
|
451
|
-
getAllThemes: async () => [],
|
|
452
|
-
getTheme: async () => undefined,
|
|
453
|
-
setTheme: async () => ({ success: false, error: "Theme changes are unavailable in ACP mode" }),
|
|
454
|
-
getToolsExpanded: () => false,
|
|
455
|
-
setToolsExpanded: () => {},
|
|
456
558
|
};
|
|
457
559
|
}
|
|
458
560
|
|
|
561
|
+
/**
|
|
562
|
+
* ACP is a pure SDK client. Session processes are created and resumed by the
|
|
563
|
+
* broker, while all per-session operations use that session's authenticated SDK
|
|
564
|
+
* endpoint. This class deliberately imports neither AgentSession nor any local
|
|
565
|
+
* runtime host component.
|
|
566
|
+
*/
|
|
459
567
|
export class AcpAgent implements Agent {
|
|
460
|
-
#connection: AgentSideConnection;
|
|
461
|
-
#
|
|
462
|
-
#
|
|
463
|
-
#
|
|
464
|
-
#
|
|
465
|
-
#
|
|
568
|
+
readonly #connection: AgentSideConnection;
|
|
569
|
+
readonly #agentDir: string;
|
|
570
|
+
readonly #sessions = new Map<string, SessionRecord>();
|
|
571
|
+
readonly #attaching = new Map<string, PendingAttachment>();
|
|
572
|
+
readonly #resolvingExisting = new Map<string, PendingAttachment>();
|
|
573
|
+
readonly #knownSessionCwds = new Map<string, string>();
|
|
574
|
+
readonly #sessionEpochs = new Map<string, number>();
|
|
575
|
+
readonly #tearingDown = new Map<string, number>();
|
|
466
576
|
#clientCapabilities: ClientCapabilities | undefined;
|
|
467
|
-
#
|
|
468
|
-
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
#authorityGeneration = 0;
|
|
472
|
-
#authoritySnapshot: AcpAuthoritySnapshot | undefined;
|
|
473
|
-
/**
|
|
474
|
-
* Same-connection retry evidence for an incomplete verified delete. Keyed by
|
|
475
|
-
* session id. Phase-aware: every cleanup_pending carries the transcript
|
|
476
|
-
* identity at failure time, which is re-verified against the fresh candidate
|
|
477
|
-
* on retry so a replaced transcript cannot authorize a replacement; the
|
|
478
|
-
* artifacts-phase result additionally carries the artifact directory
|
|
479
|
-
* identity to re-accept. Cleared once a delete reaches `kind: "deleted"`.
|
|
480
|
-
*/
|
|
481
|
-
#pendingDeleteEvidence = new Map<string, PendingDeleteEvidence>();
|
|
482
|
-
#shutdownPromise: Promise<void> | undefined;
|
|
483
|
-
#lifecycleOperations = new Set<Promise<unknown>>();
|
|
484
|
-
#shuttingDown = false;
|
|
577
|
+
#broker: Promise<BrokerConnection> | undefined;
|
|
578
|
+
readonly #startupOptions: AcpStartupOptions | undefined;
|
|
579
|
+
#disposed = false;
|
|
580
|
+
#disposePromise: Promise<void> | undefined;
|
|
485
581
|
|
|
486
|
-
constructor(
|
|
582
|
+
constructor(
|
|
583
|
+
connection: AgentSideConnection,
|
|
584
|
+
options?: { agentDir?: string; startupOptions?: AcpStartupOptions } | unknown,
|
|
585
|
+
) {
|
|
487
586
|
this.#connection = connection;
|
|
488
|
-
|
|
489
|
-
this.#
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
587
|
+
const candidate = object(options);
|
|
588
|
+
this.#agentDir = typeof candidate?.agentDir === "string" ? candidate.agentDir : getAgentDir();
|
|
589
|
+
this.#startupOptions = parseAcpStartupOptions(candidate?.startupOptions);
|
|
590
|
+
queueMicrotask(() => {
|
|
591
|
+
if (connection.signal.aborted) {
|
|
592
|
+
this.#beginDispose();
|
|
593
|
+
} else {
|
|
594
|
+
connection.signal.addEventListener("abort", () => this.#beginDispose(), { once: true });
|
|
595
|
+
}
|
|
596
|
+
});
|
|
494
597
|
}
|
|
495
598
|
|
|
496
599
|
async initialize(params: InitializeRequest): Promise<InitializeResponse> {
|
|
497
|
-
this.#registerConnectionCleanup();
|
|
498
600
|
this.#clientCapabilities = params.clientCapabilities;
|
|
499
601
|
const authMethods: AuthMethod[] = [
|
|
500
602
|
{
|
|
@@ -514,2377 +616,899 @@ export class AcpAgent implements Agent {
|
|
|
514
616
|
}
|
|
515
617
|
return {
|
|
516
618
|
protocolVersion: PROTOCOL_VERSION,
|
|
517
|
-
agentInfo: {
|
|
518
|
-
name: "sayknow-cli",
|
|
519
|
-
title: "Sayknow-CLI",
|
|
520
|
-
version: CODING_AGENT_VERSION,
|
|
521
|
-
},
|
|
619
|
+
agentInfo: { name: "sayknow-cli", title: "Sayknow-CLI", version: packageJson.version },
|
|
522
620
|
authMethods,
|
|
523
621
|
agentCapabilities: {
|
|
524
622
|
loadSession: true,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
sse: true,
|
|
528
|
-
},
|
|
529
|
-
promptCapabilities: {
|
|
530
|
-
embeddedContext: true,
|
|
531
|
-
image: true,
|
|
532
|
-
},
|
|
533
|
-
sessionCapabilities: {
|
|
534
|
-
list: {},
|
|
535
|
-
fork: {},
|
|
536
|
-
resume: {},
|
|
537
|
-
close: {},
|
|
538
|
-
delete: {},
|
|
539
|
-
},
|
|
623
|
+
promptCapabilities: { embeddedContext: true, image: true },
|
|
624
|
+
sessionCapabilities: { list: {}, fork: {}, resume: {}, close: {}, delete: {} },
|
|
540
625
|
},
|
|
541
626
|
};
|
|
542
627
|
}
|
|
543
628
|
|
|
544
629
|
async authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse> {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// authenticated and surfacing a downstream model failure later.
|
|
548
|
-
const supportsTerminalAuth = this.#clientCapabilities?.auth?.terminal === true;
|
|
549
|
-
const validMethods = supportsTerminalAuth ? ["agent", "terminal"] : ["agent"];
|
|
550
|
-
if (!validMethods.includes(params.methodId)) {
|
|
551
|
-
throw new Error(`Unknown ACP auth method: ${params.methodId}`);
|
|
552
|
-
}
|
|
630
|
+
const methods = this.#clientCapabilities?.auth?.terminal ? ["agent", "terminal"] : ["agent"];
|
|
631
|
+
if (!methods.includes(params.methodId)) throw new Error(`Unknown ACP auth method: ${params.methodId}`);
|
|
553
632
|
return {};
|
|
554
633
|
}
|
|
555
634
|
|
|
556
635
|
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
557
|
-
this.#
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
configOptions: this.#buildConfigOptions(record.session),
|
|
568
|
-
modes: this.#buildModeState(record.session),
|
|
569
|
-
};
|
|
570
|
-
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
571
|
-
return response;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
575
|
-
this.#rejectIfShuttingDown();
|
|
576
|
-
const canonical = this.#stageCanonicalScope(params.cwd);
|
|
577
|
-
const existingRecord = this.#sessions.get(params.sessionId);
|
|
578
|
-
const record = await this.#trackLifecycle(() =>
|
|
579
|
-
this.#loadManagedSession(params.sessionId, params.cwd, params.mcpServers),
|
|
636
|
+
this.#assertNoMcpServers(params);
|
|
637
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
638
|
+
const result = await (await this.#brokerAdapter()).global(
|
|
639
|
+
"session.create",
|
|
640
|
+
{
|
|
641
|
+
cwd: params.cwd,
|
|
642
|
+
target: { path: params.cwd },
|
|
643
|
+
...(this.#startupOptions?.modelPreset ? { modelPreset: this.#startupOptions.modelPreset } : {}),
|
|
644
|
+
},
|
|
645
|
+
randomUUID(),
|
|
580
646
|
);
|
|
647
|
+
const id = sessionId(result);
|
|
648
|
+
this.#knownSessionCwds.set(id, params.cwd);
|
|
581
649
|
try {
|
|
582
|
-
this.#
|
|
583
|
-
await this.#
|
|
584
|
-
this.#
|
|
585
|
-
|
|
586
|
-
// transaction so a build/schedule failure rolls back the prepared
|
|
587
|
-
// record and never pins the connection.
|
|
588
|
-
const response: LoadSessionResponse = {
|
|
589
|
-
configOptions: this.#buildConfigOptions(record.session),
|
|
590
|
-
modes: this.#buildModeState(record.session),
|
|
591
|
-
};
|
|
592
|
-
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
593
|
-
// Commit the cwd scope only after replay, response construction, and
|
|
594
|
-
// bootstrap scheduling all succeed — a failed first load leaves no
|
|
595
|
-
// half-loaded session and does not pin the connection.
|
|
596
|
-
this.#commitCanonicalScope(canonical);
|
|
597
|
-
this.#invalidateAuthority();
|
|
598
|
-
return response;
|
|
650
|
+
await this.#attach(id, params.cwd, endpoint(result));
|
|
651
|
+
await applyAcpStartupOptions(this.#adapter(id), this.#startupOptions);
|
|
652
|
+
this.#scheduleBootstrap(id);
|
|
653
|
+
return { sessionId: id, ...(await this.#sessionState(id)) };
|
|
599
654
|
} catch (error) {
|
|
600
|
-
|
|
601
|
-
// first load leaves no half-loaded session and does not pin the
|
|
602
|
-
// connection, and commit no cwd scope.
|
|
603
|
-
if (record !== existingRecord) {
|
|
604
|
-
this.#sessions.delete(record.session.sessionId);
|
|
605
|
-
await this.#disposeSessionRecord(record);
|
|
606
|
-
}
|
|
655
|
+
await this.#discardNewSession(id);
|
|
607
656
|
throw error;
|
|
608
657
|
}
|
|
609
658
|
}
|
|
610
659
|
|
|
611
|
-
async
|
|
612
|
-
this.#
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
this.#
|
|
618
|
-
// Cwd-less / global listing stays display-only and non-authorizing.
|
|
619
|
-
if (!scope) {
|
|
620
|
-
const sessions = await this.#listStoredSessions(undefined);
|
|
621
|
-
const offset = this.#parseCursor(params.cursor ?? undefined);
|
|
622
|
-
const paged = sessions.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
623
|
-
const nextOffset = offset + paged.length;
|
|
624
|
-
return {
|
|
625
|
-
sessions: paged.map(session => this.#toSessionInfo(session)),
|
|
626
|
-
nextCursor: nextOffset < sessions.length ? String(nextOffset) : undefined,
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
// Explicit scoped list: stage scope, strict raw inventory, build the full
|
|
630
|
-
// authorization/conflict snapshot BEFORE page slicing so duplicate ids
|
|
631
|
-
// beyond page 1 are known before the first page response. Scope commits
|
|
632
|
-
// only after cursor validation, stored-session read, and full response
|
|
633
|
-
// construction succeed — a failed first list never pins the connection.
|
|
634
|
-
if (params.cursor === undefined) {
|
|
635
|
-
// A new first-page request is a new issuance event. Refresh the complete
|
|
636
|
-
// snapshot and invalidate cursors minted by the previous issuance.
|
|
637
|
-
this.#invalidateAuthority();
|
|
638
|
-
}
|
|
639
|
-
const snapshot = this.#buildAuthoritySnapshot(scope);
|
|
640
|
-
// Scoped cursors carry the authority generation at which they were minted
|
|
641
|
-
// (form `${generation}:${offset}`). A lifecycle change between page 1 and
|
|
642
|
-
// page 2 bumps the generation, so the old cursor is rejected instead of
|
|
643
|
-
// silently rebuilding at the new generation. The cwd-less display cursor
|
|
644
|
-
// stays a plain offset (see #parseCursor).
|
|
645
|
-
const offset = this.#parseScopedCursor(params.cursor ?? undefined, snapshot.generation);
|
|
646
|
-
const stored = await this.#listStoredSessions(scope);
|
|
647
|
-
const storedById = new Map(stored.map(s => [s.id, s] as const));
|
|
648
|
-
const pageIds = snapshot.orderedIds.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
649
|
-
const nextOffset = offset + pageIds.length;
|
|
650
|
-
const pageSessions = pageIds.map(id => storedById.get(id)).filter((s): s is StoredSessionInfo => s !== undefined);
|
|
651
|
-
const response: ListSessionsResponse = {
|
|
652
|
-
sessions: pageSessions.map(session => this.#toSessionInfo(session)),
|
|
653
|
-
nextCursor: nextOffset < snapshot.orderedIds.length ? `${snapshot.generation}:${nextOffset}` : undefined,
|
|
654
|
-
};
|
|
655
|
-
// Commit the scope only after cursor validation, stored-session read, and
|
|
656
|
-
// full response construction succeed.
|
|
657
|
-
this.#commitCanonicalScope(scope);
|
|
658
|
-
return response;
|
|
660
|
+
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
661
|
+
this.#assertNoMcpServers(params);
|
|
662
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
663
|
+
await this.#attachExisting(params.sessionId, params.cwd);
|
|
664
|
+
await this.#replaySession(params.sessionId);
|
|
665
|
+
this.#scheduleBootstrap(params.sessionId);
|
|
666
|
+
return await this.#sessionState(params.sessionId);
|
|
659
667
|
}
|
|
660
668
|
|
|
661
669
|
async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
|
662
|
-
this.#
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
);
|
|
667
|
-
this.#rejectIfShuttingDown();
|
|
668
|
-
this.#commitCanonicalScope(canonical);
|
|
669
|
-
this.#invalidateAuthority();
|
|
670
|
-
const response: ResumeSessionResponse = {
|
|
671
|
-
configOptions: this.#buildConfigOptions(record.session),
|
|
672
|
-
modes: this.#buildModeState(record.session),
|
|
673
|
-
};
|
|
674
|
-
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
675
|
-
return response;
|
|
670
|
+
this.#assertNoMcpServers(params);
|
|
671
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
672
|
+
await this.#attachExisting(params.sessionId, params.cwd);
|
|
673
|
+
this.#scheduleBootstrap(params.sessionId);
|
|
674
|
+
return await this.#sessionState(params.sessionId);
|
|
676
675
|
}
|
|
677
676
|
|
|
678
677
|
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
|
|
679
|
-
this.#
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
this.#
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
678
|
+
this.#assertNoMcpServers(params);
|
|
679
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
680
|
+
const source = await this.#resolveSavedSession(params.sessionId, params.cwd);
|
|
681
|
+
const result = await (await this.#brokerAdapter()).global(
|
|
682
|
+
"session.fork",
|
|
683
|
+
{
|
|
684
|
+
cwd: params.cwd,
|
|
685
|
+
sourceSessionId: params.sessionId,
|
|
686
|
+
sourceSessionPath: source,
|
|
687
|
+
target: { path: params.cwd },
|
|
688
|
+
},
|
|
689
|
+
randomUUID(),
|
|
690
|
+
);
|
|
691
|
+
const id = sessionId(result);
|
|
692
|
+
this.#knownSessionCwds.set(id, params.cwd);
|
|
693
|
+
try {
|
|
694
|
+
await this.#attach(id, params.cwd, endpoint(result));
|
|
695
|
+
this.#scheduleBootstrap(id);
|
|
696
|
+
return { sessionId: id, ...(await this.#sessionState(id)) };
|
|
697
|
+
} catch (error) {
|
|
698
|
+
await this.#discardNewSession(id);
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
|
|
704
|
+
if (params.cwd) this.#assertAbsoluteCwd(params.cwd);
|
|
705
|
+
const result = object(await (await this.#brokerAdapter()).global("session.list"));
|
|
706
|
+
const listing = object(result?.result) ?? result;
|
|
707
|
+
const listed = Array.isArray(listing?.sessions) ? listing.sessions : [];
|
|
708
|
+
if (params.cwd) {
|
|
709
|
+
const discovered = new Set<string>();
|
|
710
|
+
for (const session of listed) {
|
|
711
|
+
const candidate = object(session) as BrokerSession | undefined;
|
|
712
|
+
if (
|
|
713
|
+
typeof candidate?.sessionId !== "string" ||
|
|
714
|
+
typeof candidate.locator?.repo !== "string" ||
|
|
715
|
+
path.resolve(candidate.locator.repo) !== path.resolve(params.cwd)
|
|
716
|
+
)
|
|
717
|
+
continue;
|
|
718
|
+
if (discovered.has(candidate.sessionId))
|
|
719
|
+
throw new AcpSdkAdapterError("conflict", `Broker returned duplicate session id: ${candidate.sessionId}`);
|
|
720
|
+
discovered.add(candidate.sessionId);
|
|
721
|
+
const knownCwd = this.#knownSessionCwds.get(candidate.sessionId);
|
|
722
|
+
if (knownCwd && path.resolve(knownCwd) !== path.resolve(params.cwd))
|
|
723
|
+
throw new AcpSdkAdapterError(
|
|
724
|
+
"conflict",
|
|
725
|
+
`ACP session ${candidate.sessionId} has conflicting cwd authority.`,
|
|
726
|
+
);
|
|
727
|
+
this.#knownSessionCwds.set(candidate.sessionId, params.cwd);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return paginateAcpSessions(listed, params.cwd ?? undefined, this.#cursor(params.cursor));
|
|
692
731
|
}
|
|
693
732
|
|
|
694
733
|
async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
|
|
695
734
|
const record = this.#sessions.get(params.sessionId);
|
|
696
|
-
|
|
735
|
+
const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId);
|
|
736
|
+
// ACP close has no cwd. Only connection-owned sessions may reach broker lifecycle control.
|
|
737
|
+
if (!cwd) return {};
|
|
738
|
+
this.#beginTeardown(params.sessionId);
|
|
739
|
+
try {
|
|
740
|
+
await this.#teardownSession(params.sessionId, "closed", true);
|
|
741
|
+
this.#knownSessionCwds.delete(params.sessionId);
|
|
697
742
|
return {};
|
|
743
|
+
} finally {
|
|
744
|
+
this.#finishTeardown(params.sessionId);
|
|
698
745
|
}
|
|
699
|
-
this.#rejectIfTerminal(record);
|
|
700
|
-
await this.#closeManagedSession(params.sessionId, record);
|
|
701
|
-
this.#invalidateAuthority();
|
|
702
|
-
return {};
|
|
703
746
|
}
|
|
704
747
|
|
|
705
|
-
/**
|
|
706
|
-
* Hard delete an ACP session. `DeleteSessionRequest` carries only `sessionId`;
|
|
707
|
-
* the server owns authorization through the immutable canonical cwd scope and
|
|
708
|
-
* the complete strict scoped inventory snapshot.
|
|
709
|
-
*
|
|
710
|
-
* - No scope / unknown / already-deleted id → lookup-free `{}`.
|
|
711
|
-
* - Duplicate/conflict id → visible error; neither transcript nor artifacts change.
|
|
712
|
-
* - Active session → reserve `deleting`, drain/cancel prompt, flush, strict
|
|
713
|
-
* dispose/close, recheck identity/state, then verified artifact-first deletion.
|
|
714
|
-
* - Concurrent delete callers join the terminal promise.
|
|
715
|
-
*/
|
|
716
748
|
async deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse> {
|
|
717
|
-
// Before scope: return {} without scanning/listing storage.
|
|
718
|
-
if (this.#canonicalCwdScope === undefined) {
|
|
719
|
-
return {};
|
|
720
|
-
}
|
|
721
|
-
if (this.#shuttingDown) {
|
|
722
|
-
throw new Error("ACP session delete is unavailable during shutdown");
|
|
723
|
-
}
|
|
724
749
|
const record = this.#sessions.get(params.sessionId);
|
|
725
|
-
|
|
726
|
-
|
|
750
|
+
const cwd = record?.cwd ?? this.#knownSessionCwds.get(params.sessionId);
|
|
751
|
+
// ACP's delete request has no cwd. Only delete sessions this connection has
|
|
752
|
+
// already scoped through the broker; unknown ids remain the protocol no-op.
|
|
753
|
+
if (!cwd) return {};
|
|
754
|
+
this.#beginTeardown(params.sessionId);
|
|
755
|
+
try {
|
|
756
|
+
await this.#teardownSession(params.sessionId, "deleted", true);
|
|
757
|
+
let saved: string;
|
|
758
|
+
try {
|
|
759
|
+
saved = await this.#resolveSavedSession(params.sessionId, cwd);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
if (error instanceof AcpSdkAdapterError && error.code === "not_found") {
|
|
762
|
+
this.#knownSessionCwds.delete(params.sessionId);
|
|
763
|
+
return {};
|
|
764
|
+
}
|
|
765
|
+
throw error;
|
|
766
|
+
}
|
|
767
|
+
await (await this.#brokerAdapter()).global(
|
|
768
|
+
"session.delete",
|
|
769
|
+
{ sessionId: params.sessionId, sessionPath: saved, cwd, target: { path: cwd } },
|
|
770
|
+
this.#lifecycleIdempotencyKey(params.sessionId, "session.delete"),
|
|
771
|
+
);
|
|
772
|
+
this.#knownSessionCwds.delete(params.sessionId);
|
|
773
|
+
return {};
|
|
774
|
+
} finally {
|
|
775
|
+
this.#finishTeardown(params.sessionId);
|
|
727
776
|
}
|
|
728
|
-
// Inactive: read authority from the strict inventory snapshot.
|
|
729
|
-
return await this.#deleteInactiveSession(params.sessionId);
|
|
730
777
|
}
|
|
731
778
|
|
|
732
779
|
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
this.#
|
|
736
|
-
await this.#
|
|
737
|
-
sessionId:
|
|
738
|
-
update:
|
|
780
|
+
if (params.modeId !== ACP_DEFAULT_MODE_ID && params.modeId !== ACP_PLAN_MODE_ID)
|
|
781
|
+
throw new Error(`Unsupported ACP mode: ${params.modeId}`);
|
|
782
|
+
await this.#adapter(params.sessionId).control("mode.plan.set", { on: params.modeId === ACP_PLAN_MODE_ID });
|
|
783
|
+
await this.#publishSessionUpdate(params.sessionId, {
|
|
784
|
+
sessionId: params.sessionId,
|
|
785
|
+
update: { sessionUpdate: "current_mode_update", currentModeId: params.modeId },
|
|
739
786
|
});
|
|
740
|
-
await this.#pushConfigOptionUpdate(record);
|
|
741
787
|
return {};
|
|
742
788
|
}
|
|
743
789
|
|
|
744
790
|
async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
|
|
745
|
-
|
|
746
|
-
this.#rejectIfTerminal(record);
|
|
747
|
-
if (typeof params.value === "boolean") {
|
|
791
|
+
if (typeof params.value !== "string")
|
|
748
792
|
throw new Error(`Unsupported boolean ACP config option: ${params.configId}`);
|
|
749
|
-
}
|
|
750
|
-
|
|
751
793
|
switch (params.configId) {
|
|
752
794
|
case MODE_CONFIG_ID:
|
|
753
|
-
this
|
|
795
|
+
await this.setSessionMode({ sessionId: params.sessionId, modeId: params.value });
|
|
754
796
|
break;
|
|
755
797
|
case MODEL_CONFIG_ID:
|
|
756
|
-
await this.#
|
|
798
|
+
await this.#adapter(params.sessionId).setModel(params.value);
|
|
757
799
|
break;
|
|
758
800
|
case THINKING_CONFIG_ID:
|
|
759
|
-
this.#
|
|
801
|
+
await this.#adapter(params.sessionId).control("thinking.set", { level: params.value });
|
|
760
802
|
break;
|
|
761
|
-
default:
|
|
762
|
-
|
|
803
|
+
default: {
|
|
804
|
+
const operation = ACP_CONFIG_CONTROL_OPERATIONS[params.configId];
|
|
805
|
+
if (!operation) throw new Error(`Unknown ACP config option: ${params.configId}`);
|
|
806
|
+
await this.#adapter(params.sessionId).control(operation, { mode: params.value });
|
|
807
|
+
}
|
|
763
808
|
}
|
|
809
|
+
const state = await this.#sessionState(params.sessionId);
|
|
810
|
+
await this.#publishSessionUpdate(params.sessionId, {
|
|
811
|
+
sessionId: params.sessionId,
|
|
812
|
+
update: { sessionUpdate: "config_option_update", configOptions: state.configOptions ?? [] },
|
|
813
|
+
});
|
|
814
|
+
return { configOptions: state.configOptions ?? [] };
|
|
815
|
+
}
|
|
764
816
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
817
|
+
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
818
|
+
const record = this.#sessions.get(params.sessionId);
|
|
819
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${params.sessionId}`);
|
|
820
|
+
if (record.activePrompt) throw new AcpSdkAdapterError("conflict", "ACP session already has an active prompt.");
|
|
821
|
+
const payload = acpPromptPayload(params.prompt);
|
|
822
|
+
let waiter!: PromptWaiter;
|
|
823
|
+
const response = new Promise<PromptResponse>((resolve, reject) => {
|
|
824
|
+
waiter = {
|
|
825
|
+
cancelRequested: false,
|
|
826
|
+
acknowledged: false,
|
|
827
|
+
activityObserved: false,
|
|
828
|
+
steeringAtAcknowledgement: record.busy,
|
|
829
|
+
boundary: record.inboundSequence,
|
|
830
|
+
correlation: {},
|
|
831
|
+
resolve,
|
|
832
|
+
reject,
|
|
833
|
+
};
|
|
834
|
+
record.activePrompt = waiter;
|
|
835
|
+
});
|
|
836
|
+
try {
|
|
837
|
+
const acknowledgement = await record.adapter.prompt({
|
|
838
|
+
text: payload.text,
|
|
839
|
+
...(payload.images.length ? { images: payload.images } : {}),
|
|
772
840
|
});
|
|
841
|
+
waiter.steeringAtAcknowledgement = record.busy;
|
|
842
|
+
// Capture the ingress boundary after the command acknowledgement. Frames
|
|
843
|
+
// queued before this point are stale with respect to this ACP prompt.
|
|
844
|
+
waiter.boundary = record.inboundSequence;
|
|
845
|
+
waiter.correlation = correlationFrom(acknowledgement);
|
|
846
|
+
waiter.acknowledged = true;
|
|
847
|
+
this.#settlePrompt(record, waiter);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (record.activePrompt === waiter) record.activePrompt = undefined;
|
|
850
|
+
throw error;
|
|
773
851
|
}
|
|
852
|
+
return await response;
|
|
853
|
+
}
|
|
774
854
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
855
|
+
async cancel(params: { sessionId: string }): Promise<void> {
|
|
856
|
+
const record = this.#sessions.get(params.sessionId);
|
|
857
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${params.sessionId}`);
|
|
858
|
+
const waiter = record.activePrompt;
|
|
859
|
+
const acknowledgement = await record.adapter.cancel();
|
|
860
|
+
const result = object(object(acknowledgement)?.result) ?? object(acknowledgement);
|
|
861
|
+
if (result?.aborted !== true)
|
|
862
|
+
throw new AcpSdkAdapterError(
|
|
863
|
+
"abort_unacknowledged",
|
|
864
|
+
"SDK did not acknowledge cancellation of the active prompt.",
|
|
865
|
+
);
|
|
866
|
+
// Do not retroactively mark a waiter that already settled while the abort
|
|
867
|
+
// request was in flight. A cancelled response means the abort itself won.
|
|
868
|
+
if (waiter && record.activePrompt === waiter) waiter.cancelRequested = true;
|
|
784
869
|
}
|
|
785
870
|
|
|
786
|
-
async
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
// still in flight. We deliberately swallow the prompt rejection (the
|
|
798
|
-
// owning caller already received it) but let cleanup rejections
|
|
799
|
-
// propagate — a timed-out cancel must fail this queued prompt instead
|
|
800
|
-
// of letting it run on a session that is about to be closed.
|
|
801
|
-
await previousTurn.promise.catch(() => undefined);
|
|
802
|
-
await previousTurn.cleanup;
|
|
871
|
+
async extMethod(method: string, params: JsonObject): Promise<JsonObject> {
|
|
872
|
+
try {
|
|
873
|
+
if (method === "_skc/sdk/global") {
|
|
874
|
+
const result = await (await this.#brokerAdapter()).handle(method, params);
|
|
875
|
+
return object(result) ?? {};
|
|
876
|
+
}
|
|
877
|
+
if (method === "_skc/sdk/control" || method === "_skc/sdk/query") {
|
|
878
|
+
const id = typeof params.sessionId === "string" ? params.sessionId : undefined;
|
|
879
|
+
if (!id) throw new AcpSdkAdapterError("invalid_input", "sessionId is required.");
|
|
880
|
+
const result = await this.#adapter(id).handle(method, params);
|
|
881
|
+
return object(result) ?? {};
|
|
803
882
|
}
|
|
883
|
+
throw new AcpSdkAdapterError("method_not_found", `Unknown ACP ext method: ${method}`);
|
|
884
|
+
} catch (error) {
|
|
885
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "internal";
|
|
886
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
887
|
+
return { ok: false, error: { code, message } };
|
|
888
|
+
}
|
|
889
|
+
}
|
|
804
890
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
cleanup: undefined,
|
|
813
|
-
usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
814
|
-
unsubscribe: undefined,
|
|
815
|
-
resolve: pendingPrompt.resolve,
|
|
816
|
-
reject: pendingPrompt.reject,
|
|
817
|
-
promise: pendingPrompt.promise,
|
|
818
|
-
};
|
|
891
|
+
async extNotification(_method: string, _params: JsonObject): Promise<void> {}
|
|
892
|
+
get signal(): AbortSignal {
|
|
893
|
+
return this.#connection.signal;
|
|
894
|
+
}
|
|
895
|
+
get closed(): Promise<void> {
|
|
896
|
+
return this.#connection.closed;
|
|
897
|
+
}
|
|
819
898
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
});
|
|
899
|
+
#sessionEpoch(id: string): number {
|
|
900
|
+
return this.#sessionEpochs.get(id) ?? 0;
|
|
901
|
+
}
|
|
824
902
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
903
|
+
#advanceSessionEpoch(id: string): void {
|
|
904
|
+
this.#sessionEpochs.set(id, this.#sessionEpoch(id) + 1);
|
|
905
|
+
}
|
|
828
906
|
|
|
829
|
-
|
|
830
|
-
|
|
907
|
+
#assertSessionEpoch(id: string, epoch: number): void {
|
|
908
|
+
if (this.#disposed || this.#tearingDown.has(id) || this.#sessionEpoch(id) !== epoch)
|
|
909
|
+
throw new AcpSdkAdapterError("connection_closed", `ACP session ${id} was closed while attaching.`);
|
|
831
910
|
}
|
|
832
911
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
912
|
+
#beginTeardown(id: string): void {
|
|
913
|
+
this.#tearingDown.set(id, (this.#tearingDown.get(id) ?? 0) + 1);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
#finishTeardown(id: string): void {
|
|
917
|
+
const remaining = (this.#tearingDown.get(id) ?? 1) - 1;
|
|
918
|
+
if (remaining > 0) this.#tearingDown.set(id, remaining);
|
|
919
|
+
else this.#tearingDown.delete(id);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
#lifecycleIdempotencyKey(id: string, operation: "session.close" | "session.delete"): string {
|
|
923
|
+
return `acp:${operation}:${id}`;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
#isAlreadyGone(error: unknown): boolean {
|
|
927
|
+
return (
|
|
928
|
+
typeof error === "object" &&
|
|
929
|
+
error !== null &&
|
|
930
|
+
"code" in error &&
|
|
931
|
+
((error.code === "not_found" || error.code === "resource_gone") as boolean)
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async #attachExisting(id: string, cwd: string): Promise<void> {
|
|
936
|
+
const epoch = this.#sessionEpoch(id);
|
|
937
|
+
const attached = this.#sessions.get(id);
|
|
938
|
+
if (attached) {
|
|
939
|
+
if (path.resolve(attached.cwd) !== path.resolve(cwd))
|
|
940
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const knownCwd = this.#knownSessionCwds.get(id);
|
|
944
|
+
if (knownCwd && path.resolve(knownCwd) !== path.resolve(cwd))
|
|
945
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
946
|
+
const resolving = this.#resolvingExisting.get(id);
|
|
947
|
+
if (resolving?.epoch === epoch) {
|
|
948
|
+
await resolving.task;
|
|
949
|
+
this.#assertSessionEpoch(id, epoch);
|
|
950
|
+
const resolved = this.#sessions.get(id);
|
|
951
|
+
if (!resolved) throw new AcpSdkAdapterError("unavailable", `ACP session ${id} did not attach.`);
|
|
952
|
+
if (path.resolve(resolved.cwd) !== path.resolve(cwd))
|
|
953
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const task = this.#resolveExistingAttachment(id, cwd, epoch);
|
|
958
|
+
const pending = { epoch, task };
|
|
959
|
+
this.#resolvingExisting.set(id, pending);
|
|
842
960
|
try {
|
|
843
|
-
|
|
961
|
+
await task;
|
|
962
|
+
this.#assertSessionEpoch(id, epoch);
|
|
844
963
|
} finally {
|
|
845
|
-
|
|
846
|
-
if (record.promptQueue.release === releaseQueue) {
|
|
847
|
-
record.promptQueue.release = undefined;
|
|
848
|
-
}
|
|
964
|
+
if (this.#resolvingExisting.get(id) === pending) this.#resolvingExisting.delete(id);
|
|
849
965
|
}
|
|
850
966
|
}
|
|
851
967
|
|
|
852
|
-
async #
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (
|
|
968
|
+
async #resolveExistingAttachment(id: string, cwd: string, epoch: number): Promise<void> {
|
|
969
|
+
this.#assertSessionEpoch(id, epoch);
|
|
970
|
+
const indexed = await this.#scopedBrokerSession(id, cwd);
|
|
971
|
+
this.#assertSessionEpoch(id, epoch);
|
|
972
|
+
if (indexed?.live) {
|
|
973
|
+
const result = await this.#brokerEndpoint(id, indexed.endpointGeneration);
|
|
974
|
+
this.#assertSessionEpoch(id, epoch);
|
|
975
|
+
await this.#attach(id, cwd, endpoint(result), epoch);
|
|
857
976
|
return;
|
|
858
977
|
}
|
|
859
978
|
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
cwd:
|
|
865
|
-
|
|
866
|
-
refreshCommands: () => this.#emitAvailableCommandsUpdate(record),
|
|
867
|
-
reloadPlugins: () => this.#reloadPluginState(record),
|
|
868
|
-
notifyTitleChanged: async () => {
|
|
869
|
-
await this.#connection.sessionUpdate({
|
|
870
|
-
sessionId: record.session.sessionId,
|
|
871
|
-
update: {
|
|
872
|
-
sessionUpdate: "session_info_update",
|
|
873
|
-
title: record.session.sessionName,
|
|
874
|
-
updatedAt: new Date().toISOString(),
|
|
875
|
-
},
|
|
876
|
-
});
|
|
877
|
-
},
|
|
878
|
-
notifyConfigChanged: async () => {
|
|
879
|
-
await this.#pushConfigOptionUpdate(record);
|
|
880
|
-
},
|
|
881
|
-
});
|
|
882
|
-
if (builtinResult !== false) {
|
|
883
|
-
if ("prompt" in builtinResult) {
|
|
884
|
-
await record.session.prompt(builtinResult.prompt, { images });
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
const promptTurn = record.promptTurn;
|
|
888
|
-
this.#finishPrompt(record, {
|
|
889
|
-
stopReason: "end_turn",
|
|
890
|
-
usage: this.#buildTurnUsage(
|
|
891
|
-
promptTurn?.usageBaseline ??
|
|
892
|
-
this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
893
|
-
record.session.sessionManager.getUsageStatistics(),
|
|
894
|
-
),
|
|
895
|
-
_meta: promptTurn ? { userMessageId: promptTurn.userMessageId } : undefined,
|
|
896
|
-
});
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
if (await this.#tryRunSkillCommand(record, text, { directAliasMayCollide: false })) {
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
await record.session.prompt(text, { images });
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
async #tryRunSkillCommand(
|
|
908
|
-
record: ManagedSessionRecord,
|
|
909
|
-
text: string,
|
|
910
|
-
options: { directAliasMayCollide?: boolean } = {},
|
|
911
|
-
): Promise<boolean> {
|
|
912
|
-
if (!record.session.skillsSettings?.enableSkillCommands) {
|
|
913
|
-
return false;
|
|
914
|
-
}
|
|
915
|
-
const skillsByCommandName = new Map(
|
|
916
|
-
record.session.skills
|
|
917
|
-
.filter(skill => skill.hide !== true)
|
|
918
|
-
.flatMap(skill => getSkillSlashCommandNames(skill).map(commandName => [commandName, skill] as const)),
|
|
979
|
+
const saved = await this.#resolveSavedSession(id, cwd);
|
|
980
|
+
this.#assertSessionEpoch(id, epoch);
|
|
981
|
+
const result = await (await this.#brokerAdapter()).global(
|
|
982
|
+
"session.resume",
|
|
983
|
+
{ cwd, sessionId: id, sessionPath: saved, target: { path: cwd } },
|
|
984
|
+
randomUUID(),
|
|
919
985
|
);
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
for (let index = 0; index < invocations.length; index += 1) {
|
|
923
|
-
const invocation = invocations[index];
|
|
924
|
-
if (!invocation) continue;
|
|
925
|
-
const activationResult = await resolveSubskillActivationForSkillInvocation({
|
|
926
|
-
cwd: record.session.sessionManager.getCwd(),
|
|
927
|
-
sessionId: record.session.sessionId,
|
|
928
|
-
skillName: invocation.skill.name,
|
|
929
|
-
args: invocation.args,
|
|
930
|
-
});
|
|
931
|
-
const built = await buildSkillPromptMessage(invocation.skill, activationResult.cleanedArgs, {
|
|
932
|
-
subskillActivation: activationResult.activation,
|
|
933
|
-
subskillActivationSet: activationResult.activeSubskillsToPersist,
|
|
934
|
-
cwd: record.session.sessionManager.getCwd(),
|
|
935
|
-
sessionId: record.session.sessionId,
|
|
936
|
-
});
|
|
937
|
-
if (index === invocations.length - 1) {
|
|
938
|
-
await record.session.promptCustomMessage({
|
|
939
|
-
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
940
|
-
content: built.message,
|
|
941
|
-
display: true,
|
|
942
|
-
details: built.details,
|
|
943
|
-
attribution: "user",
|
|
944
|
-
});
|
|
945
|
-
} else {
|
|
946
|
-
await record.session.sendCustomMessage({
|
|
947
|
-
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
948
|
-
content: built.message,
|
|
949
|
-
display: true,
|
|
950
|
-
details: built.details,
|
|
951
|
-
attribution: "user",
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
return true;
|
|
956
|
-
}
|
|
957
|
-
const slashText = text.trimStart();
|
|
958
|
-
if (!slashText.startsWith("/")) {
|
|
959
|
-
return false;
|
|
960
|
-
}
|
|
961
|
-
const spaceIndex = slashText.indexOf(" ");
|
|
962
|
-
const commandName = spaceIndex === -1 ? slashText.slice(1) : slashText.slice(1, spaceIndex);
|
|
963
|
-
const args = spaceIndex === -1 ? "" : slashText.slice(spaceIndex + 1).trim();
|
|
964
|
-
if (
|
|
965
|
-
!isNamespacedSkillSlashCommandName(commandName) &&
|
|
966
|
-
options.directAliasMayCollide !== true &&
|
|
967
|
-
(await this.#directSkillAliasCollides(record, commandName))
|
|
968
|
-
) {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
const skill = record.session.skills.find(candidate => isSkillSlashCommandName(commandName, candidate));
|
|
972
|
-
if (!skill || skill.hide === true) {
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
const activationResult = await resolveSubskillActivationForSkillInvocation({
|
|
976
|
-
cwd: record.session.sessionManager.getCwd(),
|
|
977
|
-
sessionId: record.session.sessionId,
|
|
978
|
-
skillName: skill.name,
|
|
979
|
-
args,
|
|
980
|
-
});
|
|
981
|
-
const built = await buildSkillPromptMessage(skill, activationResult.cleanedArgs, {
|
|
982
|
-
subskillActivation: activationResult.activation,
|
|
983
|
-
subskillActivationSet: activationResult.activeSubskillsToPersist,
|
|
984
|
-
cwd: record.session.sessionManager.getCwd(),
|
|
985
|
-
sessionId: record.session.sessionId,
|
|
986
|
-
});
|
|
987
|
-
await record.session.promptCustomMessage({
|
|
988
|
-
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
989
|
-
content: built.message,
|
|
990
|
-
display: true,
|
|
991
|
-
details: built.details,
|
|
992
|
-
attribution: "user",
|
|
993
|
-
});
|
|
994
|
-
return true;
|
|
986
|
+
this.#assertSessionEpoch(id, epoch);
|
|
987
|
+
await this.#attach(id, cwd, endpoint(result), epoch);
|
|
995
988
|
}
|
|
996
989
|
|
|
997
|
-
async #
|
|
998
|
-
|
|
999
|
-
|
|
990
|
+
async #scopedBrokerSession(id: string, cwd: string): Promise<BrokerSession | undefined> {
|
|
991
|
+
const response = object(await (await this.#brokerAdapter()).global("session.list", { cwd }));
|
|
992
|
+
const result = object(response?.result) ?? response;
|
|
993
|
+
const matches: BrokerSession[] = [];
|
|
994
|
+
for (const item of Array.isArray(result?.sessions) ? result.sessions : []) {
|
|
995
|
+
const session = object(item) as BrokerSession | undefined;
|
|
996
|
+
if (session?.sessionId !== id) continue;
|
|
997
|
+
if (typeof session.locator?.repo !== "string" || path.resolve(session.locator.repo) !== path.resolve(cwd))
|
|
998
|
+
throw new AcpSdkAdapterError("conflict", `Broker returned conflicting session scope for ${id}.`);
|
|
999
|
+
matches.push(session);
|
|
1000
1000
|
}
|
|
1001
|
-
|
|
1002
|
-
return
|
|
1001
|
+
if (matches.length > 1) throw new AcpSdkAdapterError("conflict", `Broker returned duplicate session id: ${id}`);
|
|
1002
|
+
return matches[0];
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
-
async
|
|
1006
|
-
|
|
1007
|
-
const
|
|
1008
|
-
if (
|
|
1005
|
+
async #attach(id: string, cwd: string, discovered: Endpoint, epoch = this.#sessionEpoch(id)): Promise<void> {
|
|
1006
|
+
this.#assertSessionEpoch(id, epoch);
|
|
1007
|
+
const existing = this.#sessions.get(id);
|
|
1008
|
+
if (existing) {
|
|
1009
|
+
if (path.resolve(existing.cwd) !== path.resolve(cwd))
|
|
1010
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
1009
1011
|
return;
|
|
1010
1012
|
}
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
await
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
if (
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
logger.warn("ACP cancel cleanup timed out; closing session", { sessionId: record.session.sessionId, error });
|
|
1021
|
-
await this.#closeManagedSession(record.session.sessionId, record);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
/**
|
|
1026
|
-
* Transition a still-running turn into cancellation: mark intent, drop the live-event
|
|
1027
|
-
* subscription, start the bounded `abort()` race, and resolve the ACP prompt response
|
|
1028
|
-
* with `stopReason: "cancelled"` so the client sees acceptance immediately. The
|
|
1029
|
-
* returned promise is the cleanup barrier — it resolves when `abort()` completes and
|
|
1030
|
-
* rejects when the timeout fires. Idempotent: a second call returns the same barrier.
|
|
1031
|
-
*/
|
|
1032
|
-
#beginCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
|
|
1033
|
-
if (promptTurn.cleanup) {
|
|
1034
|
-
return promptTurn.cleanup;
|
|
1035
|
-
}
|
|
1036
|
-
promptTurn.cancelRequested = true;
|
|
1037
|
-
promptTurn.unsubscribe?.();
|
|
1038
|
-
void this.#emitCancelledPromptIdleUpdate(record);
|
|
1039
|
-
const cleanup = this.#runCancelCleanup(record, promptTurn);
|
|
1040
|
-
promptTurn.cleanup = cleanup;
|
|
1041
|
-
this.#finishPrompt(record, {
|
|
1042
|
-
stopReason: "cancelled",
|
|
1043
|
-
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
1044
|
-
_meta: { userMessageId: promptTurn.userMessageId },
|
|
1045
|
-
});
|
|
1046
|
-
return cleanup;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
async #runCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
|
|
1050
|
-
let timer: NodeJS.Timeout | undefined;
|
|
1051
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
1052
|
-
timer = setTimeout(() => reject(new Error("ACP cancel cleanup timed out")), this.#cancelCleanupTimeoutMs);
|
|
1053
|
-
});
|
|
1054
|
-
try {
|
|
1055
|
-
await Promise.race([record.session.abort(), timeout]);
|
|
1056
|
-
} finally {
|
|
1057
|
-
if (timer) clearTimeout(timer);
|
|
1058
|
-
// Order matters: clear `cleanup` before evicting the slot so the slot-eviction
|
|
1059
|
-
// branch matches what `#finishPrompt` saw if it ran first.
|
|
1060
|
-
promptTurn.cleanup = undefined;
|
|
1061
|
-
if (promptTurn.settled && record.promptTurn === promptTurn) {
|
|
1062
|
-
record.promptTurn = undefined;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
async extMethod(method: string, params: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> {
|
|
1068
|
-
switch (method) {
|
|
1069
|
-
case "_skc/sessions/listAll": {
|
|
1070
|
-
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(5000, params.limit as number)) : 1000;
|
|
1071
|
-
const sessions = await SessionManager.listAll();
|
|
1072
|
-
const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
|
|
1073
|
-
return {
|
|
1074
|
-
sessions: sorted.map(s => this.#toSessionInfo(s)),
|
|
1075
|
-
total: sessions.length,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
case "_skc/projects/list": {
|
|
1079
|
-
const sessions = await SessionManager.listAll();
|
|
1080
|
-
const buckets = new Map<
|
|
1081
|
-
string,
|
|
1082
|
-
{ cwd: string; sessionCount: number; lastActivityAt: number; lastTitle: string }
|
|
1083
|
-
>();
|
|
1084
|
-
for (const s of sessions) {
|
|
1085
|
-
if (!s.cwd) continue;
|
|
1086
|
-
const ts = s.modified.getTime();
|
|
1087
|
-
const existing = buckets.get(s.cwd);
|
|
1088
|
-
if (existing) {
|
|
1089
|
-
existing.sessionCount += 1;
|
|
1090
|
-
if (ts > existing.lastActivityAt) {
|
|
1091
|
-
existing.lastActivityAt = ts;
|
|
1092
|
-
existing.lastTitle = s.title ?? "";
|
|
1093
|
-
}
|
|
1094
|
-
} else {
|
|
1095
|
-
buckets.set(s.cwd, {
|
|
1096
|
-
cwd: s.cwd,
|
|
1097
|
-
sessionCount: 1,
|
|
1098
|
-
lastActivityAt: ts,
|
|
1099
|
-
lastTitle: s.title ?? "",
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
const projects = Array.from(buckets.values()).sort((a, b) => b.lastActivityAt - a.lastActivityAt);
|
|
1104
|
-
return { projects, totalSessions: sessions.length };
|
|
1105
|
-
}
|
|
1106
|
-
case "_skc/chats/byCwd": {
|
|
1107
|
-
const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
|
|
1108
|
-
if (!cwd) throw new Error("cwd required");
|
|
1109
|
-
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(500, params.limit as number)) : 100;
|
|
1110
|
-
const sessions = await SessionManager.list(cwd);
|
|
1111
|
-
const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
|
|
1112
|
-
return { sessions: sorted.map(s => this.#toSessionInfo(s)) };
|
|
1113
|
-
}
|
|
1114
|
-
case "_skc/usage": {
|
|
1115
|
-
const [firstRecord] = this.#sessions.values();
|
|
1116
|
-
const target = firstRecord?.session ?? this.#initialSession;
|
|
1117
|
-
if (!target) {
|
|
1118
|
-
return { reports: [] };
|
|
1119
|
-
}
|
|
1120
|
-
const reports = await target.fetchUsageReports();
|
|
1121
|
-
return { reports: reports ?? [] };
|
|
1122
|
-
}
|
|
1123
|
-
case "_skc/extensions": {
|
|
1124
|
-
const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
|
|
1125
|
-
const sm = await Settings.init();
|
|
1126
|
-
const disabledIds = (sm.get("disabledExtensions") as string[] | undefined) ?? [];
|
|
1127
|
-
const extensions = await loadAllExtensions(cwd, disabledIds);
|
|
1128
|
-
return { extensions: extensions as unknown as Array<{ [key: string]: unknown }> };
|
|
1129
|
-
}
|
|
1130
|
-
case "_skc/extensions/toggle": {
|
|
1131
|
-
const providerId = params.providerId;
|
|
1132
|
-
if (typeof providerId !== "string") throw new Error("providerId required");
|
|
1133
|
-
if (params.enabled === false) {
|
|
1134
|
-
disableProvider(providerId);
|
|
1135
|
-
return { enabled: false };
|
|
1136
|
-
}
|
|
1137
|
-
enableProvider(providerId);
|
|
1138
|
-
return { enabled: true };
|
|
1139
|
-
}
|
|
1140
|
-
default:
|
|
1141
|
-
throw new Error(`Unknown ACP ext method: ${method}`);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
async extNotification(_method: string, _params: { [key: string]: unknown }): Promise<void> {}
|
|
1146
|
-
|
|
1147
|
-
get signal(): AbortSignal {
|
|
1148
|
-
return this.#connection.signal;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
get closed(): Promise<void> {
|
|
1152
|
-
return this.#connection.closed;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
#registerConnectionCleanup(): void {
|
|
1156
|
-
if (this.#cleanupRegistered) {
|
|
1013
|
+
const attaching = this.#attaching.get(id);
|
|
1014
|
+
if (attaching?.epoch === epoch) {
|
|
1015
|
+
await attaching.task;
|
|
1016
|
+
this.#assertSessionEpoch(id, epoch);
|
|
1017
|
+
const attached = this.#sessions.get(id);
|
|
1018
|
+
if (!attached) throw new AcpSdkAdapterError("unavailable", `ACP session ${id} did not attach.`);
|
|
1019
|
+
if (path.resolve(attached.cwd) !== path.resolve(cwd))
|
|
1020
|
+
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
1157
1021
|
return;
|
|
1158
1022
|
}
|
|
1159
|
-
this.#cleanupRegistered = true;
|
|
1160
|
-
this.#connection.signal.addEventListener(
|
|
1161
|
-
"abort",
|
|
1162
|
-
() => {
|
|
1163
|
-
this.#shuttingDown = true;
|
|
1164
|
-
// Mark shutdown under mutex (no actual mutex needed — single-threaded),
|
|
1165
|
-
// reject new scope/inventory/lifecycle work, then await terminal work.
|
|
1166
|
-
this.#shutdownPromise = (async () => {
|
|
1167
|
-
// Join lifecycle preparation that has not registered yet. Each tracked
|
|
1168
|
-
// operation rechecks shutdown and disposes its prepared session before settling.
|
|
1169
|
-
await Promise.allSettled(Array.from(this.#lifecycleOperations));
|
|
1170
|
-
const terminalPromises = Array.from(this.#sessions.values())
|
|
1171
|
-
.map(r => r.terminalPromise)
|
|
1172
|
-
.filter((p): p is Promise<DeleteSessionResponse> => p !== undefined);
|
|
1173
|
-
await Promise.allSettled(terminalPromises);
|
|
1174
|
-
await this.#disposeAllSessions();
|
|
1175
|
-
// Clear connection-local authority.
|
|
1176
|
-
this.#authoritySnapshot = undefined;
|
|
1177
|
-
this.#canonicalCwdScope = undefined;
|
|
1178
|
-
})();
|
|
1179
|
-
},
|
|
1180
|
-
{ once: true },
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
/**
|
|
1185
|
-
* Connection-local shutdown promise. Resolves once all terminal work (dispose,
|
|
1186
|
-
* active deletes, abort cleanup) has settled. `acp-mode.ts` awaits this after
|
|
1187
|
-
* transport closure so `process.exit` never kills in-flight deletion/disposal.
|
|
1188
|
-
*/
|
|
1189
|
-
get shutdownPromise(): Promise<void> {
|
|
1190
|
-
return this.#shutdownPromise ?? Promise.resolve();
|
|
1191
|
-
}
|
|
1192
1023
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const promise = start.promise.then(operation);
|
|
1197
|
-
this.#lifecycleOperations.add(promise);
|
|
1198
|
-
start.resolve();
|
|
1024
|
+
const task = this.#attachEndpoint(id, cwd, discovered, epoch);
|
|
1025
|
+
const pending = { epoch, task };
|
|
1026
|
+
this.#attaching.set(id, pending);
|
|
1199
1027
|
try {
|
|
1200
|
-
|
|
1028
|
+
await task;
|
|
1029
|
+
this.#assertSessionEpoch(id, epoch);
|
|
1201
1030
|
} finally {
|
|
1202
|
-
this.#
|
|
1031
|
+
if (this.#attaching.get(id) === pending) this.#attaching.delete(id);
|
|
1203
1032
|
}
|
|
1204
1033
|
}
|
|
1205
1034
|
|
|
1206
|
-
async #
|
|
1207
|
-
|
|
1035
|
+
async #attachEndpoint(id: string, cwd: string, discovered: Endpoint, epoch: number): Promise<void> {
|
|
1036
|
+
let adapter: AcpSdkAdapter | undefined;
|
|
1208
1037
|
try {
|
|
1209
|
-
await
|
|
1038
|
+
adapter = await AcpSdkAdapter.connect({
|
|
1039
|
+
url: discovered.url,
|
|
1040
|
+
token: discovered.token,
|
|
1041
|
+
connection: this.#reverseConnection(id),
|
|
1042
|
+
providers: this.#providers(),
|
|
1043
|
+
});
|
|
1044
|
+
this.#assertSessionEpoch(id, epoch);
|
|
1045
|
+
const record: SessionRecord = {
|
|
1046
|
+
cwd,
|
|
1047
|
+
adapter,
|
|
1048
|
+
unsubscribe: () => {},
|
|
1049
|
+
reconnectUnsubscribe: () => {},
|
|
1050
|
+
frameTail: Promise.resolve(),
|
|
1051
|
+
inboundSequence: 0,
|
|
1052
|
+
busy: false,
|
|
1053
|
+
};
|
|
1054
|
+
record.unsubscribe = adapter.onFrame(frame => this.#enqueueSdkFrame(id, adapter!, frame));
|
|
1055
|
+
record.reconnectUnsubscribe = adapter.onReconnectFailed(error =>
|
|
1056
|
+
this.#recoverSessionAfterTransportFailure(id, adapter!, error),
|
|
1057
|
+
);
|
|
1058
|
+
this.#sessions.set(id, record);
|
|
1059
|
+
this.#knownSessionCwds.set(id, cwd);
|
|
1060
|
+
await applyAcpPermissionMode(adapter, this.#clientCapabilities);
|
|
1061
|
+
this.#assertSessionEpoch(id, epoch);
|
|
1210
1062
|
} catch (error) {
|
|
1211
|
-
|
|
1063
|
+
if (adapter && this.#sessions.get(id)?.adapter === adapter) {
|
|
1064
|
+
try {
|
|
1065
|
+
await this.#teardownSession(id, "attachment failed", false);
|
|
1066
|
+
} finally {
|
|
1067
|
+
this.#knownSessionCwds.delete(id);
|
|
1068
|
+
}
|
|
1069
|
+
} else if (adapter) {
|
|
1070
|
+
try {
|
|
1071
|
+
await adapter.close();
|
|
1072
|
+
} catch {}
|
|
1073
|
+
}
|
|
1212
1074
|
throw error;
|
|
1213
1075
|
}
|
|
1214
|
-
return await this.#registerPreparedSession(session, mcpServers);
|
|
1215
1076
|
}
|
|
1216
1077
|
|
|
1217
|
-
|
|
1218
|
-
const
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
return existing;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
const candidate = this.#resolveStrictLifecycleCandidate(sessionId, cwd);
|
|
1227
|
-
return await this.#openStoredSession(candidate, cwd, mcpServers, sessionId);
|
|
1078
|
+
#recoverSessionAfterTransportFailure(id: string, adapter: AcpSdkAdapter, error: Error): void {
|
|
1079
|
+
const record = this.#sessions.get(id);
|
|
1080
|
+
if (!record || record.adapter !== adapter) return;
|
|
1081
|
+
const detail = error.message || "SDK transport reconnect failed.";
|
|
1082
|
+
const terminal = new AcpSdkAdapterError("connection_closed", `ACP session transport was lost: ${detail}`);
|
|
1083
|
+
void this.#recoverSessionAfterTransportFailureAsync(id, adapter, record.cwd, terminal);
|
|
1228
1084
|
}
|
|
1229
1085
|
|
|
1230
|
-
async #
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1086
|
+
async #recoverSessionAfterTransportFailureAsync(
|
|
1087
|
+
id: string,
|
|
1088
|
+
adapter: AcpSdkAdapter,
|
|
1089
|
+
cwd: string,
|
|
1090
|
+
error: AcpSdkAdapterError,
|
|
1091
|
+
): Promise<void> {
|
|
1092
|
+
await this.#failSession(id, adapter, error);
|
|
1093
|
+
if (this.#disposed || this.#knownSessionCwds.get(id) !== cwd) return;
|
|
1094
|
+
try {
|
|
1095
|
+
await this.#attachExisting(id, cwd);
|
|
1096
|
+
} catch {
|
|
1097
|
+
// The affected prompt was rejected and the stale adapter was removed. A later load/resume retries discovery.
|
|
1237
1098
|
}
|
|
1099
|
+
}
|
|
1238
1100
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1101
|
+
async #discardNewSession(id: string): Promise<void> {
|
|
1102
|
+
await this.#teardownSession(id, "discarded", true);
|
|
1103
|
+
this.#knownSessionCwds.delete(id);
|
|
1241
1104
|
}
|
|
1242
1105
|
|
|
1243
1106
|
/**
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
* exact identity before opening a stored session.
|
|
1107
|
+
* All local session disposal follows one path: remove ownership and reject a
|
|
1108
|
+
* waiting prompt before any awaited socket or broker work. A failed close is
|
|
1109
|
+
* terminally uncertain, not a reason to leave a usable-looking ACP record.
|
|
1248
1110
|
*/
|
|
1249
|
-
#
|
|
1250
|
-
const
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
throw new Error(`ACP session not found: ${sessionId}`);
|
|
1254
|
-
}
|
|
1255
|
-
if (entry.kind !== "candidate") {
|
|
1256
|
-
throw new Error(`ACP session ${sessionId} load failed: ${entry.reason}`);
|
|
1257
|
-
}
|
|
1258
|
-
return entry.candidate;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
async #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {
|
|
1262
|
-
const sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);
|
|
1263
|
-
const session = await this.#createSession(path.resolve(params.cwd));
|
|
1111
|
+
async #teardownSession(id: string, reason: string, closeRemote: boolean): Promise<void> {
|
|
1112
|
+
const record = this.#sessions.get(id);
|
|
1113
|
+
const ownershipBound = record !== undefined || this.#knownSessionCwds.has(id);
|
|
1114
|
+
this.#beginTeardown(id);
|
|
1264
1115
|
try {
|
|
1265
|
-
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1116
|
+
this.#advanceSessionEpoch(id);
|
|
1117
|
+
if (record) {
|
|
1118
|
+
this.#sessions.delete(id);
|
|
1119
|
+
record.unsubscribe();
|
|
1120
|
+
record.reconnectUnsubscribe();
|
|
1121
|
+
const waiter = record.activePrompt;
|
|
1122
|
+
record.activePrompt = undefined;
|
|
1123
|
+
waiter?.reject(new AcpSdkAdapterError("connection_closed", `ACP session was ${reason}.`));
|
|
1272
1124
|
}
|
|
1273
|
-
} catch (error) {
|
|
1274
|
-
await this.#disposeStandaloneSession(session);
|
|
1275
|
-
throw error;
|
|
1276
|
-
}
|
|
1277
|
-
return await this.#registerPreparedSession(session, params.mcpServers ?? []);
|
|
1278
|
-
}
|
|
1279
1125
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1126
|
+
const failures: unknown[] = [];
|
|
1127
|
+
try {
|
|
1128
|
+
await record?.adapter.close();
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
failures.push(error);
|
|
1131
|
+
}
|
|
1132
|
+
if (closeRemote) {
|
|
1133
|
+
try {
|
|
1134
|
+
await (await this.#brokerAdapter()).global(
|
|
1135
|
+
"session.close",
|
|
1136
|
+
{ sessionId: id },
|
|
1137
|
+
this.#lifecycleIdempotencyKey(id, "session.close"),
|
|
1138
|
+
);
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
if (!(ownershipBound && this.#isAlreadyGone(error))) failures.push(error);
|
|
1141
|
+
}
|
|
1296
1142
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1143
|
+
if (failures.length > 0) {
|
|
1144
|
+
const detail = failures
|
|
1145
|
+
.map(failure => (failure instanceof Error ? failure.message : String(failure)))
|
|
1146
|
+
.join("; ");
|
|
1147
|
+
throw aggregateAcpFailure("terminal_uncertain", `ACP session cleanup is uncertain: ${detail}`, failures);
|
|
1300
1148
|
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
throw error;
|
|
1149
|
+
} finally {
|
|
1150
|
+
this.#finishTeardown(id);
|
|
1304
1151
|
}
|
|
1305
|
-
return await this.#registerPreparedSession(session, mcpServers);
|
|
1306
1152
|
}
|
|
1307
1153
|
|
|
1308
|
-
async #
|
|
1309
|
-
const record = this.#
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1154
|
+
async #failSession(id: string, adapter: AcpSdkAdapter, error: AcpSdkAdapterError): Promise<void> {
|
|
1155
|
+
const record = this.#sessions.get(id);
|
|
1156
|
+
if (!record || record.adapter !== adapter) return;
|
|
1157
|
+
this.#advanceSessionEpoch(id);
|
|
1158
|
+
this.#sessions.delete(id);
|
|
1159
|
+
record.unsubscribe();
|
|
1160
|
+
record.reconnectUnsubscribe();
|
|
1161
|
+
const waiter = record.activePrompt;
|
|
1162
|
+
record.activePrompt = undefined;
|
|
1163
|
+
waiter?.reject(error);
|
|
1313
1164
|
try {
|
|
1314
|
-
await
|
|
1315
|
-
|
|
1316
|
-
if (this.#shuttingDown) {
|
|
1317
|
-
await this.#disposeSessionRecord(record);
|
|
1318
|
-
throw new Error("ACP session lifecycle is unavailable during shutdown");
|
|
1319
|
-
}
|
|
1320
|
-
this.#sessions.set(session.sessionId, record);
|
|
1321
|
-
return record;
|
|
1322
|
-
} catch (error) {
|
|
1323
|
-
await this.#disposeSessionRecord(record);
|
|
1324
|
-
throw error;
|
|
1325
|
-
}
|
|
1165
|
+
await adapter.close();
|
|
1166
|
+
} catch {}
|
|
1326
1167
|
}
|
|
1327
1168
|
|
|
1328
|
-
#
|
|
1329
|
-
return
|
|
1330
|
-
session,
|
|
1331
|
-
mcpManager: undefined,
|
|
1332
|
-
promptTurn: undefined,
|
|
1333
|
-
promptQueue: { promise: Promise.resolve(), release: undefined },
|
|
1334
|
-
liveMessageId: undefined,
|
|
1335
|
-
liveMessageProgress: undefined,
|
|
1336
|
-
toolArgsById: new Map(),
|
|
1337
|
-
extensionsConfigured: false,
|
|
1338
|
-
lifetimeUnsubscribe: undefined,
|
|
1339
|
-
terminalState: "active",
|
|
1340
|
-
terminalFailure: undefined,
|
|
1341
|
-
terminalPromise: undefined,
|
|
1342
|
-
};
|
|
1169
|
+
async #brokerAdapter(): Promise<AcpSdkAdapter> {
|
|
1170
|
+
return (await this.#brokerConnection()).adapter;
|
|
1343
1171
|
}
|
|
1344
1172
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
record.lifetimeUnsubscribe = record.session.subscribe(event => {
|
|
1350
|
-
void this.#handleLifetimeEvent(record, event);
|
|
1351
|
-
});
|
|
1173
|
+
/** Machine-local endpoint lookup; never routed through ACP extension methods. */
|
|
1174
|
+
async #brokerEndpoint(sessionId: string, endpointGeneration: number | undefined): Promise<unknown> {
|
|
1175
|
+
const input = { sessionId, ...(endpointGeneration === undefined ? {} : { endpointGeneration }) };
|
|
1176
|
+
return await (await this.#brokerConnection()).client.global("session.get_endpoint", input);
|
|
1352
1177
|
}
|
|
1353
1178
|
|
|
1354
|
-
async #
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
(
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
{
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
},
|
|
1372
|
-
)) {
|
|
1373
|
-
await this.#connection.sessionUpdate(notification);
|
|
1374
|
-
}
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1377
|
-
if (event.type !== "thinking_level_changed") {
|
|
1378
|
-
return;
|
|
1179
|
+
async #brokerConnection(): Promise<BrokerConnection> {
|
|
1180
|
+
if (!this.#broker) {
|
|
1181
|
+
let pending!: Promise<BrokerConnection>;
|
|
1182
|
+
pending = (async () => {
|
|
1183
|
+
await ensureBroker({ agentDir: this.#agentDir });
|
|
1184
|
+
const discovery = await readSdkBrokerDiscovery(this.#agentDir);
|
|
1185
|
+
if (!discovery) throw new AcpSdkAdapterError("unavailable", "SDK broker discovery is unavailable.");
|
|
1186
|
+
const client = await SdkClient.connect(discovery.url, discovery.token);
|
|
1187
|
+
const adapter = new AcpSdkAdapter({ url: discovery.url, token: discovery.token, client });
|
|
1188
|
+
adapter.onReconnectFailed(() => {
|
|
1189
|
+
if (this.#broker === pending) this.#broker = undefined;
|
|
1190
|
+
void adapter.close().catch(() => undefined);
|
|
1191
|
+
});
|
|
1192
|
+
await adapter.start();
|
|
1193
|
+
return { adapter, client };
|
|
1194
|
+
})();
|
|
1195
|
+
this.#broker = pending;
|
|
1379
1196
|
}
|
|
1197
|
+
const pending = this.#broker;
|
|
1380
1198
|
try {
|
|
1381
|
-
await
|
|
1199
|
+
return await pending;
|
|
1382
1200
|
} catch (error) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
error,
|
|
1386
|
-
});
|
|
1201
|
+
if (this.#broker === pending) this.#broker = undefined;
|
|
1202
|
+
throw error;
|
|
1387
1203
|
}
|
|
1388
1204
|
}
|
|
1389
1205
|
|
|
1390
|
-
#
|
|
1391
|
-
const record = this.#sessions.get(
|
|
1392
|
-
if (!record) {
|
|
1393
|
-
|
|
1394
|
-
}
|
|
1395
|
-
return record;
|
|
1206
|
+
#adapter(id: string): AcpSdkAdapter {
|
|
1207
|
+
const record = this.#sessions.get(id);
|
|
1208
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${id}`);
|
|
1209
|
+
return record.adapter;
|
|
1396
1210
|
}
|
|
1397
1211
|
|
|
1398
|
-
#
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1212
|
+
async #resolveSavedSession(id: string, cwd: string): Promise<string> {
|
|
1213
|
+
const response = object(
|
|
1214
|
+
await (await this.#brokerAdapter()).global("session.list", { resolveSessionId: id, cwd }),
|
|
1215
|
+
);
|
|
1216
|
+
const result = object(response?.result) ?? response;
|
|
1217
|
+
const saved = object(result?.savedSession);
|
|
1218
|
+
if (saved?.id !== id || typeof saved.path !== "string")
|
|
1219
|
+
throw new AcpSdkAdapterError("not_found", `Saved ACP session does not exist: ${id}`);
|
|
1220
|
+
return saved.path;
|
|
1404
1221
|
}
|
|
1405
1222
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
if (loaded) {
|
|
1409
|
-
if (loaded.terminalState !== "active") {
|
|
1410
|
-
throw new Error(`ACP session fork is unavailable while it is in terminal state: ${sessionId}`);
|
|
1411
|
-
}
|
|
1412
|
-
if (isPromptTurnInFlight(loaded.promptTurn)) {
|
|
1413
|
-
throw new Error(`ACP session fork is unavailable while a prompt is in progress: ${sessionId}`);
|
|
1414
|
-
}
|
|
1415
|
-
await loaded.session.sessionManager.flush();
|
|
1416
|
-
const sessionPath = loaded.session.sessionManager.getSessionFile();
|
|
1417
|
-
if (!sessionPath) {
|
|
1418
|
-
throw new Error(`ACP session cannot be forked before it is persisted: ${sessionId}`);
|
|
1419
|
-
}
|
|
1420
|
-
return sessionPath;
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// Inactive fork source: exact-one scoped lookup only. Never use global
|
|
1424
|
-
// listAll/first-match — that was the P1 data-loss path.
|
|
1425
|
-
const scope = this.#canonicalCwdScope;
|
|
1426
|
-
if (!scope) {
|
|
1427
|
-
throw new Error(`ACP session not found (no scope established): ${sessionId}`);
|
|
1428
|
-
}
|
|
1429
|
-
const snapshot = this.#buildFreshAuthoritySnapshot(scope);
|
|
1430
|
-
const entry = snapshot.entries.get(sessionId);
|
|
1431
|
-
if (!entry) {
|
|
1432
|
-
throw new Error(`ACP session not found: ${sessionId}`);
|
|
1433
|
-
}
|
|
1434
|
-
if (entry.kind !== "candidate") {
|
|
1435
|
-
throw new Error(`ACP session fork failed: ${entry.reason}`);
|
|
1436
|
-
}
|
|
1437
|
-
return entry.candidate.path;
|
|
1223
|
+
#providers(): AcpProviderRegistration[] {
|
|
1224
|
+
return acpProviderRegistrations(this.#clientCapabilities);
|
|
1438
1225
|
}
|
|
1439
1226
|
|
|
1440
|
-
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
getMessageProgress: message => this.#getLiveMessageProgress(record, message),
|
|
1457
|
-
getToolArgs: toolCallId => record.toolArgsById.get(toolCallId),
|
|
1458
|
-
cwd: record.session.sessionManager.getCwd(),
|
|
1459
|
-
compactionEndPhase: "responding",
|
|
1227
|
+
#reverseConnection(sessionId: string): AcpReverseConnection {
|
|
1228
|
+
const methods: Record<string, string> = {
|
|
1229
|
+
"fs.readTextFile": "readTextFile",
|
|
1230
|
+
"fs.writeTextFile": "writeTextFile",
|
|
1231
|
+
"terminal.create": "createTerminal",
|
|
1232
|
+
"permission.request": "requestPermission",
|
|
1233
|
+
"ui.elicit": "unstable_createElicitation",
|
|
1234
|
+
};
|
|
1235
|
+
return {
|
|
1236
|
+
request: async (method: string, params: JsonObject): Promise<unknown> => {
|
|
1237
|
+
const name = methods[method] ?? method;
|
|
1238
|
+
const target = (this.#connection as unknown as Record<string, unknown>)[name];
|
|
1239
|
+
if (typeof target !== "function")
|
|
1240
|
+
throw new AcpSdkAdapterError("acp_reverse_unavailable", `ACP reverse method is unavailable: ${method}`);
|
|
1241
|
+
const request = method === "permission.request" ? { ...params, sessionId } : params;
|
|
1242
|
+
return await (target as (input: JsonObject) => Promise<unknown>)(request);
|
|
1460
1243
|
},
|
|
1461
|
-
|
|
1462
|
-
await this.#connection.sessionUpdate(notification);
|
|
1463
|
-
}
|
|
1464
|
-
if (event.type === "tool_execution_end") {
|
|
1465
|
-
record.toolArgsById.delete(event.toolCallId);
|
|
1466
|
-
}
|
|
1467
|
-
this.#clearLiveAssistantMessageAfterEvent(record, event);
|
|
1468
|
-
|
|
1469
|
-
if (event.type === "agent_end") {
|
|
1470
|
-
await this.#emitEndOfTurnUpdates(record);
|
|
1471
|
-
await this.#waitForAcpPromptIdle(record);
|
|
1472
|
-
this.#finishPrompt(record, {
|
|
1473
|
-
stopReason: this.#resolveStopReason(event, promptTurn.cancelRequested),
|
|
1474
|
-
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
1475
|
-
_meta: { userMessageId: promptTurn.userMessageId },
|
|
1476
|
-
});
|
|
1477
|
-
}
|
|
1244
|
+
};
|
|
1478
1245
|
}
|
|
1479
1246
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
timeoutMs: ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS,
|
|
1485
|
-
});
|
|
1486
|
-
if (!delivered) {
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
await record.session.waitForIdle();
|
|
1247
|
+
#observeSessionActivity(record: SessionRecord, frame: JsonObject): void {
|
|
1248
|
+
const event = receivedSdkEvent(frame)?.event;
|
|
1249
|
+
if (event?.type === "agent_start") record.busy = true;
|
|
1250
|
+
else if (event?.type === "agent_end") record.busy = false;
|
|
1492
1251
|
}
|
|
1493
1252
|
|
|
1494
|
-
#
|
|
1495
|
-
if (
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
(event.type === "message_start" || !record.liveMessageId || !record.liveMessageProgress)
|
|
1499
|
-
) {
|
|
1500
|
-
record.liveMessageId = crypto.randomUUID();
|
|
1501
|
-
record.liveMessageProgress = { textEmitted: false, thoughtEmitted: false };
|
|
1502
|
-
}
|
|
1253
|
+
#frameProcessingFailure(error: unknown): AcpSdkAdapterError {
|
|
1254
|
+
if (error instanceof AcpSdkAdapterError && error.code === "frame_processing_failed") return error;
|
|
1255
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1256
|
+
return new AcpSdkAdapterError("frame_processing_failed", `ACP session frame processing failed: ${detail}`);
|
|
1503
1257
|
}
|
|
1504
1258
|
|
|
1505
|
-
#
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1259
|
+
#enqueueSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject): void {
|
|
1260
|
+
const record = this.#sessions.get(id);
|
|
1261
|
+
if (!record || record.adapter !== adapter) return;
|
|
1262
|
+
// Sequence and busy state are captured at ingress, before queued work begins.
|
|
1263
|
+
// A frame received before acknowledgement stays before that prompt's boundary.
|
|
1264
|
+
this.#observeSessionActivity(record, frame);
|
|
1265
|
+
const sequence = ++record.inboundSequence;
|
|
1266
|
+
const task = record.frameTail.then(async () => await this.#handleSdkFrame(id, adapter, frame, sequence));
|
|
1267
|
+
record.frameTail = task.catch(
|
|
1268
|
+
async error => await this.#failSession(id, adapter, this.#frameProcessingFailure(error)),
|
|
1269
|
+
);
|
|
1510
1270
|
}
|
|
1511
1271
|
|
|
1512
|
-
#
|
|
1513
|
-
|
|
1514
|
-
|
|
1272
|
+
async #handleSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject, sequence: number): Promise<void> {
|
|
1273
|
+
const record = this.#sessions.get(id);
|
|
1274
|
+
if (!record || record.adapter !== adapter) return;
|
|
1275
|
+
const received = receivedSdkEvent(frame);
|
|
1276
|
+
if (!received) return;
|
|
1277
|
+
const { event, wirePayload } = received;
|
|
1278
|
+
const correlation = correlationFrom(frame, event);
|
|
1279
|
+
const activePrompt = record.activePrompt;
|
|
1280
|
+
// Prompt ownership is updated before publishing client notifications, but a
|
|
1281
|
+
// terminal waiter is resolved only after publication succeeds so a failed
|
|
1282
|
+
// sessionUpdate rejects the prompt instead of reporting false completion.
|
|
1283
|
+
if (
|
|
1284
|
+
activePrompt?.acknowledged &&
|
|
1285
|
+
sequence > activePrompt.boundary &&
|
|
1286
|
+
!correlationsConflict(activePrompt.correlation, correlation)
|
|
1287
|
+
) {
|
|
1288
|
+
if (isPromptActivity(String(event.type))) {
|
|
1289
|
+
activePrompt.activityObserved = true;
|
|
1290
|
+
} else if (event.type === "agent_end") {
|
|
1291
|
+
if (
|
|
1292
|
+
correlationsMatch(activePrompt.correlation, correlation) ||
|
|
1293
|
+
activePrompt.activityObserved ||
|
|
1294
|
+
activePrompt.steeringAtAcknowledgement
|
|
1295
|
+
)
|
|
1296
|
+
activePrompt.pendingTerminal = correlation;
|
|
1297
|
+
}
|
|
1515
1298
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1299
|
+
if (wirePayload) {
|
|
1300
|
+
for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(wirePayload as never, id, {
|
|
1301
|
+
cwd: record.cwd,
|
|
1302
|
+
getMessageProgress: message => {
|
|
1303
|
+
if (!activePrompt || !object(message)) return undefined;
|
|
1304
|
+
activePrompt.messageProgress ??= { textEmitted: false, thoughtEmitted: false };
|
|
1305
|
+
return activePrompt.messageProgress;
|
|
1306
|
+
},
|
|
1307
|
+
}))
|
|
1308
|
+
await this.#publishSessionUpdate(id, notification, adapter);
|
|
1526
1309
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1310
|
+
if (event.type === "message_end" && object(event.message)?.role === "assistant" && activePrompt)
|
|
1311
|
+
activePrompt.messageProgress = undefined;
|
|
1312
|
+
if (event.type === "agent_end") await this.#emitEndOfTurnUpdates(id, adapter);
|
|
1313
|
+
if (activePrompt) this.#settlePrompt(record, activePrompt);
|
|
1529
1314
|
}
|
|
1530
1315
|
|
|
1531
|
-
#
|
|
1532
|
-
|
|
1533
|
-
if (
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
// evicts the slot in its finally block once both flags say it's safe.
|
|
1540
|
-
if (!promptTurn.cleanup && record.promptTurn === promptTurn) {
|
|
1541
|
-
record.promptTurn = undefined;
|
|
1542
|
-
}
|
|
1543
|
-
if (error !== undefined) {
|
|
1544
|
-
promptTurn.reject(error);
|
|
1316
|
+
#settlePrompt(record: SessionRecord, waiter: PromptWaiter): void {
|
|
1317
|
+
if (record.activePrompt !== waiter || !waiter.acknowledged || !waiter.pendingTerminal) return;
|
|
1318
|
+
if (correlationsConflict(waiter.correlation, waiter.pendingTerminal)) return;
|
|
1319
|
+
if (
|
|
1320
|
+
!correlationsMatch(waiter.correlation, waiter.pendingTerminal) &&
|
|
1321
|
+
!waiter.activityObserved &&
|
|
1322
|
+
!waiter.steeringAtAcknowledgement
|
|
1323
|
+
)
|
|
1545
1324
|
return;
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
#resolveStopReason(
|
|
1551
|
-
event: Extract<AgentSessionEvent, { type: "agent_end" }>,
|
|
1552
|
-
cancelRequested: boolean,
|
|
1553
|
-
): PromptResponse["stopReason"] {
|
|
1554
|
-
if (cancelRequested) {
|
|
1555
|
-
return "cancelled";
|
|
1556
|
-
}
|
|
1557
|
-
const lastAssistant = [...event.messages]
|
|
1558
|
-
.reverse()
|
|
1559
|
-
.find((message): message is AssistantMessage => message.role === "assistant");
|
|
1560
|
-
const reason = lastAssistant?.stopReason;
|
|
1561
|
-
switch (reason) {
|
|
1562
|
-
case "aborted":
|
|
1563
|
-
return "cancelled";
|
|
1564
|
-
case "length":
|
|
1565
|
-
return "max_tokens";
|
|
1566
|
-
case "error": {
|
|
1567
|
-
if (lastAssistant?.errorKind === "provider_safety_stop") {
|
|
1568
|
-
return "refusal";
|
|
1569
|
-
}
|
|
1570
|
-
if (isLegacyProviderSafetyStopMessage(lastAssistant?.errorMessage ?? "")) {
|
|
1571
|
-
return "refusal";
|
|
1572
|
-
}
|
|
1573
|
-
return "end_turn";
|
|
1574
|
-
}
|
|
1575
|
-
default:
|
|
1576
|
-
return "end_turn";
|
|
1577
|
-
}
|
|
1325
|
+
record.activePrompt = undefined;
|
|
1326
|
+
waiter.resolve({ stopReason: waiter.cancelRequested ? "cancelled" : "end_turn" });
|
|
1578
1327
|
}
|
|
1579
1328
|
|
|
1580
|
-
async #
|
|
1581
|
-
|
|
1582
|
-
|
|
1329
|
+
async #emitEndOfTurnUpdates(id: string, adapter: AcpSdkAdapter): Promise<void> {
|
|
1330
|
+
let usage: JsonObject | undefined;
|
|
1331
|
+
try {
|
|
1332
|
+
const response = object(await adapter.query("context.get"));
|
|
1333
|
+
const result = object(response?.result) ?? response;
|
|
1334
|
+
usage = object(result?.usage);
|
|
1335
|
+
} catch {
|
|
1336
|
+
// Context usage is advisory ACP metadata; prompt completion remains authoritative.
|
|
1337
|
+
}
|
|
1338
|
+
if (typeof usage?.tokens === "number" && typeof usage.contextWindow === "number") {
|
|
1339
|
+
await this.#publishSessionUpdate(
|
|
1340
|
+
id,
|
|
1341
|
+
{
|
|
1342
|
+
sessionId: id,
|
|
1343
|
+
update: {
|
|
1344
|
+
sessionUpdate: "usage_update",
|
|
1345
|
+
size: usage.contextWindow,
|
|
1346
|
+
used: usage.tokens,
|
|
1347
|
+
},
|
|
1348
|
+
},
|
|
1349
|
+
adapter,
|
|
1350
|
+
);
|
|
1583
1351
|
}
|
|
1584
|
-
await this.#
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1352
|
+
await this.#publishSessionUpdate(
|
|
1353
|
+
id,
|
|
1354
|
+
{
|
|
1355
|
+
sessionId: id,
|
|
1356
|
+
update: {
|
|
1357
|
+
sessionUpdate: "session_info_update",
|
|
1358
|
+
updatedAt: new Date().toISOString(),
|
|
1359
|
+
_meta: { skcPhase: "idle", running: false, skcRunning: false },
|
|
1360
|
+
},
|
|
1590
1361
|
},
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
#assertAbsoluteCwd(cwd: string): void {
|
|
1595
|
-
if (!path.isAbsolute(cwd)) {
|
|
1596
|
-
throw new Error(`ACP cwd must be absolute: ${cwd}`);
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
/**
|
|
1601
|
-
* Validate the cwd and reject cross-cwd calls against an already-committed scope,
|
|
1602
|
-
* WITHOUT committing. Returns the canonical (resolved) cwd. Pair with
|
|
1603
|
-
* {@link #commitCanonicalScope} after the lifecycle operation succeeds so a failed
|
|
1604
|
-
* first new/load/resume/fork/list never pins the connection to a cwd it never served.
|
|
1605
|
-
*/
|
|
1606
|
-
#stageCanonicalScope(cwd: string): string {
|
|
1607
|
-
this.#assertAbsoluteCwd(cwd);
|
|
1608
|
-
const canonical = path.resolve(cwd);
|
|
1609
|
-
if (this.#canonicalCwdScope !== undefined && this.#canonicalCwdScope !== canonical) {
|
|
1610
|
-
throw new Error(`ACP connection is scoped to ${this.#canonicalCwdScope}, not ${canonical}`);
|
|
1611
|
-
}
|
|
1612
|
-
return canonical;
|
|
1362
|
+
adapter,
|
|
1363
|
+
);
|
|
1613
1364
|
}
|
|
1614
1365
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1366
|
+
async #publishSessionUpdate(
|
|
1367
|
+
id: string,
|
|
1368
|
+
notification: SessionNotification,
|
|
1369
|
+
expectedAdapter?: AcpSdkAdapter,
|
|
1370
|
+
): Promise<void> {
|
|
1371
|
+
const record = this.#sessions.get(id);
|
|
1372
|
+
if (!record || (expectedAdapter && record.adapter !== expectedAdapter)) return;
|
|
1373
|
+
try {
|
|
1374
|
+
await this.#connection.sessionUpdate(notification);
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
const failure = this.#frameProcessingFailure(error);
|
|
1377
|
+
await this.#failSession(id, record.adapter, failure);
|
|
1378
|
+
throw failure;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
async #sessionState(id: string): Promise<Pick<NewSessionResponse, "configOptions" | "modes">> {
|
|
1383
|
+
const record = this.#sessions.get(id);
|
|
1384
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unsupported ACP session: ${id}`);
|
|
1385
|
+
const [config, models] = await Promise.all([
|
|
1386
|
+
record.adapter.query("config.list/get"),
|
|
1387
|
+
record.adapter.query("models.list/current"),
|
|
1388
|
+
]);
|
|
1389
|
+
return acpSessionStateFromConfig(config, models);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
async #replaySession(id: string): Promise<void> {
|
|
1393
|
+
const adapter = this.#adapter(id);
|
|
1394
|
+
let cursor: string | undefined;
|
|
1395
|
+
let imageLimitationReported = false;
|
|
1396
|
+
for (let pageCount = 0; pageCount < MAX_ACP_REPLAY_PAGES; pageCount++) {
|
|
1397
|
+
const response = object(await adapter.query("transcript.list", {}, cursor));
|
|
1398
|
+
const result = object(response?.result) ?? response;
|
|
1399
|
+
const page = object(result?.page);
|
|
1400
|
+
for (const item of Array.isArray(page?.items) ? page.items : []) {
|
|
1401
|
+
const message = object(item);
|
|
1402
|
+
if (!message || (message.role !== "user" && message.role !== "assistant")) continue;
|
|
1403
|
+
const content = transcriptReplayContent(message);
|
|
1404
|
+
if (!imageLimitationReported) {
|
|
1405
|
+
imageLimitationReported = true;
|
|
1406
|
+
await this.#publishSessionUpdate(
|
|
1407
|
+
id,
|
|
1408
|
+
{
|
|
1409
|
+
sessionId: id,
|
|
1410
|
+
update: {
|
|
1411
|
+
sessionUpdate: "session_info_update",
|
|
1412
|
+
_meta: { skcTranscriptImageReplay: content.images },
|
|
1413
|
+
},
|
|
1414
|
+
},
|
|
1415
|
+
adapter,
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
const messageId = typeof message.id === "string" ? message.id : undefined;
|
|
1419
|
+
for (const block of content.blocks) {
|
|
1420
|
+
await this.#publishSessionUpdate(
|
|
1421
|
+
id,
|
|
1422
|
+
{
|
|
1423
|
+
sessionId: id,
|
|
1424
|
+
update: {
|
|
1425
|
+
sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
|
|
1426
|
+
content: block,
|
|
1427
|
+
...(messageId ? { messageId } : {}),
|
|
1428
|
+
},
|
|
1429
|
+
},
|
|
1430
|
+
adapter,
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
cursor = typeof page?.continuationCursor === "string" ? page.continuationCursor : undefined;
|
|
1435
|
+
if (!cursor) return;
|
|
1619
1436
|
}
|
|
1437
|
+
throw new AcpSdkAdapterError("resource_exhausted", "ACP transcript replay exceeded the page limit.");
|
|
1620
1438
|
}
|
|
1621
1439
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1440
|
+
#scheduleBootstrap(id: string): void {
|
|
1441
|
+
setTimeout(() => {
|
|
1442
|
+
const record = this.#sessions.get(id);
|
|
1443
|
+
if (!record || this.#connection.signal.aborted) return;
|
|
1444
|
+
void this.#publishSessionUpdate(
|
|
1445
|
+
id,
|
|
1446
|
+
{
|
|
1447
|
+
sessionId: id,
|
|
1448
|
+
update: {
|
|
1449
|
+
sessionUpdate: "session_info_update",
|
|
1450
|
+
_meta: { skcPhase: "idle", running: false, skcRunning: false },
|
|
1451
|
+
},
|
|
1452
|
+
},
|
|
1453
|
+
record.adapter,
|
|
1454
|
+
).catch(() => undefined);
|
|
1455
|
+
}, ACP_BOOTSTRAP_RACE_GUARD_MS);
|
|
1626
1456
|
}
|
|
1627
1457
|
|
|
1628
|
-
#
|
|
1629
|
-
if (
|
|
1630
|
-
|
|
1631
|
-
}
|
|
1458
|
+
#cursor(cursor: string | null | undefined): number {
|
|
1459
|
+
if (!cursor) return 0;
|
|
1460
|
+
const value = Number.parseInt(cursor, 10);
|
|
1461
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Invalid ACP session cursor: ${cursor}`);
|
|
1462
|
+
return value;
|
|
1632
1463
|
}
|
|
1633
1464
|
|
|
1634
|
-
#
|
|
1635
|
-
if (
|
|
1636
|
-
throw new Error("ACP session lifecycle is unavailable during shutdown");
|
|
1637
|
-
}
|
|
1465
|
+
#assertAbsoluteCwd(cwd: string): void {
|
|
1466
|
+
if (!path.isAbsolute(cwd)) throw new Error(`ACP cwd must be an absolute path: ${cwd}`);
|
|
1638
1467
|
}
|
|
1639
1468
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
* Any inventory failure throws a sanitized error — it grants zero authority.
|
|
1644
|
-
* The snapshot binds every discovered id to its exact-identity candidate or a
|
|
1645
|
-
* conflict tombstone BEFORE pagination, so duplicates beyond page 1 are known
|
|
1646
|
-
* before the first page response.
|
|
1647
|
-
*/
|
|
1648
|
-
#buildAuthoritySnapshot(scope: string): AcpAuthoritySnapshot {
|
|
1649
|
-
if (this.#authoritySnapshot?.scope === scope) {
|
|
1650
|
-
return this.#authoritySnapshot;
|
|
1651
|
-
}
|
|
1652
|
-
return this.#rebuildAuthoritySnapshot(scope, true);
|
|
1469
|
+
#assertNoMcpServers(params: { mcpServers?: unknown[] }): void {
|
|
1470
|
+
if (params.mcpServers && params.mcpServers.length > 0)
|
|
1471
|
+
throw new AcpSdkAdapterError("unsupported", "MCP servers are unsupported under SDK-backed ACP.");
|
|
1653
1472
|
}
|
|
1654
1473
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
#buildFreshAuthoritySnapshot(scope: string): AcpAuthoritySnapshot {
|
|
1663
|
-
return this.#rebuildAuthoritySnapshot(scope, false);
|
|
1474
|
+
#beginDispose(): void {
|
|
1475
|
+
if (this.#disposePromise) return;
|
|
1476
|
+
this.#disposePromise = this.#dispose();
|
|
1477
|
+
// AbortSignal listeners cannot return a promise to their caller. Retain the
|
|
1478
|
+
// aggregate cleanup result while attaching a rejection handler so disposal
|
|
1479
|
+
// never creates a detached unhandled rejection.
|
|
1480
|
+
void this.#disposePromise.catch(() => undefined);
|
|
1664
1481
|
}
|
|
1665
1482
|
|
|
1666
|
-
#
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const existing = entries.get(candidate.id);
|
|
1676
|
-
if (existing) {
|
|
1677
|
-
entries.set(candidate.id, { kind: "conflict", reason: "Duplicate session id in scoped inventory" });
|
|
1678
|
-
} else {
|
|
1679
|
-
entries.set(candidate.id, { kind: "candidate", candidate });
|
|
1680
|
-
orderedIds.push(candidate.id);
|
|
1483
|
+
async #dispose(): Promise<void> {
|
|
1484
|
+
if (this.#disposed) return;
|
|
1485
|
+
this.#disposed = true;
|
|
1486
|
+
const failures: unknown[] = [];
|
|
1487
|
+
for (const id of [...this.#sessions.keys()]) {
|
|
1488
|
+
try {
|
|
1489
|
+
await this.#teardownSession(id, "connection closed", false);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
failures.push(error);
|
|
1681
1492
|
}
|
|
1682
1493
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
/**
|
|
1696
|
-
* Delete an active (loaded) session via the per-record terminal state machine.
|
|
1697
|
-
* Reserves `deleting` before the first await; concurrent deletes join the
|
|
1698
|
-
* terminal promise. On failure the state transitions to `pre_dispatch_retryable`
|
|
1699
|
-
* (close_failed_retryable), `cleanup_pending` (verified-delete partial), or
|
|
1700
|
-
* `terminal_failure` (close_unknown / quiescence / unexpected) — all non-`active`
|
|
1701
|
-
* states reject prompt/config/close/load/resume/fork. Only `pre_dispatch_retryable`
|
|
1702
|
-
* and `cleanup_pending` permit a retry delete; `terminal_failure` never reopens.
|
|
1703
|
-
*/
|
|
1704
|
-
async #deleteActiveSession(sessionId: string, record: ManagedSessionRecord): Promise<DeleteSessionResponse> {
|
|
1705
|
-
// Concurrent delete joins the in-progress terminal promise.
|
|
1706
|
-
if (record.terminalPromise) {
|
|
1707
|
-
return await record.terminalPromise;
|
|
1708
|
-
}
|
|
1709
|
-
// terminal_failure: never reopen — share the settled error.
|
|
1710
|
-
if (record.terminalState === "terminal_failure") {
|
|
1711
|
-
throw record.terminalFailure ?? new Error(`ACP session ${sessionId} has a settled terminal failure`);
|
|
1494
|
+
this.#attaching.clear();
|
|
1495
|
+
this.#resolvingExisting.clear();
|
|
1496
|
+
this.#knownSessionCwds.clear();
|
|
1497
|
+
this.#tearingDown.clear();
|
|
1498
|
+
if (this.#broker) {
|
|
1499
|
+
const broker = this.#broker;
|
|
1500
|
+
this.#broker = undefined;
|
|
1501
|
+
try {
|
|
1502
|
+
await (await broker).adapter.close();
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
failures.push(error);
|
|
1505
|
+
}
|
|
1712
1506
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1507
|
+
if (failures.length > 0) {
|
|
1508
|
+
const detail = failures
|
|
1509
|
+
.map(failure => (failure instanceof Error ? failure.message : String(failure)))
|
|
1510
|
+
.join("; ");
|
|
1511
|
+
throw aggregateAcpFailure("terminal_uncertain", `ACP connection cleanup is uncertain: ${detail}`, failures);
|
|
1716
1512
|
}
|
|
1717
|
-
record.terminalState = "deleting";
|
|
1718
|
-
const scope = this.#canonicalCwdScope!;
|
|
1719
|
-
const terminalPromise = (async (): Promise<DeleteSessionResponse> => {
|
|
1720
|
-
try {
|
|
1721
|
-
// Quiesce — failure is terminal (never swallow).
|
|
1722
|
-
try {
|
|
1723
|
-
await this.#quiesceForDelete(sessionId, record);
|
|
1724
|
-
} catch (quiesceError) {
|
|
1725
|
-
record.terminalState = "terminal_failure";
|
|
1726
|
-
record.terminalFailure = quiesceError instanceof Error ? quiesceError : new Error(String(quiesceError));
|
|
1727
|
-
throw quiesceError;
|
|
1728
|
-
}
|
|
1729
|
-
const manager = record.session.sessionManager;
|
|
1730
|
-
// Strict close — outcome determines retryability.
|
|
1731
|
-
const closeOutcome = await record.session.closeWriterStrict();
|
|
1732
|
-
if (closeOutcome.kind !== "closed") {
|
|
1733
|
-
if (closeOutcome.kind === "close_failed_retryable") {
|
|
1734
|
-
record.terminalState = "pre_dispatch_retryable";
|
|
1735
|
-
} else {
|
|
1736
|
-
record.terminalState = "terminal_failure";
|
|
1737
|
-
record.terminalFailure = closeOutcome.error;
|
|
1738
|
-
}
|
|
1739
|
-
const reason =
|
|
1740
|
-
closeOutcome.kind === "close_unknown"
|
|
1741
|
-
? "writer close outcome is unknown"
|
|
1742
|
-
: "writer close failed before dispatch";
|
|
1743
|
-
throw new Error(`ACP session ${sessionId} cannot be deleted: ${reason}`);
|
|
1744
|
-
}
|
|
1745
|
-
// Recheck: the record must still own this exact session.
|
|
1746
|
-
if (this.#sessions.get(sessionId) !== record || record.terminalState !== "deleting") {
|
|
1747
|
-
record.terminalState = "terminal_failure";
|
|
1748
|
-
record.terminalFailure = new Error(`ACP session ${sessionId} state changed during deletion`);
|
|
1749
|
-
throw new Error(`ACP session ${sessionId} state changed during deletion`);
|
|
1750
|
-
}
|
|
1751
|
-
const sessionFile = manager.getSessionFile();
|
|
1752
|
-
const sessionDir = manager.getSessionDir();
|
|
1753
|
-
if (!sessionFile) {
|
|
1754
|
-
record.terminalState = "terminal_failure";
|
|
1755
|
-
record.terminalFailure = new Error(`ACP session ${sessionId} is not persisted and cannot be deleted`);
|
|
1756
|
-
throw new Error(`ACP session ${sessionId} is not persisted and cannot be deleted`);
|
|
1757
|
-
}
|
|
1758
|
-
const snapshot = this.#buildFreshAuthoritySnapshot(scope);
|
|
1759
|
-
const entry = snapshot.entries.get(sessionId);
|
|
1760
|
-
if (entry?.kind !== "candidate") {
|
|
1761
|
-
record.terminalState = "terminal_failure";
|
|
1762
|
-
record.terminalFailure = new Error(`ACP session ${sessionId} is not an exact-one scoped candidate`);
|
|
1763
|
-
throw new Error(`ACP session ${sessionId} is not an exact-one scoped candidate`);
|
|
1764
|
-
}
|
|
1765
|
-
if (entry.candidate.path !== sessionFile) {
|
|
1766
|
-
record.terminalState = "terminal_failure";
|
|
1767
|
-
record.terminalFailure = new Error(`ACP session ${sessionId} transcript path changed during deletion`);
|
|
1768
|
-
throw new Error(`ACP session ${sessionId} transcript path changed during deletion`);
|
|
1769
|
-
}
|
|
1770
|
-
this.#assertPendingTranscriptIdentity(sessionId, entry.candidate.identity);
|
|
1771
|
-
const sessionsRoot = path.dirname(sessionDir);
|
|
1772
|
-
const target: VerifiedSessionDeleteTarget = {
|
|
1773
|
-
sessionsRoot,
|
|
1774
|
-
transcriptPath: sessionFile,
|
|
1775
|
-
sessionId,
|
|
1776
|
-
cwd: manager.getCwd(),
|
|
1777
|
-
transcriptIdentity: {
|
|
1778
|
-
dev: entry.candidate.identity.dev,
|
|
1779
|
-
ino: entry.candidate.identity.ino,
|
|
1780
|
-
},
|
|
1781
|
-
...this.#pendingArtifactsIdentity(sessionId),
|
|
1782
|
-
};
|
|
1783
|
-
const outcome = await manager.deleteSessionVerified(target);
|
|
1784
|
-
if (outcome.kind !== "deleted") {
|
|
1785
|
-
record.terminalState = "cleanup_pending";
|
|
1786
|
-
this.#recordPendingDeleteEvidence(sessionId, outcome);
|
|
1787
|
-
throw new Error(
|
|
1788
|
-
`ACP session ${sessionId} delete is incomplete: ${outcome.phase} cleanup pending (${outcome.error.message})`,
|
|
1789
|
-
);
|
|
1790
|
-
}
|
|
1791
|
-
// Fully deleted: clear retry evidence, remove from active map, dispose.
|
|
1792
|
-
record.terminalState = "deleted";
|
|
1793
|
-
this.#pendingDeleteEvidence.delete(sessionId);
|
|
1794
|
-
this.#sessions.delete(sessionId);
|
|
1795
|
-
this.#invalidateAuthority();
|
|
1796
|
-
await this.#disposeSessionRecord(record);
|
|
1797
|
-
return {};
|
|
1798
|
-
} catch (error) {
|
|
1799
|
-
// terminalState was set at the failure point above. For any
|
|
1800
|
-
// unexpected error that left the state as `deleting`, treat it
|
|
1801
|
-
// as terminal_failure so the record never silently reopens.
|
|
1802
|
-
if (record.terminalState === "deleting") {
|
|
1803
|
-
record.terminalState = "terminal_failure";
|
|
1804
|
-
record.terminalFailure = error instanceof Error ? error : new Error(String(error));
|
|
1805
|
-
}
|
|
1806
|
-
record.terminalPromise = undefined;
|
|
1807
|
-
this.#invalidateAuthority();
|
|
1808
|
-
throw error;
|
|
1809
|
-
}
|
|
1810
|
-
})();
|
|
1811
|
-
record.terminalPromise = terminalPromise;
|
|
1812
|
-
return await terminalPromise;
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
/**
|
|
1816
|
-
* Delete an inactive (not currently loaded) session. The ID must have been
|
|
1817
|
-
* issued by the last complete scoped authority snapshot; an absent/unissued ID
|
|
1818
|
-
* returns lookup-free `{}` with NO inventory scan. Only after issuance may a
|
|
1819
|
-
* fresh strict inventory revalidate the same exact candidate before mutation.
|
|
1820
|
-
* A repeat-unknown (issued but gone from the fresh inventory) is also lookup-free `{}`.
|
|
1821
|
-
*/
|
|
1822
|
-
async #deleteInactiveSession(sessionId: string): Promise<DeleteSessionResponse> {
|
|
1823
|
-
const scope = this.#canonicalCwdScope!;
|
|
1824
|
-
// Gate: only IDs issued by the last complete scoped authority snapshot
|
|
1825
|
-
// may trigger a fresh inventory scan. Absent/unissued → lookup-free {}.
|
|
1826
|
-
const issued = this.#authoritySnapshot?.entries.get(sessionId);
|
|
1827
|
-
if (!issued) {
|
|
1828
|
-
return {};
|
|
1829
|
-
}
|
|
1830
|
-
if (issued.kind === "conflict") {
|
|
1831
|
-
throw new Error(`ACP session delete failed: ${issued.reason}`);
|
|
1832
|
-
}
|
|
1833
|
-
// Fresh strict inventory to revalidate the same exact candidate.
|
|
1834
|
-
const snapshot = this.#buildFreshAuthoritySnapshot(scope);
|
|
1835
|
-
const entry = snapshot.entries.get(sessionId);
|
|
1836
|
-
if (!entry) {
|
|
1837
|
-
// Repeat unknown: issued but no longer present in the fresh inventory.
|
|
1838
|
-
return {};
|
|
1839
|
-
}
|
|
1840
|
-
if (entry.kind === "conflict") {
|
|
1841
|
-
throw new Error(`ACP session delete failed: ${entry.reason}`);
|
|
1842
|
-
}
|
|
1843
|
-
const candidate = entry.candidate;
|
|
1844
|
-
if (
|
|
1845
|
-
candidate.path !== issued.candidate.path ||
|
|
1846
|
-
candidate.cwd !== issued.candidate.cwd ||
|
|
1847
|
-
candidate.identity.dev !== issued.candidate.identity.dev ||
|
|
1848
|
-
candidate.identity.ino !== issued.candidate.identity.ino
|
|
1849
|
-
) {
|
|
1850
|
-
throw new Error(`ACP session ${sessionId} changed since authority was issued`);
|
|
1851
|
-
}
|
|
1852
|
-
this.#assertPendingTranscriptIdentity(sessionId, candidate.identity);
|
|
1853
|
-
const sessionsRoot = path.dirname(candidate.path);
|
|
1854
|
-
const target: VerifiedSessionDeleteTarget = {
|
|
1855
|
-
sessionsRoot,
|
|
1856
|
-
transcriptPath: candidate.path,
|
|
1857
|
-
sessionId,
|
|
1858
|
-
cwd: candidate.cwd,
|
|
1859
|
-
transcriptIdentity: { dev: candidate.identity.dev, ino: candidate.identity.ino },
|
|
1860
|
-
...this.#pendingArtifactsIdentity(sessionId),
|
|
1861
|
-
};
|
|
1862
|
-
const storage = new FileSessionStorage();
|
|
1863
|
-
const outcome = await storage.deleteSessionVerified(target);
|
|
1864
|
-
if (outcome.kind !== "deleted") {
|
|
1865
|
-
this.#recordPendingDeleteEvidence(sessionId, outcome);
|
|
1866
|
-
throw new Error(
|
|
1867
|
-
`ACP session ${sessionId} delete is incomplete: ${outcome.phase} cleanup pending (${outcome.error.message})`,
|
|
1868
|
-
);
|
|
1869
|
-
}
|
|
1870
|
-
this.#pendingDeleteEvidence.delete(sessionId);
|
|
1871
|
-
this.#invalidateAuthority();
|
|
1872
|
-
return {};
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
#convertPromptBlocks(blocks: PromptRequest["prompt"]): { text: string; images: AgentImageContent[] } {
|
|
1876
|
-
const textParts: string[] = [];
|
|
1877
|
-
const images: AgentImageContent[] = [];
|
|
1878
|
-
for (const block of blocks) {
|
|
1879
|
-
switch (block.type) {
|
|
1880
|
-
case "text":
|
|
1881
|
-
textParts.push(block.text);
|
|
1882
|
-
break;
|
|
1883
|
-
case "image":
|
|
1884
|
-
images.push({ type: "image", data: block.data, mimeType: block.mimeType });
|
|
1885
|
-
break;
|
|
1886
|
-
case "resource":
|
|
1887
|
-
if ("text" in block.resource) {
|
|
1888
|
-
textParts.push(block.resource.text);
|
|
1889
|
-
} else if (typeof block.resource.mimeType === "string" && block.resource.mimeType.startsWith("image/")) {
|
|
1890
|
-
// `embeddedContext: true` covers both text and blob resources, but
|
|
1891
|
-
// blobs aren't directly consumable by the LLM. Route image blobs
|
|
1892
|
-
// to the images array so the user's intent survives; everything
|
|
1893
|
-
// else falls back to the URI placeholder below.
|
|
1894
|
-
images.push({ type: "image", data: block.resource.blob, mimeType: block.resource.mimeType });
|
|
1895
|
-
} else {
|
|
1896
|
-
textParts.push(`[embedded resource: ${block.resource.uri}]`);
|
|
1897
|
-
}
|
|
1898
|
-
break;
|
|
1899
|
-
case "resource_link":
|
|
1900
|
-
textParts.push(block.title ?? block.name ?? block.uri);
|
|
1901
|
-
break;
|
|
1902
|
-
case "audio":
|
|
1903
|
-
textParts.push("[audio omitted]");
|
|
1904
|
-
break;
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
return {
|
|
1908
|
-
text: textParts.join("\n\n").trim(),
|
|
1909
|
-
images,
|
|
1910
|
-
};
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
async #pushConfigOptionUpdate(record: ManagedSessionRecord): Promise<void> {
|
|
1914
|
-
await this.#connection.sessionUpdate({
|
|
1915
|
-
sessionId: record.session.sessionId,
|
|
1916
|
-
update: {
|
|
1917
|
-
sessionUpdate: "config_option_update",
|
|
1918
|
-
configOptions: this.#buildConfigOptions(record.session),
|
|
1919
|
-
},
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
#buildConfigOptions(session: AgentSession): SessionConfigOption[] {
|
|
1924
|
-
const currentModeId = this.#getCurrentModeId(session);
|
|
1925
|
-
const modeOptions = this.#getAvailableModes(session).map(mode => ({
|
|
1926
|
-
value: mode.id,
|
|
1927
|
-
name: mode.name,
|
|
1928
|
-
description: mode.description,
|
|
1929
|
-
}));
|
|
1930
|
-
const configOptions: SessionConfigOption[] = [
|
|
1931
|
-
{
|
|
1932
|
-
id: MODE_CONFIG_ID,
|
|
1933
|
-
name: "Mode",
|
|
1934
|
-
category: "mode",
|
|
1935
|
-
type: "select",
|
|
1936
|
-
currentValue: currentModeId,
|
|
1937
|
-
options: modeOptions,
|
|
1938
|
-
},
|
|
1939
|
-
];
|
|
1940
|
-
|
|
1941
|
-
const models = session.getAvailableModels();
|
|
1942
|
-
const currentModel = session.model;
|
|
1943
|
-
if (models.length > 0) {
|
|
1944
|
-
configOptions.push({
|
|
1945
|
-
id: MODEL_CONFIG_ID,
|
|
1946
|
-
name: "Model",
|
|
1947
|
-
category: "model",
|
|
1948
|
-
type: "select",
|
|
1949
|
-
currentValue: currentModel ? this.#toModelId(currentModel) : this.#toModelId(models[0]),
|
|
1950
|
-
options: models.map(model => ({
|
|
1951
|
-
value: this.#toModelId(model),
|
|
1952
|
-
name: model.name,
|
|
1953
|
-
description: `${model.provider}/${model.id}`,
|
|
1954
|
-
})),
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
configOptions.push({
|
|
1959
|
-
id: THINKING_CONFIG_ID,
|
|
1960
|
-
name: "Thinking",
|
|
1961
|
-
category: "thought_level",
|
|
1962
|
-
type: "select",
|
|
1963
|
-
currentValue: this.#toThinkingConfigValue(session.thinkingLevel),
|
|
1964
|
-
options: this.#buildThinkingOptions(session),
|
|
1965
|
-
});
|
|
1966
|
-
return configOptions;
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
#buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
|
|
1970
|
-
return [
|
|
1971
|
-
{ value: THINKING_OFF, name: "Off" },
|
|
1972
|
-
...session.getAvailableThinkingLevels().map(level => ({
|
|
1973
|
-
value: level,
|
|
1974
|
-
name: level,
|
|
1975
|
-
})),
|
|
1976
|
-
];
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
#toThinkingConfigValue(value: string | undefined): string {
|
|
1980
|
-
return value && value !== "inherit" ? value : THINKING_OFF;
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
async #setModelById(session: AgentSession, modelId: string): Promise<void> {
|
|
1984
|
-
const model = session.getAvailableModels().find(candidate => this.#toModelId(candidate) === modelId);
|
|
1985
|
-
if (!model) {
|
|
1986
|
-
throw new Error(`Unknown ACP model: ${modelId}`);
|
|
1987
|
-
}
|
|
1988
|
-
await session.setModel(model);
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
#setThinkingLevelById(session: AgentSession, value: string): void {
|
|
1992
|
-
const thinkingLevel = parseThinkingLevel(value);
|
|
1993
|
-
if (!thinkingLevel) {
|
|
1994
|
-
throw new Error(`Unknown ACP thinking level: ${value}`);
|
|
1995
|
-
}
|
|
1996
|
-
session.setThinkingLevel(thinkingLevel);
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
#toModelId(model: Model): string {
|
|
2000
|
-
return `${model.provider}/${model.id}`;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
#getAvailableModes(session: AgentSession): Array<{ id: string; name: string; description: string }> {
|
|
2004
|
-
const modes = [{ id: ACP_DEFAULT_MODE_ID, name: "Default", description: "Standard ACP headless mode" }];
|
|
2005
|
-
if (session.settings.get("plan.enabled")) {
|
|
2006
|
-
modes.push({
|
|
2007
|
-
id: ACP_PLAN_MODE_ID,
|
|
2008
|
-
name: "Plan",
|
|
2009
|
-
description: "Read-only planning mode that drafts a plan to a markdown file before any code changes",
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
void session;
|
|
2013
|
-
return modes;
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
#getCurrentModeId(session: AgentSession): string {
|
|
2017
|
-
return session.getPlanModeState()?.enabled ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
#applyModeChange(session: AgentSession, modeId: string): void {
|
|
2021
|
-
const availableModes = this.#getAvailableModes(session);
|
|
2022
|
-
if (!availableModes.some(mode => mode.id === modeId)) {
|
|
2023
|
-
throw new Error(`Unsupported ACP mode: ${modeId}`);
|
|
2024
|
-
}
|
|
2025
|
-
if (modeId === ACP_PLAN_MODE_ID) {
|
|
2026
|
-
const previous = session.getPlanModeState();
|
|
2027
|
-
session.setPlanModeState({
|
|
2028
|
-
enabled: true,
|
|
2029
|
-
planFilePath: previous?.planFilePath ?? DEFAULT_PLAN_FILE_URL,
|
|
2030
|
-
workflow: previous?.workflow ?? "parallel",
|
|
2031
|
-
reentry: previous !== undefined,
|
|
2032
|
-
});
|
|
2033
|
-
} else {
|
|
2034
|
-
session.setPlanModeState(undefined);
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
#buildModeState(session: AgentSession): SessionModeState {
|
|
2039
|
-
return {
|
|
2040
|
-
availableModes: this.#getAvailableModes(session),
|
|
2041
|
-
currentModeId: this.#getCurrentModeId(session),
|
|
2042
|
-
};
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
#buildCurrentModeUpdate(session: AgentSession): SessionUpdate {
|
|
2046
|
-
return {
|
|
2047
|
-
sessionUpdate: "current_mode_update",
|
|
2048
|
-
currentModeId: this.#getCurrentModeId(session),
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
async #buildAvailableCommands(session: AgentSession): Promise<AvailableCommand[]> {
|
|
2053
|
-
const commands: AvailableCommand[] = [];
|
|
2054
|
-
const seenNames = new Set<string>();
|
|
2055
|
-
const appendCommand = (command: AvailableCommand): void => {
|
|
2056
|
-
if (seenNames.has(command.name)) {
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
seenNames.add(command.name);
|
|
2060
|
-
commands.push(command);
|
|
2061
|
-
};
|
|
2062
|
-
|
|
2063
|
-
// Advertise builtins first, then custom/user commands, then file-based
|
|
2064
|
-
// slash commands, then namespaced `/skill:<name>` skills.
|
|
2065
|
-
for (const command of ACP_BUILTIN_SLASH_COMMANDS) {
|
|
2066
|
-
appendCommand(command);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
for (const command of session.customCommands) {
|
|
2070
|
-
appendCommand({
|
|
2071
|
-
name: command.command.name,
|
|
2072
|
-
description: command.command.description,
|
|
2073
|
-
input: { hint: "arguments" },
|
|
2074
|
-
});
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
for (const command of await loadSlashCommands({ cwd: session.sessionManager.getCwd() })) {
|
|
2078
|
-
appendCommand({
|
|
2079
|
-
name: command.name,
|
|
2080
|
-
description: command.description,
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
if (session.skillsSettings?.enableSkillCommands) {
|
|
2085
|
-
for (const skill of session.skills) {
|
|
2086
|
-
if (skill.hide === true) continue;
|
|
2087
|
-
for (const name of getSkillSlashCommandNames(skill)) {
|
|
2088
|
-
appendCommand({
|
|
2089
|
-
name,
|
|
2090
|
-
description: skill.description || `Run ${skill.name} skill`,
|
|
2091
|
-
input: { hint: "arguments" },
|
|
2092
|
-
});
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
return commands;
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
#toSessionInfo(session: StoredSessionInfo): SessionInfo {
|
|
2101
|
-
return {
|
|
2102
|
-
sessionId: session.id,
|
|
2103
|
-
cwd: session.cwd,
|
|
2104
|
-
title: session.title,
|
|
2105
|
-
updatedAt: session.modified.toISOString(),
|
|
2106
|
-
_meta: {
|
|
2107
|
-
messageCount: session.messageCount,
|
|
2108
|
-
size: session.size,
|
|
2109
|
-
},
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
#scheduleBootstrapUpdates(sessionId: string): void {
|
|
2114
|
-
// Defer first notifications until the response has reached the client.
|
|
2115
|
-
// Zed's agent-client-protocol reader dispatches responses and
|
|
2116
|
-
// notifications to different async tasks; sending the first
|
|
2117
|
-
// `available_commands_update` from `setTimeout(0)` reliably loses the
|
|
2118
|
-
// race against the response handler and Zed logs `Received session
|
|
2119
|
-
// notification for unknown session` then drops the update — leaving
|
|
2120
|
-
// the slash-command palette empty (#1015 follow-up; see
|
|
2121
|
-
// zed-industries/zed#55965 for the same race biting other ACP agents).
|
|
2122
|
-
// `ACP_BOOTSTRAP_RACE_GUARD_MS` is invisible to the operator and large
|
|
2123
|
-
// enough that the response future has scheduled before our timer fires
|
|
2124
|
-
// on stdio-only transports.
|
|
2125
|
-
//
|
|
2126
|
-
// The session-lifetime subscription normally shares this guard so extension
|
|
2127
|
-
// work cannot notify an unknown session id. prompt() may install it earlier:
|
|
2128
|
-
// receipt of a prompt proves the client already owns the returned session id,
|
|
2129
|
-
// and closes the cancellation window before this timer fires.
|
|
2130
|
-
setTimeout(() => {
|
|
2131
|
-
if (this.#connection.signal.aborted) {
|
|
2132
|
-
return;
|
|
2133
|
-
}
|
|
2134
|
-
const record = this.#sessions.get(sessionId);
|
|
2135
|
-
if (!record) {
|
|
2136
|
-
return;
|
|
2137
|
-
}
|
|
2138
|
-
this.#ensureLifetimeSubscription(record);
|
|
2139
|
-
void this.#emitBootstrapUpdates(sessionId, record);
|
|
2140
|
-
}, ACP_BOOTSTRAP_RACE_GUARD_MS);
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
async #emitBootstrapUpdates(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
2144
|
-
if (this.#sessions.get(sessionId) !== record) {
|
|
2145
|
-
return;
|
|
2146
|
-
}
|
|
2147
|
-
await this.#connection.sessionUpdate({
|
|
2148
|
-
sessionId,
|
|
2149
|
-
update: {
|
|
2150
|
-
sessionUpdate: "available_commands_update",
|
|
2151
|
-
availableCommands: await this.#buildAvailableCommands(record.session),
|
|
2152
|
-
},
|
|
2153
|
-
});
|
|
2154
|
-
await this.#connection.sessionUpdate({
|
|
2155
|
-
sessionId,
|
|
2156
|
-
update: {
|
|
2157
|
-
sessionUpdate: "session_info_update",
|
|
2158
|
-
title: record.session.sessionName,
|
|
2159
|
-
updatedAt: record.session.sessionManager.getHeader()?.timestamp,
|
|
2160
|
-
},
|
|
2161
|
-
});
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
async #emitAvailableCommandsUpdate(record: ManagedSessionRecord): Promise<void> {
|
|
2165
|
-
await this.#connection.sessionUpdate({
|
|
2166
|
-
sessionId: record.session.sessionId,
|
|
2167
|
-
update: {
|
|
2168
|
-
sessionUpdate: "available_commands_update",
|
|
2169
|
-
availableCommands: await this.#buildAvailableCommands(record.session),
|
|
2170
|
-
},
|
|
2171
|
-
});
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
/** Ensure cancellation clears a previously emitted busy/compacting phase even if abort never produces an end event. */
|
|
2175
|
-
async #emitCancelledPromptIdleUpdate(record: ManagedSessionRecord): Promise<void> {
|
|
2176
|
-
if (this.#connection.signal.aborted) {
|
|
2177
|
-
return;
|
|
2178
|
-
}
|
|
2179
|
-
try {
|
|
2180
|
-
await this.#connection.sessionUpdate({
|
|
2181
|
-
sessionId: record.session.sessionId,
|
|
2182
|
-
update: {
|
|
2183
|
-
sessionUpdate: "session_info_update",
|
|
2184
|
-
title: record.session.sessionName,
|
|
2185
|
-
updatedAt: new Date().toISOString(),
|
|
2186
|
-
_meta: {
|
|
2187
|
-
skcPhase: "idle",
|
|
2188
|
-
running: false,
|
|
2189
|
-
skcRunning: false,
|
|
2190
|
-
},
|
|
2191
|
-
},
|
|
2192
|
-
});
|
|
2193
|
-
} catch (error) {
|
|
2194
|
-
logger.warn("Failed to emit cancelled ACP prompt idle update", { error });
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
/**
|
|
2199
|
-
* Reload plugin/registry state for an ACP session. Mirrors the interactive
|
|
2200
|
-
* `/reload-plugins` and `/move` flows: invalidates the plugin-roots cache,
|
|
2201
|
-
* resets the capability cache, refreshes the session's slash-command state,
|
|
2202
|
-
* then re-advertises commands so the client sees newly installed/disabled
|
|
2203
|
-
* plugins.
|
|
2204
|
-
*/
|
|
2205
|
-
async #reloadPluginState(record: ManagedSessionRecord): Promise<void> {
|
|
2206
|
-
const cwd = record.session.sessionManager.getCwd();
|
|
2207
|
-
const projectPath = await resolveActiveProjectRegistryPath(cwd);
|
|
2208
|
-
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
2209
|
-
resetCapabilities();
|
|
2210
|
-
const fileCommands = await loadSlashCommands({ cwd });
|
|
2211
|
-
record.session.setSlashCommands(fileCommands);
|
|
2212
|
-
await record.session.refreshSshTool({ activateIfAvailable: true });
|
|
2213
|
-
await this.#emitAvailableCommandsUpdate(record);
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
async #emitEndOfTurnUpdates(record: ManagedSessionRecord): Promise<void> {
|
|
2217
|
-
const sessionId = record.session.sessionId;
|
|
2218
|
-
|
|
2219
|
-
const contextUsage = record.session.getContextUsage();
|
|
2220
|
-
if (contextUsage?.tokens != null) {
|
|
2221
|
-
const usageStats = record.session.sessionManager.getUsageStatistics();
|
|
2222
|
-
await this.#connection.sessionUpdate({
|
|
2223
|
-
sessionId,
|
|
2224
|
-
update: {
|
|
2225
|
-
sessionUpdate: "usage_update",
|
|
2226
|
-
size: contextUsage.contextWindow,
|
|
2227
|
-
used: contextUsage.tokens,
|
|
2228
|
-
cost: usageStats.cost > 0 ? { amount: usageStats.cost, currency: "USD" } : undefined,
|
|
2229
|
-
},
|
|
2230
|
-
});
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
await this.#connection.sessionUpdate({
|
|
2234
|
-
sessionId,
|
|
2235
|
-
update: {
|
|
2236
|
-
sessionUpdate: "session_info_update",
|
|
2237
|
-
title: record.session.sessionName,
|
|
2238
|
-
updatedAt: new Date().toISOString(),
|
|
2239
|
-
_meta: {
|
|
2240
|
-
skcPhase: "idle",
|
|
2241
|
-
running: false,
|
|
2242
|
-
skcRunning: false,
|
|
2243
|
-
},
|
|
2244
|
-
},
|
|
2245
|
-
});
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
#cloneUsageStatistics(usage: UsageStatistics): UsageStatistics {
|
|
2249
|
-
return {
|
|
2250
|
-
input: usage.input,
|
|
2251
|
-
output: usage.output,
|
|
2252
|
-
cacheRead: usage.cacheRead,
|
|
2253
|
-
cacheWrite: usage.cacheWrite,
|
|
2254
|
-
premiumRequests: usage.premiumRequests,
|
|
2255
|
-
cost: usage.cost,
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
#buildTurnUsage(previous: UsageStatistics, current: UsageStatistics): Usage | undefined {
|
|
2260
|
-
const inputTokens = Math.max(0, current.input - previous.input);
|
|
2261
|
-
const outputTokens = Math.max(0, current.output - previous.output);
|
|
2262
|
-
const cachedReadTokens = Math.max(0, current.cacheRead - previous.cacheRead);
|
|
2263
|
-
const cachedWriteTokens = Math.max(0, current.cacheWrite - previous.cacheWrite);
|
|
2264
|
-
const totalTokens = inputTokens + outputTokens + cachedReadTokens + cachedWriteTokens;
|
|
2265
|
-
|
|
2266
|
-
if (totalTokens === 0) {
|
|
2267
|
-
return undefined;
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
const usage: Usage = {
|
|
2271
|
-
inputTokens,
|
|
2272
|
-
outputTokens,
|
|
2273
|
-
totalTokens,
|
|
2274
|
-
};
|
|
2275
|
-
if (cachedReadTokens > 0) {
|
|
2276
|
-
usage.cachedReadTokens = cachedReadTokens;
|
|
2277
|
-
}
|
|
2278
|
-
if (cachedWriteTokens > 0) {
|
|
2279
|
-
usage.cachedWriteTokens = cachedWriteTokens;
|
|
2280
|
-
}
|
|
2281
|
-
return usage;
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
async #listStoredSessions(cwd?: string): Promise<StoredSessionInfo[]> {
|
|
2285
|
-
const sessions = cwd ? await SessionManager.list(cwd) : await SessionManager.listAll();
|
|
2286
|
-
return sessions.sort((left, right) => right.modified.getTime() - left.modified.getTime());
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
#parseCursor(cursor: string | undefined): number {
|
|
2290
|
-
if (!cursor) {
|
|
2291
|
-
return 0;
|
|
2292
|
-
}
|
|
2293
|
-
const parsed = Number.parseInt(cursor, 10);
|
|
2294
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
2295
|
-
throw new Error(`Invalid ACP session cursor: ${cursor}`);
|
|
2296
|
-
}
|
|
2297
|
-
return parsed;
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
/**
|
|
2301
|
-
* Parse a scoped cursor of the form `${generation}:${offset}` and reject it when
|
|
2302
|
-
* its embedded generation no longer matches the current authority generation.
|
|
2303
|
-
* Unlike the cwd-less display cursor, the scoped cursor binds the page offset to
|
|
2304
|
-
* the exact authority snapshot that produced it, so a lifecycle invalidation
|
|
2305
|
-
* between pages is observable instead of silently rebuilt.
|
|
2306
|
-
*/
|
|
2307
|
-
#parseScopedCursor(cursor: string | undefined, currentGeneration: number): number {
|
|
2308
|
-
if (!cursor) {
|
|
2309
|
-
return 0;
|
|
2310
|
-
}
|
|
2311
|
-
const separator = cursor.indexOf(":");
|
|
2312
|
-
if (separator <= 0) {
|
|
2313
|
-
throw new Error(`Invalid ACP session cursor: ${cursor}`);
|
|
2314
|
-
}
|
|
2315
|
-
const generation = Number.parseInt(cursor.slice(0, separator), 10);
|
|
2316
|
-
const offset = Number.parseInt(cursor.slice(separator + 1), 10);
|
|
2317
|
-
if (!Number.isFinite(generation) || generation < 0 || !Number.isFinite(offset) || offset < 0) {
|
|
2318
|
-
throw new Error(`Invalid ACP session cursor: ${cursor}`);
|
|
2319
|
-
}
|
|
2320
|
-
if (generation !== currentGeneration) {
|
|
2321
|
-
throw new Error("ACP session list cursor is stale; the scoped session inventory changed");
|
|
2322
|
-
}
|
|
2323
|
-
return offset;
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
async #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {
|
|
2327
|
-
const cwd = record.session.sessionManager.getCwd();
|
|
2328
|
-
const replayedToolCallIds = new Set<string>();
|
|
2329
|
-
const replayedToolCallArgs = new Map<string, unknown>();
|
|
2330
|
-
for (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {
|
|
2331
|
-
for (const notification of this.#messageToReplayNotifications(
|
|
2332
|
-
record.session.sessionId,
|
|
2333
|
-
message,
|
|
2334
|
-
cwd,
|
|
2335
|
-
replayedToolCallIds,
|
|
2336
|
-
replayedToolCallArgs,
|
|
2337
|
-
)) {
|
|
2338
|
-
await this.#connection.sessionUpdate(notification);
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
#messageToReplayNotifications(
|
|
2344
|
-
sessionId: string,
|
|
2345
|
-
message: ReplayableMessage,
|
|
2346
|
-
cwd: string,
|
|
2347
|
-
replayedToolCallIds: Set<string>,
|
|
2348
|
-
replayedToolCallArgs: Map<string, unknown>,
|
|
2349
|
-
): SessionNotification[] {
|
|
2350
|
-
if (message.role === "assistant") {
|
|
2351
|
-
return this.#replayAssistantMessage(sessionId, message, cwd, replayedToolCallIds, replayedToolCallArgs);
|
|
2352
|
-
}
|
|
2353
|
-
if (
|
|
2354
|
-
message.role === "user" ||
|
|
2355
|
-
message.role === "developer" ||
|
|
2356
|
-
message.role === "custom" ||
|
|
2357
|
-
message.role === "hookMessage"
|
|
2358
|
-
) {
|
|
2359
|
-
return this.#wrapReplayContent(
|
|
2360
|
-
sessionId,
|
|
2361
|
-
this.#extractReplayContent(message.content, undefined),
|
|
2362
|
-
"user_message_chunk",
|
|
2363
|
-
crypto.randomUUID(),
|
|
2364
|
-
);
|
|
2365
|
-
}
|
|
2366
|
-
if (
|
|
2367
|
-
message.role === "toolResult" &&
|
|
2368
|
-
typeof message.toolCallId === "string" &&
|
|
2369
|
-
typeof message.toolName === "string"
|
|
2370
|
-
) {
|
|
2371
|
-
return this.#replayToolResult(
|
|
2372
|
-
sessionId,
|
|
2373
|
-
cwd,
|
|
2374
|
-
{
|
|
2375
|
-
...message,
|
|
2376
|
-
toolCallId: message.toolCallId,
|
|
2377
|
-
toolName: message.toolName,
|
|
2378
|
-
},
|
|
2379
|
-
{
|
|
2380
|
-
includeStart: !replayedToolCallIds.has(message.toolCallId),
|
|
2381
|
-
toolArgs: replayedToolCallArgs.get(message.toolCallId),
|
|
2382
|
-
},
|
|
2383
|
-
);
|
|
2384
|
-
}
|
|
2385
|
-
if (
|
|
2386
|
-
message.role === "bashExecution" ||
|
|
2387
|
-
message.role === "pythonExecution" ||
|
|
2388
|
-
message.role === "compactionSummary"
|
|
2389
|
-
) {
|
|
2390
|
-
return this.#wrapReplayContent(
|
|
2391
|
-
sessionId,
|
|
2392
|
-
this.#extractReplayContent(message.content, undefined),
|
|
2393
|
-
"user_message_chunk",
|
|
2394
|
-
crypto.randomUUID(),
|
|
2395
|
-
);
|
|
2396
|
-
}
|
|
2397
|
-
return [];
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
#replayAssistantMessage(
|
|
2401
|
-
sessionId: string,
|
|
2402
|
-
message: ReplayableMessage,
|
|
2403
|
-
cwd: string,
|
|
2404
|
-
replayedToolCallIds: Set<string>,
|
|
2405
|
-
replayedToolCallArgs: Map<string, unknown>,
|
|
2406
|
-
): SessionNotification[] {
|
|
2407
|
-
const notifications: SessionNotification[] = [];
|
|
2408
|
-
const messageId = crypto.randomUUID();
|
|
2409
|
-
if (Array.isArray(message.content)) {
|
|
2410
|
-
for (const item of message.content) {
|
|
2411
|
-
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
2412
|
-
continue;
|
|
2413
|
-
}
|
|
2414
|
-
if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
|
|
2415
|
-
notifications.push({
|
|
2416
|
-
sessionId,
|
|
2417
|
-
update: {
|
|
2418
|
-
sessionUpdate: "agent_message_chunk",
|
|
2419
|
-
content: { type: "text", text: item.text },
|
|
2420
|
-
messageId,
|
|
2421
|
-
},
|
|
2422
|
-
});
|
|
2423
|
-
continue;
|
|
2424
|
-
}
|
|
2425
|
-
if (
|
|
2426
|
-
item.type === "thinking" &&
|
|
2427
|
-
"thinking" in item &&
|
|
2428
|
-
typeof item.thinking === "string" &&
|
|
2429
|
-
item.thinking.length > 0
|
|
2430
|
-
) {
|
|
2431
|
-
notifications.push({
|
|
2432
|
-
sessionId,
|
|
2433
|
-
update: {
|
|
2434
|
-
sessionUpdate: "agent_thought_chunk",
|
|
2435
|
-
content: { type: "text", text: item.thinking },
|
|
2436
|
-
messageId,
|
|
2437
|
-
},
|
|
2438
|
-
});
|
|
2439
|
-
continue;
|
|
2440
|
-
}
|
|
2441
|
-
const toolItem = item as ReplayableToolItem;
|
|
2442
|
-
if (
|
|
2443
|
-
(toolItem.type === "toolCall" || toolItem.type === "tool_use") &&
|
|
2444
|
-
typeof toolItem.id === "string" &&
|
|
2445
|
-
typeof toolItem.name === "string"
|
|
2446
|
-
) {
|
|
2447
|
-
const args = this.#buildReplayAssistantToolArgs(toolItem);
|
|
2448
|
-
const update = buildToolCallStartUpdate({
|
|
2449
|
-
toolCallId: toolItem.id,
|
|
2450
|
-
toolName: toolItem.name,
|
|
2451
|
-
args,
|
|
2452
|
-
status: "completed",
|
|
2453
|
-
cwd,
|
|
2454
|
-
});
|
|
2455
|
-
notifications.push({ sessionId, update });
|
|
2456
|
-
replayedToolCallIds.add(toolItem.id);
|
|
2457
|
-
replayedToolCallArgs.set(toolItem.id, args);
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
if (notifications.length === 0 && message.errorMessage && !isSilentAbort(message.errorMessage)) {
|
|
2462
|
-
notifications.push({
|
|
2463
|
-
sessionId,
|
|
2464
|
-
update: {
|
|
2465
|
-
sessionUpdate: "agent_message_chunk",
|
|
2466
|
-
content: { type: "text", text: message.errorMessage },
|
|
2467
|
-
messageId,
|
|
2468
|
-
},
|
|
2469
|
-
});
|
|
2470
|
-
}
|
|
2471
|
-
return notifications;
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
#buildReplayAssistantToolArgs(item: ReplayableToolItem): unknown {
|
|
2475
|
-
if ("arguments" in item) {
|
|
2476
|
-
return normalizeReplayToolArguments(item.arguments).args;
|
|
2477
|
-
}
|
|
2478
|
-
if (item.type === "tool_use" && "input" in item) {
|
|
2479
|
-
return item.input;
|
|
2480
|
-
}
|
|
2481
|
-
return {};
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
#replayToolResult(
|
|
2485
|
-
sessionId: string,
|
|
2486
|
-
cwd: string,
|
|
2487
|
-
message: Required<Pick<ReplayableMessage, "toolCallId" | "toolName">> & ReplayableMessage,
|
|
2488
|
-
options: { includeStart?: boolean; toolArgs?: unknown } = {},
|
|
2489
|
-
): SessionNotification[] {
|
|
2490
|
-
const args = this.#buildReplayToolArgs(message.details);
|
|
2491
|
-
const startEvent: AgentSessionEvent = {
|
|
2492
|
-
type: "tool_execution_start",
|
|
2493
|
-
toolCallId: message.toolCallId,
|
|
2494
|
-
toolName: message.toolName,
|
|
2495
|
-
args,
|
|
2496
|
-
};
|
|
2497
|
-
const endEvent: AgentSessionEvent = {
|
|
2498
|
-
type: "tool_execution_end",
|
|
2499
|
-
toolCallId: message.toolCallId,
|
|
2500
|
-
toolName: message.toolName,
|
|
2501
|
-
isError: message.isError === true,
|
|
2502
|
-
result: {
|
|
2503
|
-
content: message.content,
|
|
2504
|
-
details: message.details,
|
|
2505
|
-
errorMessage: message.errorMessage,
|
|
2506
|
-
},
|
|
2507
|
-
};
|
|
2508
|
-
const notifications = mapAgentWireEventPayloadToAcpSessionUpdates(toAgentWireEventPayload(endEvent), sessionId, {
|
|
2509
|
-
cwd,
|
|
2510
|
-
getToolArgs: toolCallId => (toolCallId === message.toolCallId ? options.toolArgs : undefined),
|
|
2511
|
-
});
|
|
2512
|
-
if (options.includeStart === false) {
|
|
2513
|
-
return notifications;
|
|
2514
|
-
}
|
|
2515
|
-
return [
|
|
2516
|
-
...mapAgentWireEventPayloadToAcpSessionUpdates(toAgentWireEventPayload(startEvent), sessionId, { cwd }),
|
|
2517
|
-
...notifications,
|
|
2518
|
-
];
|
|
2519
|
-
}
|
|
2520
|
-
|
|
2521
|
-
#buildReplayToolArgs(details: unknown): { path?: string } {
|
|
2522
|
-
if (typeof details !== "object" || details === null || !("path" in details)) {
|
|
2523
|
-
return {};
|
|
2524
|
-
}
|
|
2525
|
-
const value = (details as { path?: unknown }).path;
|
|
2526
|
-
return typeof value === "string" && value.length > 0 ? { path: value } : {};
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
|
-
#wrapReplayContent(
|
|
2530
|
-
sessionId: string,
|
|
2531
|
-
content: PromptRequest["prompt"],
|
|
2532
|
-
kind: "agent_message_chunk" | "user_message_chunk",
|
|
2533
|
-
messageId: string,
|
|
2534
|
-
): SessionNotification[] {
|
|
2535
|
-
return content.map(block => ({
|
|
2536
|
-
sessionId,
|
|
2537
|
-
update: {
|
|
2538
|
-
sessionUpdate: kind,
|
|
2539
|
-
content: block,
|
|
2540
|
-
messageId,
|
|
2541
|
-
},
|
|
2542
|
-
}));
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
#extractReplayContent(content: unknown, errorMessage: string | undefined): PromptRequest["prompt"] {
|
|
2546
|
-
const replay: PromptRequest["prompt"] = [];
|
|
2547
|
-
if (Array.isArray(content)) {
|
|
2548
|
-
for (const item of content) {
|
|
2549
|
-
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
2550
|
-
continue;
|
|
2551
|
-
}
|
|
2552
|
-
if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
|
|
2553
|
-
replay.push({ type: "text", text: item.text });
|
|
2554
|
-
continue;
|
|
2555
|
-
}
|
|
2556
|
-
if (
|
|
2557
|
-
item.type === "image" &&
|
|
2558
|
-
"data" in item &&
|
|
2559
|
-
"mimeType" in item &&
|
|
2560
|
-
typeof item.data === "string" &&
|
|
2561
|
-
typeof item.mimeType === "string"
|
|
2562
|
-
) {
|
|
2563
|
-
replay.push({ type: "image", data: item.data, mimeType: item.mimeType });
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
if (replay.length === 0 && errorMessage) {
|
|
2568
|
-
replay.push({ type: "text", text: errorMessage });
|
|
2569
|
-
}
|
|
2570
|
-
return replay;
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
async #configureExtensions(record: ManagedSessionRecord): Promise<void> {
|
|
2574
|
-
if (record.extensionsConfigured) {
|
|
2575
|
-
return;
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
const extensionRunner = record.session.extensionRunner;
|
|
2579
|
-
if (!extensionRunner) {
|
|
2580
|
-
record.extensionsConfigured = true;
|
|
2581
|
-
return;
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
extensionRunner.initialize(
|
|
2585
|
-
{
|
|
2586
|
-
sendMessage: (message, options) => {
|
|
2587
|
-
record.session.sendCustomMessage(message, options).catch((error: unknown) => {
|
|
2588
|
-
logger.warn("ACP extension sendMessage failed", { error });
|
|
2589
|
-
});
|
|
2590
|
-
},
|
|
2591
|
-
sendUserMessage: (content, options) => {
|
|
2592
|
-
record.session.sendUserMessage(content, options).catch((error: unknown) => {
|
|
2593
|
-
logger.warn("ACP extension sendUserMessage failed", { error });
|
|
2594
|
-
});
|
|
2595
|
-
},
|
|
2596
|
-
appendEntry: (customType, data) => {
|
|
2597
|
-
record.session.sessionManager.appendCustomEntry(customType, data);
|
|
2598
|
-
},
|
|
2599
|
-
setLabel: (targetId, label) => {
|
|
2600
|
-
record.session.sessionManager.appendLabelChange(targetId, label);
|
|
2601
|
-
},
|
|
2602
|
-
getActiveTools: () => record.session.getActiveToolNames(),
|
|
2603
|
-
getAllTools: () => record.session.getAllToolNames(),
|
|
2604
|
-
setActiveTools: toolNames => record.session.setActiveToolsByName(toolNames),
|
|
2605
|
-
getCommands: () => getSessionSlashCommands(record.session),
|
|
2606
|
-
setModel: async model => {
|
|
2607
|
-
const apiKey = await record.session.modelRegistry.getApiKey(model);
|
|
2608
|
-
if (!apiKey) {
|
|
2609
|
-
return false;
|
|
2610
|
-
}
|
|
2611
|
-
await record.session.setModel(model);
|
|
2612
|
-
return true;
|
|
2613
|
-
},
|
|
2614
|
-
getThinkingLevel: () => record.session.thinkingLevel,
|
|
2615
|
-
setThinkingLevel: level => record.session.setThinkingLevel(level),
|
|
2616
|
-
getSessionName: () => record.session.sessionManager.getSessionName(),
|
|
2617
|
-
setSessionName: async name => {
|
|
2618
|
-
await record.session.sessionManager.setSessionName(name, "user");
|
|
2619
|
-
},
|
|
2620
|
-
},
|
|
2621
|
-
{
|
|
2622
|
-
getModel: () => record.session.model,
|
|
2623
|
-
isIdle: () => !record.session.isStreaming,
|
|
2624
|
-
abort: () => {
|
|
2625
|
-
void record.session.abort();
|
|
2626
|
-
},
|
|
2627
|
-
hasPendingMessages: () => record.session.queuedMessageCount > 0,
|
|
2628
|
-
shutdown: () => {},
|
|
2629
|
-
getContextUsage: () => record.session.getContextUsage(),
|
|
2630
|
-
getSystemPrompt: () => record.session.systemPrompt,
|
|
2631
|
-
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
2632
|
-
},
|
|
2633
|
-
{
|
|
2634
|
-
getContextUsage: () => record.session.getContextUsage(),
|
|
2635
|
-
waitForIdle: () => record.session.agent.waitForIdle(),
|
|
2636
|
-
newSession: async options => {
|
|
2637
|
-
const success = await record.session.newSession({ parentSession: options?.parentSession });
|
|
2638
|
-
if (success && options?.setup) {
|
|
2639
|
-
await options.setup(record.session.sessionManager);
|
|
2640
|
-
}
|
|
2641
|
-
return { cancelled: !success };
|
|
2642
|
-
},
|
|
2643
|
-
branch: async entryId => {
|
|
2644
|
-
const result = await record.session.branch(entryId);
|
|
2645
|
-
return { cancelled: result.cancelled };
|
|
2646
|
-
},
|
|
2647
|
-
navigateTree: async (targetId, options) => {
|
|
2648
|
-
const result = await record.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
2649
|
-
return { cancelled: result.cancelled };
|
|
2650
|
-
},
|
|
2651
|
-
switchSession: async sessionPath => {
|
|
2652
|
-
const success = await record.session.switchSession(sessionPath);
|
|
2653
|
-
return { cancelled: !success };
|
|
2654
|
-
},
|
|
2655
|
-
reload: async () => {
|
|
2656
|
-
await record.session.reload();
|
|
2657
|
-
},
|
|
2658
|
-
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
2659
|
-
},
|
|
2660
|
-
// Per-session getter: `record.session.sessionId` reads through to
|
|
2661
|
-
// `sessionManager.getSessionId()` (it's a getter, not a field), so an
|
|
2662
|
-
// extension command that calls `ctx.newSession` / `ctx.switchSession`
|
|
2663
|
-
// — both exposed in the block just above — mutates the underlying id
|
|
2664
|
-
// mid-flight. Reading lazily on each elicitation matches every other
|
|
2665
|
-
// `sessionUpdate` call in this file. Hoisting the factory to an
|
|
2666
|
-
// `AcpAgent` field would still be wrong because it would also lose
|
|
2667
|
-
// the per-`record` binding.
|
|
2668
|
-
createAcpExtensionUiContext(this.#connection, () => record.session.sessionId, this.#clientCapabilities),
|
|
2669
|
-
);
|
|
2670
|
-
await extensionRunner.emit({ type: "session_start" });
|
|
2671
|
-
record.extensionsConfigured = true;
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
async #configureMcpServers(record: ManagedSessionRecord, servers: McpServer[]): Promise<void> {
|
|
2675
|
-
if (record.mcpManager) {
|
|
2676
|
-
await record.mcpManager.disconnectAll();
|
|
2677
|
-
}
|
|
2678
|
-
if (servers.length === 0) {
|
|
2679
|
-
record.mcpManager = undefined;
|
|
2680
|
-
await record.session.refreshMCPTools([]);
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
const manager = new MCPManager(record.session.sessionManager.getCwd());
|
|
2685
|
-
const configs: MCPConfigMap = {};
|
|
2686
|
-
const sources: MCPSourceMap = {};
|
|
2687
|
-
for (const server of servers) {
|
|
2688
|
-
configs[server.name] = this.#toMcpConfig(server);
|
|
2689
|
-
sources[server.name] = {
|
|
2690
|
-
provider: "acp",
|
|
2691
|
-
providerName: "ACP Client",
|
|
2692
|
-
path: `acp://${server.name}`,
|
|
2693
|
-
level: "project",
|
|
2694
|
-
};
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
const result = await manager.connectServers(configs, sources);
|
|
2698
|
-
if (result.errors.size > 0) {
|
|
2699
|
-
throw new Error(
|
|
2700
|
-
Array.from(result.errors.entries())
|
|
2701
|
-
.map(([name, message]) => `${name}: ${message}`)
|
|
2702
|
-
.join("; "),
|
|
2703
|
-
);
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
record.mcpManager = manager;
|
|
2707
|
-
await record.session.refreshMCPTools(result.tools);
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
#toMcpConfig(server: McpServer): MCPServerConfig {
|
|
2711
|
-
if ("command" in server) {
|
|
2712
|
-
return {
|
|
2713
|
-
type: "stdio",
|
|
2714
|
-
command: server.command,
|
|
2715
|
-
args: server.args,
|
|
2716
|
-
env: this.#toNameValueMap(server.env),
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
if (server.type === "http") {
|
|
2720
|
-
return {
|
|
2721
|
-
type: "http",
|
|
2722
|
-
url: server.url,
|
|
2723
|
-
headers: this.#toNameValueMap(server.headers),
|
|
2724
|
-
};
|
|
2725
|
-
}
|
|
2726
|
-
if (server.type === "sse") {
|
|
2727
|
-
return {
|
|
2728
|
-
type: "sse",
|
|
2729
|
-
url: server.url,
|
|
2730
|
-
headers: this.#toNameValueMap(server.headers),
|
|
2731
|
-
};
|
|
2732
|
-
}
|
|
2733
|
-
throw new Error(`Unsupported ACP MCP transport: ${server.type}`);
|
|
2734
|
-
}
|
|
2735
|
-
|
|
2736
|
-
#toNameValueMap(values: Array<{ name: string; value: string }>): { [name: string]: string } {
|
|
2737
|
-
const mapped: { [name: string]: string } = {};
|
|
2738
|
-
for (const value of values) {
|
|
2739
|
-
mapped[value.name] = value.value;
|
|
2740
|
-
}
|
|
2741
|
-
return mapped;
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
2745
|
-
this.#sessions.delete(sessionId);
|
|
2746
|
-
await this.#cancelPromptForClose(record);
|
|
2747
|
-
await this.#disposeSessionRecord(record);
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
async #cancelPromptForClose(record: ManagedSessionRecord): Promise<void> {
|
|
2751
|
-
const promptTurn = record.promptTurn;
|
|
2752
|
-
if (!isPromptTurnInFlight(promptTurn)) {
|
|
2753
|
-
return;
|
|
2754
|
-
}
|
|
2755
|
-
const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
|
|
2756
|
-
try {
|
|
2757
|
-
await cleanup;
|
|
2758
|
-
} catch (error) {
|
|
2759
|
-
logger.warn("Failed to abort ACP prompt during session close", { error });
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
/**
|
|
2763
|
-
* Delete-specific strict prompt quiescence barrier. Mirrors the cancel setup of
|
|
2764
|
-
* {@link #cancelPromptForClose} but NEVER swallows abort/timeout failures, then
|
|
2765
|
-
* waits for full idle + async-delivery drain. A quiescence failure blocks the
|
|
2766
|
-
* destructive mutation that follows — it is not catch-and-logged. The best-effort
|
|
2767
|
-
* close path is preserved separately for nondestructive shutdown.
|
|
2768
|
-
*/
|
|
2769
|
-
async #quiesceForDelete(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
2770
|
-
const promptTurn = record.promptTurn;
|
|
2771
|
-
if (isPromptTurnInFlight(promptTurn)) {
|
|
2772
|
-
const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
|
|
2773
|
-
await cleanup;
|
|
2774
|
-
}
|
|
2775
|
-
await this.#waitForAcpPromptIdle(record);
|
|
2776
|
-
// Explicit post-drain status check: the drain loop's boolean return is
|
|
2777
|
-
// not trusted as proof. Treat a drain false/timeout/no-progress/max-pass
|
|
2778
|
-
// as unproven and verify the owner-scoped delivery state directly. Any
|
|
2779
|
-
// remaining queued/delivering work blocks the destructive mutation.
|
|
2780
|
-
const delivery = record.session.getAsyncDeliveryStateForAcp();
|
|
2781
|
-
if (delivery.queued > 0 || delivery.delivering) {
|
|
2782
|
-
throw new Error(`ACP session ${sessionId} cannot be deleted: async delivery is not quiesced`);
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
/**
|
|
2787
|
-
* Record phase-aware same-connection retry evidence for an incomplete verified
|
|
2788
|
-
* delete. The transcript identity (carried by every cleanup_pending) is always
|
|
2789
|
-
* recorded so a retry can reject a replaced transcript; the artifacts identity
|
|
2790
|
-
* is recorded only at the artifacts phase.
|
|
2791
|
-
*/
|
|
2792
|
-
#recordPendingDeleteEvidence(sessionId: string, result: VerifiedSessionDeleteResult): void {
|
|
2793
|
-
if (result.kind !== "cleanup_pending") {
|
|
2794
|
-
return;
|
|
2795
|
-
}
|
|
2796
|
-
if (result.phase === "artifacts") {
|
|
2797
|
-
this.#pendingDeleteEvidence.set(sessionId, {
|
|
2798
|
-
transcriptIdentity: result.transcriptIdentity,
|
|
2799
|
-
artifactsIdentity: result.artifactsIdentity,
|
|
2800
|
-
});
|
|
2801
|
-
} else {
|
|
2802
|
-
// Transcript phase: artifacts were already removed successfully, so only
|
|
2803
|
-
// the transcript identity remains for retry binding.
|
|
2804
|
-
this.#pendingDeleteEvidence.set(sessionId, {
|
|
2805
|
-
transcriptIdentity: result.transcriptIdentity,
|
|
2806
|
-
});
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
|
-
/** Build the optional `expectedArtifactsIdentity` field for a retry target. */
|
|
2811
|
-
#pendingArtifactsIdentity(sessionId: string): { expectedArtifactsIdentity?: SessionStorageFileIdentity } {
|
|
2812
|
-
const pending = this.#pendingDeleteEvidence.get(sessionId);
|
|
2813
|
-
return pending?.artifactsIdentity ? { expectedArtifactsIdentity: pending.artifactsIdentity } : {};
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
/**
|
|
2817
|
-
* Phase-aware retry gate: a preserved transcript identity must match the fresh
|
|
2818
|
-
* candidate's transcript identity before that candidate can authorize a
|
|
2819
|
-
* replacement. A mismatch (the transcript was externally replaced between the
|
|
2820
|
-
* cleanup_pending failure and the retry) fails closed — it never authorizes a
|
|
2821
|
-
* wrong mutation.
|
|
2822
|
-
*/
|
|
2823
|
-
#assertPendingTranscriptIdentity(sessionId: string, candidateIdentity: { dev: bigint; ino: bigint }): void {
|
|
2824
|
-
const pending = this.#pendingDeleteEvidence.get(sessionId);
|
|
2825
|
-
if (
|
|
2826
|
-
pending &&
|
|
2827
|
-
(pending.transcriptIdentity.dev !== candidateIdentity.dev ||
|
|
2828
|
-
pending.transcriptIdentity.ino !== candidateIdentity.ino)
|
|
2829
|
-
) {
|
|
2830
|
-
throw new Error(
|
|
2831
|
-
`ACP session ${sessionId} transcript identity changed since pending cleanup; replacement rejected`,
|
|
2832
|
-
);
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
|
|
2836
|
-
async #disposeSessionRecord(record: ManagedSessionRecord): Promise<void> {
|
|
2837
|
-
record.lifetimeUnsubscribe?.();
|
|
2838
|
-
if (record.mcpManager) {
|
|
2839
|
-
try {
|
|
2840
|
-
await record.mcpManager.disconnectAll();
|
|
2841
|
-
} catch (error) {
|
|
2842
|
-
logger.warn("Failed to disconnect ACP MCP servers", { error });
|
|
2843
|
-
}
|
|
2844
|
-
record.mcpManager = undefined;
|
|
2845
|
-
}
|
|
2846
|
-
try {
|
|
2847
|
-
await record.session.dispose();
|
|
2848
|
-
} catch (error) {
|
|
2849
|
-
logger.warn("Failed to dispose ACP session", { error });
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
|
-
|
|
2853
|
-
async #disposeStandaloneSession(session: AgentSession): Promise<void> {
|
|
2854
|
-
try {
|
|
2855
|
-
await session.dispose();
|
|
2856
|
-
} catch (error) {
|
|
2857
|
-
logger.warn("Failed to dispose ACP session", { error });
|
|
2858
|
-
}
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
async #disposeAllSessions(): Promise<void> {
|
|
2862
|
-
if (this.#disposePromise) {
|
|
2863
|
-
await this.#disposePromise;
|
|
2864
|
-
return;
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
this.#disposePromise = (async () => {
|
|
2868
|
-
const records = Array.from(this.#sessions.entries());
|
|
2869
|
-
this.#sessions.clear();
|
|
2870
|
-
await Promise.all(
|
|
2871
|
-
records.map(async ([sessionId, record]) => {
|
|
2872
|
-
try {
|
|
2873
|
-
await this.#cancelPromptForClose(record);
|
|
2874
|
-
await this.#disposeSessionRecord(record);
|
|
2875
|
-
} catch (error) {
|
|
2876
|
-
logger.warn("Failed to clean up ACP session", { sessionId, error });
|
|
2877
|
-
}
|
|
2878
|
-
}),
|
|
2879
|
-
);
|
|
2880
|
-
|
|
2881
|
-
const initialSession = this.#initialSession;
|
|
2882
|
-
this.#initialSession = undefined;
|
|
2883
|
-
if (initialSession) {
|
|
2884
|
-
await this.#disposeStandaloneSession(initialSession);
|
|
2885
|
-
}
|
|
2886
|
-
})();
|
|
2887
|
-
|
|
2888
|
-
await this.#disposePromise;
|
|
2889
1513
|
}
|
|
2890
1514
|
}
|