@sayknow-cli/coding-agent 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +9 -1
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-contract.d.ts +40 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +156 -10
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +33 -2
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +5 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +13 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +1 -1
- package/dist/types/runtime-mcp/manager.d.ts +9 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +13 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +9 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +52 -4
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +106 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +33 -9
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +286 -12
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/query/handlers.d.ts +14 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +7 -0
- package/dist/types/sdk/startup-capability.d.ts +9 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +83 -24
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +70 -10
- package/dist/types/session/internal/managed-session-storage.d.ts +34 -12
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +89 -18
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/skc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/skc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/skc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/skc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/skc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/skc-runtime/state-writer.d.ts +5 -1
- package/dist/types/skc-runtime/team-launch.d.ts +1 -1
- package/dist/types/skc-runtime/team-runtime.d.ts +9 -2
- package/dist/types/skc-runtime/team-store.d.ts +12 -0
- package/dist/types/skc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +28 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +133 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +22 -9
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +7 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -12
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/compile-args.ts +0 -1
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-docs-index.ts +4 -1
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-skc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +58 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +16 -100
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +6 -38
- package/src/cli/read-cli.ts +2 -1
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +133 -11
- package/src/commands/daemon.ts +22 -5
- package/src/commands/deep-interview.ts +26 -1
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +134 -17
- package/src/commands/team.ts +1 -1
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-contract.ts +174 -0
- package/src/config/model-profiles.ts +51 -10
- package/src/config/model-registry.ts +24 -4
- package/src/config/model-resolver.ts +2 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +157 -10
- package/src/config/settings.ts +254 -108
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/skc/skills/deep-interview/SKILL.md +147 -24
- package/src/defaults/skc/skills/ralplan/SKILL.md +91 -34
- package/src/defaults/skc/skills/team/SKILL.md +1 -1
- package/src/defaults/skc/skills/ultragoal/SKILL.md +68 -17
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/js/tool-bridge.ts +1 -1
- package/src/eval/py/executor.ts +37 -13
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/extensibility/shared-events.ts +5 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +2 -1
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/internal-urls/docs-index.generated.ts +17 -16
- package/src/internal-urls/local-protocol.ts +87 -25
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +120 -42
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +52 -0
- package/src/modes/acp/acp-agent.ts +647 -83
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/sayknow-pet-widget.ts +20 -8
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +26 -8
- package/src/modes/components/welcome.ts +50 -23
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +101 -54
- package/src/modes/controllers/event-controller.ts +119 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +100 -8
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +201 -60
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +3 -3
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +6 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +99 -4
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/architect.md +17 -3
- package/src/prompts/agents/critic.md +17 -2
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -3
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/discoverable-tool-metadata.ts +1 -1
- package/src/runtime-mcp/json-rpc.ts +28 -11
- package/src/runtime-mcp/manager.ts +77 -59
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/identity.ts +22 -12
- package/src/sdk/broker/lifecycle.ts +293 -43
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +22 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +10 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/index.ts +793 -121
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +87 -25
- package/src/sdk/bus/operator-runtime.ts +31 -5
- package/src/sdk/bus/prompt-reconciliation.ts +240 -0
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-cli.ts +6 -13
- package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +34 -9
- package/src/sdk/bus/telegram-daemon-control.ts +161 -40
- package/src/sdk/bus/telegram-daemon.ts +3649 -593
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +8 -2
- package/src/sdk/host/control/operations.ts +3 -2
- package/src/sdk/host/host.ts +27 -9
- package/src/sdk/host/query/handlers.ts +125 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +145 -0
- package/src/sdk/protocol/operation-registry.ts +24 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +69 -8
- package/src/sdk/startup-capability.ts +15 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +1112 -314
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +1426 -259
- package/src/session/internal/managed-session-storage.ts +414 -238
- package/src/session/internal/native-publish-outcome.ts +271 -0
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2079 -469
- package/src/session/session-storage.ts +151 -11
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +49 -5
- package/src/setup/provider-presets.json +2 -1
- package/src/skc-runtime/deep-interview-runtime.ts +303 -36
- package/src/skc-runtime/deep-interview-stage.ts +1016 -0
- package/src/skc-runtime/deep-interview-state.ts +8 -5
- package/src/skc-runtime/gc-render.ts +1 -0
- package/src/skc-runtime/gc-runtime.ts +11 -1
- package/src/skc-runtime/linux-proc.ts +70 -36
- package/src/skc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/skc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/skc-runtime/ralplan-runtime.ts +1057 -75
- package/src/skc-runtime/repository-binding.ts +267 -0
- package/src/skc-runtime/session-state-sidecar.ts +25 -9
- package/src/skc-runtime/state-runtime.ts +50 -12
- package/src/skc-runtime/state-writer.ts +68 -56
- package/src/skc-runtime/team-launch.ts +34 -5
- package/src/skc-runtime/team-runtime.ts +41 -1
- package/src/skc-runtime/team-store.ts +32 -0
- package/src/skc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/skc-runtime/team-workers.ts +1 -0
- package/src/skc-runtime/tmux-common.ts +13 -2
- package/src/skc-runtime/tmux-owner-isolation.ts +20 -1
- package/src/skc-runtime/tmux-sessions.ts +45 -6
- package/src/skc-runtime/ultragoal-guard.ts +93 -15
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +123 -0
- package/src/skc-runtime/ultragoal-runtime.ts +469 -38
- package/src/skc-runtime/workflow-manifest.generated.json +195 -5
- package/src/skc-runtime/workflow-manifest.ts +67 -3
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +104 -12
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/executor.ts +165 -47
- package/src/task/index.ts +123 -15
- package/src/task/provider-retry-status.ts +88 -0
- package/src/task/receipt.ts +3 -0
- package/src/task/render.ts +45 -26
- package/src/task/skc-command.ts +2 -2
- package/src/task/types.ts +55 -10
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-allowed-prefixes.ts +45 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +127 -154
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +7 -1
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1544 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/tools/subagent-render.ts +74 -16
- package/src/tools/subagent.ts +2 -0
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
|
@@ -18,6 +18,7 @@ import * as crypto from "node:crypto";
|
|
|
18
18
|
import * as fs from "node:fs";
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import { scheduler } from "node:timers/promises";
|
|
21
|
+
import * as util from "node:util";
|
|
21
22
|
import {
|
|
22
23
|
type AfterToolCallContext,
|
|
23
24
|
type AfterToolCallResult,
|
|
@@ -105,6 +106,16 @@ import {
|
|
|
105
106
|
type FallbackAttemptToken,
|
|
106
107
|
type FallbackTriggerClass,
|
|
107
108
|
} from "@sayknow-cli/ai/utils/fallback-transport";
|
|
109
|
+
import {
|
|
110
|
+
BTW_MAX_ANSWER_UTF8_BYTES,
|
|
111
|
+
BTW_MAX_QUESTION_UTF8_BYTES,
|
|
112
|
+
BTW_STREAM_IDLE_TIMEOUT_MS,
|
|
113
|
+
BTW_STREAM_TOTAL_TIMEOUT_MS,
|
|
114
|
+
type BtwTextExchange,
|
|
115
|
+
boundBtwExchanges,
|
|
116
|
+
truncateUtf8,
|
|
117
|
+
utf8ByteLength,
|
|
118
|
+
} from "./btw-contract";
|
|
108
119
|
|
|
109
120
|
export interface ForkContextSeedMetadata {
|
|
110
121
|
sourceSessionId: string;
|
|
@@ -162,6 +173,7 @@ import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../a
|
|
|
162
173
|
import { reset as resetCapabilities } from "../capability";
|
|
163
174
|
import type { Rule } from "../capability/rule";
|
|
164
175
|
import type { CasReceipt } from "../config/atomic-yaml-patch";
|
|
176
|
+
import { activateModelProfile } from "../config/model-profile-activation";
|
|
165
177
|
import {
|
|
166
178
|
isAuthenticated,
|
|
167
179
|
kNoAuth,
|
|
@@ -223,9 +235,10 @@ import type {
|
|
|
223
235
|
TurnEndEvent,
|
|
224
236
|
TurnStartEvent,
|
|
225
237
|
} from "../extensibility/extensions";
|
|
226
|
-
import type { CompactOptions, ContextUsage } from "../extensibility/extensions/types";
|
|
238
|
+
import type { CompactOptions, ContextUsage, ExtensionTranscriptEntry } from "../extensibility/extensions/types";
|
|
227
239
|
import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
|
|
228
240
|
import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
241
|
+
import type { SessionSwitchEvent } from "../extensibility/shared-events";
|
|
229
242
|
import {
|
|
230
243
|
type LoadedSubskillActivation,
|
|
231
244
|
resolveSubskillActivationForSkillInvocation,
|
|
@@ -233,13 +246,19 @@ import {
|
|
|
233
246
|
import { resolveCurrentPhaseForParent } from "../extensibility/skc-plugins/injection";
|
|
234
247
|
import { readActiveSubskillsForParent, toActiveSubskillEntry } from "../extensibility/skc-plugins/state";
|
|
235
248
|
import { loadActiveSubskillTools } from "../extensibility/skc-plugins/tools";
|
|
236
|
-
import {
|
|
249
|
+
import {
|
|
250
|
+
buildSkillPromptMessage,
|
|
251
|
+
getSkillSlashCommandName,
|
|
252
|
+
parseSkillInvocations,
|
|
253
|
+
type Skill,
|
|
254
|
+
type SkillWarning,
|
|
255
|
+
} from "../extensibility/skills";
|
|
237
256
|
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
238
257
|
import { GoalRuntime } from "../goals/runtime";
|
|
239
258
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
240
259
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
241
260
|
import { buildSkillStopOutput, ensureWorkflowSkillActivationState } from "../hooks/skill-state";
|
|
242
|
-
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
261
|
+
import { initializeLocalRoot, type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
243
262
|
import { shutdownAll as shutdownAllLspClients } from "../lsp/client";
|
|
244
263
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
245
264
|
import {
|
|
@@ -266,13 +285,20 @@ import { MCPManager } from "../runtime-mcp/manager";
|
|
|
266
285
|
import type { NotificationSessionController } from "../sdk/bus/session-control";
|
|
267
286
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
268
287
|
import { formatNoCredentialOnboardingError, formatNoModelOnboardingError } from "../setup/model-onboarding-guidance";
|
|
288
|
+
import { assertDeepInterviewIntentManifest } from "../skc-runtime/deep-interview-state";
|
|
269
289
|
import { buildSkcRuntimeSessionEnv, consumePendingGoalModeRequest } from "../skc-runtime/goal-mode-request";
|
|
290
|
+
import {
|
|
291
|
+
isMemoryGuardClaimsLease,
|
|
292
|
+
isMemoryGuardClaimsLeaseForStateDir,
|
|
293
|
+
type MemoryGuardClaimsLease,
|
|
294
|
+
} from "../skc-runtime/memory-guard-owner-claims";
|
|
270
295
|
import {
|
|
271
296
|
assertNonEmptySkcSessionId,
|
|
272
297
|
modeStatePath as sessionModeStatePath,
|
|
273
298
|
sessionStateDir,
|
|
274
299
|
} from "../skc-runtime/session-layout";
|
|
275
300
|
import {
|
|
301
|
+
ownerTerminalContextFromEnvironment,
|
|
276
302
|
persistCoordinatorRuntimeStateFromEvent,
|
|
277
303
|
registerCoordinatorRuntimeStateFinalizer,
|
|
278
304
|
} from "../skc-runtime/session-state-sidecar";
|
|
@@ -329,6 +355,7 @@ import {
|
|
|
329
355
|
import {
|
|
330
356
|
type ConfiguredFallbackChain,
|
|
331
357
|
cappedExponentialWithFullJitter,
|
|
358
|
+
compactionRetryDelay,
|
|
332
359
|
effectiveFallbackDelay,
|
|
333
360
|
FallbackChainController,
|
|
334
361
|
} from "./fallback-chain-controller";
|
|
@@ -348,6 +375,7 @@ import {
|
|
|
348
375
|
prepareContributionPrep,
|
|
349
376
|
} from "./contribution-prep";
|
|
350
377
|
import { pruneStaleFileMentions } from "./file-mention-pruning";
|
|
378
|
+
import type { MemoryGuardRestoreResult } from "./memory-guard-checkpoint-participant";
|
|
351
379
|
import {
|
|
352
380
|
type BashExecutionMessage,
|
|
353
381
|
type CompactionSummaryMessage,
|
|
@@ -367,6 +395,7 @@ import type {
|
|
|
367
395
|
CompactionEntry,
|
|
368
396
|
DefaultModelSelectionStage,
|
|
369
397
|
NewSessionOptions,
|
|
398
|
+
PreparedNewSession,
|
|
370
399
|
RecoveryHydrationContext,
|
|
371
400
|
RecoveryHydrationPromotionFence,
|
|
372
401
|
SessionContext,
|
|
@@ -592,6 +621,24 @@ export interface AgentSessionConfig {
|
|
|
592
621
|
providerCacheSessionId?: string;
|
|
593
622
|
}
|
|
594
623
|
|
|
624
|
+
export interface AgentSessionMemoryGuardRestoreInput
|
|
625
|
+
extends Omit<AgentSessionConfig, "sessionManager" | "recoveryHydrationContext"> {
|
|
626
|
+
staged: Extract<MemoryGuardRestoreResult, { kind: "staged" }>;
|
|
627
|
+
claimsLease: MemoryGuardClaimsLease;
|
|
628
|
+
claimsStateDir: string;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export interface AgentMemoryGuardPromotionFence extends RecoveryHydrationPromotionFence {
|
|
632
|
+
readonly claimsLease: MemoryGuardClaimsLease;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export type AgentMemoryGuardRestoreResult =
|
|
636
|
+
| { kind: "staged"; session: AgentSession; promotionFence: AgentMemoryGuardPromotionFence }
|
|
637
|
+
| {
|
|
638
|
+
kind: "blocked";
|
|
639
|
+
reason: "transcript-mismatch" | "hydration-context-mismatch" | "claim-mismatch" | "agent-messages-mismatch";
|
|
640
|
+
};
|
|
641
|
+
|
|
595
642
|
type MidRunMaintenanceLifecycle = Parameters<NonNullable<AgentLoopConfig["maintainContext"]>>[1];
|
|
596
643
|
|
|
597
644
|
type AutoCompactionTerminalStatus =
|
|
@@ -621,8 +668,16 @@ export interface PromptOptions {
|
|
|
621
668
|
/**
|
|
622
669
|
* Invoked after all prompt preflight checks pass and immediately before agent execution begins.
|
|
623
670
|
* Cancellation before this callback rejects the prompt.
|
|
671
|
+
* Prefer `onPreflightAcceptCommit` for async durable acceptance (#3031/#3032).
|
|
624
672
|
*/
|
|
625
673
|
onPreflightAccepted?: () => void;
|
|
674
|
+
/**
|
|
675
|
+
* Awaitable durable-accept fence. Called after preflight and before queue mutation
|
|
676
|
+
* or `#promptAgentWithIdleRetry`. SDK bus installs a closure that fsyncs acceptance.
|
|
677
|
+
*/
|
|
678
|
+
onPreflightAcceptCommit?: () => void | Promise<void>;
|
|
679
|
+
/** Skill-only: prepared metadata before the durable fence (path/lineCount/cleanedArgs). */
|
|
680
|
+
onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string }) => void;
|
|
626
681
|
}
|
|
627
682
|
|
|
628
683
|
function promptPreflightCancelledError(): Error {
|
|
@@ -773,6 +828,28 @@ type RetryErrorClassification =
|
|
|
773
828
|
|
|
774
829
|
const BARE_DEFAULT_WATCHDOG_ERROR =
|
|
775
830
|
/^(?:[A-Za-z][A-Za-z0-9-]*(?: [A-Za-z][A-Za-z0-9-]*){0,3} )stream (?:timed out while waiting for the first event|stalled while waiting for the next event)$/;
|
|
831
|
+
const KIMI_CODE_FIRST_EVENT_TIMEOUT_MESSAGES = {
|
|
832
|
+
"anthropic-messages": new Set([
|
|
833
|
+
"Provider stream timed out while waiting for the first event",
|
|
834
|
+
"Anthropic stream timed out while waiting for the first event",
|
|
835
|
+
]),
|
|
836
|
+
"openai-completions": new Set([
|
|
837
|
+
"Provider stream timed out while waiting for the first event",
|
|
838
|
+
"OpenAI completions stream timed out while waiting for the first event",
|
|
839
|
+
]),
|
|
840
|
+
} as const;
|
|
841
|
+
|
|
842
|
+
const ALIBABA_TOKEN_PLAN_PROVIDER = "alibaba-token-plan";
|
|
843
|
+
const ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MESSAGES = {
|
|
844
|
+
"openai-responses": new Set([
|
|
845
|
+
"Provider stream timed out while waiting for the first event",
|
|
846
|
+
"OpenAI responses stream timed out while waiting for the first event",
|
|
847
|
+
]),
|
|
848
|
+
"openai-completions": new Set([
|
|
849
|
+
"Provider stream timed out while waiting for the first event",
|
|
850
|
+
"OpenAI completions stream timed out while waiting for the first event",
|
|
851
|
+
]),
|
|
852
|
+
} as const;
|
|
776
853
|
|
|
777
854
|
function hasBareDefaultRetryDisqualifyingFacts(message: AssistantMessage): boolean {
|
|
778
855
|
if (message.errorKind !== undefined || message.errorStatus !== undefined) return true;
|
|
@@ -827,7 +904,6 @@ function isLocalModelEndpoint(model: Model | undefined): boolean {
|
|
|
827
904
|
}
|
|
828
905
|
|
|
829
906
|
const IRC_REPLY_MAX_BYTES = 4096;
|
|
830
|
-
const BTW_ESTABLISHMENT_TIMEOUT_MS = 15_000;
|
|
831
907
|
|
|
832
908
|
export type EphemeralTurnPurpose = "btw" | "background";
|
|
833
909
|
|
|
@@ -837,8 +913,34 @@ interface EphemeralTurnBaseArgs {
|
|
|
837
913
|
signal?: AbortSignal;
|
|
838
914
|
}
|
|
839
915
|
|
|
916
|
+
export interface BtwRoleTextMessage {
|
|
917
|
+
role: "user" | "assistant";
|
|
918
|
+
text: string;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
export interface BtwConversationScope {
|
|
922
|
+
model: Model;
|
|
923
|
+
systemPrompt: string[];
|
|
924
|
+
messages: BtwRoleTextMessage[];
|
|
925
|
+
thinkingLevel: ThinkingLevel;
|
|
926
|
+
hideThinkingSummary: boolean;
|
|
927
|
+
serviceTier: ServiceTier | undefined;
|
|
928
|
+
credentialSessionId: string;
|
|
929
|
+
providerAffinitySessionId: string;
|
|
930
|
+
sideSessionId: string;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export interface BtwTurnCapture {
|
|
934
|
+
question: string;
|
|
935
|
+
scope: BtwConversationScope | undefined;
|
|
936
|
+
}
|
|
937
|
+
|
|
840
938
|
export type EphemeralTurnArgs =
|
|
841
|
-
| (EphemeralTurnBaseArgs & {
|
|
939
|
+
| (Omit<EphemeralTurnBaseArgs, "promptText"> & {
|
|
940
|
+
purpose: "btw";
|
|
941
|
+
turn: BtwTurnCapture;
|
|
942
|
+
contextExchanges?: readonly BtwTextExchange[];
|
|
943
|
+
})
|
|
842
944
|
| (EphemeralTurnBaseArgs & {
|
|
843
945
|
purpose?: "background";
|
|
844
946
|
/** Internal caller-supplied, non-persistent context such as the IRC roster. */
|
|
@@ -990,17 +1092,37 @@ function createHandoffContext(document: string): string {
|
|
|
990
1092
|
return `<handoff-context>\n${document}\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.`;
|
|
991
1093
|
}
|
|
992
1094
|
|
|
1095
|
+
async function discardPreparedNewSessionAfterFailure(
|
|
1096
|
+
sessionManager: SessionManager,
|
|
1097
|
+
prepared: PreparedNewSession,
|
|
1098
|
+
transitionError: unknown,
|
|
1099
|
+
): Promise<unknown> {
|
|
1100
|
+
try {
|
|
1101
|
+
await sessionManager.discardPreparedNewSession(prepared);
|
|
1102
|
+
} catch (cleanupError) {
|
|
1103
|
+
throw new AggregateError(
|
|
1104
|
+
[transitionError, cleanupError],
|
|
1105
|
+
"Session transition and staged successor cleanup both failed.",
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
return transitionError;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
993
1111
|
// ============================================================================
|
|
994
1112
|
// ACP Permission Gate
|
|
995
1113
|
// ============================================================================
|
|
996
1114
|
|
|
997
1115
|
/** Tools that require user permission before execution when an ACP client is connected. */
|
|
998
|
-
const PERMISSION_REQUIRED_TOOLS = new Set(["bash", "monitor", "edit", "delete", "move"]);
|
|
1116
|
+
const PERMISSION_REQUIRED_TOOLS = new Set(["bash", "monitor", "eval", "edit", "delete", "move"]);
|
|
999
1117
|
|
|
1000
1118
|
function isShellExecutionPermissionTool(toolName: string): boolean {
|
|
1001
1119
|
return toolName === "bash" || toolName === "monitor";
|
|
1002
1120
|
}
|
|
1003
1121
|
|
|
1122
|
+
function isExecutionPermissionTool(toolName: string): boolean {
|
|
1123
|
+
return isShellExecutionPermissionTool(toolName) || toolName === "eval";
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1004
1126
|
/** Permission options presented to the client on each gated tool call. */
|
|
1005
1127
|
const PERMISSION_OPTIONS: ClientBridgePermissionOption[] = [
|
|
1006
1128
|
{ optionId: "allow_once", name: "Allow once", kind: "allow_once" },
|
|
@@ -1068,6 +1190,18 @@ function getPermissionIntent(
|
|
|
1068
1190
|
const cmd = getStringProperty(a, "command")?.slice(0, 80);
|
|
1069
1191
|
return { toolName, title: cmd || toolName, cacheKey: toolName };
|
|
1070
1192
|
}
|
|
1193
|
+
if (toolName === "eval") {
|
|
1194
|
+
const cells = Array.isArray(a.cells) ? a.cells : [];
|
|
1195
|
+
const firstCell = cells.find(cell => cell && typeof cell === "object" && !Array.isArray(cell));
|
|
1196
|
+
const cell = firstCell as Record<string, unknown> | undefined;
|
|
1197
|
+
const title = cell ? getStringProperty(cell, "title") : undefined;
|
|
1198
|
+
const language = cell ? getStringProperty(cell, "language") : undefined;
|
|
1199
|
+
return {
|
|
1200
|
+
toolName,
|
|
1201
|
+
title: title ?? (language ? `Eval ${language}` : "eval"),
|
|
1202
|
+
cacheKey: toolName,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1071
1205
|
if (toolName === "delete") {
|
|
1072
1206
|
const p = getStringProperty(a, "path");
|
|
1073
1207
|
return { toolName, title: p ? `Delete ${p}` : toolName, paths: p ? [p] : undefined, cacheKey: toolName };
|
|
@@ -1438,6 +1572,47 @@ export class AgentSession {
|
|
|
1438
1572
|
#sessionAdmissionClosed = false;
|
|
1439
1573
|
#sessionAdmissionContext = new AsyncLocalStorage<SessionAdmissionEntry>();
|
|
1440
1574
|
#defaultModelSelectionMutationRevision = 0;
|
|
1575
|
+
#thinkingLevelMutationRevision = 0;
|
|
1576
|
+
#thinkingVisibilityMutationRevision = 0;
|
|
1577
|
+
#thinkingLevelLiveMutationRevision = 0;
|
|
1578
|
+
#thinkingVisibilityLiveMutationRevision = 0;
|
|
1579
|
+
#reasoningControlContextGeneration = 0;
|
|
1580
|
+
#pendingThinkingLevelControlSuccess:
|
|
1581
|
+
| {
|
|
1582
|
+
level: ThinkingLevel;
|
|
1583
|
+
mutationRevision: number;
|
|
1584
|
+
sessionId: string;
|
|
1585
|
+
model: Model | undefined;
|
|
1586
|
+
contextGeneration: number;
|
|
1587
|
+
}
|
|
1588
|
+
| undefined;
|
|
1589
|
+
#pendingThinkingVisibilityControlSuccess:
|
|
1590
|
+
| {
|
|
1591
|
+
visibility: "visible" | "hidden";
|
|
1592
|
+
mutationRevision: number;
|
|
1593
|
+
sessionId: string;
|
|
1594
|
+
model: Model | undefined;
|
|
1595
|
+
contextGeneration: number;
|
|
1596
|
+
}
|
|
1597
|
+
| undefined;
|
|
1598
|
+
#pendingThinkingLevelControlFailure:
|
|
1599
|
+
| {
|
|
1600
|
+
mutationRevision: number;
|
|
1601
|
+
liveMutationRevision: number;
|
|
1602
|
+
sessionId: string;
|
|
1603
|
+
model: Model | undefined;
|
|
1604
|
+
contextGeneration: number;
|
|
1605
|
+
}
|
|
1606
|
+
| undefined;
|
|
1607
|
+
#pendingThinkingVisibilityControlFailure:
|
|
1608
|
+
| {
|
|
1609
|
+
mutationRevision: number;
|
|
1610
|
+
liveMutationRevision: number;
|
|
1611
|
+
sessionId: string;
|
|
1612
|
+
model: Model | undefined;
|
|
1613
|
+
contextGeneration: number;
|
|
1614
|
+
}
|
|
1615
|
+
| undefined;
|
|
1441
1616
|
#promptTemplates: PromptTemplate[];
|
|
1442
1617
|
#slashCommands: FileSlashCommand[];
|
|
1443
1618
|
|
|
@@ -1633,6 +1808,7 @@ export class AgentSession {
|
|
|
1633
1808
|
|
|
1634
1809
|
#skillsSettings: SkillsSettings | undefined;
|
|
1635
1810
|
#activeSkillState: { skill: string; sessionId?: string } | undefined;
|
|
1811
|
+
#restoredWorkflowSkillState: { skill: string; sessionId: string } | undefined;
|
|
1636
1812
|
|
|
1637
1813
|
// Model registry for API key resolution
|
|
1638
1814
|
#modelRegistry: ModelRegistry;
|
|
@@ -1688,6 +1864,7 @@ export class AgentSession {
|
|
|
1688
1864
|
#constructorMCPToolSelection: string[] | undefined;
|
|
1689
1865
|
#constructorDiscoveredBuiltinToolSelection: string[] | undefined;
|
|
1690
1866
|
#recoveryHydrationContext: RecoveryHydrationContext | undefined;
|
|
1867
|
+
#memoryGuardClaimsLease: MemoryGuardClaimsLease | undefined;
|
|
1691
1868
|
|
|
1692
1869
|
// TTSR manager for time-traveling stream rules
|
|
1693
1870
|
#ttsrManager: TtsrManager | undefined = undefined;
|
|
@@ -2126,7 +2303,7 @@ export class AgentSession {
|
|
|
2126
2303
|
this.#workerIntegrationScheduler = new WorkerIntegrationRequestScheduler(
|
|
2127
2304
|
config.workerIntegrationRequest ??
|
|
2128
2305
|
(async signal => {
|
|
2129
|
-
await requestSkcWorkerIntegrationAttempt(this.sessionManager.getCwd(), process.env).catch(
|
|
2306
|
+
await requestSkcWorkerIntegrationAttempt(this.sessionManager.getCwd(), process.env, { signal }).catch(
|
|
2130
2307
|
error => {
|
|
2131
2308
|
logger.warn("SKC team worker integration request failed", { error: String(error) });
|
|
2132
2309
|
},
|
|
@@ -2143,6 +2320,7 @@ export class AgentSession {
|
|
|
2143
2320
|
this.#unregisterResourceGc = registerResourceGcSession({
|
|
2144
2321
|
sessionId: resourceGcSessionId,
|
|
2145
2322
|
settings: this.settings,
|
|
2323
|
+
cwd: () => this.sessionManager.getCwd(),
|
|
2146
2324
|
});
|
|
2147
2325
|
}
|
|
2148
2326
|
this.#unregisterRuntimeStateFinalizer = registerCoordinatorRuntimeStateFinalizer({
|
|
@@ -2452,6 +2630,34 @@ export class AgentSession {
|
|
|
2452
2630
|
return undefined;
|
|
2453
2631
|
}
|
|
2454
2632
|
}
|
|
2633
|
+
/** Provider-facing ask metadata must expose only the active deep-interview phase. */
|
|
2634
|
+
getDeepInterviewAskStage(): "topology" | "post-topology" | undefined {
|
|
2635
|
+
const currentSessionId = this.sessionManager.getSessionId();
|
|
2636
|
+
const inMemory = this.#activeSkillState;
|
|
2637
|
+
const active =
|
|
2638
|
+
inMemory && (!inMemory.sessionId || inMemory.sessionId === currentSessionId)
|
|
2639
|
+
? inMemory
|
|
2640
|
+
: this.#restoredWorkflowSkillState?.sessionId === currentSessionId
|
|
2641
|
+
? this.#restoredWorkflowSkillState
|
|
2642
|
+
: undefined;
|
|
2643
|
+
if (active?.skill !== "deep-interview") return undefined;
|
|
2644
|
+
try {
|
|
2645
|
+
assertNonEmptySkcSessionId(currentSessionId, "AgentSession.getDeepInterviewAskStage");
|
|
2646
|
+
const stateDir = sessionStateDir(this.sessionManager.getCwd(), currentSessionId);
|
|
2647
|
+
const filePath = path.join(
|
|
2648
|
+
stateDir,
|
|
2649
|
+
path.basename(sessionModeStatePath(this.sessionManager.getCwd(), currentSessionId, active.skill)),
|
|
2650
|
+
);
|
|
2651
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
2652
|
+
const parsed = JSON.parse(raw) as { state?: { intent_contract?: unknown } };
|
|
2653
|
+
const intentContract = parsed.state?.intent_contract;
|
|
2654
|
+
if (intentContract === undefined) return "topology";
|
|
2655
|
+
assertDeepInterviewIntentManifest(intentContract);
|
|
2656
|
+
return "post-topology";
|
|
2657
|
+
} catch {
|
|
2658
|
+
return undefined;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2455
2661
|
|
|
2456
2662
|
/** Peek the in-flight directive's invocation handler for use by the resolve tool. */
|
|
2457
2663
|
peekQueueInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined {
|
|
@@ -2542,9 +2748,15 @@ export class AgentSession {
|
|
|
2542
2748
|
this.#defaultFallbackController = scope.fallbackController;
|
|
2543
2749
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
2544
2750
|
if (scope.model) {
|
|
2545
|
-
this
|
|
2751
|
+
this.#setAgentModelWithReasoningContext(scope.model);
|
|
2546
2752
|
this.#syncAppendOnlyContext(scope.model);
|
|
2547
2753
|
}
|
|
2754
|
+
this.#thinkingLevelMutationRevision++;
|
|
2755
|
+
this.#thinkingLevelLiveMutationRevision++;
|
|
2756
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
2757
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
2758
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
2759
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
2548
2760
|
this.#thinkingLevel = scope.thinkingLevel;
|
|
2549
2761
|
this.agent.setThinkingLevel(toReasoningEffort(scope.thinkingLevel));
|
|
2550
2762
|
void this.#syncEditToolModeAfterModelChange(previousEditMode);
|
|
@@ -3910,8 +4122,21 @@ export class AgentSession {
|
|
|
3910
4122
|
return;
|
|
3911
4123
|
}
|
|
3912
4124
|
const predecessorAgentEnd = this.#claimDeferredAgentEndForContinuation(predecessorAgentEndHold);
|
|
4125
|
+
const startsQueuedSuccessor =
|
|
4126
|
+
this.agent.state.messages.at(-1)?.role === "assistant" && this.agent.hasQueuedMessages();
|
|
3913
4127
|
try {
|
|
3914
|
-
await this.agent.continue(
|
|
4128
|
+
await this.agent.continue({
|
|
4129
|
+
...this.#managedFallbackPromptOptions(),
|
|
4130
|
+
// Reset only after continue() has claimed the queued turn. Skipped or stale
|
|
4131
|
+
// continuations retain predecessor accounting, and resetAttemptBudget keeps
|
|
4132
|
+
// the sticky fallback cursor unchanged.
|
|
4133
|
+
onRunAccepted: startsQueuedSuccessor
|
|
4134
|
+
? () => {
|
|
4135
|
+
this.#defaultFallbackChain().resetAttemptBudget();
|
|
4136
|
+
this.#overflowMaintenanceAttempts = 0;
|
|
4137
|
+
}
|
|
4138
|
+
: undefined,
|
|
4139
|
+
});
|
|
3915
4140
|
} catch (error) {
|
|
3916
4141
|
this.#restoreDeferredAgentEndAfterContinuationFailure(predecessorAgentEnd);
|
|
3917
4142
|
throw error;
|
|
@@ -4514,11 +4739,13 @@ export class AgentSession {
|
|
|
4514
4739
|
return resolveToCwd(normalized, this.sessionManager.getCwd());
|
|
4515
4740
|
}
|
|
4516
4741
|
|
|
4517
|
-
#localProtocolOptions(): LocalProtocolOptions {
|
|
4742
|
+
#localProtocolOptions(prepared?: PreparedNewSession): LocalProtocolOptions {
|
|
4518
4743
|
return {
|
|
4519
|
-
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
4744
|
+
getArtifactsDir: () => prepared?.artifactsDir ?? this.sessionManager.getArtifactsDir(),
|
|
4520
4745
|
isManagedDestination: () => this.sessionManager.isManagedDestination(),
|
|
4521
|
-
|
|
4746
|
+
getManagedLegacyLocalMigrationSource: () =>
|
|
4747
|
+
prepared?.managedLegacyLocalMigrationSource ?? this.sessionManager.getManagedLegacyLocalMigrationSource(),
|
|
4748
|
+
getSessionId: () => prepared?.sessionId ?? this.sessionManager.getSessionId(),
|
|
4522
4749
|
};
|
|
4523
4750
|
}
|
|
4524
4751
|
|
|
@@ -4973,6 +5200,7 @@ export class AgentSession {
|
|
|
4973
5200
|
* such event.
|
|
4974
5201
|
*/
|
|
4975
5202
|
#syncAgentSessionId(sessionId?: string): void {
|
|
5203
|
+
this.#reasoningControlContextGeneration++;
|
|
4976
5204
|
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
4977
5205
|
this.agent.sessionId = sid;
|
|
4978
5206
|
this.agent.providerSessionId = this.#providerCacheSessionId ?? sid;
|
|
@@ -5092,7 +5320,7 @@ export class AgentSession {
|
|
|
5092
5320
|
// No-op when this session opened no tabs. Failure is logged, not thrown.
|
|
5093
5321
|
this.#unregisterResourceGc?.();
|
|
5094
5322
|
this.#unregisterResourceGc = undefined;
|
|
5095
|
-
this.#unregisterRuntimeStateFinalizer?.();
|
|
5323
|
+
if (ownerTerminalContextFromEnvironment() === null) this.#unregisterRuntimeStateFinalizer?.();
|
|
5096
5324
|
this.#unregisterRuntimeStateFinalizer = undefined;
|
|
5097
5325
|
await releaseTabsForOwner(this.sessionManager.getSessionId()).catch((error: unknown) =>
|
|
5098
5326
|
logger.warn("session dispose: releaseTabsForOwner failed", { error }),
|
|
@@ -5418,6 +5646,12 @@ export class AgentSession {
|
|
|
5418
5646
|
return undefined;
|
|
5419
5647
|
}
|
|
5420
5648
|
|
|
5649
|
+
/** Get a registered tool with the same guards used for model-facing execution. */
|
|
5650
|
+
getToolForExecution(name: string): AgentTool | undefined {
|
|
5651
|
+
const tool = this.getToolByName(name);
|
|
5652
|
+
return tool ? this.#prepareToolForExecution(tool) : undefined;
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5421
5655
|
/**
|
|
5422
5656
|
* Register a UI/control-plane request handler for a currently foregrounded
|
|
5423
5657
|
* managed bash execution. This is intentionally narrower than generic
|
|
@@ -5682,6 +5916,7 @@ export class AgentSession {
|
|
|
5682
5916
|
return await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
|
|
5683
5917
|
}
|
|
5684
5918
|
const isShellExecutionTool = isShellExecutionPermissionTool(target.name);
|
|
5919
|
+
const isExecutionTool = isExecutionPermissionTool(target.name);
|
|
5685
5920
|
const command =
|
|
5686
5921
|
isShellExecutionTool && args && typeof args === "object" && !Array.isArray(args)
|
|
5687
5922
|
? getStringProperty(args as Record<string, unknown>, "command")
|
|
@@ -5724,7 +5959,7 @@ export class AgentSession {
|
|
|
5724
5959
|
toolCallId,
|
|
5725
5960
|
toolName: target.name,
|
|
5726
5961
|
title: permissionIntent.title,
|
|
5727
|
-
...(
|
|
5962
|
+
...(isExecutionTool ? { kind: "execute" } : {}),
|
|
5728
5963
|
status: "pending",
|
|
5729
5964
|
rawInput: args,
|
|
5730
5965
|
...(commandContent ? { content: commandContent } : {}),
|
|
@@ -6290,17 +6525,65 @@ export class AgentSession {
|
|
|
6290
6525
|
return this.#promptGeneration;
|
|
6291
6526
|
}
|
|
6292
6527
|
|
|
6528
|
+
static async restoreFromMemoryGuardCheckpoint(
|
|
6529
|
+
input: AgentSessionMemoryGuardRestoreInput,
|
|
6530
|
+
): Promise<AgentMemoryGuardRestoreResult> {
|
|
6531
|
+
const { staged, claimsLease, claimsStateDir, ...config } = input;
|
|
6532
|
+
if (
|
|
6533
|
+
staged.manager.getSessionId() !== staged.transcriptIdentity.sessionId ||
|
|
6534
|
+
staged.hydrationContext.identity.sessionId !== staged.transcriptIdentity.sessionId
|
|
6535
|
+
) {
|
|
6536
|
+
await staged.cleanup();
|
|
6537
|
+
return { kind: "blocked", reason: "transcript-mismatch" };
|
|
6538
|
+
}
|
|
6539
|
+
if (
|
|
6540
|
+
!isMemoryGuardClaimsLeaseForStateDir(claimsLease, claimsStateDir) ||
|
|
6541
|
+
claimsLease.owner.sessionId !== staged.transcriptIdentity.sessionId
|
|
6542
|
+
) {
|
|
6543
|
+
await staged.cleanup();
|
|
6544
|
+
return { kind: "blocked", reason: "claim-mismatch" };
|
|
6545
|
+
}
|
|
6546
|
+
const checkpointMessages = staged.manager.buildSessionContext().messages;
|
|
6547
|
+
if (!util.isDeepStrictEqual(config.agent.state.messages, checkpointMessages)) {
|
|
6548
|
+
await staged.cleanup();
|
|
6549
|
+
return { kind: "blocked", reason: "agent-messages-mismatch" };
|
|
6550
|
+
}
|
|
6551
|
+
const session = new AgentSession({
|
|
6552
|
+
...config,
|
|
6553
|
+
sessionManager: staged.manager,
|
|
6554
|
+
recoveryHydrationContext: staged.hydrationContext,
|
|
6555
|
+
});
|
|
6556
|
+
session.#memoryGuardClaimsLease = claimsLease;
|
|
6557
|
+
if (session.recoveryHydrationContext !== staged.hydrationContext) {
|
|
6558
|
+
await session.dispose();
|
|
6559
|
+
await staged.cleanup();
|
|
6560
|
+
return { kind: "blocked", reason: "hydration-context-mismatch" };
|
|
6561
|
+
}
|
|
6562
|
+
return {
|
|
6563
|
+
kind: "staged",
|
|
6564
|
+
session,
|
|
6565
|
+
promotionFence: Object.freeze({ ownershipReady: true, claimsLease }),
|
|
6566
|
+
};
|
|
6567
|
+
}
|
|
6568
|
+
|
|
6293
6569
|
/** The immutable recovery authority, present only before external ownership promotion. */
|
|
6294
6570
|
get recoveryHydrationContext(): RecoveryHydrationContext | undefined {
|
|
6295
6571
|
return this.#recoveryHydrationContext;
|
|
6296
6572
|
}
|
|
6297
6573
|
|
|
6298
6574
|
/** Enables normal session mutations after the owner has published its durable fence and writer lease. */
|
|
6299
|
-
async promoteRecoveryHydrationAfterOwnershipReadyFence(fence:
|
|
6575
|
+
async promoteRecoveryHydrationAfterOwnershipReadyFence(fence: AgentMemoryGuardPromotionFence): Promise<void> {
|
|
6300
6576
|
const context = this.#recoveryHydrationContext;
|
|
6301
6577
|
if (!context) throw new Error("Agent session is not awaiting recovery hydration promotion.");
|
|
6578
|
+
if (!isMemoryGuardClaimsLease(fence.claimsLease)) {
|
|
6579
|
+
throw new Error("Recovery hydration promotion requires a live memory-guard claims lease.");
|
|
6580
|
+
}
|
|
6581
|
+
if (this.#memoryGuardClaimsLease !== fence.claimsLease) {
|
|
6582
|
+
throw new Error("Recovery hydration promotion requires the acquired memory-guard claims lease.");
|
|
6583
|
+
}
|
|
6302
6584
|
await this.sessionManager.promoteRecoveryHydrationAfterOwnershipReadyFence(context, fence);
|
|
6303
6585
|
this.#recoveryHydrationContext = undefined;
|
|
6586
|
+
this.#memoryGuardClaimsLease = undefined;
|
|
6304
6587
|
}
|
|
6305
6588
|
|
|
6306
6589
|
/** Recovery hydration must not start a continuation before ownership promotion. */
|
|
@@ -6372,6 +6655,15 @@ export class AgentSession {
|
|
|
6372
6655
|
return { ...context, messages: this.#withoutEphemeralCustomMessages(context.messages) };
|
|
6373
6656
|
}
|
|
6374
6657
|
|
|
6658
|
+
/** Build display context from an unpublished successor without changing active session state. @internal */
|
|
6659
|
+
buildPreparedDisplaySessionContext(prepared: PreparedNewSession): SessionContext {
|
|
6660
|
+
const context = deobfuscateSessionContext(
|
|
6661
|
+
this.sessionManager.buildPreparedNewSessionContext(prepared),
|
|
6662
|
+
this.#obfuscator,
|
|
6663
|
+
);
|
|
6664
|
+
return { ...context, messages: this.#withoutEphemeralCustomMessages(context.messages) };
|
|
6665
|
+
}
|
|
6666
|
+
|
|
6375
6667
|
/** Convert session messages using the same pre-LLM pipeline as the active session. */
|
|
6376
6668
|
async convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]> {
|
|
6377
6669
|
const transformedMessages = await this.#transformContext(messages, signal);
|
|
@@ -6479,9 +6771,11 @@ export class AgentSession {
|
|
|
6479
6771
|
/** Live SDK configuration values exposed through the session query surface. */
|
|
6480
6772
|
getSdkConfigItems(): Record<string, string> {
|
|
6481
6773
|
const model = this.model;
|
|
6774
|
+
const modelPreset = this.getActiveModelProfile() ?? this.settings.get("modelProfile.default");
|
|
6482
6775
|
return {
|
|
6483
6776
|
mode: this.#planModeState?.enabled ? "plan" : "default",
|
|
6484
6777
|
...(model ? { model: `${model.provider}/${model.id}` } : {}),
|
|
6778
|
+
...(modelPreset ? { modelPreset } : {}),
|
|
6485
6779
|
thinking: this.#thinkingLevel ?? "off",
|
|
6486
6780
|
steeringMode: this.steeringMode,
|
|
6487
6781
|
followUpMode: this.followUpMode,
|
|
@@ -6500,6 +6794,7 @@ export class AgentSession {
|
|
|
6500
6794
|
async invokeSkill(
|
|
6501
6795
|
name: string,
|
|
6502
6796
|
args = "",
|
|
6797
|
+
options?: Pick<PromptOptions, "onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared">,
|
|
6503
6798
|
): Promise<{ name: string; path: string; args?: string; lineCount?: number }> {
|
|
6504
6799
|
const skillName = name.trim();
|
|
6505
6800
|
if (!skillName) throw Object.assign(new Error("skill.invoke requires a skill name."), { code: "invalid_input" });
|
|
@@ -6532,7 +6827,13 @@ export class AgentSession {
|
|
|
6532
6827
|
attribution: "user" as const,
|
|
6533
6828
|
};
|
|
6534
6829
|
this.#deepInterviewPreclaimedCustomInputEpochs.set(skillPromptMessage, deepInterviewUserIntentEpoch);
|
|
6535
|
-
|
|
6830
|
+
options?.onSkillPrepared?.({
|
|
6831
|
+
name: skill.name,
|
|
6832
|
+
path: skill.filePath,
|
|
6833
|
+
lineCount: built.details.lineCount,
|
|
6834
|
+
cleanedArgs: activation.cleanedArgs || undefined,
|
|
6835
|
+
});
|
|
6836
|
+
await this.promptCustomMessage(skillPromptMessage, options);
|
|
6536
6837
|
return {
|
|
6537
6838
|
name: skill.name,
|
|
6538
6839
|
path: skill.filePath,
|
|
@@ -6578,22 +6879,49 @@ export class AgentSession {
|
|
|
6578
6879
|
}
|
|
6579
6880
|
}
|
|
6580
6881
|
|
|
6581
|
-
getTranscript():
|
|
6882
|
+
getTranscript(): ExtensionTranscriptEntry[] {
|
|
6582
6883
|
return getEntriesForInternalRead(this.sessionManager).flatMap(entry => {
|
|
6583
6884
|
if (entry.type !== "message") return [];
|
|
6584
|
-
const message = entry.message as unknown as {
|
|
6885
|
+
const message = entry.message as unknown as {
|
|
6886
|
+
role?: unknown;
|
|
6887
|
+
content?: unknown;
|
|
6888
|
+
toolCallId?: unknown;
|
|
6889
|
+
toolName?: unknown;
|
|
6890
|
+
isError?: unknown;
|
|
6891
|
+
};
|
|
6892
|
+
const content: NonNullable<ExtensionTranscriptEntry["content"]> | undefined = Array.isArray(message.content)
|
|
6893
|
+
? message.content.reduce<NonNullable<ExtensionTranscriptEntry["content"]>>((blocks, part) => {
|
|
6894
|
+
if (typeof part !== "object" || part === null || !("type" in part)) return blocks;
|
|
6895
|
+
if (part.type === "text" && "text" in part && typeof part.text === "string") {
|
|
6896
|
+
blocks.push({ type: "text", text: part.text });
|
|
6897
|
+
return blocks;
|
|
6898
|
+
}
|
|
6899
|
+
if (part.type === "thinking" && "thinking" in part && typeof part.thinking === "string") {
|
|
6900
|
+
blocks.push({ type: "thinking", thinking: part.thinking });
|
|
6901
|
+
return blocks;
|
|
6902
|
+
}
|
|
6903
|
+
if (
|
|
6904
|
+
part.type === "toolCall" &&
|
|
6905
|
+
"id" in part &&
|
|
6906
|
+
typeof part.id === "string" &&
|
|
6907
|
+
"name" in part &&
|
|
6908
|
+
typeof part.name === "string"
|
|
6909
|
+
) {
|
|
6910
|
+
blocks.push({
|
|
6911
|
+
type: "toolCall",
|
|
6912
|
+
id: part.id,
|
|
6913
|
+
name: part.name,
|
|
6914
|
+
arguments: "arguments" in part ? part.arguments : {},
|
|
6915
|
+
});
|
|
6916
|
+
}
|
|
6917
|
+
return blocks;
|
|
6918
|
+
}, [])
|
|
6919
|
+
: undefined;
|
|
6585
6920
|
const body =
|
|
6586
6921
|
typeof message.content === "string"
|
|
6587
6922
|
? message.content
|
|
6588
|
-
:
|
|
6589
|
-
?
|
|
6590
|
-
.map(part =>
|
|
6591
|
-
typeof part === "object" && part !== null && "text" in part && typeof part.text === "string"
|
|
6592
|
-
? part.text
|
|
6593
|
-
: "",
|
|
6594
|
-
)
|
|
6595
|
-
.filter(Boolean)
|
|
6596
|
-
.join("\n")
|
|
6923
|
+
: content
|
|
6924
|
+
? content.flatMap(part => (part.type === "text" ? [part.text] : [])).join("\n")
|
|
6597
6925
|
: "";
|
|
6598
6926
|
return [
|
|
6599
6927
|
{
|
|
@@ -6602,6 +6930,10 @@ export class AgentSession {
|
|
|
6602
6930
|
textSummary: body.slice(0, 500),
|
|
6603
6931
|
ts: entry.timestamp,
|
|
6604
6932
|
body,
|
|
6933
|
+
...(content ? { content } : {}),
|
|
6934
|
+
...(typeof message.toolCallId === "string" ? { toolCallId: message.toolCallId } : {}),
|
|
6935
|
+
...(typeof message.toolName === "string" ? { toolName: message.toolName } : {}),
|
|
6936
|
+
...(typeof message.isError === "boolean" ? { isError: message.isError } : {}),
|
|
6605
6937
|
},
|
|
6606
6938
|
];
|
|
6607
6939
|
});
|
|
@@ -6639,8 +6971,7 @@ export class AgentSession {
|
|
|
6639
6971
|
return getAskAnswerSourceFromRegistry(this.sessionId);
|
|
6640
6972
|
}
|
|
6641
6973
|
|
|
6642
|
-
#constructWorkflowGateEmitter(): WorkflowGateEmitter {
|
|
6643
|
-
const sessionId = this.sessionManager.getSessionId();
|
|
6974
|
+
#constructWorkflowGateEmitter(sessionId = this.sessionManager.getSessionId()): WorkflowGateEmitter {
|
|
6644
6975
|
assertNonEmptySkcSessionId(sessionId, "AgentSession workflow-gate session");
|
|
6645
6976
|
const gateStore = this.sessionManager.isPersisted()
|
|
6646
6977
|
? new FileGateStore(path.join(sessionStateDir(this.sessionManager.getCwd(), sessionId), "workflow-gates.json"))
|
|
@@ -6745,8 +7076,9 @@ export class AgentSession {
|
|
|
6745
7076
|
if (!activeSkill) {
|
|
6746
7077
|
try {
|
|
6747
7078
|
const activeState = await readVisibleSkillActiveState(this.sessionManager.getCwd(), sessionId);
|
|
6748
|
-
activeSkill =
|
|
6749
|
-
|
|
7079
|
+
activeSkill = activeState?.active_skills?.find(
|
|
7080
|
+
entry => entry.active !== false && entry.session_id === sessionId,
|
|
7081
|
+
)?.skill;
|
|
6750
7082
|
} catch (error) {
|
|
6751
7083
|
logger.warn("Failed to read durable workflow skill state while restoring ask tool", {
|
|
6752
7084
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -6758,7 +7090,12 @@ export class AgentSession {
|
|
|
6758
7090
|
// a predecessor session's workflow state to the current identity.
|
|
6759
7091
|
if (this.#isDisposed || this.sessionManager.getSessionId() !== sessionId) return;
|
|
6760
7092
|
}
|
|
6761
|
-
if (activeSkill && isCanonicalSkcWorkflowSkill(activeSkill.trim()))
|
|
7093
|
+
if (activeSkill && isCanonicalSkcWorkflowSkill(activeSkill.trim())) {
|
|
7094
|
+
if (!inMemoryActiveSkill) this.#restoredWorkflowSkillState = { skill: activeSkill.trim(), sessionId };
|
|
7095
|
+
this.#attachAskTool();
|
|
7096
|
+
} else if (this.#restoredWorkflowSkillState?.sessionId === sessionId) {
|
|
7097
|
+
this.#restoredWorkflowSkillState = undefined;
|
|
7098
|
+
}
|
|
6762
7099
|
}
|
|
6763
7100
|
|
|
6764
7101
|
#attachAskTool(): void {
|
|
@@ -7151,8 +7488,32 @@ export class AgentSession {
|
|
|
7151
7488
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
7152
7489
|
*/
|
|
7153
7490
|
async prompt(text: string, options?: PromptOptions): Promise<void> {
|
|
7491
|
+
this.#assertRecoveryHydrationPromoted();
|
|
7154
7492
|
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
7155
7493
|
|
|
7494
|
+
if (expandPromptTemplates && text.startsWith("/skill:") && !options?.images?.length) {
|
|
7495
|
+
const skillCommands = new Map(this.skills.map(skill => [getSkillSlashCommandName(skill), skill]));
|
|
7496
|
+
const invocations = parseSkillInvocations(text, skillCommands);
|
|
7497
|
+
if (invocations.length === 1) {
|
|
7498
|
+
const invocation = invocations[0];
|
|
7499
|
+
if (invocation) {
|
|
7500
|
+
await this.invokeSkill(
|
|
7501
|
+
invocation.skill.name,
|
|
7502
|
+
invocation.args,
|
|
7503
|
+
options?.onPreflightAccepted || options?.onPreflightAcceptCommit
|
|
7504
|
+
? {
|
|
7505
|
+
...(options.onPreflightAccepted ? { onPreflightAccepted: options.onPreflightAccepted } : {}),
|
|
7506
|
+
...(options.onPreflightAcceptCommit
|
|
7507
|
+
? { onPreflightAcceptCommit: options.onPreflightAcceptCommit }
|
|
7508
|
+
: {}),
|
|
7509
|
+
}
|
|
7510
|
+
: undefined,
|
|
7511
|
+
);
|
|
7512
|
+
return;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
}
|
|
7516
|
+
|
|
7156
7517
|
// Handle extension commands first (execute immediately, even during streaming)
|
|
7157
7518
|
if (expandPromptTemplates && text.startsWith("/")) {
|
|
7158
7519
|
const handled = await this.#tryExecuteExtensionCommand(text);
|
|
@@ -7200,7 +7561,8 @@ export class AgentSession {
|
|
|
7200
7561
|
if (workflowIntentDiff) {
|
|
7201
7562
|
this.sessionManager.appendCustomEntry(WORKFLOW_INTENT_DIFF_CUSTOM_TYPE, workflowIntentDiff);
|
|
7202
7563
|
}
|
|
7203
|
-
options?.
|
|
7564
|
+
if (options?.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
7565
|
+
else options?.onPreflightAccepted?.();
|
|
7204
7566
|
return;
|
|
7205
7567
|
}
|
|
7206
7568
|
|
|
@@ -7310,6 +7672,7 @@ export class AgentSession {
|
|
|
7310
7672
|
}
|
|
7311
7673
|
}
|
|
7312
7674
|
// In-memory tracking keeps `getActiveSkillState` accurate for the chain guard.
|
|
7675
|
+
this.#restoredWorkflowSkillState = undefined;
|
|
7313
7676
|
this.#activeSkillState = active ? { skill, sessionId } : undefined;
|
|
7314
7677
|
if (active) {
|
|
7315
7678
|
await this.refreshSkcSubskillTools();
|
|
@@ -7331,7 +7694,10 @@ export class AgentSession {
|
|
|
7331
7694
|
|
|
7332
7695
|
async promptCustomMessage<T = unknown>(
|
|
7333
7696
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
7334
|
-
options?: Pick<
|
|
7697
|
+
options?: Pick<
|
|
7698
|
+
PromptOptions,
|
|
7699
|
+
"streamingBehavior" | "toolChoice" | "followUpQueuePolicy" | "onPreflightAccepted" | "onPreflightAcceptCommit"
|
|
7700
|
+
>,
|
|
7335
7701
|
): Promise<void> {
|
|
7336
7702
|
const textContent =
|
|
7337
7703
|
typeof message.content === "string"
|
|
@@ -7393,7 +7759,10 @@ export class AgentSession {
|
|
|
7393
7759
|
async #promptWithMessage(
|
|
7394
7760
|
message: AgentMessage,
|
|
7395
7761
|
expandedText: string,
|
|
7396
|
-
options?: Pick<
|
|
7762
|
+
options?: Pick<
|
|
7763
|
+
PromptOptions,
|
|
7764
|
+
"toolChoice" | "images" | "skipCompactionCheck" | "onPreflightAccepted" | "onPreflightAcceptCommit"
|
|
7765
|
+
> & {
|
|
7397
7766
|
prependMessages?: AgentMessage[];
|
|
7398
7767
|
skipPostPromptRecoveryWait?: boolean;
|
|
7399
7768
|
predecessorAgentEndHold?: symbol;
|
|
@@ -7439,7 +7808,11 @@ export class AgentSession {
|
|
|
7439
7808
|
}
|
|
7440
7809
|
|
|
7441
7810
|
// Validate API key
|
|
7442
|
-
const apiKey = await this.#
|
|
7811
|
+
const apiKey = await this.#awaitPromptPreflight(
|
|
7812
|
+
generation,
|
|
7813
|
+
preflightSignal,
|
|
7814
|
+
this.#modelRegistry.getApiKey(this.model, this.sessionId, { signal: preflightSignal }),
|
|
7815
|
+
);
|
|
7443
7816
|
if (!apiKey) {
|
|
7444
7817
|
throw new Error(formatNoCredentialOnboardingError(this.model.provider));
|
|
7445
7818
|
}
|
|
@@ -7496,7 +7869,7 @@ export class AgentSession {
|
|
|
7496
7869
|
// A newer abort/prompt cycle superseded this preflight. Callers awaiting
|
|
7497
7870
|
// acceptance (onPreflightAccepted) must be told it never ran; direct
|
|
7498
7871
|
// callers (e.g. prompt() aborted during a TTSR wait) resolve gracefully.
|
|
7499
|
-
if (options?.onPreflightAccepted) throw promptPreflightCancelledError();
|
|
7872
|
+
if (options?.onPreflightAccepted || options?.onPreflightAcceptCommit) throw promptPreflightCancelledError();
|
|
7500
7873
|
return;
|
|
7501
7874
|
}
|
|
7502
7875
|
|
|
@@ -7610,7 +7983,7 @@ export class AgentSession {
|
|
|
7610
7983
|
this.#resetInjectedContextSignatures();
|
|
7611
7984
|
// Ack-waiting callers are told the preflight never ran; direct callers
|
|
7612
7985
|
// (aborted after setup) resolve gracefully as before f24f46ff5.
|
|
7613
|
-
if (options?.onPreflightAccepted) throw promptPreflightCancelledError();
|
|
7986
|
+
if (options?.onPreflightAccepted || options?.onPreflightAcceptCommit) throw promptPreflightCancelledError();
|
|
7614
7987
|
return;
|
|
7615
7988
|
}
|
|
7616
7989
|
|
|
@@ -7623,7 +7996,8 @@ export class AgentSession {
|
|
|
7623
7996
|
if (hindsightRecall) this.getHindsightSessionState()?.markRecallSnippetInjected(hindsightRecall);
|
|
7624
7997
|
},
|
|
7625
7998
|
};
|
|
7626
|
-
options?.
|
|
7999
|
+
if (options?.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
8000
|
+
else options?.onPreflightAccepted?.();
|
|
7627
8001
|
this.#throwIfPromptPreflightCancelled(generation, preflightSignal);
|
|
7628
8002
|
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions, predecessorAgentEndHold);
|
|
7629
8003
|
const terminalAssistant = this.#findLastAssistantMessage();
|
|
@@ -7642,7 +8016,12 @@ export class AgentSession {
|
|
|
7642
8016
|
// Session identity changes historically cancel local setup silently. Only SDK
|
|
7643
8017
|
// submissions provide an acceptance callback and require an explicit terminal
|
|
7644
8018
|
// preflight failure for their remote request authority.
|
|
7645
|
-
if (
|
|
8019
|
+
if (
|
|
8020
|
+
isPromptPreflightCancelledError(error) &&
|
|
8021
|
+
!options?.onPreflightAccepted &&
|
|
8022
|
+
!options?.onPreflightAcceptCommit
|
|
8023
|
+
)
|
|
8024
|
+
return;
|
|
7646
8025
|
throw error;
|
|
7647
8026
|
} finally {
|
|
7648
8027
|
this.#removeEphemeralCustomMessages();
|
|
@@ -7726,6 +8105,7 @@ export class AgentSession {
|
|
|
7726
8105
|
return tool ? { safeSummary: tool.safeSummary, safeSummaryFields: tool.safeSummaryFields } : undefined;
|
|
7727
8106
|
},
|
|
7728
8107
|
cycleModel: () => this.cycleModel(),
|
|
8108
|
+
setModelProfile: name => this.activateModelProfileForControl(name),
|
|
7729
8109
|
cycleThinkingLevel: () => this.cycleThinkingLevel(),
|
|
7730
8110
|
setQueueMode: (kind, mode) => {
|
|
7731
8111
|
if (kind === "steering" && (mode === "all" || mode === "one-at-a-time")) {
|
|
@@ -7742,7 +8122,7 @@ export class AgentSession {
|
|
|
7742
8122
|
}
|
|
7743
8123
|
return false;
|
|
7744
8124
|
},
|
|
7745
|
-
invokeSkill: (name, args) => this.invokeSkill(name, args),
|
|
8125
|
+
invokeSkill: (name, args, options) => this.invokeSkill(name, args, options),
|
|
7746
8126
|
setPlanMode: on => this.setSdkPlanMode(on),
|
|
7747
8127
|
operateGoal: (op, objective) => this.operateGoal(op, objective),
|
|
7748
8128
|
getSkillState: () => this.skills.map(skill => ({ name: skill.name, description: skill.description })),
|
|
@@ -7753,6 +8133,7 @@ export class AgentSession {
|
|
|
7753
8133
|
getJobs: () => undefined,
|
|
7754
8134
|
sdkBindings: () => [
|
|
7755
8135
|
"cycleModel",
|
|
8136
|
+
"setModelProfile",
|
|
7756
8137
|
"cycleThinkingLevel",
|
|
7757
8138
|
"setQueueMode",
|
|
7758
8139
|
"getSkillState",
|
|
@@ -7856,6 +8237,7 @@ export class AgentSession {
|
|
|
7856
8237
|
* Queue a steering message to interrupt the agent mid-run.
|
|
7857
8238
|
*/
|
|
7858
8239
|
async steer(text: string, images?: ImageContent[]): Promise<void> {
|
|
8240
|
+
this.#assertRecoveryHydrationPromoted();
|
|
7859
8241
|
if (text.startsWith("/")) {
|
|
7860
8242
|
this.#throwIfExtensionCommand(text);
|
|
7861
8243
|
}
|
|
@@ -7873,6 +8255,7 @@ export class AgentSession {
|
|
|
7873
8255
|
images?: ImageContent[],
|
|
7874
8256
|
options?: Pick<PromptOptions, "followUpQueuePolicy">,
|
|
7875
8257
|
): Promise<void> {
|
|
8258
|
+
this.#assertRecoveryHydrationPromoted();
|
|
7876
8259
|
if (text.startsWith("/")) {
|
|
7877
8260
|
this.#throwIfExtensionCommand(text);
|
|
7878
8261
|
}
|
|
@@ -8107,6 +8490,7 @@ export class AgentSession {
|
|
|
8107
8490
|
followUpQueuePolicy?: "respect-mode" | "sequential";
|
|
8108
8491
|
},
|
|
8109
8492
|
): Promise<void> {
|
|
8493
|
+
this.#assertRecoveryHydrationPromoted();
|
|
8110
8494
|
const appMessage: CustomMessage<T> = {
|
|
8111
8495
|
role: "custom",
|
|
8112
8496
|
customType: message.customType,
|
|
@@ -8248,8 +8632,13 @@ export class AgentSession {
|
|
|
8248
8632
|
*/
|
|
8249
8633
|
async sendUserMessage(
|
|
8250
8634
|
content: string | (TextContent | ImageContent)[],
|
|
8251
|
-
options?: {
|
|
8635
|
+
options?: {
|
|
8636
|
+
deliverAs?: "steer" | "followUp";
|
|
8637
|
+
onPreflightAccepted?: () => void;
|
|
8638
|
+
onPreflightAcceptCommit?: () => void | Promise<void>;
|
|
8639
|
+
},
|
|
8252
8640
|
): Promise<void> {
|
|
8641
|
+
this.#assertRecoveryHydrationPromoted();
|
|
8253
8642
|
// Normalize content to text string + optional images
|
|
8254
8643
|
let text: string;
|
|
8255
8644
|
let images: ImageContent[] | undefined;
|
|
@@ -8271,11 +8660,13 @@ export class AgentSession {
|
|
|
8271
8660
|
}
|
|
8272
8661
|
|
|
8273
8662
|
if (options?.deliverAs === "followUp") {
|
|
8663
|
+
if (options.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
8274
8664
|
await this.#queueFollowUp(text, images, { claimsGenuineUserIntent: true });
|
|
8275
8665
|
options.onPreflightAccepted?.();
|
|
8276
8666
|
return;
|
|
8277
8667
|
}
|
|
8278
8668
|
if (options?.deliverAs === "steer") {
|
|
8669
|
+
if (options.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
8279
8670
|
await this.#queueSteer(text, images, { claimsGenuineUserIntent: true });
|
|
8280
8671
|
options.onPreflightAccepted?.();
|
|
8281
8672
|
return;
|
|
@@ -8287,6 +8678,7 @@ export class AgentSession {
|
|
|
8287
8678
|
// in-flight compaction internally, and #queueSteer would otherwise park
|
|
8288
8679
|
// the message in the steering queue with no turn to consume it.
|
|
8289
8680
|
if (this.isStreaming) {
|
|
8681
|
+
if (options?.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
8290
8682
|
await this.#queueSteer(text, images, { claimsGenuineUserIntent: true });
|
|
8291
8683
|
options?.onPreflightAccepted?.();
|
|
8292
8684
|
return;
|
|
@@ -8297,6 +8689,7 @@ export class AgentSession {
|
|
|
8297
8689
|
expandPromptTemplates: false,
|
|
8298
8690
|
images,
|
|
8299
8691
|
onPreflightAccepted: options?.onPreflightAccepted,
|
|
8692
|
+
onPreflightAcceptCommit: options?.onPreflightAcceptCommit,
|
|
8300
8693
|
});
|
|
8301
8694
|
}
|
|
8302
8695
|
|
|
@@ -8921,7 +9314,14 @@ export class AgentSession {
|
|
|
8921
9314
|
this.#rebindProviderSessionState(new Map());
|
|
8922
9315
|
this.agent.reset();
|
|
8923
9316
|
if (!options?.drop) await this.sessionManager.flush();
|
|
8924
|
-
await this.sessionManager.
|
|
9317
|
+
const prepared = await this.sessionManager.prepareNewSession(options);
|
|
9318
|
+
try {
|
|
9319
|
+
// Last fallible gate while public getters still show the predecessor (#3138).
|
|
9320
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
9321
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
9322
|
+
} catch (error) {
|
|
9323
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
9324
|
+
}
|
|
8925
9325
|
this.setTodoPhases([]);
|
|
8926
9326
|
this.#syncAgentSessionId();
|
|
8927
9327
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
@@ -8983,12 +9383,18 @@ export class AgentSession {
|
|
|
8983
9383
|
if (!(await manager.cancelAndSettleOwnerJobs(ownerId))) {
|
|
8984
9384
|
throw new Error("Owned async jobs did not settle before session replacement.");
|
|
8985
9385
|
}
|
|
8986
|
-
|
|
9386
|
+
const prepared = await this.sessionManager.prepareNewSession(options);
|
|
9387
|
+
try {
|
|
9388
|
+
// Last fallible gate while public getters still show the predecessor (#3138).
|
|
9389
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
9390
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
9391
|
+
} catch (error) {
|
|
9392
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
9393
|
+
}
|
|
8987
9394
|
this.#disconnectFromAgent();
|
|
8988
9395
|
this.#closeAllProviderSessions("new session");
|
|
8989
9396
|
this.#rebindProviderSessionState(new Map());
|
|
8990
9397
|
this.agent.reset();
|
|
8991
|
-
await this.sessionManager.newSession(options);
|
|
8992
9398
|
this.setTodoPhases([]);
|
|
8993
9399
|
this.#syncAgentSessionId();
|
|
8994
9400
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
@@ -9023,6 +9429,12 @@ export class AgentSession {
|
|
|
9023
9429
|
): Promise<void> {
|
|
9024
9430
|
this.#clearConstructorToolSelectionAuthority();
|
|
9025
9431
|
const inheritedThinkingLevel = resolveThinkingLevelForModel(this.model, this.#getInheritedThinkingLevel());
|
|
9432
|
+
this.#thinkingLevelMutationRevision++;
|
|
9433
|
+
this.#thinkingLevelLiveMutationRevision++;
|
|
9434
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
9435
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
9436
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
9437
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
9026
9438
|
this.#thinkingLevel = inheritedThinkingLevel;
|
|
9027
9439
|
this.agent.setThinkingLevel(toReasoningEffort(inheritedThinkingLevel));
|
|
9028
9440
|
this.sessionManager.appendThinkingLevelChange(ThinkingLevel.Inherit);
|
|
@@ -9031,10 +9443,17 @@ export class AgentSession {
|
|
|
9031
9443
|
}
|
|
9032
9444
|
this.sessionManager.appendServiceTierChange(this.serviceTier ?? null);
|
|
9033
9445
|
if (nextDiscoverySessionToolNames) {
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9446
|
+
// Avoid a full system-prompt rebuild when the active set is unchanged; rebuild
|
|
9447
|
+
// can block on shared workspace-tree work across repeated /new transitions.
|
|
9448
|
+
const current = this.getActiveToolNames().slice().sort().join("\0");
|
|
9449
|
+
const next = [...new Set(nextDiscoverySessionToolNames.map(n => n.toLowerCase()))].sort().join("\0");
|
|
9450
|
+
if (current !== next) {
|
|
9451
|
+
await this.#applyActiveToolsByName(nextDiscoverySessionToolNames, {
|
|
9452
|
+
persistMCPSelection: false,
|
|
9453
|
+
nextSelectedDiscoveredBuiltinToolNames: [],
|
|
9454
|
+
});
|
|
9455
|
+
} else {
|
|
9456
|
+
}
|
|
9038
9457
|
}
|
|
9039
9458
|
this.#todoReminderCount = 0;
|
|
9040
9459
|
this.#planReferenceSent = false;
|
|
@@ -9047,6 +9466,7 @@ export class AgentSession {
|
|
|
9047
9466
|
reason: "new",
|
|
9048
9467
|
previousSessionFile,
|
|
9049
9468
|
});
|
|
9469
|
+
} else {
|
|
9050
9470
|
}
|
|
9051
9471
|
}
|
|
9052
9472
|
|
|
@@ -9126,13 +9546,18 @@ export class AgentSession {
|
|
|
9126
9546
|
// Flush current session to ensure all entries are written
|
|
9127
9547
|
await this.sessionManager.flush();
|
|
9128
9548
|
|
|
9129
|
-
//
|
|
9130
|
-
|
|
9131
|
-
|
|
9549
|
+
// Prepare the copied successor and complete local:// readiness while all
|
|
9550
|
+
// public manager getters remain bound to the predecessor.
|
|
9551
|
+
const prepared = await this.sessionManager.prepareFork();
|
|
9552
|
+
if (!prepared) {
|
|
9132
9553
|
return false;
|
|
9133
9554
|
}
|
|
9134
|
-
|
|
9135
|
-
|
|
9555
|
+
try {
|
|
9556
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
9557
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
9558
|
+
} catch (error) {
|
|
9559
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
9560
|
+
}
|
|
9136
9561
|
this.#syncAgentSessionId();
|
|
9137
9562
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
9138
9563
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
@@ -9221,6 +9646,17 @@ export class AgentSession {
|
|
|
9221
9646
|
return this.#activeModelProfile;
|
|
9222
9647
|
}
|
|
9223
9648
|
|
|
9649
|
+
/** Activate a complete model profile through a nonvisual session control. */
|
|
9650
|
+
async activateModelProfileForControl(profileName: string): Promise<boolean> {
|
|
9651
|
+
await activateModelProfile({
|
|
9652
|
+
session: this,
|
|
9653
|
+
modelRegistry: this.#modelRegistry,
|
|
9654
|
+
settings: this.settings,
|
|
9655
|
+
profileName,
|
|
9656
|
+
});
|
|
9657
|
+
return this.getActiveModelProfile() === profileName;
|
|
9658
|
+
}
|
|
9659
|
+
|
|
9224
9660
|
/** Return the persisted configured fallback selectors for a model role. */
|
|
9225
9661
|
getConfiguredModelChain(role: string): readonly string[] | undefined {
|
|
9226
9662
|
return getSessionContextForInternalRead(this.sessionManager).configuredModelChains[role]?.entries;
|
|
@@ -9348,7 +9784,7 @@ export class AgentSession {
|
|
|
9348
9784
|
const ownsScope = scope !== undefined && !suppliedScope;
|
|
9349
9785
|
try {
|
|
9350
9786
|
if (isTemporaryOperation) {
|
|
9351
|
-
this
|
|
9787
|
+
this.#setAgentModelWithReasoningContext(model);
|
|
9352
9788
|
this.#syncAppendOnlyContext(model);
|
|
9353
9789
|
} else {
|
|
9354
9790
|
this.#setModelAuthoritatively(model, options?.cause ?? "temporary-operation");
|
|
@@ -9433,6 +9869,12 @@ export class AgentSession {
|
|
|
9433
9869
|
this.#clearActiveRetryFallback();
|
|
9434
9870
|
this.#setModelWithProviderSessionReset(model);
|
|
9435
9871
|
const thinkingLevelChanged = this.#thinkingLevel !== thinkingLevel;
|
|
9872
|
+
this.#thinkingLevelMutationRevision++;
|
|
9873
|
+
this.#thinkingLevelLiveMutationRevision++;
|
|
9874
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
9875
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
9876
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
9877
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
9436
9878
|
this.#thinkingLevel = thinkingLevel;
|
|
9437
9879
|
this.agent.setThinkingLevel(toReasoningEffort(thinkingLevel));
|
|
9438
9880
|
if (thinkingLevelChanged) {
|
|
@@ -9741,7 +10183,19 @@ export class AgentSession {
|
|
|
9741
10183
|
* Set thinking level.
|
|
9742
10184
|
* Saves the effective metadata-clamped level to the session, and to settings when requested.
|
|
9743
10185
|
*/
|
|
10186
|
+
#assertDurableSettingsWritable(): void {
|
|
10187
|
+
if (this.settings.canWriteDurableConfig()) return;
|
|
10188
|
+
throw new Error(
|
|
10189
|
+
"Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
|
|
10190
|
+
);
|
|
10191
|
+
}
|
|
10192
|
+
|
|
9744
10193
|
setThinkingLevel(level: ThinkingLevel | undefined, persist: boolean = false): void {
|
|
10194
|
+
if (persist) this.#assertDurableSettingsWritable();
|
|
10195
|
+
this.#thinkingLevelMutationRevision++;
|
|
10196
|
+
this.#thinkingLevelLiveMutationRevision++;
|
|
10197
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
10198
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
9745
10199
|
const effectiveLevel = resolveThinkingLevelForModel(this.model, level);
|
|
9746
10200
|
const isChanging = effectiveLevel !== this.#thinkingLevel;
|
|
9747
10201
|
|
|
@@ -9761,11 +10215,10 @@ export class AgentSession {
|
|
|
9761
10215
|
}
|
|
9762
10216
|
|
|
9763
10217
|
/**
|
|
9764
|
-
* Set thinking level from a control surface. Global changes
|
|
10218
|
+
* Set thinking level from a control surface. Global changes commit before affecting live state.
|
|
9765
10219
|
*/
|
|
9766
10220
|
async setThinkingLevelForControl(level: ThinkingLevel, persist: boolean): Promise<void> {
|
|
9767
10221
|
const previousThinkingLevel = this.thinkingLevel;
|
|
9768
|
-
const previousScope = this.getThinkingScopeForControl();
|
|
9769
10222
|
if (!persist) {
|
|
9770
10223
|
this.setThinkingLevel(level === ThinkingLevel.Inherit ? this.#getInheritedThinkingLevel() : level);
|
|
9771
10224
|
if (level === ThinkingLevel.Inherit || this.thinkingLevel === previousThinkingLevel) {
|
|
@@ -9774,26 +10227,99 @@ export class AgentSession {
|
|
|
9774
10227
|
return;
|
|
9775
10228
|
}
|
|
9776
10229
|
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
this
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
10230
|
+
this.#assertDurableSettingsWritable();
|
|
10231
|
+
const effectiveLevel = resolveThinkingLevelForModel(
|
|
10232
|
+
this.model,
|
|
10233
|
+
level === ThinkingLevel.Inherit ? this.#getInheritedThinkingLevel() : level,
|
|
10234
|
+
);
|
|
10235
|
+
const persistedLevel = level === ThinkingLevel.Inherit ? getDefault("defaultThinkingLevel") : effectiveLevel;
|
|
10236
|
+
const mutationRevision = ++this.#thinkingLevelMutationRevision;
|
|
10237
|
+
const expectedLiveMutationRevision = this.#thinkingLevelLiveMutationRevision;
|
|
10238
|
+
const expectedSessionId = this.sessionManager.getSessionId();
|
|
10239
|
+
const expectedModel = this.model;
|
|
10240
|
+
const expectedContextGeneration = this.#reasoningControlContextGeneration;
|
|
9785
10241
|
try {
|
|
9786
|
-
await this.settings.
|
|
9787
|
-
this.sessionManager.appendThinkingLevelChange(ThinkingLevel.Inherit);
|
|
10242
|
+
await this.settings.commitAtomicBatch([{ path: "defaultThinkingLevel", op: "set", value: persistedLevel }]);
|
|
9788
10243
|
} catch {
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
10244
|
+
if (
|
|
10245
|
+
mutationRevision === this.#thinkingLevelMutationRevision &&
|
|
10246
|
+
this.#reasoningControlContextGeneration === expectedContextGeneration &&
|
|
10247
|
+
this.sessionManager.getSessionId() === expectedSessionId &&
|
|
10248
|
+
this.model === expectedModel
|
|
10249
|
+
) {
|
|
10250
|
+
const pending = this.#pendingThinkingLevelControlSuccess;
|
|
10251
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
10252
|
+
if (
|
|
10253
|
+
pending &&
|
|
10254
|
+
pending.contextGeneration === expectedContextGeneration &&
|
|
10255
|
+
this.sessionManager.getSessionId() === pending.sessionId &&
|
|
10256
|
+
this.model === pending.model
|
|
10257
|
+
) {
|
|
10258
|
+
this.setThinkingLevel(
|
|
10259
|
+
pending.level === ThinkingLevel.Inherit ? this.#getInheritedThinkingLevel() : pending.level,
|
|
10260
|
+
);
|
|
10261
|
+
this.sessionManager.appendThinkingLevelChange(ThinkingLevel.Inherit);
|
|
10262
|
+
} else {
|
|
10263
|
+
this.#pendingThinkingLevelControlFailure = {
|
|
10264
|
+
mutationRevision,
|
|
10265
|
+
liveMutationRevision: expectedLiveMutationRevision,
|
|
10266
|
+
sessionId: expectedSessionId,
|
|
10267
|
+
model: expectedModel,
|
|
10268
|
+
contextGeneration: expectedContextGeneration,
|
|
10269
|
+
};
|
|
10270
|
+
}
|
|
10271
|
+
}
|
|
9795
10272
|
throw new Error("Unable to persist reasoning settings.");
|
|
9796
10273
|
}
|
|
10274
|
+
|
|
10275
|
+
if (
|
|
10276
|
+
mutationRevision === this.#thinkingLevelMutationRevision &&
|
|
10277
|
+
this.#thinkingLevelLiveMutationRevision === expectedLiveMutationRevision
|
|
10278
|
+
) {
|
|
10279
|
+
this.setThinkingLevel(level === ThinkingLevel.Inherit ? this.#getInheritedThinkingLevel() : level);
|
|
10280
|
+
this.sessionManager.appendThinkingLevelChange(ThinkingLevel.Inherit);
|
|
10281
|
+
return;
|
|
10282
|
+
}
|
|
10283
|
+
if (
|
|
10284
|
+
mutationRevision !== this.#thinkingLevelMutationRevision ||
|
|
10285
|
+
this.#reasoningControlContextGeneration !== expectedContextGeneration ||
|
|
10286
|
+
this.sessionManager.getSessionId() !== expectedSessionId ||
|
|
10287
|
+
this.model !== expectedModel
|
|
10288
|
+
) {
|
|
10289
|
+
if (
|
|
10290
|
+
this.#thinkingLevelLiveMutationRevision === expectedLiveMutationRevision &&
|
|
10291
|
+
this.#reasoningControlContextGeneration === expectedContextGeneration &&
|
|
10292
|
+
this.sessionManager.getSessionId() === expectedSessionId &&
|
|
10293
|
+
this.model === expectedModel &&
|
|
10294
|
+
(this.#pendingThinkingLevelControlSuccess?.mutationRevision ?? -1) < mutationRevision
|
|
10295
|
+
) {
|
|
10296
|
+
this.#pendingThinkingLevelControlSuccess = {
|
|
10297
|
+
level,
|
|
10298
|
+
mutationRevision,
|
|
10299
|
+
sessionId: expectedSessionId,
|
|
10300
|
+
model: expectedModel,
|
|
10301
|
+
contextGeneration: expectedContextGeneration,
|
|
10302
|
+
};
|
|
10303
|
+
const failure = this.#pendingThinkingLevelControlFailure;
|
|
10304
|
+
if (
|
|
10305
|
+
failure &&
|
|
10306
|
+
failure.mutationRevision === this.#thinkingLevelMutationRevision &&
|
|
10307
|
+
failure.liveMutationRevision === expectedLiveMutationRevision &&
|
|
10308
|
+
failure.sessionId === expectedSessionId &&
|
|
10309
|
+
failure.model === expectedModel &&
|
|
10310
|
+
failure.contextGeneration === expectedContextGeneration
|
|
10311
|
+
) {
|
|
10312
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
10313
|
+
this.setThinkingLevel(
|
|
10314
|
+
level === ThinkingLevel.Inherit ? this.#getInheritedThinkingLevel() : effectiveLevel,
|
|
10315
|
+
);
|
|
10316
|
+
this.sessionManager.appendThinkingLevelChange(ThinkingLevel.Inherit);
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
return;
|
|
10320
|
+
}
|
|
10321
|
+
this.setThinkingLevel(level === ThinkingLevel.Inherit ? this.#getInheritedThinkingLevel() : effectiveLevel);
|
|
10322
|
+
this.sessionManager.appendThinkingLevelChange(ThinkingLevel.Inherit);
|
|
9797
10323
|
}
|
|
9798
10324
|
|
|
9799
10325
|
getThinkingScopeForControl(): "session" | "global config" {
|
|
@@ -9809,6 +10335,11 @@ export class AgentSession {
|
|
|
9809
10335
|
}
|
|
9810
10336
|
|
|
9811
10337
|
setThinkingVisibility(visibility: "visible" | "hidden", persist: boolean = false): void {
|
|
10338
|
+
if (persist) this.#assertDurableSettingsWritable();
|
|
10339
|
+
this.#thinkingVisibilityMutationRevision++;
|
|
10340
|
+
this.#thinkingVisibilityLiveMutationRevision++;
|
|
10341
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
10342
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
9812
10343
|
this.agent.hideThinkingSummary = visibility === "hidden";
|
|
9813
10344
|
if (persist) {
|
|
9814
10345
|
this.settings.set("hideThinkingBlock", visibility === "hidden");
|
|
@@ -9816,7 +10347,7 @@ export class AgentSession {
|
|
|
9816
10347
|
}
|
|
9817
10348
|
|
|
9818
10349
|
/**
|
|
9819
|
-
* Set thinking visibility from a control surface. Global changes
|
|
10350
|
+
* Set thinking visibility from a control surface. Global changes commit before affecting live state.
|
|
9820
10351
|
*/
|
|
9821
10352
|
async setThinkingVisibilityForControl(visibility: "visible" | "hidden", persist: boolean): Promise<void> {
|
|
9822
10353
|
if (!persist) {
|
|
@@ -9824,17 +10355,88 @@ export class AgentSession {
|
|
|
9824
10355
|
return;
|
|
9825
10356
|
}
|
|
9826
10357
|
|
|
9827
|
-
|
|
9828
|
-
const
|
|
9829
|
-
this
|
|
10358
|
+
this.#assertDurableSettingsWritable();
|
|
10359
|
+
const mutationRevision = ++this.#thinkingVisibilityMutationRevision;
|
|
10360
|
+
const expectedLiveMutationRevision = this.#thinkingVisibilityLiveMutationRevision;
|
|
10361
|
+
const expectedSessionId = this.sessionManager.getSessionId();
|
|
10362
|
+
const expectedModel = this.model;
|
|
10363
|
+
const expectedContextGeneration = this.#reasoningControlContextGeneration;
|
|
9830
10364
|
try {
|
|
9831
|
-
await this.settings.
|
|
10365
|
+
await this.settings.commitAtomicBatch([
|
|
10366
|
+
{ path: "hideThinkingBlock", op: "set", value: visibility === "hidden" },
|
|
10367
|
+
]);
|
|
9832
10368
|
} catch {
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
10369
|
+
if (
|
|
10370
|
+
mutationRevision === this.#thinkingVisibilityMutationRevision &&
|
|
10371
|
+
this.#reasoningControlContextGeneration === expectedContextGeneration &&
|
|
10372
|
+
this.sessionManager.getSessionId() === expectedSessionId &&
|
|
10373
|
+
this.model === expectedModel
|
|
10374
|
+
) {
|
|
10375
|
+
const pending = this.#pendingThinkingVisibilityControlSuccess;
|
|
10376
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
10377
|
+
if (
|
|
10378
|
+
pending &&
|
|
10379
|
+
pending.contextGeneration === expectedContextGeneration &&
|
|
10380
|
+
this.sessionManager.getSessionId() === pending.sessionId &&
|
|
10381
|
+
this.model === pending.model
|
|
10382
|
+
) {
|
|
10383
|
+
this.setThinkingVisibility(pending.visibility);
|
|
10384
|
+
} else {
|
|
10385
|
+
this.#pendingThinkingVisibilityControlFailure = {
|
|
10386
|
+
mutationRevision,
|
|
10387
|
+
liveMutationRevision: expectedLiveMutationRevision,
|
|
10388
|
+
sessionId: expectedSessionId,
|
|
10389
|
+
model: expectedModel,
|
|
10390
|
+
contextGeneration: expectedContextGeneration,
|
|
10391
|
+
};
|
|
10392
|
+
}
|
|
10393
|
+
}
|
|
9836
10394
|
throw new Error("Unable to persist reasoning settings.");
|
|
9837
10395
|
}
|
|
10396
|
+
if (
|
|
10397
|
+
mutationRevision === this.#thinkingVisibilityMutationRevision &&
|
|
10398
|
+
this.#thinkingVisibilityLiveMutationRevision === expectedLiveMutationRevision
|
|
10399
|
+
) {
|
|
10400
|
+
this.setThinkingVisibility(visibility);
|
|
10401
|
+
return;
|
|
10402
|
+
}
|
|
10403
|
+
|
|
10404
|
+
if (
|
|
10405
|
+
mutationRevision !== this.#thinkingVisibilityMutationRevision ||
|
|
10406
|
+
this.#reasoningControlContextGeneration !== expectedContextGeneration ||
|
|
10407
|
+
this.sessionManager.getSessionId() !== expectedSessionId ||
|
|
10408
|
+
this.model !== expectedModel
|
|
10409
|
+
) {
|
|
10410
|
+
if (
|
|
10411
|
+
this.#thinkingVisibilityLiveMutationRevision === expectedLiveMutationRevision &&
|
|
10412
|
+
this.#reasoningControlContextGeneration === expectedContextGeneration &&
|
|
10413
|
+
this.sessionManager.getSessionId() === expectedSessionId &&
|
|
10414
|
+
this.model === expectedModel &&
|
|
10415
|
+
(this.#pendingThinkingVisibilityControlSuccess?.mutationRevision ?? -1) < mutationRevision
|
|
10416
|
+
) {
|
|
10417
|
+
this.#pendingThinkingVisibilityControlSuccess = {
|
|
10418
|
+
visibility,
|
|
10419
|
+
mutationRevision,
|
|
10420
|
+
sessionId: expectedSessionId,
|
|
10421
|
+
model: expectedModel,
|
|
10422
|
+
contextGeneration: expectedContextGeneration,
|
|
10423
|
+
};
|
|
10424
|
+
const failure = this.#pendingThinkingVisibilityControlFailure;
|
|
10425
|
+
if (
|
|
10426
|
+
failure &&
|
|
10427
|
+
failure.mutationRevision === this.#thinkingVisibilityMutationRevision &&
|
|
10428
|
+
failure.liveMutationRevision === expectedLiveMutationRevision &&
|
|
10429
|
+
failure.sessionId === expectedSessionId &&
|
|
10430
|
+
failure.model === expectedModel &&
|
|
10431
|
+
failure.contextGeneration === expectedContextGeneration
|
|
10432
|
+
) {
|
|
10433
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
10434
|
+
this.setThinkingVisibility(visibility);
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
10437
|
+
return;
|
|
10438
|
+
}
|
|
10439
|
+
this.setThinkingVisibility(visibility);
|
|
9838
10440
|
}
|
|
9839
10441
|
|
|
9840
10442
|
/**
|
|
@@ -10011,6 +10613,7 @@ export class AgentSession {
|
|
|
10011
10613
|
* Saves to settings.
|
|
10012
10614
|
*/
|
|
10013
10615
|
setSteeringMode(mode: "all" | "one-at-a-time"): void {
|
|
10616
|
+
this.#assertDurableSettingsWritable();
|
|
10014
10617
|
this.agent.setSteeringMode(mode);
|
|
10015
10618
|
this.settings.set("steeringMode", mode);
|
|
10016
10619
|
}
|
|
@@ -10020,6 +10623,7 @@ export class AgentSession {
|
|
|
10020
10623
|
* Saves to settings.
|
|
10021
10624
|
*/
|
|
10022
10625
|
setFollowUpMode(mode: "all" | "one-at-a-time"): void {
|
|
10626
|
+
this.#assertDurableSettingsWritable();
|
|
10023
10627
|
this.agent.setFollowUpMode(mode);
|
|
10024
10628
|
this.settings.set("followUpMode", mode);
|
|
10025
10629
|
}
|
|
@@ -10029,6 +10633,7 @@ export class AgentSession {
|
|
|
10029
10633
|
* Saves to settings.
|
|
10030
10634
|
*/
|
|
10031
10635
|
setInterruptMode(mode: "immediate" | "wait"): void {
|
|
10636
|
+
this.#assertDurableSettingsWritable();
|
|
10032
10637
|
this.agent.setInterruptMode(mode);
|
|
10033
10638
|
this.settings.set("interruptMode", mode);
|
|
10034
10639
|
}
|
|
@@ -10523,22 +11128,36 @@ export class AgentSession {
|
|
|
10523
11128
|
// user work that agent.reset() would otherwise clear.
|
|
10524
11129
|
const rollbackAgentSteeringQueue = this.agent.snapshotSteering();
|
|
10525
11130
|
const rollbackAgentFollowUpQueue = this.agent.snapshotFollowUp();
|
|
10526
|
-
let successorSessionFile: string | undefined;
|
|
10527
11131
|
let savedPath: string | undefined;
|
|
10528
11132
|
let committed = false;
|
|
10529
|
-
|
|
10530
|
-
// commit or resumed on rollback. Suspension runs inside the transaction so a
|
|
10531
|
-
// listener fault during suspension is rolled back like any other prepare step.
|
|
10532
|
-
let suspendedWorkflowGateEmitter: WorkflowGateEmitter | undefined;
|
|
11133
|
+
let prepared: PreparedNewSession | undefined;
|
|
10533
11134
|
try {
|
|
10534
|
-
|
|
10535
|
-
//
|
|
10536
|
-
//
|
|
10537
|
-
|
|
10538
|
-
await this.sessionManager.newSession(
|
|
11135
|
+
// Prepare successor entries, persistence, display state, and gate
|
|
11136
|
+
// construction without publishing manager identity. Managed local-root
|
|
11137
|
+
// readiness is the last fallible step before the sole commit (#3138).
|
|
11138
|
+
prepared = await this.sessionManager.prepareNewSession(
|
|
10539
11139
|
previousSessionFile ? { parentSession: previousSessionFile } : undefined,
|
|
10540
11140
|
);
|
|
10541
|
-
|
|
11141
|
+
if (model) this.sessionManager.appendPreparedModelChange(prepared, `${model.provider}/${model.id}`);
|
|
11142
|
+
this.sessionManager.appendPreparedThinkingLevelChange(prepared, this.thinkingLevel);
|
|
11143
|
+
this.sessionManager.appendPreparedServiceTierChange(prepared, this.serviceTier ?? null);
|
|
11144
|
+
this.sessionManager.appendPreparedCustomMessageEntry(
|
|
11145
|
+
prepared,
|
|
11146
|
+
"handoff",
|
|
11147
|
+
createHandoffContext(handoffText),
|
|
11148
|
+
true,
|
|
11149
|
+
undefined,
|
|
11150
|
+
"agent",
|
|
11151
|
+
);
|
|
11152
|
+
await this.sessionManager.ensurePreparedNewSessionOnDisk(prepared);
|
|
11153
|
+
const sessionContext = this.buildPreparedDisplaySessionContext(prepared);
|
|
11154
|
+
const successorGateEmitter = this.#constructWorkflowGateEmitter(prepared.sessionId);
|
|
11155
|
+
// Last fallible action: verified local:// readiness from immutable staged options.
|
|
11156
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
11157
|
+
|
|
11158
|
+
// --- Commit boundary: synchronous adoption is the sole identity publication.
|
|
11159
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
11160
|
+
committed = true;
|
|
10542
11161
|
this.agent.reset();
|
|
10543
11162
|
this.#syncAgentSessionId();
|
|
10544
11163
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
@@ -10547,16 +11166,8 @@ export class AgentSession {
|
|
|
10547
11166
|
this.#pendingNextTurnMessages = [];
|
|
10548
11167
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
10549
11168
|
this.#todoReminderCount = 0;
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
}
|
|
10553
|
-
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
|
|
10554
|
-
this.sessionManager.appendServiceTierChange(this.serviceTier ?? null);
|
|
10555
|
-
|
|
10556
|
-
// Inject the handoff document as a custom message
|
|
10557
|
-
const handoffContent = createHandoffContext(handoffText);
|
|
10558
|
-
this.sessionManager.appendCustomMessageEntry("handoff", handoffContent, true, undefined, "agent");
|
|
10559
|
-
await this.sessionManager.ensureOnDisk();
|
|
11169
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
11170
|
+
this.#syncTodoPhasesFromBranch();
|
|
10560
11171
|
if (options?.autoTriggered && this.settings.get("compaction.handoffSaveToDisk")) {
|
|
10561
11172
|
try {
|
|
10562
11173
|
const artifactId = await this.sessionManager.saveArtifact(`${handoffText}\n`, "handoff");
|
|
@@ -10568,30 +11179,9 @@ export class AgentSession {
|
|
|
10568
11179
|
}
|
|
10569
11180
|
}
|
|
10570
11181
|
|
|
10571
|
-
//
|
|
10572
|
-
const sessionContext = this.buildDisplaySessionContext();
|
|
10573
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
10574
|
-
this.#syncTodoPhasesFromBranch();
|
|
10575
|
-
|
|
10576
|
-
// Construct the successor workflow-gate emitter in the reversible window:
|
|
10577
|
-
// FileGateStore load / broker init can throw, and a failure here must roll
|
|
10578
|
-
// back rather than corrupt an already-committed switch. Publication is
|
|
10579
|
-
// deferred to the (no-throw) commit step below.
|
|
10580
|
-
const successorGateEmitter = this.#constructWorkflowGateEmitter();
|
|
10581
|
-
|
|
10582
|
-
// --- Commit boundary: the successor is durably persisted and every
|
|
10583
|
-
// reversible, fallible step above has succeeded. Cross the irreversible
|
|
10584
|
-
// commit BEFORE any identity rotation. Fencing the predecessor gate
|
|
10585
|
-
// emitter and rotating provider sessions cannot be undone, so once we
|
|
10586
|
-
// begin them a failure must RETAIN the committed successor rather than
|
|
10587
|
-
// attempt a (now-impossible) rollback that would resume a fenced emitter.
|
|
10588
|
-
committed = true;
|
|
11182
|
+
// The forward-only suffix starts only after the committed successor is live.
|
|
10589
11183
|
this.#resetInjectedContextSignatures();
|
|
10590
|
-
this.#publishWorkflowGateEmitter(
|
|
10591
|
-
successorGateEmitter,
|
|
10592
|
-
rollbackSessionState.sessionId,
|
|
10593
|
-
suspendedWorkflowGateEmitter,
|
|
10594
|
-
);
|
|
11184
|
+
this.#publishWorkflowGateEmitter(successorGateEmitter, rollbackSessionState.sessionId);
|
|
10595
11185
|
this.#closeAllProviderSessions("session handoff");
|
|
10596
11186
|
this.#rebindProviderSessionState(new Map());
|
|
10597
11187
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
@@ -10627,12 +11217,12 @@ export class AgentSession {
|
|
|
10627
11217
|
return { document: handoffText, savedPath };
|
|
10628
11218
|
} catch (switchError) {
|
|
10629
11219
|
if (committed) {
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
11220
|
+
throw Object.assign(new Error("Handoff committed but successor initialization was degraded."), {
|
|
11221
|
+
code: "handoff_committed_degraded",
|
|
11222
|
+
cause: switchError,
|
|
11223
|
+
handoffDocument: handoffText,
|
|
11224
|
+
savedPath,
|
|
10634
11225
|
});
|
|
10635
|
-
return { document: handoffText, savedPath };
|
|
10636
11226
|
}
|
|
10637
11227
|
// Reversible window: roll back to the pre-handoff session so the
|
|
10638
11228
|
// failure is non-destructive. Predecessor gate emitter, provider
|
|
@@ -10640,7 +11230,6 @@ export class AgentSession {
|
|
|
10640
11230
|
// were never mutated before commit, so they survive intact.
|
|
10641
11231
|
this.sessionManager.restoreState(rollbackSessionState);
|
|
10642
11232
|
this.#syncAgentSessionId(rollbackSessionState.sessionId);
|
|
10643
|
-
this.#restoreWorkflowGateEmitter(suspendedWorkflowGateEmitter);
|
|
10644
11233
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
10645
11234
|
this.agent.replaceMessages(rollbackAgentMessages);
|
|
10646
11235
|
this.agent.clearAllQueues();
|
|
@@ -10652,25 +11241,17 @@ export class AgentSession {
|
|
|
10652
11241
|
this.#scheduledHiddenNextTurnGeneration = rollbackScheduledHiddenNextTurnGeneration;
|
|
10653
11242
|
this.#todoReminderCount = rollbackTodoReminderCount;
|
|
10654
11243
|
this.#syncTodoPhasesFromBranch();
|
|
10655
|
-
//
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
await this.sessionManager.discardUncommittedSession(successorSessionFile);
|
|
10660
|
-
} catch (cleanupError) {
|
|
10661
|
-
logger.warn("Failed to remove orphaned handoff session after rollback", {
|
|
10662
|
-
path: successorSessionFile,
|
|
10663
|
-
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
10664
|
-
});
|
|
10665
|
-
}
|
|
10666
|
-
}
|
|
11244
|
+
// Exact-discard only the staged successor; predecessor state was never adopted.
|
|
11245
|
+
const rollbackError = prepared
|
|
11246
|
+
? await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, switchError)
|
|
11247
|
+
: switchError;
|
|
10667
11248
|
// Map to cancellation only for a genuine handoff-signal abort; a
|
|
10668
11249
|
// downstream error keeps its cause and the generated document.
|
|
10669
11250
|
if (handoffSignal.aborted) {
|
|
10670
11251
|
throw new Error("Handoff cancelled");
|
|
10671
11252
|
}
|
|
10672
11253
|
// Preserve the generated handoff document for copy/retry.
|
|
10673
|
-
throw Object.assign(
|
|
11254
|
+
throw Object.assign(rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)), {
|
|
10674
11255
|
handoffDocument: handoffText,
|
|
10675
11256
|
});
|
|
10676
11257
|
}
|
|
@@ -10678,7 +11259,7 @@ export class AgentSession {
|
|
|
10678
11259
|
// Genuine handoff-signal cancellation maps to a cancellation error.
|
|
10679
11260
|
// Errors surfaced by the inner transaction (which already rolled back
|
|
10680
11261
|
// and, for non-aborts, attached the generated document) pass through.
|
|
10681
|
-
if (handoffSignal.aborted) {
|
|
11262
|
+
if (handoffSignal.aborted && !(error instanceof AggregateError)) {
|
|
10682
11263
|
throw new Error("Handoff cancelled");
|
|
10683
11264
|
}
|
|
10684
11265
|
throw error;
|
|
@@ -10720,7 +11301,11 @@ export class AgentSession {
|
|
|
10720
11301
|
* @param assistantMessage The assistant message to check
|
|
10721
11302
|
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
10722
11303
|
*/
|
|
10723
|
-
async #checkCompaction(
|
|
11304
|
+
async #checkCompaction(
|
|
11305
|
+
assistantMessage: AssistantMessage,
|
|
11306
|
+
skipAbortedCheck = true,
|
|
11307
|
+
onTerminalOverflowNoop?: () => void,
|
|
11308
|
+
): Promise<boolean> {
|
|
10724
11309
|
// Safety stops are terminal and must not trigger context maintenance.
|
|
10725
11310
|
if (
|
|
10726
11311
|
assistantMessage.errorKind === "provider_safety_stop" ||
|
|
@@ -10756,8 +11341,10 @@ export class AgentSession {
|
|
|
10756
11341
|
// Remove the error message from agent state (it IS saved to session for history,
|
|
10757
11342
|
// but we don't want it in context for the retry)
|
|
10758
11343
|
const messages = this.agent.state.messages;
|
|
11344
|
+
let removedOverflowAssistant = false;
|
|
10759
11345
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
10760
11346
|
this.agent.replaceMessages(messages.slice(0, -1));
|
|
11347
|
+
removedOverflowAssistant = true;
|
|
10761
11348
|
}
|
|
10762
11349
|
|
|
10763
11350
|
// Try context promotion first - switch to a larger model and retry without compacting
|
|
@@ -10772,7 +11359,15 @@ export class AgentSession {
|
|
|
10772
11359
|
// No promotion target available fall through to compaction
|
|
10773
11360
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
10774
11361
|
if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
|
|
10775
|
-
const status = await this.#runAutoCompaction("overflow", true
|
|
11362
|
+
const status = await this.#runAutoCompaction("overflow", true, false, {
|
|
11363
|
+
beforeTerminalOverflowNoop: () => {
|
|
11364
|
+
if (onTerminalOverflowNoop) {
|
|
11365
|
+
onTerminalOverflowNoop();
|
|
11366
|
+
} else if (removedOverflowAssistant) {
|
|
11367
|
+
this.agent.appendMessage(assistantMessage);
|
|
11368
|
+
}
|
|
11369
|
+
},
|
|
11370
|
+
});
|
|
10776
11371
|
return "continuationScheduled" in status && status.continuationScheduled === true;
|
|
10777
11372
|
}
|
|
10778
11373
|
return this.#scheduleOverflowRetryContinuation(generation);
|
|
@@ -11540,13 +12135,18 @@ export class AgentSession {
|
|
|
11540
12135
|
this.#defaultFallbackExhaustedLastTurn = false;
|
|
11541
12136
|
}
|
|
11542
12137
|
|
|
12138
|
+
#setAgentModelWithReasoningContext(model: Model | undefined): void {
|
|
12139
|
+
this.#reasoningControlContextGeneration++;
|
|
12140
|
+
this.agent.setModel(model);
|
|
12141
|
+
}
|
|
12142
|
+
|
|
11543
12143
|
#setModelWithProviderSessionReset(model: Model | undefined): void {
|
|
11544
12144
|
this.#defaultModelSelectionMutationRevision++;
|
|
11545
12145
|
const currentModel = this.model;
|
|
11546
12146
|
if (currentModel) {
|
|
11547
12147
|
this.#closeProviderSessionsForModelSwitch(currentModel, model);
|
|
11548
12148
|
}
|
|
11549
|
-
this
|
|
12149
|
+
this.#setAgentModelWithReasoningContext(model);
|
|
11550
12150
|
}
|
|
11551
12151
|
|
|
11552
12152
|
#setModelAuthoritatively(model: Model, cause: ModelChangeCause): void {
|
|
@@ -11555,7 +12155,7 @@ export class AgentSession {
|
|
|
11555
12155
|
if (cause !== "temporary-operation") this.#commitAllTemporaryProviderSessionScopes();
|
|
11556
12156
|
const currentModel = this.model;
|
|
11557
12157
|
if (currentModel) this.#closeProviderSessionsForModelSwitch(currentModel, model);
|
|
11558
|
-
this
|
|
12158
|
+
this.#setAgentModelWithReasoningContext(model);
|
|
11559
12159
|
this.#syncAppendOnlyContext(model);
|
|
11560
12160
|
}
|
|
11561
12161
|
|
|
@@ -12081,6 +12681,7 @@ export class AgentSession {
|
|
|
12081
12681
|
deferHandoffMaintenance?: boolean;
|
|
12082
12682
|
force?: boolean;
|
|
12083
12683
|
signal?: AbortSignal;
|
|
12684
|
+
beforeTerminalOverflowNoop?: () => void;
|
|
12084
12685
|
},
|
|
12085
12686
|
): Promise<AutoCompactionTerminalStatus> {
|
|
12086
12687
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
@@ -12218,16 +12819,42 @@ export class AgentSession {
|
|
|
12218
12819
|
if (autoCompactionSignal.aborted) return await emitAborted();
|
|
12219
12820
|
|
|
12220
12821
|
if (!preparation) {
|
|
12221
|
-
|
|
12822
|
+
// #checkCompaction removes the failed overflow assistant before entering
|
|
12823
|
+
// this path. A resumable tail would therefore resend the same oversized
|
|
12824
|
+
// request even though maintenance made no change. Non-resumable tails
|
|
12825
|
+
// retain the existing synthetic auto-continue recovery, which changes
|
|
12826
|
+
// the request and is covered by the continuation contract.
|
|
12827
|
+
const overflowNoopWouldReplay = reason === "overflow" && willRetry && this.#isResumableAgentTail();
|
|
12828
|
+
const continuationSkipReason =
|
|
12829
|
+
!overflowNoopWouldReplay && willRetry ? this.#detectOverflowRetryContinuationSkip() : undefined;
|
|
12830
|
+
if (overflowNoopWouldReplay) {
|
|
12831
|
+
options?.beforeTerminalOverflowNoop?.();
|
|
12832
|
+
}
|
|
12222
12833
|
await this.#emitSessionEvent({
|
|
12223
12834
|
type: "auto_compaction_end",
|
|
12224
12835
|
action,
|
|
12225
12836
|
result: undefined,
|
|
12226
12837
|
aborted: false,
|
|
12227
|
-
willRetry: willRetry && !continuationSkipReason,
|
|
12838
|
+
willRetry: overflowNoopWouldReplay ? false : willRetry && !continuationSkipReason,
|
|
12839
|
+
errorMessage: overflowNoopWouldReplay
|
|
12840
|
+
? "Context overflow recovery skipped: nothing eligible to compact. Run /clear to preserve this session ID, or switch to a larger-context model before retrying."
|
|
12841
|
+
: undefined,
|
|
12228
12842
|
skipped: true,
|
|
12229
12843
|
continuationSkipReason,
|
|
12230
12844
|
});
|
|
12845
|
+
if (overflowNoopWouldReplay) {
|
|
12846
|
+
if (continueAfterMaintenance && this.agent.hasQueuedMessages()) {
|
|
12847
|
+
this.#scheduleAgentContinue({
|
|
12848
|
+
delayMs: 100,
|
|
12849
|
+
generation,
|
|
12850
|
+
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
12851
|
+
onSkip: skipReason => this.#logCompactionContinuationSkipped("queued_continue", skipReason),
|
|
12852
|
+
onError: error => this.#logCompactionContinuationError("queued_continue", error),
|
|
12853
|
+
});
|
|
12854
|
+
return { kind: "skipped", continuationScheduled: true };
|
|
12855
|
+
}
|
|
12856
|
+
return { kind: "skipped" };
|
|
12857
|
+
}
|
|
12231
12858
|
if (willRetry) {
|
|
12232
12859
|
return { kind: "skipped", continuationScheduled: this.#scheduleOverflowRetryContinuation(generation) };
|
|
12233
12860
|
}
|
|
@@ -12357,6 +12984,7 @@ export class AgentSession {
|
|
|
12357
12984
|
}
|
|
12358
12985
|
const retryAfterMs = this.#parseRetryAfterMsFromError(message);
|
|
12359
12986
|
const shouldRetry =
|
|
12987
|
+
!this.#isTerminalProviderCompactionTimeout(candidate, message) &&
|
|
12360
12988
|
retrySettings.enabled &&
|
|
12361
12989
|
attempt < retrySettings.maxRetries &&
|
|
12362
12990
|
(retryAfterMs !== undefined ||
|
|
@@ -12367,8 +12995,14 @@ export class AgentSession {
|
|
|
12367
12995
|
break;
|
|
12368
12996
|
}
|
|
12369
12997
|
|
|
12370
|
-
|
|
12371
|
-
|
|
12998
|
+
// Legacy parsed Retry-After is capped at retry.maxDelayMs (see
|
|
12999
|
+
// compactionRetryDelay); only managed fallback is uncapped.
|
|
13000
|
+
const delayMs = compactionRetryDelay(
|
|
13001
|
+
retrySettings.baseDelayMs,
|
|
13002
|
+
retrySettings.maxDelayMs,
|
|
13003
|
+
attempt,
|
|
13004
|
+
retryAfterMs,
|
|
13005
|
+
);
|
|
12372
13006
|
|
|
12373
13007
|
// If retry delay is too long (>30s), try next candidate instead of waiting
|
|
12374
13008
|
const maxAcceptableDelayMs = 30_000;
|
|
@@ -12542,7 +13176,66 @@ export class AgentSession {
|
|
|
12542
13176
|
* unknown/no-code errors are retryable.
|
|
12543
13177
|
*/
|
|
12544
13178
|
|
|
13179
|
+
#isKimiCodeFirstEventTimeout(message: AssistantMessage): boolean {
|
|
13180
|
+
if (message.stopReason !== "error" || message.provider !== "kimi-code") return false;
|
|
13181
|
+
const errorMessage = message.errorMessage ?? "";
|
|
13182
|
+
if (message.api === "anthropic-messages") {
|
|
13183
|
+
return KIMI_CODE_FIRST_EVENT_TIMEOUT_MESSAGES["anthropic-messages"].has(errorMessage);
|
|
13184
|
+
}
|
|
13185
|
+
if (message.api === "openai-completions") {
|
|
13186
|
+
return KIMI_CODE_FIRST_EVENT_TIMEOUT_MESSAGES["openai-completions"].has(errorMessage);
|
|
13187
|
+
}
|
|
13188
|
+
return false;
|
|
13189
|
+
}
|
|
13190
|
+
|
|
13191
|
+
#isKimiCodeCompactionTimeout(candidate: Model, errorMessage: string): boolean {
|
|
13192
|
+
if (candidate.provider !== "kimi-code") return false;
|
|
13193
|
+
return /^(?:Summarization failed|Turn prefix summarization failed): (?:Provider|Anthropic|OpenAI completions) stream timed out while waiting for the first event$/.test(
|
|
13194
|
+
errorMessage,
|
|
13195
|
+
);
|
|
13196
|
+
}
|
|
13197
|
+
|
|
13198
|
+
#alibabaTokenPlanTimeoutMessagesForApi(api: string): ReadonlySet<string> | undefined {
|
|
13199
|
+
if (api === "openai-responses" || api === "openai-completions") {
|
|
13200
|
+
return ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MESSAGES[api];
|
|
13201
|
+
}
|
|
13202
|
+
return undefined;
|
|
13203
|
+
}
|
|
13204
|
+
|
|
13205
|
+
#isAlibabaTokenPlanFirstEventTimeout(message: AssistantMessage): boolean {
|
|
13206
|
+
if (message.stopReason !== "error" || message.provider !== ALIBABA_TOKEN_PLAN_PROVIDER) return false;
|
|
13207
|
+
const timeoutMessages = this.#alibabaTokenPlanTimeoutMessagesForApi(message.api);
|
|
13208
|
+
return timeoutMessages?.has(message.errorMessage ?? "") ?? false;
|
|
13209
|
+
}
|
|
13210
|
+
|
|
13211
|
+
#isAlibabaTokenPlanCompactionTimeout(candidate: Model, errorMessage: string): boolean {
|
|
13212
|
+
if (candidate.provider !== ALIBABA_TOKEN_PLAN_PROVIDER) return false;
|
|
13213
|
+
const timeoutMessages = this.#alibabaTokenPlanTimeoutMessagesForApi(candidate.api);
|
|
13214
|
+
if (!timeoutMessages) return false;
|
|
13215
|
+
for (const timeoutMessage of timeoutMessages) {
|
|
13216
|
+
if (
|
|
13217
|
+
errorMessage === `Summarization failed: ${timeoutMessage}` ||
|
|
13218
|
+
errorMessage === `Turn prefix summarization failed: ${timeoutMessage}`
|
|
13219
|
+
) {
|
|
13220
|
+
return true;
|
|
13221
|
+
}
|
|
13222
|
+
}
|
|
13223
|
+
return false;
|
|
13224
|
+
}
|
|
13225
|
+
|
|
13226
|
+
#isTerminalProviderFirstEventTimeout(message: AssistantMessage): boolean {
|
|
13227
|
+
return this.#isKimiCodeFirstEventTimeout(message) || this.#isAlibabaTokenPlanFirstEventTimeout(message);
|
|
13228
|
+
}
|
|
13229
|
+
|
|
13230
|
+
#isTerminalProviderCompactionTimeout(candidate: Model, errorMessage: string): boolean {
|
|
13231
|
+
return (
|
|
13232
|
+
this.#isKimiCodeCompactionTimeout(candidate, errorMessage) ||
|
|
13233
|
+
this.#isAlibabaTokenPlanCompactionTimeout(candidate, errorMessage)
|
|
13234
|
+
);
|
|
13235
|
+
}
|
|
13236
|
+
|
|
12545
13237
|
#isRetryableError(message: AssistantMessage): boolean {
|
|
13238
|
+
if (this.#isTerminalProviderFirstEventTimeout(message)) return false;
|
|
12546
13239
|
if (message.errorMessage?.startsWith("Model fallback chain exhausted;")) return false;
|
|
12547
13240
|
const transportFailure = message.transportFailure;
|
|
12548
13241
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
@@ -12692,6 +13385,9 @@ export class AgentSession {
|
|
|
12692
13385
|
if (isTerminalHttp4xx && (explicitStatus !== undefined || !/rate.?limit|too many requests/i.test(err))) {
|
|
12693
13386
|
return "terminal";
|
|
12694
13387
|
}
|
|
13388
|
+
if (this.#isTerminalProviderFirstEventTimeout(message)) {
|
|
13389
|
+
return "terminal";
|
|
13390
|
+
}
|
|
12695
13391
|
// A first-event timeout on ollama-cloud (the ollama-chat API) must not
|
|
12696
13392
|
// join the unbounded transient class: each continuation retry re-issues
|
|
12697
13393
|
// the full request to a remote, billable backend, so an unbounded loop
|
|
@@ -12866,8 +13562,15 @@ export class AgentSession {
|
|
|
12866
13562
|
type: "maintenance",
|
|
12867
13563
|
continuation: async ownership => {
|
|
12868
13564
|
if (!ownership.isCurrent()) return;
|
|
12869
|
-
|
|
12870
|
-
|
|
13565
|
+
let terminalized = false;
|
|
13566
|
+
const successorScheduled = await this.#checkCompaction(outcome.message, true, () => {
|
|
13567
|
+
terminalized = true;
|
|
13568
|
+
this.agent.requestRunTerminal(ownership.logicalRunId, {
|
|
13569
|
+
stopReason: "error",
|
|
13570
|
+
messages: [outcome.message],
|
|
13571
|
+
});
|
|
13572
|
+
});
|
|
13573
|
+
if (terminalized || successorScheduled || !ownership.isCurrent()) return;
|
|
12871
13574
|
this.agent.requestRunTerminal(ownership.logicalRunId, {
|
|
12872
13575
|
stopReason: "error",
|
|
12873
13576
|
messages: [outcome.message],
|
|
@@ -12875,6 +13578,18 @@ export class AgentSession {
|
|
|
12875
13578
|
},
|
|
12876
13579
|
};
|
|
12877
13580
|
}
|
|
13581
|
+
if (this.#isTerminalProviderFirstEventTimeout(outcome.failure.message)) {
|
|
13582
|
+
// The managed transport discarded this attempt before session policy saw it.
|
|
13583
|
+
// Remove its provisional charge without changing sticky fallback selection.
|
|
13584
|
+
this.#defaultFallbackChain().discardStartedAttempt();
|
|
13585
|
+
return {
|
|
13586
|
+
type: "terminal",
|
|
13587
|
+
terminal: {
|
|
13588
|
+
stopReason: "error",
|
|
13589
|
+
messages: [outcome.failure.message],
|
|
13590
|
+
},
|
|
13591
|
+
};
|
|
13592
|
+
}
|
|
12878
13593
|
return this.#handleRetryableError(
|
|
12879
13594
|
outcome.failure.message,
|
|
12880
13595
|
true,
|
|
@@ -13994,167 +14709,127 @@ export class AgentSession {
|
|
|
13994
14709
|
this.#ircRosterClaim = null;
|
|
13995
14710
|
}
|
|
13996
14711
|
|
|
14712
|
+
createBtwConversationScope(instruction: string): BtwConversationScope {
|
|
14713
|
+
const model = this.model;
|
|
14714
|
+
if (!model) throw new Error("No active model on session");
|
|
14715
|
+
const providerAffinitySessionId = this.agent.providerSessionId ?? this.agent.sessionId ?? this.sessionId;
|
|
14716
|
+
return {
|
|
14717
|
+
model,
|
|
14718
|
+
systemPrompt: [...this.systemPrompt, instruction],
|
|
14719
|
+
messages: this.#projectBtwVisibleText(this.buildDisplaySessionContext().messages),
|
|
14720
|
+
thinkingLevel: this.thinkingLevel ?? ThinkingLevel.Off,
|
|
14721
|
+
hideThinkingSummary: this.agent.hideThinkingSummary ?? false,
|
|
14722
|
+
serviceTier: this.serviceTier,
|
|
14723
|
+
credentialSessionId: providerAffinitySessionId,
|
|
14724
|
+
providerAffinitySessionId,
|
|
14725
|
+
sideSessionId: `${providerAffinitySessionId}:btw:${crypto.randomUUID()}`,
|
|
14726
|
+
};
|
|
14727
|
+
}
|
|
14728
|
+
|
|
14729
|
+
#projectBtwVisibleText(messages: readonly AgentMessage[]): BtwRoleTextMessage[] {
|
|
14730
|
+
const projected: BtwRoleTextMessage[] = [];
|
|
14731
|
+
for (const message of messages) {
|
|
14732
|
+
if (message.role !== "user" && message.role !== "assistant") continue;
|
|
14733
|
+
const text = (
|
|
14734
|
+
typeof message.content === "string"
|
|
14735
|
+
? message.content
|
|
14736
|
+
: message.content
|
|
14737
|
+
.filter((block): block is TextContent => block.type === "text")
|
|
14738
|
+
.map(block => block.text)
|
|
14739
|
+
.join("")
|
|
14740
|
+
).trim();
|
|
14741
|
+
if (!text) continue;
|
|
14742
|
+
projected.push({ role: message.role, text });
|
|
14743
|
+
}
|
|
14744
|
+
return projected;
|
|
14745
|
+
}
|
|
14746
|
+
|
|
14747
|
+
#buildBtwAssistantMessage(text: string): AssistantMessage {
|
|
14748
|
+
return {
|
|
14749
|
+
role: "assistant",
|
|
14750
|
+
content: [{ type: "text", text }],
|
|
14751
|
+
api: "btw",
|
|
14752
|
+
provider: "btw",
|
|
14753
|
+
model: "btw",
|
|
14754
|
+
usage: {
|
|
14755
|
+
input: 0,
|
|
14756
|
+
output: 0,
|
|
14757
|
+
cacheRead: 0,
|
|
14758
|
+
cacheWrite: 0,
|
|
14759
|
+
totalTokens: 0,
|
|
14760
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
14761
|
+
},
|
|
14762
|
+
stopReason: "stop",
|
|
14763
|
+
timestamp: 0,
|
|
14764
|
+
};
|
|
14765
|
+
}
|
|
13997
14766
|
/**
|
|
13998
|
-
* Run a single ephemeral side-channel turn
|
|
13999
|
-
*
|
|
14000
|
-
*
|
|
14001
|
-
*
|
|
14767
|
+
* Run a single ephemeral side-channel turn without modifying session history.
|
|
14768
|
+
* Background turns retain IRC/session behavior. `/btw` turns require a
|
|
14769
|
+
* pre-frozen, visible-text-only scope and bypass extension context transforms,
|
|
14770
|
+
* provider observability hooks, and session persistence surfaces.
|
|
14002
14771
|
*/
|
|
14003
14772
|
async runEphemeralTurn(args: EphemeralTurnArgs): Promise<EphemeralTurnResult> {
|
|
14004
14773
|
args.signal?.throwIfAborted();
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
const
|
|
14009
|
-
? this.#buildBtwEphemeralSnapshot(
|
|
14010
|
-
cloneJsonValueForForkSeed(this.buildDisplaySessionContext().messages),
|
|
14011
|
-
args.promptText,
|
|
14012
|
-
)
|
|
14013
|
-
: undefined;
|
|
14014
|
-
const model = this.model;
|
|
14015
|
-
const systemPrompt = isBtw ? [...this.systemPrompt] : undefined;
|
|
14016
|
-
const thinkingLevel = this.thinkingLevel;
|
|
14017
|
-
const hideThinkingSummary = this.agent.hideThinkingSummary;
|
|
14018
|
-
const serviceTier = this.serviceTier;
|
|
14019
|
-
const providerAffinitySessionId = this.agent.providerSessionId ?? this.agent.sessionId ?? this.sessionId;
|
|
14020
|
-
const sideSessionId = isBtw ? `${providerAffinitySessionId}:btw:${crypto.randomUUID()}` : undefined;
|
|
14021
|
-
const backgroundArgs = isBtw ? undefined : args;
|
|
14022
|
-
const callerOwnsRosterClaim = backgroundArgs?.ircRosterClaim !== undefined;
|
|
14023
|
-
const rosterClaim = isBtw
|
|
14024
|
-
? null
|
|
14025
|
-
: callerOwnsRosterClaim
|
|
14026
|
-
? backgroundArgs?.ircRosterClaim
|
|
14027
|
-
: this.#claimIrcRosterCandidate();
|
|
14774
|
+
if (args.purpose === "btw") return await this.#runBtwTurn(args);
|
|
14775
|
+
|
|
14776
|
+
const callerOwnsRosterClaim = args.ircRosterClaim !== undefined;
|
|
14777
|
+
const rosterClaim = callerOwnsRosterClaim ? args.ircRosterClaim : this.#claimIrcRosterCandidate();
|
|
14028
14778
|
const rosterClaimIsCurrent = () =>
|
|
14029
14779
|
!rosterClaim || this.#isCurrentIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
14030
|
-
const prependMessagesValid = () => rosterClaimIsCurrent() &&
|
|
14780
|
+
const prependMessagesValid = () => rosterClaimIsCurrent() && args.prependMessagesValid?.() !== false;
|
|
14031
14781
|
const rosterMessage = !callerOwnsRosterClaim && rosterClaimIsCurrent() ? rosterClaim?.message : undefined;
|
|
14032
14782
|
try {
|
|
14783
|
+
const model = this.model;
|
|
14033
14784
|
if (!model) throw new Error("No active model on session");
|
|
14034
|
-
const
|
|
14035
|
-
const apiKey = await awaitEphemeralAbort(
|
|
14036
|
-
this.#modelRegistry.getApiKey(model, credentialSessionId),
|
|
14037
|
-
args.signal,
|
|
14038
|
-
);
|
|
14785
|
+
const apiKey = await awaitEphemeralAbort(this.#modelRegistry.getApiKey(model, this.sessionId), args.signal);
|
|
14039
14786
|
if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
14040
|
-
|
|
14041
14787
|
const prependMessages = prependMessagesValid()
|
|
14042
|
-
? [...(rosterMessage ? [rosterMessage] : []), ...(
|
|
14788
|
+
? [...(rosterMessage ? [rosterMessage] : []), ...(args.prependMessages ?? [])]
|
|
14043
14789
|
: undefined;
|
|
14044
|
-
let snapshot =
|
|
14790
|
+
let snapshot = this.#buildEphemeralSnapshot(args.promptText, prependMessages);
|
|
14045
14791
|
let llmMessages = await awaitEphemeralAbort(this.convertMessagesToLlm(snapshot, args.signal), args.signal);
|
|
14046
|
-
if (
|
|
14792
|
+
if (prependMessages && !prependMessagesValid()) {
|
|
14047
14793
|
snapshot = this.#buildEphemeralSnapshot(args.promptText);
|
|
14048
14794
|
llmMessages = await awaitEphemeralAbort(this.convertMessagesToLlm(snapshot, args.signal), args.signal);
|
|
14049
14795
|
}
|
|
14050
|
-
const context: Context = {
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14796
|
+
const context: Context = { systemPrompt: this.systemPrompt, messages: llmMessages, tools: [] };
|
|
14797
|
+
const ephemeralSessionId = crypto.randomUUID();
|
|
14798
|
+
const options = this.prepareSimpleStreamOptions(
|
|
14799
|
+
{
|
|
14800
|
+
apiKey,
|
|
14801
|
+
sessionId: ephemeralSessionId,
|
|
14802
|
+
metadata: buildSessionMetadata(
|
|
14803
|
+
ephemeralSessionId,
|
|
14804
|
+
model.provider,
|
|
14805
|
+
this.#modelRegistry.authStorage,
|
|
14806
|
+
this.sessionId,
|
|
14807
|
+
),
|
|
14808
|
+
reasoning: toReasoningEffort(this.thinkingLevel),
|
|
14809
|
+
hideThinkingSummary: this.agent.hideThinkingSummary,
|
|
14810
|
+
serviceTier: this.serviceTier,
|
|
14811
|
+
signal: args.signal,
|
|
14812
|
+
toolChoice: "none",
|
|
14813
|
+
},
|
|
14814
|
+
model.provider,
|
|
14815
|
+
);
|
|
14816
|
+
args.signal?.throwIfAborted();
|
|
14056
14817
|
let replyText = "";
|
|
14057
14818
|
let assistantMessage: AssistantMessage | undefined;
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
)
|
|
14066
|
-
|
|
14067
|
-
let establishmentTimer: NodeJS.Timeout | undefined;
|
|
14068
|
-
try {
|
|
14069
|
-
requestSignal.throwIfAborted();
|
|
14070
|
-
const options: SimpleStreamOptions = {
|
|
14071
|
-
apiKey,
|
|
14072
|
-
sessionId: sideSessionId!,
|
|
14073
|
-
metadata: buildSessionMetadata(
|
|
14074
|
-
sideSessionId!,
|
|
14075
|
-
model.provider,
|
|
14076
|
-
this.#modelRegistry.authStorage,
|
|
14077
|
-
providerAffinitySessionId,
|
|
14078
|
-
),
|
|
14079
|
-
reasoning: toReasoningEffort(thinkingLevel),
|
|
14080
|
-
hideThinkingSummary,
|
|
14081
|
-
serviceTier,
|
|
14082
|
-
onPayload: this.#onPayload,
|
|
14083
|
-
signal: requestSignal,
|
|
14084
|
-
toolChoice: "none",
|
|
14085
|
-
requestMaxRetries: 0,
|
|
14086
|
-
streamMaxRetries: 0,
|
|
14087
|
-
streamFirstEventTimeoutMs: 0,
|
|
14088
|
-
};
|
|
14089
|
-
establishmentTimer = setTimeout(
|
|
14090
|
-
() => establishmentAbort.abort(timeoutError),
|
|
14091
|
-
BTW_ESTABLISHMENT_TIMEOUT_MS,
|
|
14092
|
-
);
|
|
14093
|
-
establishmentTimer.unref?.();
|
|
14094
|
-
const stream = streamSimple(model, context, options);
|
|
14095
|
-
let receivedProviderEvent = false;
|
|
14096
|
-
for await (const event of stream) {
|
|
14097
|
-
if (!receivedProviderEvent && event.type !== "start") {
|
|
14098
|
-
receivedProviderEvent = true;
|
|
14099
|
-
clearTimeout(establishmentTimer);
|
|
14100
|
-
establishmentTimer = undefined;
|
|
14101
|
-
}
|
|
14102
|
-
if (event.type === "text_delta") {
|
|
14103
|
-
replyText += event.delta;
|
|
14104
|
-
args.onTextDelta?.(event.delta);
|
|
14105
|
-
} else if (event.type === "done") {
|
|
14106
|
-
assistantMessage = event.message;
|
|
14107
|
-
break;
|
|
14108
|
-
} else if (event.type === "error") {
|
|
14109
|
-
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14110
|
-
}
|
|
14111
|
-
}
|
|
14112
|
-
requestSignal.throwIfAborted();
|
|
14113
|
-
} catch (error) {
|
|
14114
|
-
if (establishmentAbort.signal.aborted && !args.signal?.aborted) throw timeoutError;
|
|
14115
|
-
throw error;
|
|
14116
|
-
} finally {
|
|
14117
|
-
if (establishmentTimer) clearTimeout(establishmentTimer);
|
|
14118
|
-
}
|
|
14119
|
-
} else {
|
|
14120
|
-
const ephemeralSessionId = crypto.randomUUID();
|
|
14121
|
-
const options = this.prepareSimpleStreamOptions(
|
|
14122
|
-
{
|
|
14123
|
-
apiKey,
|
|
14124
|
-
sessionId: ephemeralSessionId,
|
|
14125
|
-
metadata: buildSessionMetadata(
|
|
14126
|
-
ephemeralSessionId,
|
|
14127
|
-
model.provider,
|
|
14128
|
-
this.#modelRegistry.authStorage,
|
|
14129
|
-
this.sessionId,
|
|
14130
|
-
),
|
|
14131
|
-
reasoning: toReasoningEffort(thinkingLevel),
|
|
14132
|
-
hideThinkingSummary,
|
|
14133
|
-
serviceTier,
|
|
14134
|
-
signal: args.signal,
|
|
14135
|
-
toolChoice: "none",
|
|
14136
|
-
},
|
|
14137
|
-
model.provider,
|
|
14138
|
-
);
|
|
14139
|
-
args.signal?.throwIfAborted();
|
|
14140
|
-
const stream = streamSimple(model, context, options);
|
|
14141
|
-
for await (const event of stream) {
|
|
14142
|
-
if (event.type === "text_delta") {
|
|
14143
|
-
replyText += event.delta;
|
|
14144
|
-
args.onTextDelta?.(event.delta);
|
|
14145
|
-
} else if (event.type === "done") {
|
|
14146
|
-
assistantMessage = event.message;
|
|
14147
|
-
break;
|
|
14148
|
-
} else if (event.type === "error") {
|
|
14149
|
-
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14150
|
-
}
|
|
14819
|
+
for await (const event of streamSimple(model, context, options)) {
|
|
14820
|
+
if (event.type === "text_delta") {
|
|
14821
|
+
replyText += event.delta;
|
|
14822
|
+
args.onTextDelta?.(event.delta);
|
|
14823
|
+
} else if (event.type === "done") {
|
|
14824
|
+
assistantMessage = event.message;
|
|
14825
|
+
break;
|
|
14826
|
+
} else if (event.type === "error") {
|
|
14827
|
+
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14151
14828
|
}
|
|
14152
14829
|
}
|
|
14153
|
-
|
|
14154
14830
|
if (!assistantMessage) throw new Error("Ephemeral turn ended without a final message");
|
|
14155
14831
|
args.signal?.throwIfAborted();
|
|
14156
14832
|
if (
|
|
14157
|
-
!isBtw &&
|
|
14158
14833
|
!callerOwnsRosterClaim &&
|
|
14159
14834
|
rosterClaim &&
|
|
14160
14835
|
assistantMessage.stopReason !== "error" &&
|
|
@@ -14162,21 +14837,117 @@ export class AgentSession {
|
|
|
14162
14837
|
) {
|
|
14163
14838
|
this.#commitIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
14164
14839
|
}
|
|
14165
|
-
args.signal?.throwIfAborted();
|
|
14166
14840
|
return { replyText: replyText.trim(), assistantMessage };
|
|
14167
14841
|
} finally {
|
|
14168
|
-
if (!
|
|
14169
|
-
|
|
14842
|
+
if (!callerOwnsRosterClaim && rosterClaim) this.#releaseIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
14843
|
+
}
|
|
14844
|
+
}
|
|
14845
|
+
|
|
14846
|
+
async #runBtwTurn(args: Extract<EphemeralTurnArgs, { purpose: "btw" }>): Promise<EphemeralTurnResult> {
|
|
14847
|
+
const model = args.turn.scope?.model;
|
|
14848
|
+
const credentialSessionId = args.turn.scope?.credentialSessionId;
|
|
14849
|
+
if (!model || !credentialSessionId) throw new Error("The /btw conversation scope was scrubbed.");
|
|
14850
|
+
if (utf8ByteLength(args.turn.question) > BTW_MAX_QUESTION_UTF8_BYTES) {
|
|
14851
|
+
throw new RangeError(`/btw questions are limited to ${BTW_MAX_QUESTION_UTF8_BYTES} UTF-8 bytes.`);
|
|
14852
|
+
}
|
|
14853
|
+
const apiKey = await awaitEphemeralAbort(this.#modelRegistry.getApiKey(model, credentialSessionId), args.signal);
|
|
14854
|
+
if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
14855
|
+
const scope = args.turn.scope;
|
|
14856
|
+
if (!scope) throw new Error("The /btw conversation scope was scrubbed.");
|
|
14857
|
+
const messages = scope.messages.map(message => ({
|
|
14858
|
+
role: message.role,
|
|
14859
|
+
content: [{ type: "text" as const, text: message.text }],
|
|
14860
|
+
})) as Message[];
|
|
14861
|
+
for (const exchange of boundBtwExchanges(args.contextExchanges ?? [])) {
|
|
14862
|
+
messages.push({
|
|
14863
|
+
role: "user",
|
|
14864
|
+
content: [{ type: "text", text: exchange.question }],
|
|
14865
|
+
} as Message);
|
|
14866
|
+
messages.push({
|
|
14867
|
+
role: "assistant",
|
|
14868
|
+
content: [{ type: "text", text: exchange.answer }],
|
|
14869
|
+
} as Message);
|
|
14870
|
+
}
|
|
14871
|
+
messages.push({
|
|
14872
|
+
role: "user",
|
|
14873
|
+
content: [{ type: "text", text: args.turn.question }],
|
|
14874
|
+
} as Message);
|
|
14875
|
+
const context: Context = { systemPrompt: scope.systemPrompt, messages, tools: [] };
|
|
14876
|
+
const timeoutAbort = new AbortController();
|
|
14877
|
+
const requestSignal = args.signal ? AbortSignal.any([args.signal, timeoutAbort.signal]) : timeoutAbort.signal;
|
|
14878
|
+
const options: SimpleStreamOptions = {
|
|
14879
|
+
apiKey,
|
|
14880
|
+
sessionId: scope.sideSessionId,
|
|
14881
|
+
reasoning: toReasoningEffort(scope.thinkingLevel),
|
|
14882
|
+
hideThinkingSummary: scope.hideThinkingSummary,
|
|
14883
|
+
serviceTier: scope.serviceTier,
|
|
14884
|
+
signal: requestSignal,
|
|
14885
|
+
toolChoice: "none",
|
|
14886
|
+
requestMaxRetries: 0,
|
|
14887
|
+
streamMaxRetries: 0,
|
|
14888
|
+
streamFirstEventTimeoutMs: 0,
|
|
14889
|
+
};
|
|
14890
|
+
const iterator = streamSimple(scope.model, context, options)[Symbol.asyncIterator]();
|
|
14891
|
+
let replyText = "";
|
|
14892
|
+
let completed = false;
|
|
14893
|
+
let active = true;
|
|
14894
|
+
let onTextDelta = args.onTextDelta;
|
|
14895
|
+
let idleTimer: NodeJS.Timeout | undefined;
|
|
14896
|
+
const timeout = (message: string) => {
|
|
14897
|
+
const error = new Error(message);
|
|
14898
|
+
error.name = "TimeoutError";
|
|
14899
|
+
timeoutAbort.abort(error);
|
|
14900
|
+
};
|
|
14901
|
+
const resetIdleTimer = () => {
|
|
14902
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
14903
|
+
idleTimer = setTimeout(
|
|
14904
|
+
() => timeout(`/btw provider was idle for ${BTW_STREAM_IDLE_TIMEOUT_MS / 1000} seconds`),
|
|
14905
|
+
BTW_STREAM_IDLE_TIMEOUT_MS,
|
|
14906
|
+
);
|
|
14907
|
+
idleTimer.unref?.();
|
|
14908
|
+
};
|
|
14909
|
+
const totalTimer = setTimeout(
|
|
14910
|
+
() => timeout(`/btw provider exceeded ${BTW_STREAM_TOTAL_TIMEOUT_MS / 1000} seconds`),
|
|
14911
|
+
BTW_STREAM_TOTAL_TIMEOUT_MS,
|
|
14912
|
+
);
|
|
14913
|
+
totalTimer.unref?.();
|
|
14914
|
+
resetIdleTimer();
|
|
14915
|
+
const consume = async () => {
|
|
14916
|
+
while (active) {
|
|
14917
|
+
const result = await iterator.next();
|
|
14918
|
+
if (result.done || !active) break;
|
|
14919
|
+
resetIdleTimer();
|
|
14920
|
+
const event = result.value;
|
|
14921
|
+
if (event.type === "text_delta") {
|
|
14922
|
+
const bounded = truncateUtf8(replyText + event.delta, BTW_MAX_ANSWER_UTF8_BYTES);
|
|
14923
|
+
const delta = bounded.slice(replyText.length);
|
|
14924
|
+
replyText = bounded;
|
|
14925
|
+
if (delta) onTextDelta?.(delta);
|
|
14926
|
+
} else if (event.type === "done") {
|
|
14927
|
+
completed = true;
|
|
14928
|
+
break;
|
|
14929
|
+
} else if (event.type === "error") {
|
|
14930
|
+
throw new Error(event.error.errorMessage || "Ephemeral turn failed");
|
|
14931
|
+
}
|
|
14170
14932
|
}
|
|
14933
|
+
};
|
|
14934
|
+
try {
|
|
14935
|
+
await awaitEphemeralAbort(consume(), requestSignal);
|
|
14936
|
+
requestSignal.throwIfAborted();
|
|
14937
|
+
if (!completed) throw new Error("Ephemeral turn ended without a final message");
|
|
14938
|
+
const finalText = replyText.trim();
|
|
14939
|
+
return { replyText: finalText, assistantMessage: this.#buildBtwAssistantMessage(finalText) };
|
|
14940
|
+
} finally {
|
|
14941
|
+
active = false;
|
|
14942
|
+
onTextDelta = undefined;
|
|
14943
|
+
replyText = "";
|
|
14944
|
+
context.messages = [];
|
|
14945
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
14946
|
+
clearTimeout(totalTimer);
|
|
14947
|
+
void iterator.return?.().catch(() => undefined);
|
|
14171
14948
|
}
|
|
14172
14949
|
}
|
|
14173
14950
|
|
|
14174
|
-
/** Build a `/btw` snapshot from an already-cloned committed context. */
|
|
14175
|
-
#buildBtwEphemeralSnapshot(messages: AgentMessage[], promptText: string): AgentMessage[] {
|
|
14176
|
-
messages.push(this.#buildEphemeralPromptMessage(promptText));
|
|
14177
|
-
return messages;
|
|
14178
|
-
}
|
|
14179
|
-
|
|
14180
14951
|
/** Build a background snapshot with in-flight assistant and optional context. */
|
|
14181
14952
|
#buildEphemeralSnapshot(promptText: string, prependMessages?: AgentMessage[]): AgentMessage[] {
|
|
14182
14953
|
const messages = [...this.messages];
|
|
@@ -14285,7 +15056,10 @@ export class AgentSession {
|
|
|
14285
15056
|
* Listeners are preserved and will continue receiving events.
|
|
14286
15057
|
* @returns true if switch completed, false if cancelled by hook
|
|
14287
15058
|
*/
|
|
14288
|
-
async switchSession(
|
|
15059
|
+
async switchSession(
|
|
15060
|
+
sessionPath: string,
|
|
15061
|
+
options?: { transition?: SessionSwitchEvent["transition"] },
|
|
15062
|
+
): Promise<boolean> {
|
|
14289
15063
|
this.#beginSessionTransition("switch-session");
|
|
14290
15064
|
try {
|
|
14291
15065
|
const previousSessionFile = this.sessionManager.getSessionFile();
|
|
@@ -14341,6 +15115,12 @@ export class AgentSession {
|
|
|
14341
15115
|
|
|
14342
15116
|
try {
|
|
14343
15117
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
15118
|
+
// The successor identity is already rotated in the manager but not yet
|
|
15119
|
+
// published; gate its local:// root before publication so the agent,
|
|
15120
|
+
// workflow-gate emitter, and hooks cannot resolve against an ungated
|
|
15121
|
+
// root. The manager-rotation window itself is tracked in #3138
|
|
15122
|
+
// (#2797 / #2925).
|
|
15123
|
+
if (switchingToDifferentSession) await initializeLocalRoot(this.#localProtocolOptions());
|
|
14344
15124
|
this.#syncAgentSessionId();
|
|
14345
15125
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
14346
15126
|
|
|
@@ -14405,6 +15185,12 @@ export class AgentSession {
|
|
|
14405
15185
|
? this.#getInheritedThinkingLevel()
|
|
14406
15186
|
: persistedThinkingLevel,
|
|
14407
15187
|
);
|
|
15188
|
+
this.#thinkingLevelMutationRevision++;
|
|
15189
|
+
this.#thinkingLevelLiveMutationRevision++;
|
|
15190
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
15191
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
15192
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
15193
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
14408
15194
|
this.#thinkingLevel = nextThinkingLevel;
|
|
14409
15195
|
this.agent.setThinkingLevel(toReasoningEffort(nextThinkingLevel));
|
|
14410
15196
|
this.agent.serviceTier = hasServiceTierEntry
|
|
@@ -14415,8 +15201,11 @@ export class AgentSession {
|
|
|
14415
15201
|
// Establish the successor's durable session identity only after every
|
|
14416
15202
|
// restored state facet is live. Identity-bound extension hooks run below.
|
|
14417
15203
|
await this.sessionManager.ensureOnDisk();
|
|
15204
|
+
if (!switchingToDifferentSession) await initializeLocalRoot(this.#localProtocolOptions());
|
|
14418
15205
|
|
|
14419
15206
|
if (switchingToDifferentSession) {
|
|
15207
|
+
// The local:// migration gate for this successor already ran above,
|
|
15208
|
+
// before the identity was published (#2797 / #2925).
|
|
14420
15209
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
14421
15210
|
this.#resetIrcRosterDeliveryState();
|
|
14422
15211
|
}
|
|
@@ -14435,6 +15224,7 @@ export class AgentSession {
|
|
|
14435
15224
|
type: "session_switch",
|
|
14436
15225
|
reason: "resume",
|
|
14437
15226
|
previousSessionFile,
|
|
15227
|
+
...(options?.transition ? { transition: options.transition } : {}),
|
|
14438
15228
|
});
|
|
14439
15229
|
}
|
|
14440
15230
|
return true;
|
|
@@ -14470,8 +15260,14 @@ export class AgentSession {
|
|
|
14470
15260
|
this.agent.restoreSteering(previousAgentSteeringQueue);
|
|
14471
15261
|
this.agent.restoreFollowUp(previousAgentFollowUpQueue);
|
|
14472
15262
|
if (previousModel) {
|
|
14473
|
-
this
|
|
14474
|
-
}
|
|
15263
|
+
this.#setAgentModelWithReasoningContext(previousModel);
|
|
15264
|
+
}
|
|
15265
|
+
this.#thinkingLevelMutationRevision++;
|
|
15266
|
+
this.#thinkingLevelLiveMutationRevision++;
|
|
15267
|
+
this.#pendingThinkingLevelControlSuccess = undefined;
|
|
15268
|
+
this.#pendingThinkingLevelControlFailure = undefined;
|
|
15269
|
+
this.#pendingThinkingVisibilityControlSuccess = undefined;
|
|
15270
|
+
this.#pendingThinkingVisibilityControlFailure = undefined;
|
|
14475
15271
|
this.#thinkingLevel = previousThinkingLevel;
|
|
14476
15272
|
this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
|
|
14477
15273
|
this.agent.serviceTier = previousServiceTier;
|
|
@@ -14535,19 +15331,21 @@ export class AgentSession {
|
|
|
14535
15331
|
skipConversationRestore = result?.skipConversationRestore ?? false;
|
|
14536
15332
|
}
|
|
14537
15333
|
|
|
14538
|
-
//
|
|
15334
|
+
// Flush pending writes before preparing the successor.
|
|
15335
|
+
await this.sessionManager.flush();
|
|
15336
|
+
const prepared = selectedEntry.parentId
|
|
15337
|
+
? await this.sessionManager.prepareBranchedSession(selectedEntry.parentId)
|
|
15338
|
+
: await this.sessionManager.prepareNewSession({ parentSession: previousSessionFile });
|
|
15339
|
+
try {
|
|
15340
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
15341
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
15342
|
+
} catch (error) {
|
|
15343
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
15344
|
+
}
|
|
14539
15345
|
this.#pendingNextTurnMessages = [];
|
|
14540
15346
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
14541
|
-
|
|
14542
|
-
// Flush pending writes before branching
|
|
14543
|
-
await this.sessionManager.flush();
|
|
14544
15347
|
this.#cancelOwnAsyncJobs();
|
|
14545
15348
|
|
|
14546
|
-
if (!selectedEntry.parentId) {
|
|
14547
|
-
await this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
14548
|
-
} else {
|
|
14549
|
-
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
14550
|
-
}
|
|
14551
15349
|
this.#syncTodoPhasesFromBranch();
|
|
14552
15350
|
this.#syncAgentSessionId();
|
|
14553
15351
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|