@otto-code/server 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +31 -0
- package/README.md +111 -0
- package/dist/scripts/dev-runner.js +37 -0
- package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
- package/dist/scripts/supervisor-entrypoint.js +141 -0
- package/dist/scripts/supervisor-log-config.js +31 -0
- package/dist/scripts/supervisor.js +252 -0
- package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
- package/dist/server/executable-resolution/executable-resolution.js +118 -0
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +65 -0
- package/dist/server/server/agent/activity-curator.d.ts +28 -0
- package/dist/server/server/agent/activity-curator.js +227 -0
- package/dist/server/server/agent/agent-archive.d.ts +11 -0
- package/dist/server/server/agent/agent-archive.js +16 -0
- package/dist/server/server/agent/agent-loading.d.ts +14 -0
- package/dist/server/server/agent/agent-loading.js +54 -0
- package/dist/server/server/agent/agent-manager.d.ts +470 -0
- package/dist/server/server/agent/agent-manager.js +3049 -0
- package/dist/server/server/agent/agent-personalities.d.ts +59 -0
- package/dist/server/server/agent/agent-personalities.js +103 -0
- package/dist/server/server/agent/agent-projections.d.ts +35 -0
- package/dist/server/server/agent/agent-projections.js +488 -0
- package/dist/server/server/agent/agent-prompt.d.ts +76 -0
- package/dist/server/server/agent/agent-prompt.js +230 -0
- package/dist/server/server/agent/agent-response-loop.d.ts +72 -0
- package/dist/server/server/agent/agent-response-loop.js +333 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +736 -0
- package/dist/server/server/agent/agent-sdk-types.js +22 -0
- package/dist/server/server/agent/agent-storage.d.ts +147 -0
- package/dist/server/server/agent/agent-storage.js +326 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +50 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +27 -0
- package/dist/server/server/agent/agent-timeline-store.js +230 -0
- package/dist/server/server/agent/audio-utils.d.ts +3 -0
- package/dist/server/server/agent/audio-utils.js +19 -0
- package/dist/server/server/agent/create-agent/create.d.ts +91 -0
- package/dist/server/server/agent/create-agent/create.js +370 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +54 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +152 -0
- package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
- package/dist/server/server/agent/create-agent-mode.js +62 -0
- package/dist/server/server/agent/create-agent-title.d.ts +10 -0
- package/dist/server/server/agent/create-agent-title.js +34 -0
- package/dist/server/server/agent/dictation-debug.d.ts +13 -0
- package/dist/server/server/agent/dictation-debug.js +50 -0
- package/dist/server/server/agent/effort-levels.d.ts +2 -0
- package/dist/server/server/agent/effort-levels.js +7 -0
- package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
- package/dist/server/server/agent/foreground-run-state.js +160 -0
- package/dist/server/server/agent/import-sessions.d.ts +47 -0
- package/dist/server/server/agent/import-sessions.js +145 -0
- package/dist/server/server/agent/lifecycle-command.d.ts +73 -0
- package/dist/server/server/agent/lifecycle-command.js +92 -0
- package/dist/server/server/agent/mcp-server.d.ts +5 -0
- package/dist/server/server/agent/mcp-server.js +66 -0
- package/dist/server/server/agent/mcp-shared.d.ts +192 -0
- package/dist/server/server/agent/mcp-shared.js +201 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
- package/dist/server/server/agent/pcm16-resampler.js +63 -0
- package/dist/server/server/agent/permission-response.d.ts +15 -0
- package/dist/server/server/agent/permission-response.js +14 -0
- package/dist/server/server/agent/personality-stats-store.d.ts +23 -0
- package/dist/server/server/agent/personality-stats-store.js +65 -0
- package/dist/server/server/agent/prompt-attachments.d.ts +7 -0
- package/dist/server/server/agent/prompt-attachments.js +117 -0
- package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
- package/dist/server/server/agent/provider-history-timestamps.js +16 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +43 -0
- package/dist/server/server/agent/provider-launch-config.js +167 -0
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +41 -0
- package/dist/server/server/agent/provider-registry.js +532 -0
- package/dist/server/server/agent/provider-session-import.d.ts +10 -0
- package/dist/server/server/agent/provider-session-import.js +49 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +142 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +690 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +353 -0
- package/dist/server/server/agent/providers/acp-agent.js +2439 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +66 -0
- package/dist/server/server/agent/providers/claude/agent.js +4535 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
- package/dist/server/server/agent/providers/claude/model-manifest.d.ts +90 -0
- package/dist/server/server/agent/providers/claude/model-manifest.js +205 -0
- package/dist/server/server/agent/providers/claude/models.d.ts +11 -0
- package/dist/server/server/agent/providers/claude/models.js +97 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
- package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
- package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
- package/dist/server/server/agent/providers/claude/query.d.ts +17 -0
- package/dist/server/server/agent/providers/claude/query.js +86 -0
- package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
- package/dist/server/server/agent/providers/claude/rewind.js +30 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +22 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +232 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +35 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +272 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +90 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.js +262 -0
- package/dist/server/server/agent/providers/codex/rewind.d.ts +21 -0
- package/dist/server/server/agent/providers/codex/rewind.js +46 -0
- package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.d.ts +36 -0
- package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.js +217 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +830 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +308 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js +4515 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js +45 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js +188 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +18 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +355 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +42 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +160 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +85 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +1010 -0
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
- package/dist/server/server/agent/providers/mock-slow-provider.js +39 -0
- package/dist/server/server/agent/providers/openai-compat-agent.d.ts +422 -0
- package/dist/server/server/agent/providers/openai-compat-agent.js +2536 -0
- package/dist/server/server/agent/providers/openai-compat-feature-definitions.d.ts +40 -0
- package/dist/server/server/agent/providers/openai-compat-feature-definitions.js +82 -0
- package/dist/server/server/agent/providers/openai-compat-mcp.d.ts +87 -0
- package/dist/server/server/agent/providers/openai-compat-mcp.js +353 -0
- package/dist/server/server/agent/providers/openai-compat-otto-tool-permissions.d.ts +29 -0
- package/dist/server/server/agent/providers/openai-compat-otto-tool-permissions.js +96 -0
- package/dist/server/server/agent/providers/openai-compat-tools.d.ts +69 -0
- package/dist/server/server/agent/providers/openai-compat-tools.js +1224 -0
- package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/paths.js +7 -0
- package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +91 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +425 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +25 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.js +39 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.d.ts +92 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.js +260 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +286 -0
- package/dist/server/server/agent/providers/opencode-agent.js +2830 -0
- package/dist/server/server/agent/providers/pi/agent.d.ts +132 -0
- package/dist/server/server/agent/providers/pi/agent.js +1649 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.js +261 -0
- package/dist/server/server/agent/providers/pi/history-mapper.d.ts +9 -0
- package/dist/server/server/agent/providers/pi/history-mapper.js +140 -0
- package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
- package/dist/server/server/agent/providers/pi/rewind.js +8 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +208 -0
- package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
- package/dist/server/server/agent/providers/pi/runtime.d.ts +57 -0
- package/dist/server/server/agent/providers/pi/runtime.js +56 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +346 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +83 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +184 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +112 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.js +284 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
- package/dist/server/server/agent/providers/provider-image-output.js +111 -0
- package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
- package/dist/server/server/agent/providers/provider-runner.js +81 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +60 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
- package/dist/server/server/agent/providers/trae-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/trae-acp-agent.js +18 -0
- package/dist/server/server/agent/recordings-debug.d.ts +3 -0
- package/dist/server/server/agent/recordings-debug.js +19 -0
- package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
- package/dist/server/server/agent/rewind/rewind.js +29 -0
- package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
- package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
- package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
- package/dist/server/server/agent/runtime-mcp-config.js +53 -0
- package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
- package/dist/server/server/agent/structured-generation-providers.js +236 -0
- package/dist/server/server/agent/stt-debug.d.ts +10 -0
- package/dist/server/server/agent/stt-debug.js +33 -0
- package/dist/server/server/agent/stt-manager.d.ts +38 -0
- package/dist/server/server/agent/stt-manager.js +246 -0
- package/dist/server/server/agent/system-prompt.d.ts +2 -0
- package/dist/server/server/agent/system-prompt.js +8 -0
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-projection.d.ts +71 -0
- package/dist/server/server/agent/timeline-projection.js +373 -0
- package/dist/server/server/agent/tools/otto-tools.d.ts +80 -0
- package/dist/server/server/agent/tools/otto-tools.js +2912 -0
- package/dist/server/server/agent/tools/types.d.ts +36 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/agent/tts-debug.d.ts +8 -0
- package/dist/server/server/agent/tts-debug.js +24 -0
- package/dist/server/server/agent/tts-manager.d.ts +41 -0
- package/dist/server/server/agent/tts-manager.js +375 -0
- package/dist/server/server/agent-attention-policy.d.ts +29 -0
- package/dist/server/server/agent-attention-policy.js +36 -0
- package/dist/server/server/artifact/artifact-prompt.d.ts +2 -0
- package/dist/server/server/artifact/artifact-prompt.js +15 -0
- package/dist/server/server/artifact/artifact-service.d.ts +91 -0
- package/dist/server/server/artifact/artifact-service.js +419 -0
- package/dist/server/server/artifact/artifact-store.d.ts +24 -0
- package/dist/server/server/artifact/artifact-store.js +168 -0
- package/dist/server/server/artifact/artifact-watcher.d.ts +34 -0
- package/dist/server/server/artifact/artifact-watcher.js +146 -0
- package/dist/server/server/artifact/html-validator.d.ts +26 -0
- package/dist/server/server/artifact/html-validator.js +92 -0
- package/dist/server/server/atomic-file.d.ts +5 -0
- package/dist/server/server/atomic-file.js +19 -0
- package/dist/server/server/auth.d.ts +38 -0
- package/dist/server/server/auth.js +143 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +92 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts +8 -0
- package/dist/server/server/auto-archive-on-merge/index.js +15 -0
- package/dist/server/server/bootstrap.d.ts +127 -0
- package/dist/server/server/bootstrap.js +1331 -0
- package/dist/server/server/browser-tools/broker.d.ts +45 -0
- package/dist/server/server/browser-tools/broker.js +390 -0
- package/dist/server/server/browser-tools/errors.d.ts +22 -0
- package/dist/server/server/browser-tools/errors.js +27 -0
- package/dist/server/server/browser-tools/policy.d.ts +10 -0
- package/dist/server/server/browser-tools/policy.js +16 -0
- package/dist/server/server/browser-tools/tools.d.ts +24 -0
- package/dist/server/server/browser-tools/tools.js +967 -0
- package/dist/server/server/chat/chat-mentions.d.ts +47 -0
- package/dist/server/server/chat/chat-mentions.js +113 -0
- package/dist/server/server/chat/chat-service.d.ts +78 -0
- package/dist/server/server/chat/chat-service.js +337 -0
- package/dist/server/server/checkout/status-projection.d.ts +19 -0
- package/dist/server/server/checkout/status-projection.js +117 -0
- package/dist/server/server/checkout-diff-manager.d.ts +41 -0
- package/dist/server/server/checkout-diff-manager.js +189 -0
- package/dist/server/server/checkout-git-utils.d.ts +11 -0
- package/dist/server/server/checkout-git-utils.js +34 -0
- package/dist/server/server/client-message-id.d.ts +3 -0
- package/dist/server/server/client-message-id.js +12 -0
- package/dist/server/server/config.d.ts +22 -0
- package/dist/server/server/config.js +330 -0
- package/dist/server/server/connection-offer.d.ts +20 -0
- package/dist/server/server/connection-offer.js +59 -0
- package/dist/server/server/daemon-config-store.d.ts +44 -0
- package/dist/server/server/daemon-config-store.js +530 -0
- package/dist/server/server/daemon-keypair.d.ts +8 -0
- package/dist/server/server/daemon-keypair.js +42 -0
- package/dist/server/server/daemon-version.d.ts +5 -0
- package/dist/server/server/daemon-version.js +22 -0
- package/dist/server/server/daemon-worker.d.ts +2 -0
- package/dist/server/server/daemon-worker.js +251 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +87 -0
- package/dist/server/server/dictation/dictation-stream-manager.js +572 -0
- package/dist/server/server/exports.d.ts +24 -0
- package/dist/server/server/exports.js +24 -0
- package/dist/server/server/file-download/token-store.d.ts +24 -0
- package/dist/server/server/file-download/token-store.js +40 -0
- package/dist/server/server/file-explorer/code-index.d.ts +28 -0
- package/dist/server/server/file-explorer/code-index.js +127 -0
- package/dist/server/server/file-explorer/file-search.d.ts +72 -0
- package/dist/server/server/file-explorer/file-search.js +297 -0
- package/dist/server/server/file-explorer/file-watcher.d.ts +40 -0
- package/dist/server/server/file-explorer/file-watcher.js +0 -0
- package/dist/server/server/file-explorer/gitignore.d.ts +39 -0
- package/dist/server/server/file-explorer/gitignore.js +191 -0
- package/dist/server/server/file-explorer/service.d.ts +103 -0
- package/dist/server/server/file-explorer/service.js +420 -0
- package/dist/server/server/file-upload/index.d.ts +27 -0
- package/dist/server/server/file-upload/index.js +158 -0
- package/dist/server/server/hostnames.d.ts +13 -0
- package/dist/server/server/hostnames.js +94 -0
- package/dist/server/server/json-utils.d.ts +11 -0
- package/dist/server/server/json-utils.js +45 -0
- package/dist/server/server/lifecycle-reasons.d.ts +4 -0
- package/dist/server/server/lifecycle-reasons.js +6 -0
- package/dist/server/server/logger.d.ts +29 -0
- package/dist/server/server/logger.js +125 -0
- package/dist/server/server/loop-service.d.ts +234 -0
- package/dist/server/server/loop-service.js +804 -0
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/messages.d.ts +9 -0
- package/dist/server/server/messages.js +29 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
- package/dist/server/server/otto-env.d.ts +13 -0
- package/dist/server/server/otto-env.js +53 -0
- package/dist/server/server/otto-home.d.ts +2 -0
- package/dist/server/server/otto-home.js +19 -0
- package/dist/server/server/otto-worktree-service.d.ts +52 -0
- package/dist/server/server/otto-worktree-service.js +294 -0
- package/dist/server/server/package-version.d.ts +19 -0
- package/dist/server/server/package-version.js +60 -0
- package/dist/server/server/pagination/cursor.d.ts +16 -0
- package/dist/server/server/pagination/cursor.js +62 -0
- package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
- package/dist/server/server/pagination/sortable-pager.js +68 -0
- package/dist/server/server/pairing-offer.d.ts +18 -0
- package/dist/server/server/pairing-offer.js +47 -0
- package/dist/server/server/pairing-qr.d.ts +7 -0
- package/dist/server/server/pairing-qr.js +45 -0
- package/dist/server/server/path-utils.d.ts +5 -0
- package/dist/server/server/path-utils.js +34 -0
- package/dist/server/server/persisted-config.d.ts +417 -0
- package/dist/server/server/persisted-config.js +471 -0
- package/dist/server/server/persistence-hooks.d.ts +31 -0
- package/dist/server/server/persistence-hooks.js +93 -0
- package/dist/server/server/pid-lock.d.ts +32 -0
- package/dist/server/server/pid-lock.js +170 -0
- package/dist/server/server/preview/dev-server-manager.d.ts +108 -0
- package/dist/server/server/preview/dev-server-manager.js +426 -0
- package/dist/server/server/preview/launch-config.d.ts +37 -0
- package/dist/server/server/preview/launch-config.js +63 -0
- package/dist/server/server/preview/preview-tools.d.ts +30 -0
- package/dist/server/server/preview/preview-tools.js +249 -0
- package/dist/server/server/private-files.d.ts +6 -0
- package/dist/server/server/private-files.js +37 -0
- package/dist/server/server/process-diagnostics.d.ts +17 -0
- package/dist/server/server/process-diagnostics.js +22 -0
- package/dist/server/server/push/notifications.d.ts +9 -0
- package/dist/server/server/push/notifications.js +15 -0
- package/dist/server/server/push/push-service.d.ts +20 -0
- package/dist/server/server/push/push-service.js +66 -0
- package/dist/server/server/push/token-store.d.ts +18 -0
- package/dist/server/server/push/token-store.js +68 -0
- package/dist/server/server/relay-transport.d.ts +31 -0
- package/dist/server/server/relay-transport.js +514 -0
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +32 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +121 -0
- package/dist/server/server/schedule/cron.d.ts +4 -0
- package/dist/server/server/schedule/cron.js +81 -0
- package/dist/server/server/schedule/service.d.ts +89 -0
- package/dist/server/server/schedule/service.js +882 -0
- package/dist/server/server/schedule/store.d.ts +27 -0
- package/dist/server/server/schedule/store.js +190 -0
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.js +159 -0
- package/dist/server/server/script-proxy.d.ts +3 -0
- package/dist/server/server/script-proxy.js +2 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.js +11 -0
- package/dist/server/server/script-status-projection.d.ts +36 -0
- package/dist/server/server/script-status-projection.js +200 -0
- package/dist/server/server/server-id.d.ts +17 -0
- package/dist/server/server/server-id.js +68 -0
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +714 -0
- package/dist/server/server/session/agent-config/agent-config-session.d.ts +59 -0
- package/dist/server/server/session/agent-config/agent-config-session.js +110 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +65 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +232 -0
- package/dist/server/server/session/artifact/artifact-session.d.ts +45 -0
- package/dist/server/server/session/artifact/artifact-session.js +293 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +146 -0
- package/dist/server/server/session/checkout/checkout-session.js +1000 -0
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
- package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +76 -0
- package/dist/server/server/session/daemon/daemon-session.js +148 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +431 -0
- package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
- package/dist/server/server/session/daemon/install-origin.js +64 -0
- package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
- package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
- package/dist/server/server/session/files/workspace-files-session.d.ts +60 -0
- package/dist/server/server/session/files/workspace-files-session.js +518 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
- package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
- package/dist/server/server/session/project-config/project-config-session.js +125 -0
- package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +343 -0
- package/dist/server/server/session/voice/voice-session.d.ts +167 -0
- package/dist/server/server/session/voice/voice-session.js +912 -0
- package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
- package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
- package/dist/server/server/session.d.ts +450 -0
- package/dist/server/server/session.js +4763 -0
- package/dist/server/server/speech/audio.d.ts +10 -0
- package/dist/server/server/speech/audio.js +101 -0
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/providers/local/config.d.ts +30 -0
- package/dist/server/server/speech/providers/local/config.js +133 -0
- package/dist/server/server/speech/providers/local/models.d.ts +13 -0
- package/dist/server/server/speech/providers/local/models.js +21 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
- package/dist/server/server/speech/providers/local/runtime.js +135 -0
- package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +109 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +129 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +16 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +195 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +24 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +145 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
- package/dist/server/server/speech/providers/local/sherpa/tts-voices.d.ts +10 -0
- package/dist/server/server/speech/providers/local/sherpa/tts-voices.js +86 -0
- package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
- package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
- package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
- package/dist/server/server/speech/providers/local/worker-client.js +591 -0
- package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
- package/dist/server/server/speech/providers/local/worker-process.js +270 -0
- package/dist/server/server/speech/providers/local/worker-protocol.d.ts +96 -0
- package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
- package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/config.js +160 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
- package/dist/server/server/speech/providers/openai/runtime.js +102 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
- package/dist/server/server/speech/providers/openai/stt.js +209 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts +20 -0
- package/dist/server/server/speech/providers/openai/tts.js +70 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
- package/dist/server/server/speech/speech-config-resolver.js +132 -0
- package/dist/server/server/speech/speech-provider.d.ts +71 -0
- package/dist/server/server/speech/speech-provider.js +2 -0
- package/dist/server/server/speech/speech-runtime.d.ts +67 -0
- package/dist/server/server/speech/speech-runtime.js +615 -0
- package/dist/server/server/speech/speech-settings-options.d.ts +14 -0
- package/dist/server/server/speech/speech-settings-options.js +73 -0
- package/dist/server/server/speech/speech-types.d.ts +22 -0
- package/dist/server/server/speech/speech-types.js +8 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
- package/dist/server/server/speech/turn-detection-provider.js +2 -0
- package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
- package/dist/server/server/utils/diff-highlighter.js +279 -0
- package/dist/server/server/voice-config.d.ts +15 -0
- package/dist/server/server/voice-config.js +54 -0
- package/dist/server/server/voice-permission-policy.d.ts +4 -0
- package/dist/server/server/voice-permission-policy.js +13 -0
- package/dist/server/server/voice-types.d.ts +12 -0
- package/dist/server/server/voice-types.js +2 -0
- package/dist/server/server/web-ui.d.ts +10 -0
- package/dist/server/server/web-ui.js +205 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +94 -0
- package/dist/server/server/websocket/runtime-metrics.js +148 -0
- package/dist/server/server/websocket-server.d.ts +187 -0
- package/dist/server/server/websocket-server.js +1766 -0
- package/dist/server/server/workspace-archive-service.d.ts +65 -0
- package/dist/server/server/workspace-archive-service.js +229 -0
- package/dist/server/server/workspace-auto-name.d.ts +55 -0
- package/dist/server/server/workspace-auto-name.js +128 -0
- package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
- package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
- package/dist/server/server/workspace-directory.d.ts +93 -0
- package/dist/server/server/workspace-directory.js +422 -0
- package/dist/server/server/workspace-git-metadata.d.ts +24 -0
- package/dist/server/server/workspace-git-metadata.js +55 -0
- package/dist/server/server/workspace-git-service.d.ts +270 -0
- package/dist/server/server/workspace-git-service.js +1423 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts +57 -0
- package/dist/server/server/workspace-reconciliation-service.js +278 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts +13 -0
- package/dist/server/server/workspace-registry-bootstrap.js +121 -0
- package/dist/server/server/workspace-registry-model.d.ts +55 -0
- package/dist/server/server/workspace-registry-model.js +190 -0
- package/dist/server/server/workspace-registry.d.ts +117 -0
- package/dist/server/server/workspace-registry.js +194 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-service-env.d.ts +18 -0
- package/dist/server/server/workspace-service-env.js +85 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
- package/dist/server/server/workspace-service-port-registry.js +59 -0
- package/dist/server/server/worktree/commands.d.ts +55 -0
- package/dist/server/server/worktree/commands.js +107 -0
- package/dist/server/server/worktree-bootstrap.d.ts +76 -0
- package/dist/server/server/worktree-bootstrap.js +720 -0
- package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
- package/dist/server/server/worktree-branch-name-generator.js +91 -0
- package/dist/server/server/worktree-core.d.ts +31 -0
- package/dist/server/server/worktree-core.js +105 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.js +28 -0
- package/dist/server/server/worktree-session.d.ts +152 -0
- package/dist/server/server/worktree-session.js +466 -0
- package/dist/server/services/git-hosting/bitbucket-cloud-service.d.ts +24 -0
- package/dist/server/services/git-hosting/bitbucket-cloud-service.js +754 -0
- package/dist/server/services/git-hosting/github-hosting-service.d.ts +4 -0
- package/dist/server/services/git-hosting/github-hosting-service.js +11 -0
- package/dist/server/services/git-hosting/hosting-http-client.d.ts +20 -0
- package/dist/server/services/git-hosting/hosting-http-client.js +131 -0
- package/dist/server/services/git-hosting/request-cache.d.ts +20 -0
- package/dist/server/services/git-hosting/request-cache.js +68 -0
- package/dist/server/services/git-hosting/resolver.d.ts +32 -0
- package/dist/server/services/git-hosting/resolver.js +152 -0
- package/dist/server/services/git-hosting/router.d.ts +4 -0
- package/dist/server/services/git-hosting/router.js +105 -0
- package/dist/server/services/git-hosting/status-poller.d.ts +39 -0
- package/dist/server/services/git-hosting/status-poller.js +149 -0
- package/dist/server/services/git-hosting/types.d.ts +53 -0
- package/dist/server/services/git-hosting/types.js +75 -0
- package/dist/server/services/github-service.d.ts +339 -0
- package/dist/server/services/github-service.js +2109 -0
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +52 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
- package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/otto-integration.zsh +32 -0
- package/dist/server/terminal/terminal-capture.d.ts +12 -0
- package/dist/server/terminal/terminal-capture.js +43 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
- package/dist/server/terminal/terminal-manager-factory.js +5 -0
- package/dist/server/terminal/terminal-manager.d.ts +78 -0
- package/dist/server/terminal/terminal-manager.js +319 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
- package/dist/server/terminal/terminal-output-coalescer.js +73 -0
- package/dist/server/terminal/terminal-restore.d.ts +18 -0
- package/dist/server/terminal/terminal-restore.js +52 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +85 -0
- package/dist/server/terminal/terminal-session-controller.js +819 -0
- package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
- package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
- package/dist/server/terminal/terminal-worker-process.js +304 -0
- package/dist/server/terminal/terminal-worker-protocol.d.ts +119 -0
- package/dist/server/terminal/terminal-worker-protocol.js +2 -0
- package/dist/server/terminal/terminal.d.ts +151 -0
- package/dist/server/terminal/terminal.js +1155 -0
- package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
- package/dist/server/terminal/worker-terminal-manager.js +688 -0
- package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
- package/dist/server/utils/build-metadata-prompt.js +29 -0
- package/dist/server/utils/checkout-git.d.ts +253 -0
- package/dist/server/utils/checkout-git.js +2241 -0
- package/dist/server/utils/directory-suggestions.d.ts +26 -0
- package/dist/server/utils/directory-suggestions.js +830 -0
- package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
- package/dist/server/utils/git-rev-parse-path.js +21 -0
- package/dist/server/utils/github-remote.d.ts +9 -0
- package/dist/server/utils/github-remote.js +62 -0
- package/dist/server/utils/otto-config-file.d.ts +30 -0
- package/dist/server/utils/otto-config-file.js +90 -0
- package/dist/server/utils/path.d.ts +18 -0
- package/dist/server/utils/path.js +123 -0
- package/dist/server/utils/project-icon.d.ts +39 -0
- package/dist/server/utils/project-icon.js +364 -0
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/run-git-command.d.ts +37 -0
- package/dist/server/utils/run-git-command.js +232 -0
- package/dist/server/utils/spawn.d.ts +25 -0
- package/dist/server/utils/spawn.js +59 -0
- package/dist/server/utils/string-command-shell.d.ts +13 -0
- package/dist/server/utils/string-command-shell.js +37 -0
- package/dist/server/utils/tree-kill.d.ts +18 -0
- package/dist/server/utils/tree-kill.js +77 -0
- package/dist/server/utils/windows-command.d.ts +15 -0
- package/dist/server/utils/windows-command.js +41 -0
- package/dist/server/utils/worktree-metadata.d.ts +39 -0
- package/dist/server/utils/worktree-metadata.js +174 -0
- package/dist/server/utils/worktree.d.ts +205 -0
- package/dist/server/utils/worktree.js +1120 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a25c6055b348629689f75618db2c83e1.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a25c6055b348629689f75618db2c83e1.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a25c6055b348629689f75618db2c83e1.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7dc26ea1611eb9d7c2971a6745e5bf5c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7dc26ea1611eb9d7c2971a6745e5bf5c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7dc26ea1611eb9d7c2971a6745e5bf5c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-ea91f496cee180fa5bf308c8cc177646.js +3199 -0
- package/dist/server/web-ui/_expo/static/js/web/index-ea91f496cee180fa5bf308c8cc177646.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-ea91f496cee180fa5bf308c8cc177646.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-b6b853a967343babf305512fe8c5958c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-b6b853a967343babf305512fe8c5958c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-b6b853a967343babf305512fe8c5958c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-17223e158406802adc686d02d946573d.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-17223e158406802adc686d02d946573d.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-17223e158406802adc686d02d946573d.js.gz +0 -0
- package/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@expo-google-fonts/inter/400Regular/Inter_400Regular.51b6ad87261f18b6433ec52871ddfabc.ttf +0 -0
- package/dist/server/web-ui/assets/__node_modules/@expo-google-fonts/jetbrains-mono/400Regular/JetBrainsMono_400Regular.a0147b5ab9e4946e81879aef45313def.ttf +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/clion.baa1f4eb572e962a6117bdbee95749e4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/goland.0796ab2b9be2637fd1cdcde891fad3ca.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/intellij-idea.33bc9ff9c0815d8ad43f618c8c244401.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/phpstorm.c60b33bc7a8309143d232e14b76554c5.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/pycharm.88f25ac484989c45c25323e0adccfa9f.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/rider.1a1091dc3a0ede0461f0aa38a54f080a.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/rubymine.0e5b66fd7d43f8cbac0f05bba97c9a21.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/rustrover.3590ef05482369f41d3484c90272570d.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.3b2c331d11122d8f743c73a5878593be.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-running.6077c2ab71021b31c82a42e0b42ad837.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark.12984efe6d435a13794a74cd2f858dcd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-attention.3b2c331d11122d8f743c73a5878593be.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-running.6077c2ab71021b31c82a42e0b42ad837.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light.12984efe6d435a13794a74cd2f858dcd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/notification-icon.2394c3b427c0f5ee2bb84864d8fb0f45.png +0 -0
- package/dist/server/web-ui/favicon.ico +0 -0
- package/dist/server/web-ui/index.html +147 -0
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/server/web-ui/manifest.json +27 -0
- package/dist/server/web-ui/manifest.json.br +0 -0
- package/dist/server/web-ui/manifest.json.gz +0 -0
- package/dist/server/web-ui/metadata.json +1 -0
- package/dist/server/web-ui/metadata.json.br +1 -0
- package/dist/server/web-ui/metadata.json.gz +0 -0
- package/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/dist/server/web-ui/robots.txt +2 -0
- package/dist/src/executable-resolution/executable-resolution.js +118 -0
- package/dist/src/executable-resolution/windows.js +65 -0
- package/dist/src/server/agent/provider-launch-config.js +167 -0
- package/dist/src/server/otto-env.js +53 -0
- package/dist/src/server/otto-home.js +19 -0
- package/dist/src/server/persisted-config.js +471 -0
- package/dist/src/server/pid-lock.js +170 -0
- package/dist/src/server/private-files.js +37 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/otto-integration.zsh +32 -0
- package/dist/src/utils/spawn.js +59 -0
- package/dist/src/utils/windows-command.js +41 -0
- package/package.json +118 -0
|
@@ -0,0 +1,3049 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { stat } from "node:fs/promises";
|
|
4
|
+
import { AGENT_LIFECYCLE_STATUSES, } from "@otto-code/protocol/agent-lifecycle";
|
|
5
|
+
import { getParentAgentIdFromLabels, isDelegatedAgent, PARENT_AGENT_ID_LABEL, } from "@otto-code/protocol/agent-labels";
|
|
6
|
+
import { normalizePersonalityRoles } from "@otto-code/protocol/agent-personalities";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { getAgentStreamEventTurnId, } from "./agent-sdk-types.js";
|
|
9
|
+
import { toObservedSubagentPayload } from "./agent-projections.js";
|
|
10
|
+
import { buildArchivedAgentRecord } from "./agent-archive.js";
|
|
11
|
+
import { InMemoryAgentTimelineStore, } from "./agent-timeline-store.js";
|
|
12
|
+
import { AGENT_STREAM_COALESCE_DEFAULT_WINDOW_MS, AgentStreamCoalescer, } from "./agent-stream-coalescer.js";
|
|
13
|
+
import { ForegroundRunState } from "./foreground-run-state.js";
|
|
14
|
+
import { getAgentProviderDefinition } from "@otto-code/protocol/provider-manifest";
|
|
15
|
+
import { invokeRewindCapability } from "./rewind/rewind.js";
|
|
16
|
+
import { isSystemInjectedEnvelope } from "./agent-prompt.js";
|
|
17
|
+
import { stripInternalOttoMcpServer, withRuntimeOttoMcpServer } from "./runtime-mcp-config.js";
|
|
18
|
+
import { resolveCreateAgentTitles } from "./create-agent-title.js";
|
|
19
|
+
const RELOAD_SESSION_CLOSE_TIMEOUT_MS = 3000;
|
|
20
|
+
const INTERRUPT_SESSION_TIMEOUT_MS = 2000;
|
|
21
|
+
const STORED_AGENT_CAPABILITIES = {
|
|
22
|
+
supportsStreaming: false,
|
|
23
|
+
supportsSessionPersistence: true,
|
|
24
|
+
supportsDynamicModes: false,
|
|
25
|
+
supportsMcpServers: false,
|
|
26
|
+
supportsReasoningStream: false,
|
|
27
|
+
supportsToolInvocations: true,
|
|
28
|
+
supportsRewindConversation: false,
|
|
29
|
+
supportsRewindFiles: false,
|
|
30
|
+
supportsRewindBoth: false,
|
|
31
|
+
};
|
|
32
|
+
function formatProviderList(providers) {
|
|
33
|
+
return providers.length > 0 ? providers.join(", ") : "none";
|
|
34
|
+
}
|
|
35
|
+
function buildStoredAgentConfig(record) {
|
|
36
|
+
const config = {
|
|
37
|
+
provider: record.provider,
|
|
38
|
+
cwd: record.cwd,
|
|
39
|
+
};
|
|
40
|
+
if (!record.config) {
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
43
|
+
if (record.config.modeId != null)
|
|
44
|
+
config.modeId = record.config.modeId;
|
|
45
|
+
if (record.config.model != null)
|
|
46
|
+
config.model = record.config.model;
|
|
47
|
+
if (record.config.thinkingOptionId != null) {
|
|
48
|
+
config.thinkingOptionId = record.config.thinkingOptionId;
|
|
49
|
+
}
|
|
50
|
+
if (record.config.featureValues != null) {
|
|
51
|
+
config.featureValues = record.config.featureValues;
|
|
52
|
+
}
|
|
53
|
+
if (record.config.extra != null)
|
|
54
|
+
config.extra = record.config.extra;
|
|
55
|
+
if (record.config.systemPrompt != null) {
|
|
56
|
+
config.systemPrompt = record.config.systemPrompt;
|
|
57
|
+
}
|
|
58
|
+
if (record.config.mcpServers != null)
|
|
59
|
+
config.mcpServers = record.config.mcpServers;
|
|
60
|
+
if (record.config.personalitySnapshot != null) {
|
|
61
|
+
// Storage keeps roles as a loose string array; normalize back to the known
|
|
62
|
+
// PersonalityRole set on the way in.
|
|
63
|
+
config.personalitySnapshot = {
|
|
64
|
+
...record.config.personalitySnapshot,
|
|
65
|
+
roles: normalizePersonalityRoles(record.config.personalitySnapshot.roles),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return stripInternalOttoMcpServer(config);
|
|
69
|
+
}
|
|
70
|
+
export { AGENT_LIFECYCLE_STATUSES };
|
|
71
|
+
function resolveInitialAttention(input) {
|
|
72
|
+
if (input == null || !input.requiresAttention) {
|
|
73
|
+
return { requiresAttention: false };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
requiresAttention: true,
|
|
77
|
+
attentionReason: input.attentionReason,
|
|
78
|
+
attentionTimestamp: new Date(input.attentionTimestamp),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const SYSTEM_ERROR_PREFIX = "[System Error]";
|
|
82
|
+
function attachPersistenceCwd(handle, cwd) {
|
|
83
|
+
if (!handle) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
...handle,
|
|
88
|
+
metadata: {
|
|
89
|
+
...handle.metadata,
|
|
90
|
+
cwd,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const BUSY_STATUSES = new Set(["initializing", "running"]);
|
|
95
|
+
const AgentIdSchema = z.guid();
|
|
96
|
+
function isAgentBusy(status) {
|
|
97
|
+
return BUSY_STATUSES.has(status);
|
|
98
|
+
}
|
|
99
|
+
function isTurnTerminalEvent(event) {
|
|
100
|
+
return (event.type === "turn_completed" ||
|
|
101
|
+
event.type === "turn_failed" ||
|
|
102
|
+
event.type === "turn_canceled");
|
|
103
|
+
}
|
|
104
|
+
function abortMessage(reason, fallbackMessage) {
|
|
105
|
+
if (typeof reason === "string")
|
|
106
|
+
return reason;
|
|
107
|
+
if (reason instanceof Error)
|
|
108
|
+
return reason.message;
|
|
109
|
+
return fallbackMessage;
|
|
110
|
+
}
|
|
111
|
+
function createAbortError(signal, fallbackMessage) {
|
|
112
|
+
const message = abortMessage(signal?.reason, fallbackMessage);
|
|
113
|
+
return Object.assign(new Error(message), { name: "AbortError" });
|
|
114
|
+
}
|
|
115
|
+
function validateAgentId(agentId, source) {
|
|
116
|
+
const result = AgentIdSchema.safeParse(agentId);
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
throw new Error(`${source}: agentId must be a UUID`);
|
|
119
|
+
}
|
|
120
|
+
return result.data;
|
|
121
|
+
}
|
|
122
|
+
function applyLabelPatch(labels, patch) {
|
|
123
|
+
const nextLabels = { ...labels };
|
|
124
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
125
|
+
if (value === null) {
|
|
126
|
+
delete nextLabels[key];
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
nextLabels[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return nextLabels;
|
|
133
|
+
}
|
|
134
|
+
function buildExplicitTimelineSeedForRegister(now, options) {
|
|
135
|
+
const hasTimeline = Boolean(options?.timeline?.length);
|
|
136
|
+
const hasTimelineRows = Boolean(options?.timelineRows?.length);
|
|
137
|
+
const hasTimelineNextSeq = options?.timelineNextSeq !== undefined;
|
|
138
|
+
if (!hasTimeline && !hasTimelineRows && !hasTimelineNextSeq) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
items: options?.timeline,
|
|
143
|
+
rows: options?.timelineRows,
|
|
144
|
+
nextSeq: options?.timelineNextSeq,
|
|
145
|
+
timestamp: (options?.updatedAt ?? options?.createdAt ?? now).toISOString(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function buildImportedTimelineRows(entries) {
|
|
149
|
+
const rows = [];
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (entry.item.type === "user_message" && isSystemInjectedEnvelope(entry.item.text)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
rows.push({
|
|
155
|
+
seq: rows.length + 1,
|
|
156
|
+
timestamp: entry.timestamp ?? new Date().toISOString(),
|
|
157
|
+
item: entry.item,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return rows;
|
|
161
|
+
}
|
|
162
|
+
function resolveImportedAgentTitle(config, timelineRows) {
|
|
163
|
+
const initialPrompt = getFirstUserMessageTextFromRows(timelineRows);
|
|
164
|
+
if (!initialPrompt) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
|
|
168
|
+
configTitle: config.title,
|
|
169
|
+
initialPrompt,
|
|
170
|
+
});
|
|
171
|
+
return explicitTitle ?? provisionalTitle ?? null;
|
|
172
|
+
}
|
|
173
|
+
function getFirstUserMessageTextFromRows(rows) {
|
|
174
|
+
for (const row of rows) {
|
|
175
|
+
const item = row.item;
|
|
176
|
+
if (item.type !== "user_message") {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const text = item.text.trim();
|
|
180
|
+
if (text) {
|
|
181
|
+
return text;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
export class AgentManager {
|
|
187
|
+
constructor(options) {
|
|
188
|
+
this.clients = new Map();
|
|
189
|
+
this.providerEnabled = new Map();
|
|
190
|
+
this.agents = new Map();
|
|
191
|
+
this.timelineStore = new InMemoryAgentTimelineStore();
|
|
192
|
+
this.agentsAwaitingInitialSnapshotPersist = new Set();
|
|
193
|
+
this.sessionEventTails = new Map();
|
|
194
|
+
this.foregroundRuns = new ForegroundRunState();
|
|
195
|
+
this.subscribers = new Set();
|
|
196
|
+
this.previousStatuses = new Map();
|
|
197
|
+
// Observed subagents (Claude Task / ultracode fan-out): id -> resolution info
|
|
198
|
+
// for the stop RPC. These are ephemeral projections, not ManagedAgents. See
|
|
199
|
+
// projects/observed-subagents/observed-subagents.md.
|
|
200
|
+
this.observedSubagents = new Map();
|
|
201
|
+
this.backgroundTasks = new Set();
|
|
202
|
+
this.ottoToolsEnabled = true;
|
|
203
|
+
this.ottoToolCatalogFactory = null;
|
|
204
|
+
/**
|
|
205
|
+
* Config mutations on one agent serialize through a per-agent promise chain:
|
|
206
|
+
* the multi-await setters (setAgentPersonality resolves snapshots and applies
|
|
207
|
+
* prompt + brain across several RPC hops) must not interleave with a racing
|
|
208
|
+
* set_agent_model/mode/thinking and persist a mixed half-and-half state.
|
|
209
|
+
*/
|
|
210
|
+
this.agentConfigMutations = new Map();
|
|
211
|
+
this.idFactory = options?.idFactory ?? (() => randomUUID());
|
|
212
|
+
this.registry = options?.registry;
|
|
213
|
+
this.durableTimelineStore = options?.durableTimelineStore;
|
|
214
|
+
this.onAgentAttention = options?.onAgentAttention;
|
|
215
|
+
this.onWorkspaceStateMayHaveChanged = options?.onWorkspaceStateMayHaveChanged;
|
|
216
|
+
this.onPersonalitySpawn = options?.onPersonalitySpawn;
|
|
217
|
+
this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
|
|
218
|
+
this.mcpAuthToken = options?.mcpAuthToken ?? null;
|
|
219
|
+
this.configureOttoTools(options);
|
|
220
|
+
this.appendSystemPrompt = options.appendSystemPrompt ?? "";
|
|
221
|
+
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
|
222
|
+
this.rescueTimeouts = {
|
|
223
|
+
reloadSessionCloseMs: options.rescueTimeouts?.reloadSessionCloseMs ?? RELOAD_SESSION_CLOSE_TIMEOUT_MS,
|
|
224
|
+
interruptSessionMs: options.rescueTimeouts?.interruptSessionMs ?? INTERRUPT_SESSION_TIMEOUT_MS,
|
|
225
|
+
};
|
|
226
|
+
this.agentStreamCoalescer = new AgentStreamCoalescer({
|
|
227
|
+
windowMs: options.agentStreamCoalesceWindowMs ?? AGENT_STREAM_COALESCE_DEFAULT_WINDOW_MS,
|
|
228
|
+
timers: { setTimeout, clearTimeout },
|
|
229
|
+
onFlush: ({ agentId, item, provider, turnId }) => {
|
|
230
|
+
const event = this.recordAndDispatchTimelineItem(agentId, item, provider, turnId);
|
|
231
|
+
this.notifyForegroundTurnWaiters(agentId, event);
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
this.updateProviderRegistry({
|
|
235
|
+
providerDefinitions: options.providerDefinitions ?? {},
|
|
236
|
+
clients: options.clients ?? {},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
configureOttoTools(options) {
|
|
240
|
+
this.ottoToolsEnabled = options.ottoToolsEnabled ?? true;
|
|
241
|
+
this.ottoToolCatalogFactory = options.ottoToolCatalogFactory ?? null;
|
|
242
|
+
}
|
|
243
|
+
registerClient(provider, client) {
|
|
244
|
+
this.clients.set(provider, client);
|
|
245
|
+
}
|
|
246
|
+
updateProviderRegistry(input) {
|
|
247
|
+
for (const [provider, definition] of Object.entries(input.providerDefinitions)) {
|
|
248
|
+
if (definition) {
|
|
249
|
+
this.providerEnabled.set(provider, definition.enabled);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const [provider, client] of Object.entries(input.clients)) {
|
|
253
|
+
if (client) {
|
|
254
|
+
this.clients.set(provider, client);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Live sessions absorb provider-level compaction edits (default level,
|
|
258
|
+
// hidden selector) so open chats reflect settings changes immediately.
|
|
259
|
+
for (const agent of this.agents.values()) {
|
|
260
|
+
if (!agent.session?.applyCompactionConfig)
|
|
261
|
+
continue;
|
|
262
|
+
const definition = input.providerDefinitions[agent.provider];
|
|
263
|
+
if (!definition || definition.compaction === undefined)
|
|
264
|
+
continue;
|
|
265
|
+
if (agent.session.applyCompactionConfig(definition.compaction)) {
|
|
266
|
+
this.emitState(agent);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
getRegisteredProviderIds() {
|
|
271
|
+
return Array.from(this.clients.keys());
|
|
272
|
+
}
|
|
273
|
+
setAgentAttentionCallback(callback) {
|
|
274
|
+
this.onAgentAttention = callback;
|
|
275
|
+
}
|
|
276
|
+
setAgentArchivedCallback(callback) {
|
|
277
|
+
this.onAgentArchived = callback;
|
|
278
|
+
}
|
|
279
|
+
setMcpBaseUrl(url) {
|
|
280
|
+
this.mcpBaseUrl = url;
|
|
281
|
+
}
|
|
282
|
+
setOttoToolsEnabled(enabled) {
|
|
283
|
+
this.ottoToolsEnabled = enabled;
|
|
284
|
+
}
|
|
285
|
+
setOttoToolCatalogFactory(factory) {
|
|
286
|
+
this.ottoToolCatalogFactory = factory;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Capability token the daemon's own MCP clients must present to the Agent MCP
|
|
290
|
+
* endpoint when a daemon password is configured. Read by the per-client
|
|
291
|
+
* session to authenticate its own MCP connection. Stays in the daemon — never
|
|
292
|
+
* sent to remote clients.
|
|
293
|
+
*/
|
|
294
|
+
getMcpAuthToken() {
|
|
295
|
+
return this.mcpAuthToken;
|
|
296
|
+
}
|
|
297
|
+
setAppendSystemPrompt(prompt) {
|
|
298
|
+
this.appendSystemPrompt = prompt ?? "";
|
|
299
|
+
}
|
|
300
|
+
getMetricsSnapshot() {
|
|
301
|
+
const byLifecycle = {};
|
|
302
|
+
let withActiveForegroundTurn = 0;
|
|
303
|
+
let totalItems = 0;
|
|
304
|
+
let maxItemsPerAgent = 0;
|
|
305
|
+
for (const agent of this.agents.values()) {
|
|
306
|
+
byLifecycle[agent.lifecycle] = (byLifecycle[agent.lifecycle] ?? 0) + 1;
|
|
307
|
+
if (agent.activeForegroundTurnId !== null) {
|
|
308
|
+
withActiveForegroundTurn++;
|
|
309
|
+
}
|
|
310
|
+
if (!this.timelineStore.has(agent.id)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const len = this.timelineStore.getItems(agent.id).length;
|
|
314
|
+
totalItems += len;
|
|
315
|
+
if (len > maxItemsPerAgent) {
|
|
316
|
+
maxItemsPerAgent = len;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
total: this.agents.size,
|
|
321
|
+
byLifecycle,
|
|
322
|
+
withActiveForegroundTurn,
|
|
323
|
+
timelineStats: {
|
|
324
|
+
totalItems,
|
|
325
|
+
maxItemsPerAgent,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
touchUpdatedAt(agent) {
|
|
330
|
+
const nowMs = Date.now();
|
|
331
|
+
const previousMs = agent.updatedAt.getTime();
|
|
332
|
+
const nextMs = nowMs > previousMs ? nowMs : previousMs + 1;
|
|
333
|
+
const next = new Date(nextMs);
|
|
334
|
+
agent.updatedAt = next;
|
|
335
|
+
return next;
|
|
336
|
+
}
|
|
337
|
+
nextStoredUpdatedAt(record) {
|
|
338
|
+
const previousMs = Date.parse(record.updatedAt);
|
|
339
|
+
const nowMs = Date.now();
|
|
340
|
+
const nextMs = nowMs > previousMs ? nowMs : previousMs + 1;
|
|
341
|
+
return new Date(nextMs).toISOString();
|
|
342
|
+
}
|
|
343
|
+
hasInFlightRun(agentId) {
|
|
344
|
+
const agent = this.agents.get(agentId);
|
|
345
|
+
if (!agent) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return (agent.lifecycle === "running" ||
|
|
349
|
+
Boolean(agent.activeForegroundTurnId) ||
|
|
350
|
+
this.foregroundRuns.hasPendingRun(agentId));
|
|
351
|
+
}
|
|
352
|
+
subscribe(callback, options) {
|
|
353
|
+
const targetAgentId = options?.agentId == null ? null : validateAgentId(options.agentId, "subscribe");
|
|
354
|
+
const record = {
|
|
355
|
+
callback,
|
|
356
|
+
agentId: targetAgentId,
|
|
357
|
+
};
|
|
358
|
+
this.subscribers.add(record);
|
|
359
|
+
if (options?.replayState !== false) {
|
|
360
|
+
if (record.agentId) {
|
|
361
|
+
const agent = this.agents.get(record.agentId);
|
|
362
|
+
if (agent) {
|
|
363
|
+
callback({
|
|
364
|
+
type: "agent_state",
|
|
365
|
+
agent: { ...agent },
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// For global subscribers, skip internal agents during replay
|
|
371
|
+
for (const agent of this.agents.values()) {
|
|
372
|
+
if (agent.internal) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
callback({
|
|
376
|
+
type: "agent_state",
|
|
377
|
+
agent: { ...agent },
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return () => {
|
|
383
|
+
this.subscribers.delete(record);
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
listAgents() {
|
|
387
|
+
return Array.from(this.agents.values())
|
|
388
|
+
.filter((agent) => !agent.internal)
|
|
389
|
+
.map((agent) => Object.assign({}, agent));
|
|
390
|
+
}
|
|
391
|
+
async listImportableSessions(options) {
|
|
392
|
+
const providerEntries = Array.from(this.clients.entries()).filter(([provider, client]) => client.capabilities.supportsSessionListing &&
|
|
393
|
+
!!client.listImportableSessions &&
|
|
394
|
+
this.isProviderImportable(provider, options?.providerFilter));
|
|
395
|
+
const sessionLists = await Promise.all(providerEntries.map(async ([provider, client]) => {
|
|
396
|
+
try {
|
|
397
|
+
return (await client.listImportableSessions({
|
|
398
|
+
limit: options?.limit,
|
|
399
|
+
cwd: options?.cwd,
|
|
400
|
+
})).map((session) => Object.assign(session, { provider }));
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
this.logger.warn({ err: error, provider }, "Failed to list importable sessions for provider");
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
const sessions = sessionLists.flat();
|
|
408
|
+
const limit = options?.limit ?? 20;
|
|
409
|
+
return sessions
|
|
410
|
+
.sort((a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime())
|
|
411
|
+
.slice(0, limit);
|
|
412
|
+
}
|
|
413
|
+
isProviderImportable(provider, providerFilter) {
|
|
414
|
+
if (this.providerEnabled.get(provider) === false) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
if (providerFilter && !providerFilter.has(provider)) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
async listProviderAvailability() {
|
|
423
|
+
return Promise.all(Array.from(this.clients.keys()).map((provider) => this.getProviderAvailability(provider)));
|
|
424
|
+
}
|
|
425
|
+
async getProviderAvailability(provider) {
|
|
426
|
+
const client = this.clients.get(provider);
|
|
427
|
+
if (!client) {
|
|
428
|
+
return {
|
|
429
|
+
provider,
|
|
430
|
+
available: false,
|
|
431
|
+
error: `No client registered for provider '${provider}'`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
const available = await client.isAvailable();
|
|
436
|
+
return {
|
|
437
|
+
provider,
|
|
438
|
+
available,
|
|
439
|
+
error: null,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
444
|
+
this.logger.warn({ err: error, provider }, "Failed to check provider availability");
|
|
445
|
+
return {
|
|
446
|
+
provider,
|
|
447
|
+
available: false,
|
|
448
|
+
error: message,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async listDraftCommands(config) {
|
|
453
|
+
const normalizedConfig = await this.normalizeConfig(config, { resolveDefaultModel: false });
|
|
454
|
+
const client = this.requireClient(normalizedConfig.provider);
|
|
455
|
+
if (!normalizedConfig.model) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
const available = await client.isAvailable();
|
|
459
|
+
if (!available) {
|
|
460
|
+
throw new Error(`Provider '${normalizedConfig.provider}' is not available. Please ensure the CLI is installed.`);
|
|
461
|
+
}
|
|
462
|
+
if (client.listCommands) {
|
|
463
|
+
return await client.listCommands(normalizedConfig);
|
|
464
|
+
}
|
|
465
|
+
const session = await client.createSession(normalizedConfig);
|
|
466
|
+
try {
|
|
467
|
+
if (!session.listCommands) {
|
|
468
|
+
throw new Error(`Provider '${normalizedConfig.provider}' does not support listing commands`);
|
|
469
|
+
}
|
|
470
|
+
return await session.listCommands();
|
|
471
|
+
}
|
|
472
|
+
finally {
|
|
473
|
+
try {
|
|
474
|
+
await session.close();
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
this.logger.warn({ err: error, provider: normalizedConfig.provider }, "Failed to close draft command listing session");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async listDraftFeatures(config) {
|
|
482
|
+
const normalizedConfig = await this.normalizeConfig(config, { resolveDefaultModel: false });
|
|
483
|
+
const client = this.requireClient(normalizedConfig.provider);
|
|
484
|
+
if (!normalizedConfig.model) {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
const available = await client.isAvailable();
|
|
488
|
+
if (!available) {
|
|
489
|
+
throw new Error(`Provider '${normalizedConfig.provider}' is not available. Please ensure the CLI is installed.`);
|
|
490
|
+
}
|
|
491
|
+
if (client.listFeatures) {
|
|
492
|
+
return await client.listFeatures(normalizedConfig);
|
|
493
|
+
}
|
|
494
|
+
const session = await client.createSession(normalizedConfig);
|
|
495
|
+
try {
|
|
496
|
+
return session.features ?? [];
|
|
497
|
+
}
|
|
498
|
+
finally {
|
|
499
|
+
try {
|
|
500
|
+
await session.close();
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
this.logger.warn({ err: error, provider: normalizedConfig.provider }, "Failed to close draft feature listing session");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
getAgent(id) {
|
|
508
|
+
const agent = this.agents.get(id);
|
|
509
|
+
return agent ? { ...agent } : null;
|
|
510
|
+
}
|
|
511
|
+
getTimeline(id) {
|
|
512
|
+
if (!this.observedSubagents.has(id)) {
|
|
513
|
+
this.requireAgent(id);
|
|
514
|
+
}
|
|
515
|
+
return this.timelineStore.getItems(id);
|
|
516
|
+
}
|
|
517
|
+
async getTimelineRows(id) {
|
|
518
|
+
if (!this.observedSubagents.has(id)) {
|
|
519
|
+
this.requireAgent(id);
|
|
520
|
+
}
|
|
521
|
+
if (this.durableTimelineStore) {
|
|
522
|
+
return await this.durableTimelineStore.getCommittedRows(id);
|
|
523
|
+
}
|
|
524
|
+
return this.timelineStore.getRows(id);
|
|
525
|
+
}
|
|
526
|
+
fetchTimeline(id, options) {
|
|
527
|
+
if (!this.observedSubagents.has(id)) {
|
|
528
|
+
this.requireAgent(id);
|
|
529
|
+
}
|
|
530
|
+
return this.timelineStore.fetch(id, options);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Last emitted snapshot for an observed subagent, or null when the id is not
|
|
534
|
+
* an observed subagent. Lets read paths (timeline fetch) serve observed rows
|
|
535
|
+
* that have no ManagedAgent or stored record. See projects/observed-subagents/observed-subagents.md.
|
|
536
|
+
*/
|
|
537
|
+
getObservedSubagentPayload(id) {
|
|
538
|
+
return this.observedSubagents.get(id)?.lastPayload ?? null;
|
|
539
|
+
}
|
|
540
|
+
async createAgent(config, agentId, options) {
|
|
541
|
+
const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
|
|
542
|
+
const { storedConfig, launchConfig } = await this.prepareSessionConfig(config, resolvedAgentId);
|
|
543
|
+
this.requireEnabledProvider(storedConfig.provider);
|
|
544
|
+
const client = await this.requireAvailableClient({
|
|
545
|
+
provider: storedConfig.provider,
|
|
546
|
+
});
|
|
547
|
+
const launchContext = await this.buildLaunchContext(resolvedAgentId, client, options?.env);
|
|
548
|
+
const providerLaunchConfig = this.resolveProviderLaunchConfig(launchConfig, launchContext);
|
|
549
|
+
const createOptions = this.buildCreateSessionOptions(options);
|
|
550
|
+
const session = await client.createSession(providerLaunchConfig, launchContext, createOptions);
|
|
551
|
+
const managed = this.registerSession(session, storedConfig, resolvedAgentId, {
|
|
552
|
+
labels: options.labels,
|
|
553
|
+
initialTitle: options.initialTitle,
|
|
554
|
+
workspaceId: options.workspaceId,
|
|
555
|
+
});
|
|
556
|
+
const spawnedPersonalityId = storedConfig.personalitySnapshot?.personalityId;
|
|
557
|
+
if (spawnedPersonalityId) {
|
|
558
|
+
this.onPersonalitySpawn?.(spawnedPersonalityId);
|
|
559
|
+
}
|
|
560
|
+
return managed;
|
|
561
|
+
}
|
|
562
|
+
buildCreateSessionOptions(options) {
|
|
563
|
+
return options?.persistSession === undefined
|
|
564
|
+
? undefined
|
|
565
|
+
: { persistSession: options.persistSession };
|
|
566
|
+
}
|
|
567
|
+
// Reconstruct an agent from provider persistence. Callers should explicitly
|
|
568
|
+
// hydrate timeline history after resume.
|
|
569
|
+
async resumeAgentFromPersistence(handle, overrides, agentId, options) {
|
|
570
|
+
const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "resumeAgentFromPersistence");
|
|
571
|
+
const metadata = (handle.metadata ?? {});
|
|
572
|
+
const mergedConfig = {
|
|
573
|
+
...metadata,
|
|
574
|
+
...overrides,
|
|
575
|
+
provider: handle.provider,
|
|
576
|
+
};
|
|
577
|
+
const { storedConfig, launchConfig } = await this.prepareSessionConfig(mergedConfig, resolvedAgentId);
|
|
578
|
+
const client = this.requireClient(handle.provider);
|
|
579
|
+
const available = await client.isAvailable();
|
|
580
|
+
if (!available) {
|
|
581
|
+
throw new Error(`Provider '${handle.provider}' is not available. Please ensure the CLI is installed.`);
|
|
582
|
+
}
|
|
583
|
+
const launchContext = await this.buildLaunchContext(resolvedAgentId, client);
|
|
584
|
+
const providerLaunchConfig = this.resolveProviderLaunchConfig(launchConfig, launchContext);
|
|
585
|
+
const session = await client.resumeSession(handle, providerLaunchConfig, launchContext);
|
|
586
|
+
return this.registerSession(session, storedConfig, resolvedAgentId, options);
|
|
587
|
+
}
|
|
588
|
+
async importProviderSession(input) {
|
|
589
|
+
const resolvedAgentId = validateAgentId(this.idFactory(), "importProviderSession");
|
|
590
|
+
this.requireEnabledProvider(input.provider);
|
|
591
|
+
const client = await this.requireAvailableClient({ provider: input.provider });
|
|
592
|
+
if (!client.importSession) {
|
|
593
|
+
throw new Error(`Provider '${input.provider}' does not support importing sessions`);
|
|
594
|
+
}
|
|
595
|
+
const { storedConfig, launchConfig } = await this.prepareSessionConfig({
|
|
596
|
+
provider: input.provider,
|
|
597
|
+
cwd: input.cwd,
|
|
598
|
+
}, resolvedAgentId);
|
|
599
|
+
const launchContext = await this.buildLaunchContext(resolvedAgentId, client);
|
|
600
|
+
const providerLaunchConfig = this.resolveProviderLaunchConfig(launchConfig, launchContext);
|
|
601
|
+
const imported = await client.importSession({
|
|
602
|
+
providerHandleId: input.providerHandleId,
|
|
603
|
+
cwd: input.cwd,
|
|
604
|
+
}, { config: providerLaunchConfig, storedConfig, launchContext });
|
|
605
|
+
const importedConfig = await this.normalizeConfig(stripInternalOttoMcpServer(imported.config));
|
|
606
|
+
const timelineRows = buildImportedTimelineRows(imported.timeline);
|
|
607
|
+
const initialTitle = resolveImportedAgentTitle(importedConfig, timelineRows);
|
|
608
|
+
return this.registerSession(imported.session, importedConfig, resolvedAgentId, {
|
|
609
|
+
labels: input.labels,
|
|
610
|
+
workspaceId: input.workspaceId,
|
|
611
|
+
timelineRows,
|
|
612
|
+
timelineNextSeq: timelineRows.length + 1,
|
|
613
|
+
persistence: imported.persistence,
|
|
614
|
+
historyPrimed: true,
|
|
615
|
+
initialTitle,
|
|
616
|
+
publishWhenReady: true,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
// Hot-reload an active agent session with config overrides. By default the
|
|
620
|
+
// in-memory timeline is preserved (used for voice-mode toggles and similar
|
|
621
|
+
// config swaps). When `rehydrateFromDisk` is set, the timeline is wiped so a
|
|
622
|
+
// new epoch is minted and provider history is re-streamed — this is what the
|
|
623
|
+
// user-facing "Reload agent" action wants when the on-disk session was
|
|
624
|
+
// mutated outside Otto.
|
|
625
|
+
async reloadAgentSession(agentId, overrides, options) {
|
|
626
|
+
let existing = this.requireSessionAgent(agentId);
|
|
627
|
+
if (this.hasInFlightRun(agentId)) {
|
|
628
|
+
await this.cancelAgentRun(agentId);
|
|
629
|
+
existing = this.requireSessionAgent(agentId);
|
|
630
|
+
}
|
|
631
|
+
const rehydrateFromDisk = options?.rehydrateFromDisk ?? false;
|
|
632
|
+
const preservedHistoryPrimed = existing.historyPrimed;
|
|
633
|
+
const preservedLastUsage = existing.lastUsage;
|
|
634
|
+
const preservedLastError = existing.lastError;
|
|
635
|
+
const preservedAttention = existing.attention;
|
|
636
|
+
const handle = existing.persistence;
|
|
637
|
+
const provider = handle?.provider ?? existing.provider;
|
|
638
|
+
const client = this.requireClient(provider);
|
|
639
|
+
const refreshConfig = {
|
|
640
|
+
...existing.config,
|
|
641
|
+
...overrides,
|
|
642
|
+
provider,
|
|
643
|
+
};
|
|
644
|
+
const { storedConfig, launchConfig } = await this.prepareSessionConfig(refreshConfig, agentId);
|
|
645
|
+
const launchContext = await this.buildLaunchContext(agentId, client);
|
|
646
|
+
const providerLaunchConfig = this.resolveProviderLaunchConfig(launchConfig, launchContext);
|
|
647
|
+
const session = handle
|
|
648
|
+
? await client.resumeSession(handle, providerLaunchConfig, launchContext)
|
|
649
|
+
: await client.createSession(providerLaunchConfig, launchContext);
|
|
650
|
+
this.agentStreamCoalescer.flushAndDiscard(agentId);
|
|
651
|
+
// Remove the existing agent entry before swapping sessions
|
|
652
|
+
this.agents.delete(agentId);
|
|
653
|
+
if (existing.unsubscribeSession) {
|
|
654
|
+
existing.unsubscribeSession();
|
|
655
|
+
existing.unsubscribeSession = null;
|
|
656
|
+
}
|
|
657
|
+
this.foregroundRuns.clearAgent(agentId, existing);
|
|
658
|
+
await this.closeReloadedSession(existing.session, agentId);
|
|
659
|
+
if (rehydrateFromDisk) {
|
|
660
|
+
// Wipe both durable and in-memory timeline so registerSession mints a
|
|
661
|
+
// new epoch and hydrateTimelineFromProvider re-streams the freshly read
|
|
662
|
+
// provider history into an empty timeline.
|
|
663
|
+
await this.deleteCommittedTimeline(agentId);
|
|
664
|
+
this.timelineStore.delete(agentId);
|
|
665
|
+
}
|
|
666
|
+
// Preserve existing labels and timeline during reload.
|
|
667
|
+
return this.registerSession(session, storedConfig, agentId, {
|
|
668
|
+
labels: existing.labels,
|
|
669
|
+
workspaceId: existing.workspaceId,
|
|
670
|
+
createdAt: existing.createdAt,
|
|
671
|
+
updatedAt: existing.updatedAt,
|
|
672
|
+
lastUserMessageAt: existing.lastUserMessageAt,
|
|
673
|
+
historyPrimed: rehydrateFromDisk ? false : preservedHistoryPrimed,
|
|
674
|
+
lastUsage: preservedLastUsage,
|
|
675
|
+
lastError: preservedLastError,
|
|
676
|
+
attention: preservedAttention,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
async closeReloadedSession(session, agentId) {
|
|
680
|
+
try {
|
|
681
|
+
const result = await this.waitWithTimeout({
|
|
682
|
+
operation: session.close(),
|
|
683
|
+
timeoutMs: this.rescueTimeouts.reloadSessionCloseMs,
|
|
684
|
+
onLateError: (error) => {
|
|
685
|
+
this.logger.warn({ err: error, agentId }, "Previous session close failed after refresh timeout");
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
if (result === "timed_out") {
|
|
689
|
+
this.logger.warn({ agentId, timeoutMs: this.rescueTimeouts.reloadSessionCloseMs }, "Timed out closing previous session during refresh");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
this.logger.warn({ err: error, agentId }, "Failed to close previous session during refresh");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async waitWithTimeout(options) {
|
|
697
|
+
let didTimeOut = false;
|
|
698
|
+
let timer = null;
|
|
699
|
+
const operation = options.operation
|
|
700
|
+
.then(() => "completed")
|
|
701
|
+
.catch((error) => {
|
|
702
|
+
if (didTimeOut) {
|
|
703
|
+
options.onLateError?.(error);
|
|
704
|
+
return "timed_out";
|
|
705
|
+
}
|
|
706
|
+
throw error;
|
|
707
|
+
});
|
|
708
|
+
try {
|
|
709
|
+
return await Promise.race([
|
|
710
|
+
operation,
|
|
711
|
+
new Promise((resolvePromise) => {
|
|
712
|
+
timer = setTimeout(() => {
|
|
713
|
+
didTimeOut = true;
|
|
714
|
+
resolvePromise("timed_out");
|
|
715
|
+
}, options.timeoutMs);
|
|
716
|
+
}),
|
|
717
|
+
]);
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
if (timer) {
|
|
721
|
+
clearTimeout(timer);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async closeAgent(agentId) {
|
|
726
|
+
const agent = this.requireAgent(agentId);
|
|
727
|
+
this.logger.trace({
|
|
728
|
+
agentId,
|
|
729
|
+
provider: agent.provider,
|
|
730
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
731
|
+
turnId: agent.activeForegroundTurnId ?? undefined,
|
|
732
|
+
lifecycle: agent.lifecycle,
|
|
733
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
734
|
+
pendingPermissions: agent.pendingPermissions.size,
|
|
735
|
+
}, "agent.manager.close.start");
|
|
736
|
+
const closedAgent = this.prepareAgentForClosure(agent, "agent closed");
|
|
737
|
+
await agent.session.close();
|
|
738
|
+
this.timelineStore.delete(agentId);
|
|
739
|
+
await this.persistSnapshot(closedAgent);
|
|
740
|
+
this.emitClosedAgent(closedAgent, { persist: false });
|
|
741
|
+
this.logger.trace({
|
|
742
|
+
agentId,
|
|
743
|
+
provider: closedAgent.provider,
|
|
744
|
+
sessionId: closedAgent.persistence?.sessionId ?? undefined,
|
|
745
|
+
}, "agent.manager.close.complete");
|
|
746
|
+
}
|
|
747
|
+
async archiveAgent(agentId) {
|
|
748
|
+
const agent = this.requireAgent(agentId);
|
|
749
|
+
if (!this.registry) {
|
|
750
|
+
throw new Error("Agent storage is not configured");
|
|
751
|
+
}
|
|
752
|
+
await this.registry.applySnapshot(agent, {
|
|
753
|
+
internal: agent.internal,
|
|
754
|
+
});
|
|
755
|
+
const stored = await this.registry.get(agentId);
|
|
756
|
+
if (!stored) {
|
|
757
|
+
throw new Error(`Agent ${agentId} not found in storage after snapshot`);
|
|
758
|
+
}
|
|
759
|
+
const { archivedAt } = await this.markRecordArchived(stored);
|
|
760
|
+
agent.updatedAt = new Date(archivedAt);
|
|
761
|
+
await this.closeAgent(agentId);
|
|
762
|
+
await this.cascadeArchiveChildren(agentId);
|
|
763
|
+
return { archivedAt };
|
|
764
|
+
}
|
|
765
|
+
// Children created via the MCP `create_agent` tool carry the parent-agent-id
|
|
766
|
+
// label pointing back at the caller. Archiving the parent cascades to those
|
|
767
|
+
// children so subagent fleets don't outlive their orchestrator. Detached
|
|
768
|
+
// handoff agents omit this label, so they stand outside the cascade.
|
|
769
|
+
async cascadeArchiveChildren(parentAgentId) {
|
|
770
|
+
const registry = this.registry;
|
|
771
|
+
if (!registry) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const records = await registry.list();
|
|
775
|
+
for (const record of records) {
|
|
776
|
+
if (record.archivedAt) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (record.labels?.[PARENT_AGENT_ID_LABEL] !== parentAgentId) {
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (this.agents.has(record.id)) {
|
|
783
|
+
await this.archiveAgent(record.id);
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
await this.markRecordArchived(record);
|
|
787
|
+
await this.cascadeArchiveChildren(record.id);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async markRecordArchived(record) {
|
|
792
|
+
const registry = this.requireRegistry();
|
|
793
|
+
const archivedAt = new Date().toISOString();
|
|
794
|
+
const archivedRecord = buildArchivedAgentRecord(record, { archivedAt, updatedAt: archivedAt });
|
|
795
|
+
await registry.upsert(archivedRecord);
|
|
796
|
+
await this.archiveNativeSessionBestEffort(record.provider, record.persistence);
|
|
797
|
+
if (this.agents.has(record.id)) {
|
|
798
|
+
this.notifyAgentState(record.id);
|
|
799
|
+
}
|
|
800
|
+
else if (!archivedRecord.internal) {
|
|
801
|
+
this.dispatchArchivedStoredAgent(archivedRecord);
|
|
802
|
+
}
|
|
803
|
+
await this.fireAgentArchived(record.id);
|
|
804
|
+
return archivedRecord;
|
|
805
|
+
}
|
|
806
|
+
async fireAgentArchived(agentId) {
|
|
807
|
+
const callback = this.onAgentArchived;
|
|
808
|
+
if (!callback) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
await callback(agentId);
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
this.logger.warn({ err: error, agentId }, "onAgentArchived callback failed");
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
dispatchArchivedStoredAgent(record) {
|
|
819
|
+
const updatedAt = new Date(record.updatedAt);
|
|
820
|
+
this.dispatch({
|
|
821
|
+
type: "agent_state",
|
|
822
|
+
agent: {
|
|
823
|
+
id: record.id,
|
|
824
|
+
provider: record.provider,
|
|
825
|
+
cwd: record.cwd,
|
|
826
|
+
workspaceId: record.workspaceId,
|
|
827
|
+
session: null,
|
|
828
|
+
capabilities: STORED_AGENT_CAPABILITIES,
|
|
829
|
+
config: buildStoredAgentConfig(record),
|
|
830
|
+
runtimeInfo: undefined,
|
|
831
|
+
lifecycle: "closed",
|
|
832
|
+
createdAt: new Date(record.createdAt),
|
|
833
|
+
updatedAt,
|
|
834
|
+
availableModes: [],
|
|
835
|
+
features: record.features,
|
|
836
|
+
currentModeId: record.lastModeId ?? null,
|
|
837
|
+
pendingPermissions: new Map(),
|
|
838
|
+
bufferedPermissionResolutions: new Map(),
|
|
839
|
+
inFlightPermissionResponses: new Set(),
|
|
840
|
+
pendingReplacement: false,
|
|
841
|
+
activeForegroundTurnId: null,
|
|
842
|
+
foregroundTurnWaiters: new Set(),
|
|
843
|
+
finalizedForegroundTurnIds: new Set(),
|
|
844
|
+
unsubscribeSession: null,
|
|
845
|
+
persistence: record.persistence ?? null,
|
|
846
|
+
historyPrimed: true,
|
|
847
|
+
lastUserMessageAt: record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null,
|
|
848
|
+
lastUsage: undefined,
|
|
849
|
+
lastError: record.lastError ?? undefined,
|
|
850
|
+
attention: { requiresAttention: false },
|
|
851
|
+
internal: record.internal,
|
|
852
|
+
labels: record.labels,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async withAgentConfigLock(agentId, operation) {
|
|
857
|
+
const previous = this.agentConfigMutations.get(agentId) ?? Promise.resolve();
|
|
858
|
+
const run = previous.then(operation);
|
|
859
|
+
const settled = run.then(() => undefined, () => undefined);
|
|
860
|
+
this.agentConfigMutations.set(agentId, settled);
|
|
861
|
+
const cleanup = async () => {
|
|
862
|
+
await settled;
|
|
863
|
+
if (this.agentConfigMutations.get(agentId) === settled) {
|
|
864
|
+
this.agentConfigMutations.delete(agentId);
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
void cleanup();
|
|
868
|
+
return run;
|
|
869
|
+
}
|
|
870
|
+
async setAgentMode(agentId, modeId) {
|
|
871
|
+
return this.withAgentConfigLock(agentId, () => this.setAgentModeUnlocked(agentId, modeId));
|
|
872
|
+
}
|
|
873
|
+
async setAgentModeUnlocked(agentId, modeId) {
|
|
874
|
+
const agent = this.requireSessionAgent(agentId);
|
|
875
|
+
const notice = (await agent.session.setMode(modeId)) ?? null;
|
|
876
|
+
const currentMode = (await agent.session.getCurrentMode()) ?? modeId;
|
|
877
|
+
agent.config.modeId = currentMode ?? undefined;
|
|
878
|
+
agent.currentModeId = currentMode;
|
|
879
|
+
// Update runtimeInfo to reflect the new mode
|
|
880
|
+
if (agent.runtimeInfo) {
|
|
881
|
+
agent.runtimeInfo = { ...agent.runtimeInfo, modeId: currentMode };
|
|
882
|
+
}
|
|
883
|
+
this.touchUpdatedAt(agent);
|
|
884
|
+
this.emitState(agent);
|
|
885
|
+
return notice;
|
|
886
|
+
}
|
|
887
|
+
async setAgentModel(agentId, modelId) {
|
|
888
|
+
return this.withAgentConfigLock(agentId, () => this.setAgentModelUnlocked(agentId, modelId));
|
|
889
|
+
}
|
|
890
|
+
async setAgentModelUnlocked(agentId, modelId) {
|
|
891
|
+
const agent = this.requireSessionAgent(agentId);
|
|
892
|
+
const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
|
|
893
|
+
if (agent.session.setModel) {
|
|
894
|
+
await agent.session.setModel(normalizedModelId);
|
|
895
|
+
}
|
|
896
|
+
agent.config.model = normalizedModelId ?? undefined;
|
|
897
|
+
if (agent.runtimeInfo) {
|
|
898
|
+
agent.runtimeInfo = { ...agent.runtimeInfo, model: normalizedModelId };
|
|
899
|
+
}
|
|
900
|
+
this.touchUpdatedAt(agent);
|
|
901
|
+
this.emitState(agent);
|
|
902
|
+
}
|
|
903
|
+
async setAgentThinkingOption(agentId, thinkingOptionId) {
|
|
904
|
+
return this.withAgentConfigLock(agentId, () => this.setAgentThinkingOptionUnlocked(agentId, thinkingOptionId));
|
|
905
|
+
}
|
|
906
|
+
async setAgentThinkingOptionUnlocked(agentId, thinkingOptionId) {
|
|
907
|
+
const agent = this.requireSessionAgent(agentId);
|
|
908
|
+
const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
|
|
909
|
+
? thinkingOptionId
|
|
910
|
+
: null;
|
|
911
|
+
let notice = null;
|
|
912
|
+
if (agent.session.setThinkingOption) {
|
|
913
|
+
notice = (await agent.session.setThinkingOption(normalizedThinkingOptionId)) ?? null;
|
|
914
|
+
}
|
|
915
|
+
agent.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
|
|
916
|
+
if (agent.runtimeInfo) {
|
|
917
|
+
agent.runtimeInfo = {
|
|
918
|
+
...agent.runtimeInfo,
|
|
919
|
+
thinkingOptionId: normalizedThinkingOptionId,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
this.touchUpdatedAt(agent);
|
|
923
|
+
this.emitState(agent);
|
|
924
|
+
return notice;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Live-switch a running agent's personality (or clear it with null). The
|
|
928
|
+
* prompt half goes through the session's applyPersonality (providers without
|
|
929
|
+
* it reject — they cannot change a system prompt mid-conversation); the brain
|
|
930
|
+
* half (model/mode/effort) rides the existing setters. Identity (name/spinner)
|
|
931
|
+
* follows automatically: agent_state projects it from config.personalitySnapshot.
|
|
932
|
+
* The caller resolves the roster personality against the agent's cwd and
|
|
933
|
+
* guarantees provider match; this method only applies.
|
|
934
|
+
*/
|
|
935
|
+
async setAgentPersonality(agentId, snapshot) {
|
|
936
|
+
return this.withAgentConfigLock(agentId, () => this.setAgentPersonalityUnlocked(agentId, snapshot));
|
|
937
|
+
}
|
|
938
|
+
async setAgentPersonalityUnlocked(agentId, snapshot) {
|
|
939
|
+
const agent = this.requireSessionAgent(agentId);
|
|
940
|
+
const session = agent.session;
|
|
941
|
+
if (!session.applyPersonality) {
|
|
942
|
+
throw new Error(`Provider '${agent.config.provider}' does not support switching personality on a running agent`);
|
|
943
|
+
}
|
|
944
|
+
if (snapshot && snapshot.provider !== agent.config.provider) {
|
|
945
|
+
throw new Error(`Personality "${snapshot.name}" targets provider '${snapshot.provider}', but this agent runs '${agent.config.provider}'`);
|
|
946
|
+
}
|
|
947
|
+
// Prompt half. The personality prompt only owns config.systemPrompt when
|
|
948
|
+
// the caller set none at spawn (mirrors applyPersonalityIdentityToConfig);
|
|
949
|
+
// a caller-authored prompt survives the switch.
|
|
950
|
+
const outgoingPersonalityPrompt = agent.config.personalitySnapshot?.systemPrompt;
|
|
951
|
+
const promptIsPersonalityOwned = agent.config.systemPrompt === undefined ||
|
|
952
|
+
agent.config.systemPrompt === outgoingPersonalityPrompt;
|
|
953
|
+
const nextSystemPrompt = promptIsPersonalityOwned
|
|
954
|
+
? snapshot?.systemPrompt
|
|
955
|
+
: agent.config.systemPrompt;
|
|
956
|
+
const daemonAppendSystemPrompt = this.appendSystemPrompt.trim();
|
|
957
|
+
const personalityUpdate = {
|
|
958
|
+
personalitySnapshot: snapshot ?? undefined,
|
|
959
|
+
systemPrompt: nextSystemPrompt,
|
|
960
|
+
// Same rule as applyDaemonAppendSystemPrompt: a personality with
|
|
961
|
+
// respectGlobalAppendPrompt === false owns its whole prompt.
|
|
962
|
+
daemonAppendSystemPrompt: daemonAppendSystemPrompt && snapshot?.respectGlobalAppendPrompt !== false
|
|
963
|
+
? daemonAppendSystemPrompt
|
|
964
|
+
: undefined,
|
|
965
|
+
};
|
|
966
|
+
const notices = [];
|
|
967
|
+
// Brain half first — only when binding a personality; clearing keeps the
|
|
968
|
+
// brain. Ordering matters for Claude: applyPersonality flags a query
|
|
969
|
+
// restart, and the brain's setModel/setMode call ensureQuery, which would
|
|
970
|
+
// then tear down + respawn the CLI synchronously inside this RPC. Brain
|
|
971
|
+
// first rides the live query; the restart stays lazy (next turn).
|
|
972
|
+
if (snapshot) {
|
|
973
|
+
notices.push(...(await this.applyPersonalityBrain(agent, session, snapshot)));
|
|
974
|
+
}
|
|
975
|
+
notices.push((await session.applyPersonality(personalityUpdate)) ?? null);
|
|
976
|
+
// Stored config: personality + prompt persist; daemonAppendSystemPrompt is
|
|
977
|
+
// deliberately runtime-only (re-derived from daemon settings on resume).
|
|
978
|
+
agent.config.personalitySnapshot = snapshot ?? undefined;
|
|
979
|
+
agent.config.systemPrompt = nextSystemPrompt;
|
|
980
|
+
this.touchUpdatedAt(agent);
|
|
981
|
+
this.emitState(agent);
|
|
982
|
+
return notices.find((notice) => notice !== null) ?? null;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Apply a personality's brain fields (model/effort/mode) to a live session and
|
|
986
|
+
* mirror them onto the managed agent's config/runtimeInfo. Split out of
|
|
987
|
+
* setAgentPersonality purely to keep that method under the complexity ceiling.
|
|
988
|
+
*/
|
|
989
|
+
async applyPersonalityBrain(agent, session, snapshot) {
|
|
990
|
+
const notices = [];
|
|
991
|
+
// Ordering carries two invariants:
|
|
992
|
+
// 1. setMode is the fallible step (Claude rejects e.g. "auto" on
|
|
993
|
+
// Bedrock/Vertex), so it runs FIRST — a mode failure must not leave a
|
|
994
|
+
// half-applied switch (new model + old identity) behind.
|
|
995
|
+
// 2. Mode and model ride the live query (Claude: ensureQuery + SDK
|
|
996
|
+
// setters) and run before setThinkingOption, which flags a query
|
|
997
|
+
// restart.
|
|
998
|
+
if (snapshot.modeId !== undefined) {
|
|
999
|
+
notices.push((await session.setMode(snapshot.modeId)) ?? null);
|
|
1000
|
+
const currentMode = (await session.getCurrentMode()) ?? snapshot.modeId;
|
|
1001
|
+
agent.config.modeId = currentMode ?? undefined;
|
|
1002
|
+
agent.currentModeId = currentMode;
|
|
1003
|
+
if (agent.runtimeInfo) {
|
|
1004
|
+
agent.runtimeInfo = { ...agent.runtimeInfo, modeId: currentMode };
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (session.setModel) {
|
|
1008
|
+
await session.setModel(snapshot.model);
|
|
1009
|
+
agent.config.model = snapshot.model;
|
|
1010
|
+
if (agent.runtimeInfo) {
|
|
1011
|
+
agent.runtimeInfo = { ...agent.runtimeInfo, model: snapshot.model };
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (session.setThinkingOption) {
|
|
1015
|
+
// Always set — a snapshot without an effort (degraded or unspecified)
|
|
1016
|
+
// clears the previous personality's thinking option back to the model
|
|
1017
|
+
// default instead of silently carrying it onto the new model.
|
|
1018
|
+
notices.push((await session.setThinkingOption(snapshot.thinkingOptionId ?? null)) ?? null);
|
|
1019
|
+
agent.config.thinkingOptionId = snapshot.thinkingOptionId;
|
|
1020
|
+
if (agent.runtimeInfo) {
|
|
1021
|
+
agent.runtimeInfo = {
|
|
1022
|
+
...agent.runtimeInfo,
|
|
1023
|
+
thinkingOptionId: snapshot.thinkingOptionId,
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return notices;
|
|
1028
|
+
}
|
|
1029
|
+
async setAgentFeature(agentId, featureId, value) {
|
|
1030
|
+
return this.withAgentConfigLock(agentId, () => this.setAgentFeatureUnlocked(agentId, featureId, value));
|
|
1031
|
+
}
|
|
1032
|
+
async setAgentFeatureUnlocked(agentId, featureId, value) {
|
|
1033
|
+
const agent = this.requireAgent(agentId);
|
|
1034
|
+
if (!agent.session.setFeature) {
|
|
1035
|
+
throw new Error("Agent session does not support setting features");
|
|
1036
|
+
}
|
|
1037
|
+
await agent.session.setFeature(featureId, value);
|
|
1038
|
+
agent.config.featureValues = { ...agent.config.featureValues, [featureId]: value };
|
|
1039
|
+
this.touchUpdatedAt(agent);
|
|
1040
|
+
this.emitState(agent);
|
|
1041
|
+
}
|
|
1042
|
+
async setTitle(agentId, title) {
|
|
1043
|
+
const agent = this.requireAgent(agentId);
|
|
1044
|
+
const normalizedTitle = title.trim();
|
|
1045
|
+
if (!normalizedTitle) {
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (this.agentsAwaitingInitialSnapshotPersist.has(agent.id) &&
|
|
1049
|
+
this.registry &&
|
|
1050
|
+
(await this.registry.get(agent.id)) === null) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
this.touchUpdatedAt(agent);
|
|
1054
|
+
await this.persistSnapshot(agent, { title: normalizedTitle });
|
|
1055
|
+
this.emitState(agent, { persist: false });
|
|
1056
|
+
}
|
|
1057
|
+
async setLabels(agentId, labels) {
|
|
1058
|
+
const agent = this.requireAgent(agentId);
|
|
1059
|
+
await this.writeLabels(agent.id, labels);
|
|
1060
|
+
}
|
|
1061
|
+
async writeLabels(agentId, patch) {
|
|
1062
|
+
const liveAgent = this.agents.get(agentId);
|
|
1063
|
+
if (liveAgent) {
|
|
1064
|
+
liveAgent.labels = applyLabelPatch(liveAgent.labels, patch);
|
|
1065
|
+
this.touchUpdatedAt(liveAgent);
|
|
1066
|
+
await this.persistSnapshot(liveAgent);
|
|
1067
|
+
this.emitState(liveAgent, { persist: false });
|
|
1068
|
+
const record = this.registry ? await this.registry.get(agentId) : null;
|
|
1069
|
+
return { record, live: true };
|
|
1070
|
+
}
|
|
1071
|
+
const nextRecord = await this.writeStoredMetadata(agentId, { labels: patch });
|
|
1072
|
+
return { record: nextRecord, live: false };
|
|
1073
|
+
}
|
|
1074
|
+
async writeStoredMetadata(agentId, patch) {
|
|
1075
|
+
const registry = this.requireRegistry();
|
|
1076
|
+
const record = await registry.get(agentId);
|
|
1077
|
+
if (!record) {
|
|
1078
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
1079
|
+
}
|
|
1080
|
+
const nextRecord = {
|
|
1081
|
+
...record,
|
|
1082
|
+
...(patch.title ? { title: patch.title } : {}),
|
|
1083
|
+
...(patch.labels ? { labels: applyLabelPatch(record.labels, patch.labels) } : {}),
|
|
1084
|
+
updatedAt: this.nextStoredUpdatedAt(record),
|
|
1085
|
+
};
|
|
1086
|
+
await registry.upsert(nextRecord);
|
|
1087
|
+
return nextRecord;
|
|
1088
|
+
}
|
|
1089
|
+
async detachAgent(agentId) {
|
|
1090
|
+
const registry = this.requireRegistry();
|
|
1091
|
+
const liveAgent = this.agents.get(agentId);
|
|
1092
|
+
if (liveAgent) {
|
|
1093
|
+
const previousParentAgentId = getParentAgentIdFromLabels(liveAgent.labels);
|
|
1094
|
+
if (!previousParentAgentId) {
|
|
1095
|
+
await this.persistSnapshot(liveAgent);
|
|
1096
|
+
const record = await registry.get(agentId);
|
|
1097
|
+
if (!record) {
|
|
1098
|
+
throw new Error(`Agent not found in storage after detach: ${agentId}`);
|
|
1099
|
+
}
|
|
1100
|
+
return { record, live: true, previousParentAgentId: null };
|
|
1101
|
+
}
|
|
1102
|
+
const { record } = await this.writeLabels(agentId, { [PARENT_AGENT_ID_LABEL]: null });
|
|
1103
|
+
if (!record) {
|
|
1104
|
+
throw new Error(`Agent not found in storage after detach: ${agentId}`);
|
|
1105
|
+
}
|
|
1106
|
+
return { record, live: true, previousParentAgentId };
|
|
1107
|
+
}
|
|
1108
|
+
const record = await registry.get(agentId);
|
|
1109
|
+
if (!record) {
|
|
1110
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
1111
|
+
}
|
|
1112
|
+
const previousParentAgentId = getParentAgentIdFromLabels(record.labels);
|
|
1113
|
+
if (!previousParentAgentId) {
|
|
1114
|
+
return { record, live: false, previousParentAgentId: null };
|
|
1115
|
+
}
|
|
1116
|
+
const result = await this.writeLabels(agentId, { [PARENT_AGENT_ID_LABEL]: null });
|
|
1117
|
+
if (!result.record) {
|
|
1118
|
+
throw new Error(`Agent not found in storage after detach: ${agentId}`);
|
|
1119
|
+
}
|
|
1120
|
+
return { record: result.record, live: false, previousParentAgentId };
|
|
1121
|
+
}
|
|
1122
|
+
notifyAgentState(agentId) {
|
|
1123
|
+
const agent = this.agents.get(agentId);
|
|
1124
|
+
if (!agent || agent.internal) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
this.touchUpdatedAt(agent);
|
|
1128
|
+
this.emitState(agent);
|
|
1129
|
+
}
|
|
1130
|
+
async clearAgentAttention(agentId) {
|
|
1131
|
+
const agent = this.requireAgent(agentId);
|
|
1132
|
+
if (agent.attention.requiresAttention) {
|
|
1133
|
+
agent.attention = { requiresAttention: false };
|
|
1134
|
+
await this.persistSnapshot(agent);
|
|
1135
|
+
this.emitState(agent, { persist: false });
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
async archiveSnapshot(agentId, archivedAt) {
|
|
1139
|
+
const registry = this.requireRegistry();
|
|
1140
|
+
const liveAgent = this.getAgent(agentId);
|
|
1141
|
+
if (liveAgent) {
|
|
1142
|
+
await this.persistSnapshot(liveAgent, {
|
|
1143
|
+
internal: liveAgent.internal,
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
const record = await registry.get(agentId);
|
|
1147
|
+
if (!record) {
|
|
1148
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
1149
|
+
}
|
|
1150
|
+
const nextRecord = buildArchivedAgentRecord(record, { archivedAt });
|
|
1151
|
+
await registry.upsert(nextRecord);
|
|
1152
|
+
await this.archiveNativeSessionBestEffort(record.provider, record.persistence);
|
|
1153
|
+
if (this.agents.has(agentId)) {
|
|
1154
|
+
this.notifyAgentState(agentId);
|
|
1155
|
+
}
|
|
1156
|
+
else if (!nextRecord.internal) {
|
|
1157
|
+
this.dispatchArchivedStoredAgent(nextRecord);
|
|
1158
|
+
}
|
|
1159
|
+
await this.fireAgentArchived(agentId);
|
|
1160
|
+
return nextRecord;
|
|
1161
|
+
}
|
|
1162
|
+
async unarchiveSnapshot(agentId) {
|
|
1163
|
+
const registry = this.requireRegistry();
|
|
1164
|
+
const record = await registry.get(agentId);
|
|
1165
|
+
if (!record || !record.archivedAt) {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
await this.unarchiveNativeSession(record.provider, record.persistence);
|
|
1169
|
+
await registry.upsert({
|
|
1170
|
+
...record,
|
|
1171
|
+
archivedAt: null,
|
|
1172
|
+
updatedAt: new Date().toISOString(),
|
|
1173
|
+
});
|
|
1174
|
+
if (this.getAgent(agentId)) {
|
|
1175
|
+
this.notifyAgentState(agentId);
|
|
1176
|
+
}
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
async unarchiveSnapshotByHandle(handle) {
|
|
1180
|
+
const registry = this.requireRegistry();
|
|
1181
|
+
const records = await registry.list();
|
|
1182
|
+
const matched = records.find((record) => record.persistence?.provider === handle.provider &&
|
|
1183
|
+
record.persistence?.sessionId === handle.sessionId);
|
|
1184
|
+
if (!matched) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
await this.unarchiveSnapshot(matched.id);
|
|
1188
|
+
}
|
|
1189
|
+
async updateAgentMetadata(agentId, updates) {
|
|
1190
|
+
const liveAgent = this.getAgent(agentId);
|
|
1191
|
+
if (liveAgent) {
|
|
1192
|
+
if (updates.title) {
|
|
1193
|
+
await this.setTitle(agentId, updates.title);
|
|
1194
|
+
}
|
|
1195
|
+
if (updates.labels) {
|
|
1196
|
+
await this.writeLabels(agentId, updates.labels);
|
|
1197
|
+
}
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
await this.writeStoredMetadata(agentId, updates);
|
|
1201
|
+
}
|
|
1202
|
+
async runAgent(agentId, prompt, options) {
|
|
1203
|
+
const events = this.streamAgent(agentId, prompt, options);
|
|
1204
|
+
const timeline = [];
|
|
1205
|
+
let finalText = "";
|
|
1206
|
+
let usage;
|
|
1207
|
+
let canceled = false;
|
|
1208
|
+
for await (const event of events) {
|
|
1209
|
+
if (event.type === "timeline") {
|
|
1210
|
+
timeline.push(event.item);
|
|
1211
|
+
}
|
|
1212
|
+
else if (event.type === "turn_completed") {
|
|
1213
|
+
usage = event.usage;
|
|
1214
|
+
}
|
|
1215
|
+
else if (event.type === "turn_failed") {
|
|
1216
|
+
throw new Error(this.formatTurnFailedMessage(event));
|
|
1217
|
+
}
|
|
1218
|
+
else if (event.type === "turn_canceled") {
|
|
1219
|
+
canceled = true;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
finalText = this.getLastAssistantMessageFromTimeline(timeline) ?? "";
|
|
1223
|
+
const agent = this.requireAgent(agentId);
|
|
1224
|
+
const sessionId = agent.persistence?.sessionId;
|
|
1225
|
+
if (!sessionId) {
|
|
1226
|
+
throw new Error(`Agent ${agentId} has no persistence.sessionId after run completed`);
|
|
1227
|
+
}
|
|
1228
|
+
return {
|
|
1229
|
+
sessionId,
|
|
1230
|
+
finalText,
|
|
1231
|
+
usage,
|
|
1232
|
+
timeline,
|
|
1233
|
+
canceled,
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Try to run a prompt out-of-band — i.e. without allocating a foreground turn
|
|
1238
|
+
* and without canceling any active turn. Returns true when the session
|
|
1239
|
+
* accepted the prompt as a side-effect command (e.g. /goal pause). Events
|
|
1240
|
+
* emitted by the handler flow through dispatchStream so they persist and
|
|
1241
|
+
* broadcast like normal timeline events.
|
|
1242
|
+
*/
|
|
1243
|
+
tryRunOutOfBand(agentId, prompt) {
|
|
1244
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1245
|
+
const handler = agent.session.tryHandleOutOfBand?.(prompt);
|
|
1246
|
+
if (!handler) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
const dispatch = (event) => {
|
|
1250
|
+
// Persist timeline items so they show up in fetchAgentTimeline; broadcast
|
|
1251
|
+
// for live subscribers. Other event types are broadcast only.
|
|
1252
|
+
if (event.type === "timeline") {
|
|
1253
|
+
this.touchUpdatedAt(agent);
|
|
1254
|
+
const row = this.recordTimeline(agent.id, event.item);
|
|
1255
|
+
this.dispatchStream(agent.id, event, {
|
|
1256
|
+
seq: row.seq,
|
|
1257
|
+
epoch: this.timelineStore.getEpoch(agent.id),
|
|
1258
|
+
timestamp: row.timestamp,
|
|
1259
|
+
});
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
this.dispatchStream(agent.id, event, { timestamp: new Date().toISOString() });
|
|
1263
|
+
};
|
|
1264
|
+
void (async () => {
|
|
1265
|
+
try {
|
|
1266
|
+
await handler.run({ emit: dispatch });
|
|
1267
|
+
}
|
|
1268
|
+
catch (error) {
|
|
1269
|
+
const text = error instanceof Error ? error.message : "Out-of-band command failed";
|
|
1270
|
+
dispatch({
|
|
1271
|
+
type: "timeline",
|
|
1272
|
+
provider: agent.provider,
|
|
1273
|
+
item: { type: "assistant_message", text: `[Error] ${text}` },
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
})();
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1279
|
+
async appendTimelineItem(agentId, item) {
|
|
1280
|
+
const agent = this.requireAgent(agentId);
|
|
1281
|
+
this.touchUpdatedAt(agent);
|
|
1282
|
+
const row = this.recordTimeline(agentId, item);
|
|
1283
|
+
this.dispatchStream(agentId, {
|
|
1284
|
+
type: "timeline",
|
|
1285
|
+
item,
|
|
1286
|
+
provider: agent.provider,
|
|
1287
|
+
}, {
|
|
1288
|
+
seq: row.seq,
|
|
1289
|
+
epoch: this.timelineStore.getEpoch(agentId),
|
|
1290
|
+
timestamp: row.timestamp,
|
|
1291
|
+
});
|
|
1292
|
+
await this.persistSnapshot(agent);
|
|
1293
|
+
}
|
|
1294
|
+
async emitLiveTimelineItem(agentId, item) {
|
|
1295
|
+
const agent = this.requireAgent(agentId);
|
|
1296
|
+
this.touchUpdatedAt(agent);
|
|
1297
|
+
this.dispatchStream(agentId, {
|
|
1298
|
+
type: "timeline",
|
|
1299
|
+
item,
|
|
1300
|
+
provider: agent.provider,
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
streamAgent(agentId, prompt, options) {
|
|
1304
|
+
const existingAgent = this.requireSessionAgent(agentId);
|
|
1305
|
+
this.logger.trace({
|
|
1306
|
+
agentId,
|
|
1307
|
+
provider: existingAgent.provider,
|
|
1308
|
+
sessionId: existingAgent.persistence?.sessionId ?? undefined,
|
|
1309
|
+
turnId: existingAgent.activeForegroundTurnId ?? undefined,
|
|
1310
|
+
lifecycle: existingAgent.lifecycle,
|
|
1311
|
+
activeForegroundTurnId: existingAgent.activeForegroundTurnId,
|
|
1312
|
+
hasPendingForegroundRun: this.foregroundRuns.hasPendingRun(agentId),
|
|
1313
|
+
promptType: typeof prompt === "string" ? "string" : "structured",
|
|
1314
|
+
hasRunOptions: Boolean(options),
|
|
1315
|
+
}, "agent.manager.stream.request");
|
|
1316
|
+
if (existingAgent.activeForegroundTurnId || this.foregroundRuns.hasPendingRun(agentId)) {
|
|
1317
|
+
this.logger.trace({
|
|
1318
|
+
agentId,
|
|
1319
|
+
provider: existingAgent.provider,
|
|
1320
|
+
sessionId: existingAgent.persistence?.sessionId ?? undefined,
|
|
1321
|
+
turnId: existingAgent.activeForegroundTurnId ?? undefined,
|
|
1322
|
+
lifecycle: existingAgent.lifecycle,
|
|
1323
|
+
hasPendingForegroundRun: this.foregroundRuns.hasPendingRun(agentId),
|
|
1324
|
+
}, "agent.manager.stream.reject");
|
|
1325
|
+
throw new Error(`Agent ${agentId} already has an active run`);
|
|
1326
|
+
}
|
|
1327
|
+
const agent = existingAgent;
|
|
1328
|
+
agent.pendingReplacement = false;
|
|
1329
|
+
agent.lastError = undefined;
|
|
1330
|
+
const pendingRun = this.foregroundRuns.createPendingRun(agentId);
|
|
1331
|
+
const streamForwarder = async function* streamForwarder() {
|
|
1332
|
+
let turnId;
|
|
1333
|
+
let turnStream = null;
|
|
1334
|
+
try {
|
|
1335
|
+
const result = await agent.session.startTurn(prompt, options);
|
|
1336
|
+
turnId = result.turnId;
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1339
|
+
const errorMsg = error instanceof Error ? error.message : "Failed to start turn";
|
|
1340
|
+
await this.handleStreamEvent(agent, {
|
|
1341
|
+
type: "turn_failed",
|
|
1342
|
+
provider: agent.provider,
|
|
1343
|
+
error: errorMsg,
|
|
1344
|
+
});
|
|
1345
|
+
this.finalizeForegroundTurn(agent);
|
|
1346
|
+
this.foregroundRuns.settlePendingRun(agentId, pendingRun.token);
|
|
1347
|
+
throw error;
|
|
1348
|
+
}
|
|
1349
|
+
pendingRun.started = true;
|
|
1350
|
+
agent.activeForegroundTurnId = turnId;
|
|
1351
|
+
agent.lifecycle = "running";
|
|
1352
|
+
this.touchUpdatedAt(agent);
|
|
1353
|
+
this.emitState(agent);
|
|
1354
|
+
this.logger.trace({
|
|
1355
|
+
agentId,
|
|
1356
|
+
provider: agent.provider,
|
|
1357
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
1358
|
+
turnId,
|
|
1359
|
+
lifecycle: agent.lifecycle,
|
|
1360
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
1361
|
+
}, "agent.manager.stream.start");
|
|
1362
|
+
turnStream = this.foregroundRuns.createTurnStream(turnId);
|
|
1363
|
+
this.foregroundRuns.addWaiter(agent, turnStream.waiter);
|
|
1364
|
+
try {
|
|
1365
|
+
for await (const event of turnStream.events(isTurnTerminalEvent)) {
|
|
1366
|
+
yield event;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
finally {
|
|
1370
|
+
if (turnStream) {
|
|
1371
|
+
this.foregroundRuns.deleteWaiter(agent, turnStream.waiter);
|
|
1372
|
+
}
|
|
1373
|
+
this.foregroundRuns.settlePendingRun(agentId, pendingRun.token);
|
|
1374
|
+
if (!agent.activeForegroundTurnId) {
|
|
1375
|
+
await this.refreshRuntimeInfo(agent);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}.call(this);
|
|
1379
|
+
return streamForwarder;
|
|
1380
|
+
}
|
|
1381
|
+
finalizeForegroundTurn(agent, turnId) {
|
|
1382
|
+
const mutableAgent = agent;
|
|
1383
|
+
if (turnId) {
|
|
1384
|
+
this.foregroundRuns.rememberFinalizedTurn(mutableAgent, turnId);
|
|
1385
|
+
}
|
|
1386
|
+
mutableAgent.activeForegroundTurnId = null;
|
|
1387
|
+
const terminalError = mutableAgent.lastError;
|
|
1388
|
+
const shouldHoldBusyForReplacement = mutableAgent.pendingReplacement && !terminalError;
|
|
1389
|
+
let nextLifecycle;
|
|
1390
|
+
if (shouldHoldBusyForReplacement) {
|
|
1391
|
+
nextLifecycle = "running";
|
|
1392
|
+
}
|
|
1393
|
+
else if (terminalError) {
|
|
1394
|
+
nextLifecycle = "error";
|
|
1395
|
+
}
|
|
1396
|
+
else {
|
|
1397
|
+
nextLifecycle = "idle";
|
|
1398
|
+
}
|
|
1399
|
+
mutableAgent.lifecycle = nextLifecycle;
|
|
1400
|
+
const persistenceHandle = mutableAgent.session.describePersistence() ??
|
|
1401
|
+
(mutableAgent.runtimeInfo?.sessionId
|
|
1402
|
+
? { provider: mutableAgent.provider, sessionId: mutableAgent.runtimeInfo.sessionId }
|
|
1403
|
+
: null);
|
|
1404
|
+
if (persistenceHandle) {
|
|
1405
|
+
mutableAgent.persistence = attachPersistenceCwd(persistenceHandle, mutableAgent.cwd);
|
|
1406
|
+
}
|
|
1407
|
+
this.logger.trace({
|
|
1408
|
+
agentId: agent.id,
|
|
1409
|
+
provider: agent.provider,
|
|
1410
|
+
sessionId: mutableAgent.persistence?.sessionId ?? undefined,
|
|
1411
|
+
turnId,
|
|
1412
|
+
lifecycle: mutableAgent.lifecycle,
|
|
1413
|
+
terminalError,
|
|
1414
|
+
pendingReplacement: mutableAgent.pendingReplacement,
|
|
1415
|
+
}, "agent.manager.finalize");
|
|
1416
|
+
if (!shouldHoldBusyForReplacement) {
|
|
1417
|
+
this.touchUpdatedAt(mutableAgent);
|
|
1418
|
+
this.emitState(mutableAgent);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
replaceAgentRun(agentId, prompt, options) {
|
|
1422
|
+
const snapshot = this.requireAgent(agentId);
|
|
1423
|
+
if (snapshot.lifecycle !== "running" &&
|
|
1424
|
+
!snapshot.activeForegroundTurnId &&
|
|
1425
|
+
!this.foregroundRuns.hasPendingRun(agentId)) {
|
|
1426
|
+
return this.streamAgent(agentId, prompt, options);
|
|
1427
|
+
}
|
|
1428
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1429
|
+
agent.pendingReplacement = true;
|
|
1430
|
+
agent.lifecycle = "running";
|
|
1431
|
+
this.touchUpdatedAt(agent);
|
|
1432
|
+
this.emitState(agent);
|
|
1433
|
+
return async function* replaceRunForwarder() {
|
|
1434
|
+
try {
|
|
1435
|
+
await this.cancelAgentRun(agentId);
|
|
1436
|
+
const nextRun = this.streamAgent(agentId, prompt, options);
|
|
1437
|
+
for await (const event of nextRun) {
|
|
1438
|
+
yield event;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
catch (error) {
|
|
1442
|
+
const latest = this.agents.get(agentId);
|
|
1443
|
+
if (latest) {
|
|
1444
|
+
const latestActive = latest;
|
|
1445
|
+
latestActive.pendingReplacement = false;
|
|
1446
|
+
if (!latestActive.activeForegroundTurnId && latestActive.lifecycle === "running") {
|
|
1447
|
+
latestActive.lifecycle = "idle";
|
|
1448
|
+
this.touchUpdatedAt(latestActive);
|
|
1449
|
+
this.emitState(latestActive);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
throw error;
|
|
1453
|
+
}
|
|
1454
|
+
}.call(this);
|
|
1455
|
+
}
|
|
1456
|
+
async waitForAgentRunStart(agentId, options) {
|
|
1457
|
+
const snapshot = this.getAgent(agentId);
|
|
1458
|
+
if (!snapshot) {
|
|
1459
|
+
throw new Error(`Agent ${agentId} not found`);
|
|
1460
|
+
}
|
|
1461
|
+
const pendingRun = this.foregroundRuns.getPendingRun(agentId);
|
|
1462
|
+
if ((snapshot.lifecycle === "running" || pendingRun?.started) && !snapshot.pendingReplacement) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
if (!snapshot.activeForegroundTurnId && !pendingRun && !snapshot.pendingReplacement) {
|
|
1466
|
+
throw new Error(`Agent ${agentId} has no pending run`);
|
|
1467
|
+
}
|
|
1468
|
+
if (options?.signal?.aborted) {
|
|
1469
|
+
throw createAbortError(options.signal, "wait_for_agent_start aborted");
|
|
1470
|
+
}
|
|
1471
|
+
await new Promise((resolvePromise, reject) => {
|
|
1472
|
+
if (options?.signal?.aborted) {
|
|
1473
|
+
reject(createAbortError(options.signal, "wait_for_agent_start aborted"));
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
let unsubscribe = null;
|
|
1477
|
+
let abortHandler = null;
|
|
1478
|
+
const cleanup = () => {
|
|
1479
|
+
if (unsubscribe) {
|
|
1480
|
+
try {
|
|
1481
|
+
unsubscribe();
|
|
1482
|
+
}
|
|
1483
|
+
catch {
|
|
1484
|
+
// ignore cleanup errors
|
|
1485
|
+
}
|
|
1486
|
+
unsubscribe = null;
|
|
1487
|
+
}
|
|
1488
|
+
if (abortHandler && options?.signal) {
|
|
1489
|
+
try {
|
|
1490
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
1491
|
+
}
|
|
1492
|
+
catch {
|
|
1493
|
+
// ignore cleanup errors
|
|
1494
|
+
}
|
|
1495
|
+
abortHandler = null;
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
const finishOk = () => {
|
|
1499
|
+
cleanup();
|
|
1500
|
+
resolvePromise();
|
|
1501
|
+
};
|
|
1502
|
+
const finishErr = (error) => {
|
|
1503
|
+
cleanup();
|
|
1504
|
+
reject(error);
|
|
1505
|
+
};
|
|
1506
|
+
if (options?.signal) {
|
|
1507
|
+
abortHandler = () => finishErr(createAbortError(options.signal, "wait_for_agent_start aborted"));
|
|
1508
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
1509
|
+
}
|
|
1510
|
+
const checkCurrentState = () => {
|
|
1511
|
+
const current = this.getAgent(agentId);
|
|
1512
|
+
if (!current) {
|
|
1513
|
+
finishErr(new Error(`Agent ${agentId} not found`));
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1516
|
+
const currentPendingRun = this.foregroundRuns.getPendingRun(agentId);
|
|
1517
|
+
if ((current.lifecycle === "running" || currentPendingRun?.started) &&
|
|
1518
|
+
!current.pendingReplacement) {
|
|
1519
|
+
finishOk();
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
if (current.lifecycle === "error" && !currentPendingRun?.started) {
|
|
1523
|
+
finishErr(new Error(current.lastError ?? `Agent ${agentId} failed to start`));
|
|
1524
|
+
return true;
|
|
1525
|
+
}
|
|
1526
|
+
if (!currentPendingRun && !current.activeForegroundTurnId && !current.pendingReplacement) {
|
|
1527
|
+
finishErr(new Error(`Agent ${agentId} run finished before starting`));
|
|
1528
|
+
return true;
|
|
1529
|
+
}
|
|
1530
|
+
return false;
|
|
1531
|
+
};
|
|
1532
|
+
unsubscribe = this.subscribe((event) => {
|
|
1533
|
+
if (event.type !== "agent_state" || event.agent.id !== agentId) {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
checkCurrentState();
|
|
1537
|
+
}, { agentId, replayState: false });
|
|
1538
|
+
checkCurrentState();
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
async respondToPermission(agentId, requestId, response) {
|
|
1542
|
+
const agent = this.requireAgent(agentId);
|
|
1543
|
+
agent.inFlightPermissionResponses.add(requestId);
|
|
1544
|
+
try {
|
|
1545
|
+
const result = await agent.session.respondToPermission(requestId, response);
|
|
1546
|
+
agent.pendingPermissions.delete(requestId);
|
|
1547
|
+
try {
|
|
1548
|
+
await this.refreshSessionState(agent);
|
|
1549
|
+
}
|
|
1550
|
+
catch {
|
|
1551
|
+
// Ignore refresh errors - state sync after permission approval is best effort.
|
|
1552
|
+
}
|
|
1553
|
+
this.touchUpdatedAt(agent);
|
|
1554
|
+
await this.persistSnapshot(agent);
|
|
1555
|
+
this.emitState(agent);
|
|
1556
|
+
const bufferedResolution = agent.bufferedPermissionResolutions.get(requestId);
|
|
1557
|
+
if (bufferedResolution) {
|
|
1558
|
+
agent.bufferedPermissionResolutions.delete(requestId);
|
|
1559
|
+
this.dispatchStream(agent.id, bufferedResolution, { timestamp: new Date().toISOString() });
|
|
1560
|
+
}
|
|
1561
|
+
return result;
|
|
1562
|
+
}
|
|
1563
|
+
finally {
|
|
1564
|
+
agent.inFlightPermissionResponses.delete(requestId);
|
|
1565
|
+
agent.bufferedPermissionResolutions.delete(requestId);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
async cancelAgentRun(agentId) {
|
|
1569
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1570
|
+
const pendingRun = this.foregroundRuns.getPendingRun(agentId);
|
|
1571
|
+
const foregroundTurnId = agent.activeForegroundTurnId;
|
|
1572
|
+
const hasForegroundTurn = Boolean(foregroundTurnId);
|
|
1573
|
+
const isAutonomousRunning = agent.lifecycle === "running" && !hasForegroundTurn && !pendingRun;
|
|
1574
|
+
if (!hasForegroundTurn && !isAutonomousRunning && !pendingRun) {
|
|
1575
|
+
return false;
|
|
1576
|
+
}
|
|
1577
|
+
await this.interruptSession(agent.session, agentId);
|
|
1578
|
+
// The interrupt will produce a turn_canceled/turn_failed event via subscribe(),
|
|
1579
|
+
// which flows through the session event dispatcher and settles the foreground turn waiter.
|
|
1580
|
+
// Wait briefly for the event to propagate if there's an active foreground turn.
|
|
1581
|
+
if (foregroundTurnId) {
|
|
1582
|
+
const waiter = Array.from(agent.foregroundTurnWaiters).find((candidate) => candidate.turnId === foregroundTurnId);
|
|
1583
|
+
const timeout = new Promise((resolvePromise) => setTimeout(resolvePromise, 2000));
|
|
1584
|
+
if (waiter) {
|
|
1585
|
+
await Promise.race([waiter.settledPromise, timeout]);
|
|
1586
|
+
}
|
|
1587
|
+
else if (agent.activeForegroundTurnId === foregroundTurnId) {
|
|
1588
|
+
await Promise.race([
|
|
1589
|
+
new Promise((resolvePromise) => {
|
|
1590
|
+
const unsubscribe = this.subscribe((event) => {
|
|
1591
|
+
if (event.type === "agent_state" &&
|
|
1592
|
+
event.agent.id === agentId &&
|
|
1593
|
+
!event.agent.activeForegroundTurnId) {
|
|
1594
|
+
unsubscribe();
|
|
1595
|
+
resolvePromise();
|
|
1596
|
+
}
|
|
1597
|
+
}, { agentId, replayState: false });
|
|
1598
|
+
}),
|
|
1599
|
+
timeout,
|
|
1600
|
+
]);
|
|
1601
|
+
}
|
|
1602
|
+
// The waiter settling wakes up the streamForwarder generator, but its
|
|
1603
|
+
// finally block (which deletes the pendingForegroundRun) runs asynchronously.
|
|
1604
|
+
// Wait for the pending run to be fully cleaned up so the next streamAgent
|
|
1605
|
+
// call doesn't see a stale entry and reject with "already has an active run".
|
|
1606
|
+
if (pendingRun && !pendingRun.settled) {
|
|
1607
|
+
await Promise.race([pendingRun.settledPromise, timeout]);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
else if (pendingRun) {
|
|
1611
|
+
const timeout = new Promise((resolvePromise) => setTimeout(resolvePromise, 2000));
|
|
1612
|
+
await Promise.race([pendingRun.settledPromise, timeout]);
|
|
1613
|
+
}
|
|
1614
|
+
// If the foreground turn is still stuck after the timeout, force-dispatch a
|
|
1615
|
+
// synthetic turn_canceled so the normal event pipeline cleans up
|
|
1616
|
+
// activeForegroundTurnId, settles waiters, and unblocks the streamForwarder.
|
|
1617
|
+
if (foregroundTurnId && agent.activeForegroundTurnId === foregroundTurnId) {
|
|
1618
|
+
this.logger.warn({ agentId, foregroundTurnId }, "cancelAgentRun: foreground turn still active after timeout, force-canceling");
|
|
1619
|
+
void this.dispatchSessionEvent(agent, {
|
|
1620
|
+
type: "turn_canceled",
|
|
1621
|
+
provider: agent.provider,
|
|
1622
|
+
reason: "interrupted",
|
|
1623
|
+
turnId: foregroundTurnId,
|
|
1624
|
+
});
|
|
1625
|
+
// The synthetic event unblocks the streamForwarder generator, whose finally
|
|
1626
|
+
// block settles the pending foreground run asynchronously. Wait for it.
|
|
1627
|
+
const staleRun = this.foregroundRuns.getPendingRun(agentId);
|
|
1628
|
+
if (staleRun && !staleRun.settled) {
|
|
1629
|
+
await staleRun.settledPromise;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
// Clear any pending permissions that weren't cleaned up by handleStreamEvent.
|
|
1633
|
+
if (agent.pendingPermissions.size > 0) {
|
|
1634
|
+
for (const [requestId] of agent.pendingPermissions) {
|
|
1635
|
+
this.dispatchStream(agent.id, {
|
|
1636
|
+
type: "permission_resolved",
|
|
1637
|
+
provider: agent.provider,
|
|
1638
|
+
requestId,
|
|
1639
|
+
resolution: { behavior: "deny", message: "Interrupted" },
|
|
1640
|
+
}, { timestamp: new Date().toISOString() });
|
|
1641
|
+
}
|
|
1642
|
+
agent.pendingPermissions.clear();
|
|
1643
|
+
this.touchUpdatedAt(agent);
|
|
1644
|
+
this.emitState(agent);
|
|
1645
|
+
}
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
async interruptSession(session, agentId) {
|
|
1649
|
+
try {
|
|
1650
|
+
const result = await this.waitWithTimeout({
|
|
1651
|
+
operation: session.interrupt(),
|
|
1652
|
+
timeoutMs: this.rescueTimeouts.interruptSessionMs,
|
|
1653
|
+
onLateError: (error) => {
|
|
1654
|
+
this.logger.warn({ err: error, agentId }, "Session interrupt failed after timeout during cancel");
|
|
1655
|
+
},
|
|
1656
|
+
});
|
|
1657
|
+
if (result === "timed_out") {
|
|
1658
|
+
this.logger.warn({ agentId, timeoutMs: this.rescueTimeouts.interruptSessionMs }, "Timed out interrupting session during cancel");
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
catch (error) {
|
|
1662
|
+
this.logger.error({ err: error, agentId }, "Failed to interrupt session");
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
getPendingPermissions(agentId) {
|
|
1666
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1667
|
+
return Array.from(agent.pendingPermissions.values());
|
|
1668
|
+
}
|
|
1669
|
+
peekPendingPermission(agent) {
|
|
1670
|
+
const iterator = agent.pendingPermissions.values().next();
|
|
1671
|
+
return iterator.done ? null : iterator.value;
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Hydrates the timeline from provider history if the agent's durable
|
|
1675
|
+
* timeline is empty (e.g., imported agents that have provider history
|
|
1676
|
+
* on disk but no persisted timeline rows). No-ops if already hydrated.
|
|
1677
|
+
*/
|
|
1678
|
+
async hydrateTimelineFromProvider(agentId, options) {
|
|
1679
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1680
|
+
await this.hydrateTimelineFromLegacyProviderHistory(agent, options);
|
|
1681
|
+
}
|
|
1682
|
+
async rewind(agentId, messageId, mode) {
|
|
1683
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1684
|
+
const hadActiveRun = Boolean(agent.activeForegroundTurnId) || this.foregroundRuns.hasPendingRun(agentId);
|
|
1685
|
+
if (hadActiveRun) {
|
|
1686
|
+
await this.cancelAgentRun(agentId);
|
|
1687
|
+
}
|
|
1688
|
+
const lock = this.foregroundRuns.createPendingRun(agentId);
|
|
1689
|
+
try {
|
|
1690
|
+
this.logger.info({ agentId, provider: agent.provider, messageId, mode }, "agent.rewind.start");
|
|
1691
|
+
await invokeRewindCapability(agent.session, { messageId, mode });
|
|
1692
|
+
if (mode !== "files") {
|
|
1693
|
+
await this.hydrateTimelineFromProvider(agentId, { force: true, broadcast: true });
|
|
1694
|
+
}
|
|
1695
|
+
await this.refreshRuntimeInfo(agent);
|
|
1696
|
+
await this.persistSnapshot(agent);
|
|
1697
|
+
this.logger.info({ agentId, provider: agent.provider, messageId, mode }, "agent.rewind.complete");
|
|
1698
|
+
}
|
|
1699
|
+
catch (error) {
|
|
1700
|
+
this.logger.warn({ err: error, agentId, provider: agent.provider, messageId, mode }, "agent.rewind.failed");
|
|
1701
|
+
throw error;
|
|
1702
|
+
}
|
|
1703
|
+
finally {
|
|
1704
|
+
this.foregroundRuns.settlePendingRun(agentId, lock.token);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
async deleteCommittedTimeline(agentId) {
|
|
1708
|
+
if (!this.durableTimelineStore) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
await this.durableTimelineStore.deleteAgent(agentId);
|
|
1712
|
+
}
|
|
1713
|
+
async getLastAssistantMessage(agentId) {
|
|
1714
|
+
const agent = this.agents.get(agentId);
|
|
1715
|
+
if (!agent) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
return await this.getLastAssistantMessageFromStores(agentId);
|
|
1719
|
+
}
|
|
1720
|
+
getLastAssistantMessageFromTimeline(timeline) {
|
|
1721
|
+
return this.getLastAssistantMessageSegmentFromTimeline(timeline)?.text ?? null;
|
|
1722
|
+
}
|
|
1723
|
+
getLastAssistantMessageSegmentFromTimeline(timeline) {
|
|
1724
|
+
// Collect the last contiguous assistant messages (Claude streams chunks)
|
|
1725
|
+
const chunks = [];
|
|
1726
|
+
let startsAtBeginning = false;
|
|
1727
|
+
for (let i = timeline.length - 1; i >= 0; i--) {
|
|
1728
|
+
const item = timeline[i];
|
|
1729
|
+
if (item.type !== "assistant_message") {
|
|
1730
|
+
if (chunks.length) {
|
|
1731
|
+
break;
|
|
1732
|
+
}
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
chunks.push(item.text);
|
|
1736
|
+
startsAtBeginning = i === 0;
|
|
1737
|
+
}
|
|
1738
|
+
if (!chunks.length) {
|
|
1739
|
+
return null;
|
|
1740
|
+
}
|
|
1741
|
+
return {
|
|
1742
|
+
text: chunks.toReversed().join(""),
|
|
1743
|
+
startsAtBeginning,
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
async getLastAssistantMessageFromStores(agentId) {
|
|
1747
|
+
const liveTimeline = this.timelineStore.getItems(agentId);
|
|
1748
|
+
const liveSegment = this.getLastAssistantMessageSegmentFromTimeline(liveTimeline);
|
|
1749
|
+
if (!this.durableTimelineStore) {
|
|
1750
|
+
return liveSegment?.text ?? null;
|
|
1751
|
+
}
|
|
1752
|
+
if (!liveSegment) {
|
|
1753
|
+
return await this.durableTimelineStore.getLastAssistantMessage(agentId);
|
|
1754
|
+
}
|
|
1755
|
+
if (!liveSegment.startsAtBeginning) {
|
|
1756
|
+
return liveSegment.text;
|
|
1757
|
+
}
|
|
1758
|
+
const lastDurableItem = await this.durableTimelineStore.getLastItem(agentId);
|
|
1759
|
+
if (lastDurableItem?.type !== "assistant_message") {
|
|
1760
|
+
return liveSegment.text;
|
|
1761
|
+
}
|
|
1762
|
+
const durableMessage = await this.durableTimelineStore.getLastAssistantMessage(agentId);
|
|
1763
|
+
return durableMessage ? `${durableMessage}${liveSegment.text}` : liveSegment.text;
|
|
1764
|
+
}
|
|
1765
|
+
async getLastItemFromStores(agentId) {
|
|
1766
|
+
const lastLiveItem = this.timelineStore.getLastItem(agentId);
|
|
1767
|
+
if (lastLiveItem) {
|
|
1768
|
+
return lastLiveItem;
|
|
1769
|
+
}
|
|
1770
|
+
if (!this.durableTimelineStore) {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
return await this.durableTimelineStore.getLastItem(agentId);
|
|
1774
|
+
}
|
|
1775
|
+
async waitForAgentEvent(agentId, options) {
|
|
1776
|
+
const snapshot = this.getAgent(agentId);
|
|
1777
|
+
if (!snapshot) {
|
|
1778
|
+
throw new Error(`Agent ${agentId} not found`);
|
|
1779
|
+
}
|
|
1780
|
+
const pendingForegroundRun = this.foregroundRuns.getPendingRun(agentId);
|
|
1781
|
+
const hasForegroundTurn = Boolean(snapshot.activeForegroundTurnId) || Boolean(pendingForegroundRun);
|
|
1782
|
+
const immediatePermission = this.peekPendingPermission(snapshot);
|
|
1783
|
+
if (immediatePermission) {
|
|
1784
|
+
return {
|
|
1785
|
+
status: snapshot.lifecycle,
|
|
1786
|
+
permission: immediatePermission,
|
|
1787
|
+
lastMessage: await this.getLastAssistantMessage(agentId),
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
const initialStatus = snapshot.lifecycle;
|
|
1791
|
+
const initialBusy = isAgentBusy(initialStatus) || hasForegroundTurn;
|
|
1792
|
+
const waitForActive = options?.waitForActive ?? false;
|
|
1793
|
+
if (!waitForActive && !initialBusy) {
|
|
1794
|
+
return {
|
|
1795
|
+
status: initialStatus,
|
|
1796
|
+
permission: null,
|
|
1797
|
+
lastMessage: await this.getLastAssistantMessage(agentId),
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
if (waitForActive && !initialBusy && !hasForegroundTurn) {
|
|
1801
|
+
return {
|
|
1802
|
+
status: initialStatus,
|
|
1803
|
+
permission: null,
|
|
1804
|
+
lastMessage: await this.getLastAssistantMessage(agentId),
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
if (options?.signal?.aborted) {
|
|
1808
|
+
throw createAbortError(options.signal, "wait_for_agent aborted");
|
|
1809
|
+
}
|
|
1810
|
+
return await new Promise((resolvePromise, reject) => {
|
|
1811
|
+
// Bug #1 Fix: Check abort signal AGAIN inside Promise constructor
|
|
1812
|
+
// to avoid race condition between pre-Promise check and abort listener registration
|
|
1813
|
+
if (options?.signal?.aborted) {
|
|
1814
|
+
reject(createAbortError(options.signal, "wait_for_agent aborted"));
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
let currentStatus = initialStatus;
|
|
1818
|
+
let hasStarted = isAgentBusy(initialStatus) ||
|
|
1819
|
+
Boolean(snapshot.activeForegroundTurnId) ||
|
|
1820
|
+
Boolean(pendingForegroundRun?.started);
|
|
1821
|
+
let terminalStatusOverride = null;
|
|
1822
|
+
let finished = false;
|
|
1823
|
+
// Bug #3 Fix: Declare unsubscribe and abortHandler upfront so cleanup can reference them
|
|
1824
|
+
let unsubscribe = null;
|
|
1825
|
+
let abortHandler = null;
|
|
1826
|
+
const cleanup = () => {
|
|
1827
|
+
// Clean up subscription
|
|
1828
|
+
if (unsubscribe) {
|
|
1829
|
+
try {
|
|
1830
|
+
unsubscribe();
|
|
1831
|
+
}
|
|
1832
|
+
catch {
|
|
1833
|
+
// ignore cleanup errors
|
|
1834
|
+
}
|
|
1835
|
+
unsubscribe = null;
|
|
1836
|
+
}
|
|
1837
|
+
// Clean up abort listener
|
|
1838
|
+
if (abortHandler && options?.signal) {
|
|
1839
|
+
try {
|
|
1840
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
1841
|
+
}
|
|
1842
|
+
catch {
|
|
1843
|
+
// ignore cleanup errors
|
|
1844
|
+
}
|
|
1845
|
+
abortHandler = null;
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
const finish = (permission) => {
|
|
1849
|
+
if (finished) {
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
finished = true;
|
|
1853
|
+
cleanup();
|
|
1854
|
+
void this.getLastAssistantMessage(agentId)
|
|
1855
|
+
.then((lastMessage) => {
|
|
1856
|
+
resolvePromise({
|
|
1857
|
+
status: currentStatus,
|
|
1858
|
+
permission,
|
|
1859
|
+
lastMessage,
|
|
1860
|
+
});
|
|
1861
|
+
return;
|
|
1862
|
+
})
|
|
1863
|
+
.catch(reject);
|
|
1864
|
+
};
|
|
1865
|
+
// Bug #3 Fix: Set up abort handler BEFORE subscription
|
|
1866
|
+
// to ensure cleanup handlers exist before callback can fire
|
|
1867
|
+
if (options?.signal) {
|
|
1868
|
+
abortHandler = () => {
|
|
1869
|
+
cleanup();
|
|
1870
|
+
reject(createAbortError(options.signal, "wait_for_agent aborted"));
|
|
1871
|
+
};
|
|
1872
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
1873
|
+
}
|
|
1874
|
+
// Bug #3 Fix: Now subscribe with cleanup handlers already in place
|
|
1875
|
+
// This prevents race condition if callback fires synchronously with replayState: true
|
|
1876
|
+
unsubscribe = this.subscribe((event) => {
|
|
1877
|
+
if (event.type === "agent_state") {
|
|
1878
|
+
currentStatus = event.agent.lifecycle;
|
|
1879
|
+
const pending = this.peekPendingPermission(event.agent);
|
|
1880
|
+
if (pending) {
|
|
1881
|
+
finish(pending);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
if (isAgentBusy(event.agent.lifecycle)) {
|
|
1885
|
+
hasStarted = true;
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
if (!waitForActive || hasStarted) {
|
|
1889
|
+
if (terminalStatusOverride) {
|
|
1890
|
+
currentStatus = terminalStatusOverride;
|
|
1891
|
+
}
|
|
1892
|
+
finish(null);
|
|
1893
|
+
}
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (event.type === "agent_stream") {
|
|
1897
|
+
if (event.event.type === "permission_requested") {
|
|
1898
|
+
finish(event.event.request);
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (event.event.type === "turn_failed") {
|
|
1902
|
+
hasStarted = true;
|
|
1903
|
+
terminalStatusOverride = "error";
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (event.event.type === "turn_completed") {
|
|
1907
|
+
hasStarted = true;
|
|
1908
|
+
}
|
|
1909
|
+
if (event.event.type === "turn_canceled") {
|
|
1910
|
+
hasStarted = true;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}, { agentId, replayState: true });
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
async registerSession(session, config, agentId, options) {
|
|
1917
|
+
const resolvedAgentId = validateAgentId(agentId, "registerSession");
|
|
1918
|
+
if (this.agents.has(resolvedAgentId)) {
|
|
1919
|
+
throw new Error(`Agent with id ${resolvedAgentId} already exists`);
|
|
1920
|
+
}
|
|
1921
|
+
const initialPersistedTitle = await this.resolveInitialPersistedTitle(resolvedAgentId, config, options?.initialTitle ?? null);
|
|
1922
|
+
const now = new Date();
|
|
1923
|
+
const { durableTimelineHasRows } = await this.initializeAgentTimelineForRegister({
|
|
1924
|
+
agentId: resolvedAgentId,
|
|
1925
|
+
now,
|
|
1926
|
+
options,
|
|
1927
|
+
});
|
|
1928
|
+
const managed = this.buildManagedAgentForRegister({
|
|
1929
|
+
resolvedAgentId,
|
|
1930
|
+
session,
|
|
1931
|
+
config,
|
|
1932
|
+
now,
|
|
1933
|
+
durableTimelineHasRows,
|
|
1934
|
+
options,
|
|
1935
|
+
});
|
|
1936
|
+
this.agents.set(resolvedAgentId, managed);
|
|
1937
|
+
// Initialize previousStatus to track transitions
|
|
1938
|
+
this.previousStatuses.set(resolvedAgentId, managed.lifecycle);
|
|
1939
|
+
await this.refreshRuntimeInfo(managed, { emit: !options?.publishWhenReady });
|
|
1940
|
+
await this.persistSnapshot(managed, {
|
|
1941
|
+
title: initialPersistedTitle,
|
|
1942
|
+
});
|
|
1943
|
+
if (!options?.publishWhenReady) {
|
|
1944
|
+
this.emitState(managed, { persist: false });
|
|
1945
|
+
}
|
|
1946
|
+
await this.refreshSessionState(managed, { emit: !options?.publishWhenReady });
|
|
1947
|
+
managed.lifecycle = "idle";
|
|
1948
|
+
await this.persistSnapshot(managed);
|
|
1949
|
+
this.emitState(managed, { persist: false });
|
|
1950
|
+
this.subscribeToSession(managed);
|
|
1951
|
+
return { ...managed };
|
|
1952
|
+
}
|
|
1953
|
+
async initializeAgentTimelineForRegister(params) {
|
|
1954
|
+
const { agentId, now, options } = params;
|
|
1955
|
+
const explicitTimelineSeed = buildExplicitTimelineSeedForRegister(now, options);
|
|
1956
|
+
const shouldSeedFromDurable = !explicitTimelineSeed &&
|
|
1957
|
+
!this.timelineStore.has(agentId) &&
|
|
1958
|
+
this.durableTimelineStore !== undefined;
|
|
1959
|
+
const durableTimelineSeed = shouldSeedFromDurable
|
|
1960
|
+
? await this.loadCommittedTimelineSeed(agentId, now)
|
|
1961
|
+
: null;
|
|
1962
|
+
const durableTimelineHasRows = durableTimelineSeed != null && (durableTimelineSeed.nextSeq ?? 1) > 1;
|
|
1963
|
+
const timelineSeed = explicitTimelineSeed ?? durableTimelineSeed;
|
|
1964
|
+
if (timelineSeed || !this.timelineStore.has(agentId)) {
|
|
1965
|
+
this.timelineStore.initialize(agentId, timelineSeed ?? { timestamp: now.toISOString() });
|
|
1966
|
+
}
|
|
1967
|
+
if (options?.timelineRows?.length) {
|
|
1968
|
+
this.enqueueDurableTimelineBulkInsert(agentId, options.timelineRows);
|
|
1969
|
+
}
|
|
1970
|
+
return { durableTimelineHasRows };
|
|
1971
|
+
}
|
|
1972
|
+
buildManagedAgentForRegister(params) {
|
|
1973
|
+
const { resolvedAgentId, session, config, now, durableTimelineHasRows, options } = params;
|
|
1974
|
+
return {
|
|
1975
|
+
id: resolvedAgentId,
|
|
1976
|
+
provider: config.provider,
|
|
1977
|
+
cwd: config.cwd,
|
|
1978
|
+
workspaceId: options?.workspaceId,
|
|
1979
|
+
session,
|
|
1980
|
+
capabilities: session.capabilities,
|
|
1981
|
+
config,
|
|
1982
|
+
runtimeInfo: undefined,
|
|
1983
|
+
lifecycle: "initializing",
|
|
1984
|
+
createdAt: options?.createdAt ?? now,
|
|
1985
|
+
updatedAt: options?.updatedAt ?? now,
|
|
1986
|
+
availableModes: [],
|
|
1987
|
+
currentModeId: null,
|
|
1988
|
+
pendingPermissions: new Map(),
|
|
1989
|
+
bufferedPermissionResolutions: new Map(),
|
|
1990
|
+
inFlightPermissionResponses: new Set(),
|
|
1991
|
+
pendingReplacement: false,
|
|
1992
|
+
activeForegroundTurnId: null,
|
|
1993
|
+
foregroundTurnWaiters: new Set(),
|
|
1994
|
+
finalizedForegroundTurnIds: new Set(),
|
|
1995
|
+
unsubscribeSession: null,
|
|
1996
|
+
persistence: attachPersistenceCwd(options?.persistence ?? session.describePersistence(), config.cwd),
|
|
1997
|
+
historyPrimed: options?.historyPrimed ?? durableTimelineHasRows,
|
|
1998
|
+
lastUserMessageAt: options?.lastUserMessageAt ?? null,
|
|
1999
|
+
lastUsage: options?.lastUsage,
|
|
2000
|
+
lastError: options?.lastError,
|
|
2001
|
+
attention: resolveInitialAttention(options?.attention),
|
|
2002
|
+
internal: config.internal ?? false,
|
|
2003
|
+
observable: config.observable ?? false,
|
|
2004
|
+
labels: options?.labels ?? {},
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
async loadCommittedTimelineSeed(agentId, now) {
|
|
2008
|
+
if (!this.durableTimelineStore) {
|
|
2009
|
+
return { timestamp: now.toISOString() };
|
|
2010
|
+
}
|
|
2011
|
+
return {
|
|
2012
|
+
nextSeq: (await this.durableTimelineStore.getLatestCommittedSeq(agentId)) + 1,
|
|
2013
|
+
timestamp: now.toISOString(),
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
prepareAgentForClosure(agent, cancelReason) {
|
|
2017
|
+
this.agentStreamCoalescer.flushAndDiscard(agent.id);
|
|
2018
|
+
this.agents.delete(agent.id);
|
|
2019
|
+
this.previousStatuses.delete(agent.id);
|
|
2020
|
+
if (agent.unsubscribeSession) {
|
|
2021
|
+
agent.unsubscribeSession();
|
|
2022
|
+
agent.unsubscribeSession = null;
|
|
2023
|
+
}
|
|
2024
|
+
this.foregroundRuns.cancelWaiters(agent, (turnId) => ({
|
|
2025
|
+
type: "turn_canceled",
|
|
2026
|
+
provider: agent.provider,
|
|
2027
|
+
reason: cancelReason,
|
|
2028
|
+
turnId,
|
|
2029
|
+
}));
|
|
2030
|
+
this.foregroundRuns.settlePendingRun(agent.id);
|
|
2031
|
+
return {
|
|
2032
|
+
...agent,
|
|
2033
|
+
lifecycle: "closed",
|
|
2034
|
+
session: null,
|
|
2035
|
+
activeForegroundTurnId: null,
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
emitClosedAgent(agent, options) {
|
|
2039
|
+
this.emitState(agent, options);
|
|
2040
|
+
}
|
|
2041
|
+
subscribeToSession(agent) {
|
|
2042
|
+
if (agent.unsubscribeSession) {
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const agentId = agent.id;
|
|
2046
|
+
const unsubscribe = agent.session.subscribe((event) => {
|
|
2047
|
+
this.enqueueSessionEvent(agentId, event);
|
|
2048
|
+
});
|
|
2049
|
+
agent.unsubscribeSession = unsubscribe;
|
|
2050
|
+
}
|
|
2051
|
+
enqueueSessionEvent(agentId, event) {
|
|
2052
|
+
this.logger.trace({
|
|
2053
|
+
agentId,
|
|
2054
|
+
provider: event.provider,
|
|
2055
|
+
sessionId: this.agents.get(agentId)?.persistence?.sessionId ?? undefined,
|
|
2056
|
+
turnId: getAgentStreamEventTurnId(event),
|
|
2057
|
+
event,
|
|
2058
|
+
}, "agent.manager.enqueue");
|
|
2059
|
+
const previous = this.sessionEventTails.get(agentId) ?? Promise.resolve();
|
|
2060
|
+
const next = previous
|
|
2061
|
+
.catch(() => undefined)
|
|
2062
|
+
.then(async () => {
|
|
2063
|
+
const current = this.agents.get(agentId);
|
|
2064
|
+
if (!current) {
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
if (current.session == null) {
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
this.logger.trace({
|
|
2071
|
+
agentId,
|
|
2072
|
+
provider: event.provider,
|
|
2073
|
+
sessionId: current.persistence?.sessionId ?? undefined,
|
|
2074
|
+
turnId: getAgentStreamEventTurnId(event),
|
|
2075
|
+
event,
|
|
2076
|
+
}, "agent.manager.dequeue");
|
|
2077
|
+
await this.dispatchSessionEvent(current, event);
|
|
2078
|
+
return;
|
|
2079
|
+
})
|
|
2080
|
+
.catch((err) => {
|
|
2081
|
+
this.logger.error({ err, agentId, eventType: event.type }, "Failed to process session event");
|
|
2082
|
+
});
|
|
2083
|
+
this.sessionEventTails.set(agentId, next);
|
|
2084
|
+
this.trackBackgroundTask(next);
|
|
2085
|
+
void next.finally(() => {
|
|
2086
|
+
if (this.sessionEventTails.get(agentId) === next) {
|
|
2087
|
+
this.sessionEventTails.delete(agentId);
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
async dispatchSessionEvent(agent, event) {
|
|
2092
|
+
const turnId = getAgentStreamEventTurnId(event);
|
|
2093
|
+
const matchingWaiters = this.foregroundRuns.getMatchingWaiters(agent, turnId);
|
|
2094
|
+
this.logger.trace({
|
|
2095
|
+
agentId: agent.id,
|
|
2096
|
+
provider: event.provider,
|
|
2097
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2098
|
+
turnId,
|
|
2099
|
+
matchingWaiterCount: matchingWaiters.length,
|
|
2100
|
+
event,
|
|
2101
|
+
}, "agent.manager.dispatch_session_event");
|
|
2102
|
+
const shouldNotifyWaiters = await this.handleStreamEvent(agent, event);
|
|
2103
|
+
if (!shouldNotifyWaiters) {
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
this.foregroundRuns.notifyWaiters(matchingWaiters, event, {
|
|
2107
|
+
terminal: isTurnTerminalEvent(event),
|
|
2108
|
+
});
|
|
2109
|
+
this.logger.trace({
|
|
2110
|
+
agentId: agent.id,
|
|
2111
|
+
provider: event.provider,
|
|
2112
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2113
|
+
turnId,
|
|
2114
|
+
notifiedWaiterCount: matchingWaiters.length,
|
|
2115
|
+
terminal: isTurnTerminalEvent(event),
|
|
2116
|
+
event,
|
|
2117
|
+
}, "agent.manager.notify_waiters");
|
|
2118
|
+
}
|
|
2119
|
+
async resolveInitialPersistedTitle(agentId, config, fallbackTitle) {
|
|
2120
|
+
const existing = await this.registry?.get(agentId);
|
|
2121
|
+
if (existing) {
|
|
2122
|
+
return existing.title ?? null;
|
|
2123
|
+
}
|
|
2124
|
+
const explicitTitle = typeof config.title === "string" && config.title.trim().length > 0
|
|
2125
|
+
? config.title.trim()
|
|
2126
|
+
: null;
|
|
2127
|
+
return explicitTitle ?? fallbackTitle;
|
|
2128
|
+
}
|
|
2129
|
+
async persistSnapshot(agent, options) {
|
|
2130
|
+
if (!this.registry) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
// Don't persist internal agents - they're ephemeral system tasks
|
|
2134
|
+
if (agent.internal) {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
await this.registry.applySnapshot(agent, options);
|
|
2138
|
+
}
|
|
2139
|
+
requireRegistry() {
|
|
2140
|
+
if (!this.registry) {
|
|
2141
|
+
throw new Error("Agent storage unavailable");
|
|
2142
|
+
}
|
|
2143
|
+
return this.registry;
|
|
2144
|
+
}
|
|
2145
|
+
async refreshSessionState(agent, options) {
|
|
2146
|
+
try {
|
|
2147
|
+
const modes = await agent.session.getAvailableModes();
|
|
2148
|
+
agent.availableModes = modes;
|
|
2149
|
+
}
|
|
2150
|
+
catch {
|
|
2151
|
+
agent.availableModes = [];
|
|
2152
|
+
}
|
|
2153
|
+
try {
|
|
2154
|
+
agent.currentModeId = await agent.session.getCurrentMode();
|
|
2155
|
+
}
|
|
2156
|
+
catch {
|
|
2157
|
+
agent.currentModeId = null;
|
|
2158
|
+
}
|
|
2159
|
+
try {
|
|
2160
|
+
const pending = agent.session.getPendingPermissions();
|
|
2161
|
+
agent.pendingPermissions = new Map(pending.map((request) => [request.id, request]));
|
|
2162
|
+
}
|
|
2163
|
+
catch {
|
|
2164
|
+
agent.pendingPermissions.clear();
|
|
2165
|
+
}
|
|
2166
|
+
this.syncFeaturesFromSession(agent);
|
|
2167
|
+
await this.refreshRuntimeInfo(agent, options);
|
|
2168
|
+
}
|
|
2169
|
+
async refreshRuntimeInfo(agent, options) {
|
|
2170
|
+
try {
|
|
2171
|
+
const newInfo = await agent.session.getRuntimeInfo();
|
|
2172
|
+
const changed = newInfo.model !== agent.runtimeInfo?.model ||
|
|
2173
|
+
newInfo.thinkingOptionId !== agent.runtimeInfo?.thinkingOptionId ||
|
|
2174
|
+
newInfo.sessionId !== agent.runtimeInfo?.sessionId ||
|
|
2175
|
+
newInfo.modeId !== agent.runtimeInfo?.modeId;
|
|
2176
|
+
agent.runtimeInfo = newInfo;
|
|
2177
|
+
if (!agent.persistence && newInfo.sessionId) {
|
|
2178
|
+
agent.persistence = attachPersistenceCwd({ provider: agent.provider, sessionId: newInfo.sessionId }, agent.cwd);
|
|
2179
|
+
}
|
|
2180
|
+
// Emit state if runtimeInfo changed so clients get the updated model
|
|
2181
|
+
if (changed && options?.emit !== false) {
|
|
2182
|
+
this.emitState(agent);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
catch {
|
|
2186
|
+
// Keep existing runtimeInfo if refresh fails.
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
async hydrateTimelineFromLegacyProviderHistory(agent, options) {
|
|
2190
|
+
if (agent.historyPrimed && !options?.force) {
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (options?.force) {
|
|
2194
|
+
const historyEvents = [];
|
|
2195
|
+
for await (const event of agent.session.streamHistory()) {
|
|
2196
|
+
if (event.type === "timeline") {
|
|
2197
|
+
if (event.item.type === "user_message" && isSystemInjectedEnvelope(event.item.text)) {
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
historyEvents.push(event);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
this.agentStreamCoalescer.flushAndDiscard(agent.id);
|
|
2204
|
+
await this.deleteCommittedTimeline(agent.id);
|
|
2205
|
+
this.timelineStore.delete(agent.id);
|
|
2206
|
+
this.timelineStore.initialize(agent.id, { timestamp: new Date().toISOString() });
|
|
2207
|
+
agent.historyPrimed = true;
|
|
2208
|
+
for (const event of historyEvents) {
|
|
2209
|
+
const row = this.recordTimeline(agent.id, event.item, event.timestamp ? { timestamp: event.timestamp } : undefined);
|
|
2210
|
+
if (options?.broadcast) {
|
|
2211
|
+
this.dispatchStream(agent.id, event, {
|
|
2212
|
+
seq: row.seq,
|
|
2213
|
+
epoch: this.timelineStore.getEpoch(agent.id),
|
|
2214
|
+
timestamp: row.timestamp,
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
this.touchUpdatedAt(agent);
|
|
2219
|
+
this.emitState(agent);
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
agent.historyPrimed = true;
|
|
2223
|
+
try {
|
|
2224
|
+
for await (const event of agent.session.streamHistory()) {
|
|
2225
|
+
if (event.type !== "timeline") {
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
if (event.item.type === "user_message" && isSystemInjectedEnvelope(event.item.text)) {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
this.recordTimeline(agent.id, event.item, event.timestamp ? { timestamp: event.timestamp } : undefined);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
catch {
|
|
2235
|
+
// ignore history failures
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
notifyForegroundTurnWaiters(agentId, event) {
|
|
2239
|
+
const turnId = getAgentStreamEventTurnId(event);
|
|
2240
|
+
if (turnId == null) {
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
const agent = this.agents.get(agentId);
|
|
2244
|
+
if (!agent) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
this.foregroundRuns.notifyAgentWaiters(agent, event);
|
|
2248
|
+
this.logger.trace({
|
|
2249
|
+
agentId,
|
|
2250
|
+
provider: event.provider,
|
|
2251
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2252
|
+
turnId,
|
|
2253
|
+
event,
|
|
2254
|
+
}, "agent.manager.notify_waiters.coalesced");
|
|
2255
|
+
}
|
|
2256
|
+
async handleStreamEvent(agent, event, options) {
|
|
2257
|
+
const eventTurnId = getAgentStreamEventTurnId(event);
|
|
2258
|
+
const isForegroundEvent = Boolean(eventTurnId && agent.activeForegroundTurnId === eventTurnId);
|
|
2259
|
+
this.traceHandleStreamEventStart(agent, event, eventTurnId, isForegroundEvent);
|
|
2260
|
+
if (eventTurnId &&
|
|
2261
|
+
isTurnTerminalEvent(event) &&
|
|
2262
|
+
this.foregroundRuns.hasFinalizedTurn(agent, eventTurnId)) {
|
|
2263
|
+
return false;
|
|
2264
|
+
}
|
|
2265
|
+
// Only update timestamp for live events, not history replay
|
|
2266
|
+
if (!options?.fromHistory) {
|
|
2267
|
+
this.touchUpdatedAt(agent);
|
|
2268
|
+
if (this.agentStreamCoalescer.handle(agent.id, event)) {
|
|
2269
|
+
this.traceCoalescerBuffered(agent, event, eventTurnId);
|
|
2270
|
+
return false;
|
|
2271
|
+
}
|
|
2272
|
+
this.agentStreamCoalescer.flushFor(agent.id);
|
|
2273
|
+
}
|
|
2274
|
+
const flags = { shouldDispatchEvent: true, shouldNotifyWaiters: true };
|
|
2275
|
+
const dispatchPromise = this.dispatchStreamEventByType({
|
|
2276
|
+
agent,
|
|
2277
|
+
event,
|
|
2278
|
+
options,
|
|
2279
|
+
isForegroundEvent,
|
|
2280
|
+
eventTurnId,
|
|
2281
|
+
flags,
|
|
2282
|
+
});
|
|
2283
|
+
if (dispatchPromise) {
|
|
2284
|
+
await dispatchPromise;
|
|
2285
|
+
}
|
|
2286
|
+
if (!options?.fromHistory && isForegroundEvent && isTurnTerminalEvent(event)) {
|
|
2287
|
+
this.finalizeForegroundTurn(agent, eventTurnId);
|
|
2288
|
+
}
|
|
2289
|
+
if (!options?.fromHistory && flags.shouldDispatchEvent) {
|
|
2290
|
+
this.dispatchStream(agent.id, event, { timestamp: new Date().toISOString() });
|
|
2291
|
+
}
|
|
2292
|
+
this.traceHandleStreamEventEnd(agent, event, eventTurnId, flags);
|
|
2293
|
+
return flags.shouldNotifyWaiters;
|
|
2294
|
+
}
|
|
2295
|
+
traceHandleStreamEventStart(agent, event, turnId, isForegroundEvent) {
|
|
2296
|
+
this.logger.trace({
|
|
2297
|
+
agentId: agent.id,
|
|
2298
|
+
provider: event.provider,
|
|
2299
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2300
|
+
turnId,
|
|
2301
|
+
lifecycle: agent.lifecycle,
|
|
2302
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2303
|
+
isForegroundEvent,
|
|
2304
|
+
event,
|
|
2305
|
+
}, "agent.manager.handle_stream_event.start");
|
|
2306
|
+
}
|
|
2307
|
+
traceCoalescerBuffered(agent, event, turnId) {
|
|
2308
|
+
this.logger.trace({
|
|
2309
|
+
agentId: agent.id,
|
|
2310
|
+
provider: event.provider,
|
|
2311
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2312
|
+
turnId,
|
|
2313
|
+
event,
|
|
2314
|
+
}, "agent.manager.coalescer.buffer");
|
|
2315
|
+
}
|
|
2316
|
+
traceHandleStreamEventEnd(agent, event, turnId, flags) {
|
|
2317
|
+
this.logger.trace({
|
|
2318
|
+
agentId: agent.id,
|
|
2319
|
+
provider: event.provider,
|
|
2320
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2321
|
+
turnId,
|
|
2322
|
+
lifecycle: agent.lifecycle,
|
|
2323
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2324
|
+
shouldDispatchEvent: flags.shouldDispatchEvent,
|
|
2325
|
+
shouldNotifyWaiters: flags.shouldNotifyWaiters,
|
|
2326
|
+
event,
|
|
2327
|
+
}, "agent.manager.handle_stream_event.end");
|
|
2328
|
+
}
|
|
2329
|
+
dispatchStreamEventByType(params) {
|
|
2330
|
+
const { agent, event, options, isForegroundEvent, eventTurnId, flags } = params;
|
|
2331
|
+
switch (event.type) {
|
|
2332
|
+
case "thread_started":
|
|
2333
|
+
this.onStreamThreadStarted(agent);
|
|
2334
|
+
return undefined;
|
|
2335
|
+
case "usage_updated":
|
|
2336
|
+
agent.lastUsage = event.usage;
|
|
2337
|
+
this.emitState(agent);
|
|
2338
|
+
return undefined;
|
|
2339
|
+
case "mode_changed":
|
|
2340
|
+
agent.currentModeId = event.currentModeId;
|
|
2341
|
+
agent.availableModes = event.availableModes;
|
|
2342
|
+
if (agent.runtimeInfo) {
|
|
2343
|
+
agent.runtimeInfo = { ...agent.runtimeInfo, modeId: event.currentModeId };
|
|
2344
|
+
}
|
|
2345
|
+
flags.shouldDispatchEvent = false;
|
|
2346
|
+
this.emitState(agent);
|
|
2347
|
+
return undefined;
|
|
2348
|
+
case "model_changed":
|
|
2349
|
+
agent.runtimeInfo = event.runtimeInfo;
|
|
2350
|
+
if (!agent.persistence && event.runtimeInfo.sessionId) {
|
|
2351
|
+
agent.persistence = attachPersistenceCwd({ provider: agent.provider, sessionId: event.runtimeInfo.sessionId }, agent.cwd);
|
|
2352
|
+
}
|
|
2353
|
+
agent.currentModeId = event.runtimeInfo.modeId ?? agent.currentModeId;
|
|
2354
|
+
flags.shouldDispatchEvent = false;
|
|
2355
|
+
this.emitState(agent);
|
|
2356
|
+
return undefined;
|
|
2357
|
+
case "thinking_option_changed":
|
|
2358
|
+
if (agent.runtimeInfo) {
|
|
2359
|
+
agent.runtimeInfo = {
|
|
2360
|
+
...agent.runtimeInfo,
|
|
2361
|
+
thinkingOptionId: event.thinkingOptionId,
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
flags.shouldDispatchEvent = false;
|
|
2365
|
+
this.emitState(agent);
|
|
2366
|
+
return undefined;
|
|
2367
|
+
case "timeline":
|
|
2368
|
+
return this.onStreamTimelineEvent({ agent, event, options, isForegroundEvent, flags });
|
|
2369
|
+
case "observed_subagent_updated":
|
|
2370
|
+
this.onObservedSubagentUpdated(agent, event);
|
|
2371
|
+
flags.shouldDispatchEvent = false;
|
|
2372
|
+
return undefined;
|
|
2373
|
+
case "observed_subagent_timeline":
|
|
2374
|
+
this.onObservedSubagentTimeline(agent, event);
|
|
2375
|
+
flags.shouldDispatchEvent = false;
|
|
2376
|
+
return undefined;
|
|
2377
|
+
case "turn_completed":
|
|
2378
|
+
this.onStreamTurnCompleted({ agent, event, eventTurnId, isForegroundEvent });
|
|
2379
|
+
return undefined;
|
|
2380
|
+
case "turn_failed":
|
|
2381
|
+
return this.onStreamTurnFailed({
|
|
2382
|
+
agent,
|
|
2383
|
+
event,
|
|
2384
|
+
eventTurnId,
|
|
2385
|
+
isForegroundEvent,
|
|
2386
|
+
options,
|
|
2387
|
+
});
|
|
2388
|
+
case "turn_canceled":
|
|
2389
|
+
this.onStreamTurnCanceled({ agent, event, eventTurnId, isForegroundEvent, options });
|
|
2390
|
+
return undefined;
|
|
2391
|
+
case "turn_started":
|
|
2392
|
+
this.onStreamTurnStarted({ agent, eventTurnId, isForegroundEvent });
|
|
2393
|
+
return undefined;
|
|
2394
|
+
case "permission_requested":
|
|
2395
|
+
this.onStreamPermissionRequested(agent, event);
|
|
2396
|
+
return undefined;
|
|
2397
|
+
case "permission_resolved":
|
|
2398
|
+
this.onStreamPermissionResolved({ agent, event, options, flags });
|
|
2399
|
+
return undefined;
|
|
2400
|
+
default:
|
|
2401
|
+
return undefined;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
onStreamThreadStarted(agent) {
|
|
2405
|
+
const previousSessionId = agent.persistence?.sessionId ?? null;
|
|
2406
|
+
const handle = agent.session.describePersistence();
|
|
2407
|
+
if (handle) {
|
|
2408
|
+
agent.persistence = attachPersistenceCwd(handle, agent.cwd);
|
|
2409
|
+
if (agent.persistence?.sessionId !== previousSessionId) {
|
|
2410
|
+
this.emitState(agent);
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
void this.refreshRuntimeInfo(agent);
|
|
2414
|
+
}
|
|
2415
|
+
async onStreamTimelineEvent(params) {
|
|
2416
|
+
const { agent, event, options, flags } = params;
|
|
2417
|
+
if (event.item.type === "user_message" && isSystemInjectedEnvelope(event.item.text)) {
|
|
2418
|
+
flags.shouldDispatchEvent = false;
|
|
2419
|
+
flags.shouldNotifyWaiters = false;
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
if (options?.fromHistory) {
|
|
2423
|
+
this.recordTimeline(agent.id, event.item, event.timestamp ? { timestamp: event.timestamp } : undefined);
|
|
2424
|
+
flags.shouldDispatchEvent = false;
|
|
2425
|
+
flags.shouldNotifyWaiters = false;
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
this.recordAndDispatchTimelineItem(agent.id, event.item, event.provider, event.turnId);
|
|
2429
|
+
if (event.item.type === "user_message") {
|
|
2430
|
+
agent.lastUserMessageAt = new Date();
|
|
2431
|
+
this.emitState(agent);
|
|
2432
|
+
}
|
|
2433
|
+
flags.shouldDispatchEvent = false;
|
|
2434
|
+
flags.shouldNotifyWaiters = true;
|
|
2435
|
+
}
|
|
2436
|
+
onStreamTurnCompleted(params) {
|
|
2437
|
+
const { agent, event, eventTurnId, isForegroundEvent } = params;
|
|
2438
|
+
this.logger.trace({
|
|
2439
|
+
agentId: agent.id,
|
|
2440
|
+
provider: agent.provider,
|
|
2441
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2442
|
+
turnId: eventTurnId,
|
|
2443
|
+
lifecycle: agent.lifecycle,
|
|
2444
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2445
|
+
}, "agent.manager.turn.completed");
|
|
2446
|
+
agent.lastUsage = event.usage;
|
|
2447
|
+
agent.lastError = undefined;
|
|
2448
|
+
if (!isForegroundEvent && agent.lifecycle !== "idle" && !agent.pendingReplacement) {
|
|
2449
|
+
agent.lifecycle = "idle";
|
|
2450
|
+
this.emitState(agent);
|
|
2451
|
+
}
|
|
2452
|
+
void this.refreshRuntimeInfo(agent);
|
|
2453
|
+
}
|
|
2454
|
+
async onStreamTurnFailed(params) {
|
|
2455
|
+
const { agent, event, eventTurnId, isForegroundEvent, options } = params;
|
|
2456
|
+
this.logger.warn({
|
|
2457
|
+
agentId: agent.id,
|
|
2458
|
+
provider: agent.provider,
|
|
2459
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2460
|
+
turnId: eventTurnId,
|
|
2461
|
+
lifecycle: agent.lifecycle,
|
|
2462
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2463
|
+
eventTurnId,
|
|
2464
|
+
error: event.error,
|
|
2465
|
+
code: event.code,
|
|
2466
|
+
diagnostic: event.diagnostic,
|
|
2467
|
+
}, "handleStreamEvent: turn_failed");
|
|
2468
|
+
if (!isForegroundEvent) {
|
|
2469
|
+
agent.lifecycle = "error";
|
|
2470
|
+
}
|
|
2471
|
+
agent.lastError = event.error;
|
|
2472
|
+
await this.appendSystemErrorTimelineMessage(agent, event.provider, this.formatTurnFailedMessage(event), options);
|
|
2473
|
+
this.resolvePendingPermissionsForAgent(agent, event.provider, options, "Turn failed");
|
|
2474
|
+
if (!isForegroundEvent) {
|
|
2475
|
+
this.emitState(agent);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
onStreamTurnCanceled(params) {
|
|
2479
|
+
const { agent, event, eventTurnId, isForegroundEvent, options } = params;
|
|
2480
|
+
this.logger.trace({
|
|
2481
|
+
agentId: agent.id,
|
|
2482
|
+
provider: agent.provider,
|
|
2483
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2484
|
+
turnId: eventTurnId,
|
|
2485
|
+
lifecycle: agent.lifecycle,
|
|
2486
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2487
|
+
eventTurnId,
|
|
2488
|
+
}, "agent.manager.turn.canceled");
|
|
2489
|
+
if (!isForegroundEvent && !agent.pendingReplacement) {
|
|
2490
|
+
agent.lifecycle = "idle";
|
|
2491
|
+
}
|
|
2492
|
+
agent.lastError = undefined;
|
|
2493
|
+
this.resolvePendingPermissionsForAgent(agent, event.provider, options, "Interrupted");
|
|
2494
|
+
if (!isForegroundEvent) {
|
|
2495
|
+
this.emitState(agent);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
onStreamTurnStarted(params) {
|
|
2499
|
+
const { agent, eventTurnId, isForegroundEvent } = params;
|
|
2500
|
+
this.logger.trace({
|
|
2501
|
+
agentId: agent.id,
|
|
2502
|
+
provider: agent.provider,
|
|
2503
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2504
|
+
turnId: eventTurnId,
|
|
2505
|
+
lifecycle: agent.lifecycle,
|
|
2506
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2507
|
+
}, "agent.manager.turn.started");
|
|
2508
|
+
if (!isForegroundEvent) {
|
|
2509
|
+
agent.lifecycle = "running";
|
|
2510
|
+
this.emitState(agent);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
onStreamPermissionRequested(agent, event) {
|
|
2514
|
+
const hadPendingPermissions = agent.pendingPermissions.size > 0;
|
|
2515
|
+
agent.pendingPermissions.set(event.request.id, event.request);
|
|
2516
|
+
if (!hadPendingPermissions && !agent.internal) {
|
|
2517
|
+
this.broadcastAgentAttention(agent, "permission");
|
|
2518
|
+
}
|
|
2519
|
+
this.emitState(agent);
|
|
2520
|
+
}
|
|
2521
|
+
onStreamPermissionResolved(params) {
|
|
2522
|
+
const { agent, event, options, flags } = params;
|
|
2523
|
+
agent.pendingPermissions.delete(event.requestId);
|
|
2524
|
+
if (!options?.fromHistory && agent.inFlightPermissionResponses.has(event.requestId)) {
|
|
2525
|
+
agent.bufferedPermissionResolutions.set(event.requestId, event);
|
|
2526
|
+
flags.shouldDispatchEvent = false;
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
this.emitState(agent);
|
|
2530
|
+
}
|
|
2531
|
+
resolvePendingPermissionsForAgent(agent, provider, options, message) {
|
|
2532
|
+
for (const [requestId] of agent.pendingPermissions) {
|
|
2533
|
+
agent.pendingPermissions.delete(requestId);
|
|
2534
|
+
if (!options?.fromHistory) {
|
|
2535
|
+
this.dispatchStream(agent.id, {
|
|
2536
|
+
type: "permission_resolved",
|
|
2537
|
+
provider,
|
|
2538
|
+
requestId,
|
|
2539
|
+
resolution: { behavior: "deny", message },
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
observedSubagentId(parentAgentId, key) {
|
|
2545
|
+
return `${parentAgentId}::sub::${key}`;
|
|
2546
|
+
}
|
|
2547
|
+
emitObservedSubagentState(input) {
|
|
2548
|
+
const payload = toObservedSubagentPayload(input);
|
|
2549
|
+
const entry = this.observedSubagents.get(input.id);
|
|
2550
|
+
if (entry) {
|
|
2551
|
+
entry.lastPayload = payload;
|
|
2552
|
+
}
|
|
2553
|
+
this.dispatch({
|
|
2554
|
+
type: "observed_agent_state",
|
|
2555
|
+
payload,
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
onObservedSubagentUpdated(agent, event) {
|
|
2559
|
+
const id = this.observedSubagentId(agent.id, event.update.key);
|
|
2560
|
+
const existing = this.observedSubagents.get(id);
|
|
2561
|
+
const createdAt = existing?.createdAt ?? new Date().toISOString();
|
|
2562
|
+
this.observedSubagents.set(id, {
|
|
2563
|
+
parentAgentId: agent.id,
|
|
2564
|
+
taskId: event.update.taskId ?? existing?.taskId,
|
|
2565
|
+
provider: event.provider,
|
|
2566
|
+
createdAt,
|
|
2567
|
+
});
|
|
2568
|
+
this.emitObservedSubagentState({
|
|
2569
|
+
id,
|
|
2570
|
+
parentAgentId: agent.id,
|
|
2571
|
+
provider: event.provider,
|
|
2572
|
+
cwd: agent.cwd,
|
|
2573
|
+
...(agent.workspaceId ? { workspaceId: agent.workspaceId } : {}),
|
|
2574
|
+
createdAt,
|
|
2575
|
+
update: event.update,
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
onObservedSubagentTimeline(agent, event) {
|
|
2579
|
+
const id = this.observedSubagentId(agent.id, event.key);
|
|
2580
|
+
// Timeline may arrive before the first lifecycle update; materialize the
|
|
2581
|
+
// observed subagent so its pane can open.
|
|
2582
|
+
if (!this.observedSubagents.has(id)) {
|
|
2583
|
+
const createdAt = new Date().toISOString();
|
|
2584
|
+
this.observedSubagents.set(id, {
|
|
2585
|
+
parentAgentId: agent.id,
|
|
2586
|
+
provider: event.provider,
|
|
2587
|
+
createdAt,
|
|
2588
|
+
});
|
|
2589
|
+
this.emitObservedSubagentState({
|
|
2590
|
+
id,
|
|
2591
|
+
parentAgentId: agent.id,
|
|
2592
|
+
provider: event.provider,
|
|
2593
|
+
cwd: agent.cwd,
|
|
2594
|
+
...(agent.workspaceId ? { workspaceId: agent.workspaceId } : {}),
|
|
2595
|
+
createdAt,
|
|
2596
|
+
update: { key: event.key, status: "running" },
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
this.recordAndDispatchTimelineItem(id, event.item, event.provider, event.turnId);
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Stop a running observed subagent by resolving it to its owning provider
|
|
2603
|
+
* session's task. See projects/observed-subagents/observed-subagents.md.
|
|
2604
|
+
*/
|
|
2605
|
+
async stopObservedSubagent(observedId) {
|
|
2606
|
+
const entry = this.observedSubagents.get(observedId);
|
|
2607
|
+
if (!entry) {
|
|
2608
|
+
// Not a synthetic task-fan-out subagent — check for a real internal
|
|
2609
|
+
// agent (e.g. artifact generation) rendered read-only via `attend:
|
|
2610
|
+
// "observed"`, and stop it the normal way instead.
|
|
2611
|
+
const agent = this.agents.get(observedId);
|
|
2612
|
+
if (agent?.internal) {
|
|
2613
|
+
await this.cancelAgentRun(observedId);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
throw new Error(`Observed subagent not found: ${observedId}`);
|
|
2617
|
+
}
|
|
2618
|
+
if (!entry.taskId) {
|
|
2619
|
+
throw new Error(`Observed subagent has no task id to stop: ${observedId}`);
|
|
2620
|
+
}
|
|
2621
|
+
const parent = this.agents.get(entry.parentAgentId);
|
|
2622
|
+
const session = parent && "session" in parent ? parent.session : undefined;
|
|
2623
|
+
if (!session?.stopTask) {
|
|
2624
|
+
throw new Error(`Parent session cannot stop observed subagents: ${observedId}`);
|
|
2625
|
+
}
|
|
2626
|
+
await session.stopTask(entry.taskId);
|
|
2627
|
+
}
|
|
2628
|
+
recordAndDispatchTimelineItem(agentId, item, provider, turnId) {
|
|
2629
|
+
const row = this.recordTimeline(agentId, item);
|
|
2630
|
+
const event = {
|
|
2631
|
+
type: "timeline",
|
|
2632
|
+
item,
|
|
2633
|
+
provider,
|
|
2634
|
+
...(turnId !== undefined ? { turnId } : {}),
|
|
2635
|
+
};
|
|
2636
|
+
this.dispatchStream(agentId, event, {
|
|
2637
|
+
seq: row.seq,
|
|
2638
|
+
epoch: this.timelineStore.getEpoch(agentId),
|
|
2639
|
+
timestamp: row.timestamp,
|
|
2640
|
+
});
|
|
2641
|
+
if (item.type === "tool_call" &&
|
|
2642
|
+
item.status === "completed" &&
|
|
2643
|
+
item.detail?.type === "shell" &&
|
|
2644
|
+
commandMayHaveChangedExternalState(item.detail.command)) {
|
|
2645
|
+
const agent = this.agents.get(agentId);
|
|
2646
|
+
if (agent) {
|
|
2647
|
+
this.onWorkspaceStateMayHaveChanged?.({ cwd: agent.cwd });
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
return event;
|
|
2651
|
+
}
|
|
2652
|
+
async appendSystemErrorTimelineMessage(agent, provider, message, options) {
|
|
2653
|
+
if (options?.fromHistory) {
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
const normalized = message.trim();
|
|
2657
|
+
if (!normalized) {
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
const text = `${SYSTEM_ERROR_PREFIX} ${normalized}`;
|
|
2661
|
+
const lastItem = await this.getLastItemFromStores(agent.id);
|
|
2662
|
+
if (lastItem?.type === "assistant_message" && lastItem.text === text) {
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
const item = { type: "assistant_message", text };
|
|
2666
|
+
const row = this.recordTimeline(agent.id, item);
|
|
2667
|
+
this.dispatchStream(agent.id, {
|
|
2668
|
+
type: "timeline",
|
|
2669
|
+
item,
|
|
2670
|
+
provider,
|
|
2671
|
+
}, {
|
|
2672
|
+
seq: row.seq,
|
|
2673
|
+
epoch: this.timelineStore.getEpoch(agent.id),
|
|
2674
|
+
timestamp: row.timestamp,
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
formatTurnFailedMessage(event) {
|
|
2678
|
+
const base = event.error.trim();
|
|
2679
|
+
const parts = [base.length > 0 ? base : "Provider run failed"];
|
|
2680
|
+
const code = event.code?.trim();
|
|
2681
|
+
if (code) {
|
|
2682
|
+
parts.push(`code: ${code}`);
|
|
2683
|
+
}
|
|
2684
|
+
const diagnostic = event.diagnostic?.trim();
|
|
2685
|
+
if (diagnostic && diagnostic !== base) {
|
|
2686
|
+
parts.push(diagnostic);
|
|
2687
|
+
}
|
|
2688
|
+
return parts.join("\n\n");
|
|
2689
|
+
}
|
|
2690
|
+
recordTimeline(agentId, item, options) {
|
|
2691
|
+
const row = this.timelineStore.append(agentId, item, options);
|
|
2692
|
+
this.enqueueDurableTimelineAppend(agentId, row);
|
|
2693
|
+
return row;
|
|
2694
|
+
}
|
|
2695
|
+
emitState(agent, options) {
|
|
2696
|
+
// Keep attention as an edge-triggered unread signal, not a level signal.
|
|
2697
|
+
this.checkAndSetAttention(agent);
|
|
2698
|
+
if (options?.persist !== false) {
|
|
2699
|
+
this.enqueueBackgroundPersist(agent);
|
|
2700
|
+
}
|
|
2701
|
+
this.syncFeaturesFromSession(agent);
|
|
2702
|
+
this.logger.trace({
|
|
2703
|
+
agentId: agent.id,
|
|
2704
|
+
provider: agent.provider,
|
|
2705
|
+
sessionId: agent.persistence?.sessionId ?? undefined,
|
|
2706
|
+
turnId: agent.activeForegroundTurnId ?? undefined,
|
|
2707
|
+
lifecycle: agent.lifecycle,
|
|
2708
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
2709
|
+
pendingPermissions: agent.pendingPermissions.size,
|
|
2710
|
+
persist: options?.persist !== false,
|
|
2711
|
+
}, "agent.manager.emit_state");
|
|
2712
|
+
this.dispatch({
|
|
2713
|
+
type: "agent_state",
|
|
2714
|
+
agent: { ...agent },
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
syncFeaturesFromSession(agent) {
|
|
2718
|
+
if ("session" in agent && agent.session?.features) {
|
|
2719
|
+
agent.features = agent.session.features;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
checkAndSetAttention(agent) {
|
|
2723
|
+
const previousStatus = this.previousStatuses.get(agent.id);
|
|
2724
|
+
const currentStatus = agent.lifecycle;
|
|
2725
|
+
// Track the new status
|
|
2726
|
+
this.previousStatuses.set(agent.id, currentStatus);
|
|
2727
|
+
// Skip attention tracking for internal agents
|
|
2728
|
+
if (agent.internal) {
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
// Skip if already requires attention
|
|
2732
|
+
if (agent.attention.requiresAttention) {
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
// Check if agent transitioned from running to idle (finished)
|
|
2736
|
+
if (previousStatus === "running" && currentStatus === "idle") {
|
|
2737
|
+
agent.attention = {
|
|
2738
|
+
requiresAttention: true,
|
|
2739
|
+
attentionReason: "finished",
|
|
2740
|
+
attentionTimestamp: new Date(),
|
|
2741
|
+
};
|
|
2742
|
+
this.broadcastAgentAttention(agent, "finished");
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
// Check if agent entered error state
|
|
2746
|
+
if (previousStatus !== "error" && currentStatus === "error") {
|
|
2747
|
+
agent.attention = {
|
|
2748
|
+
requiresAttention: true,
|
|
2749
|
+
attentionReason: "error",
|
|
2750
|
+
attentionTimestamp: new Date(),
|
|
2751
|
+
};
|
|
2752
|
+
this.broadcastAgentAttention(agent, "error");
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
enqueueBackgroundPersist(agent) {
|
|
2757
|
+
const task = this.persistSnapshot(agent).catch((err) => {
|
|
2758
|
+
this.logger.error({ err, agentId: agent.id }, "Failed to persist agent snapshot");
|
|
2759
|
+
});
|
|
2760
|
+
this.trackBackgroundTask(task);
|
|
2761
|
+
}
|
|
2762
|
+
enqueueDurableTimelineAppend(agentId, row) {
|
|
2763
|
+
if (!this.durableTimelineStore) {
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
const task = this.durableTimelineStore
|
|
2767
|
+
.bulkInsert(agentId, [row])
|
|
2768
|
+
.then(() => undefined)
|
|
2769
|
+
.catch((err) => {
|
|
2770
|
+
this.logger.error({ err, agentId, seq: row.seq, itemType: row.item.type }, "Failed to append timeline row to durable store");
|
|
2771
|
+
});
|
|
2772
|
+
this.trackBackgroundTask(task);
|
|
2773
|
+
}
|
|
2774
|
+
enqueueDurableTimelineBulkInsert(agentId, rows) {
|
|
2775
|
+
if (!this.durableTimelineStore || rows.length === 0) {
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
const task = this.durableTimelineStore.bulkInsert(agentId, rows).catch((err) => {
|
|
2779
|
+
this.logger.error({ err, agentId, rowCount: rows.length }, "Failed to seed durable timeline store");
|
|
2780
|
+
});
|
|
2781
|
+
this.trackBackgroundTask(task);
|
|
2782
|
+
}
|
|
2783
|
+
trackBackgroundTask(task) {
|
|
2784
|
+
this.backgroundTasks.add(task);
|
|
2785
|
+
void task.finally(() => {
|
|
2786
|
+
this.backgroundTasks.delete(task);
|
|
2787
|
+
});
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Flush any background persistence work (best-effort).
|
|
2791
|
+
* Used by daemon shutdown paths to avoid unhandled rejections after cleanup.
|
|
2792
|
+
*/
|
|
2793
|
+
async flush() {
|
|
2794
|
+
this.agentStreamCoalescer.flushAll();
|
|
2795
|
+
// Drain tasks, including tasks spawned while awaiting.
|
|
2796
|
+
while (this.backgroundTasks.size > 0) {
|
|
2797
|
+
const pending = Array.from(this.backgroundTasks);
|
|
2798
|
+
await Promise.allSettled(pending);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
broadcastAgentAttention(agent, reason) {
|
|
2802
|
+
if (isDelegatedAgent(agent)) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
this.onAgentAttention?.({
|
|
2806
|
+
agentId: agent.id,
|
|
2807
|
+
provider: agent.provider,
|
|
2808
|
+
reason,
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
dispatchStream(agentId, event, metadata) {
|
|
2812
|
+
const agent = this.agents.get(agentId);
|
|
2813
|
+
this.logger.trace({
|
|
2814
|
+
agentId,
|
|
2815
|
+
provider: event.provider,
|
|
2816
|
+
sessionId: agent?.persistence?.sessionId ?? undefined,
|
|
2817
|
+
turnId: getAgentStreamEventTurnId(event),
|
|
2818
|
+
metadata,
|
|
2819
|
+
event,
|
|
2820
|
+
}, "agent.manager.dispatch_stream");
|
|
2821
|
+
this.dispatch({ type: "agent_stream", agentId, event, ...metadata });
|
|
2822
|
+
}
|
|
2823
|
+
dispatch(event) {
|
|
2824
|
+
for (const subscriber of this.subscribers) {
|
|
2825
|
+
if (subscriber.agentId &&
|
|
2826
|
+
event.type === "agent_stream" &&
|
|
2827
|
+
subscriber.agentId !== event.agentId) {
|
|
2828
|
+
continue;
|
|
2829
|
+
}
|
|
2830
|
+
if (subscriber.agentId &&
|
|
2831
|
+
event.type === "agent_state" &&
|
|
2832
|
+
subscriber.agentId !== event.agent.id) {
|
|
2833
|
+
continue;
|
|
2834
|
+
}
|
|
2835
|
+
// Skip internal agents for global subscribers (those without a specific agentId)
|
|
2836
|
+
if (!subscriber.agentId) {
|
|
2837
|
+
if (event.type === "agent_state" && event.agent.internal) {
|
|
2838
|
+
continue;
|
|
2839
|
+
}
|
|
2840
|
+
if (event.type === "agent_stream") {
|
|
2841
|
+
const agent = this.agents.get(event.agentId);
|
|
2842
|
+
// Observable internal agents (e.g. artifact generation) still stream
|
|
2843
|
+
// so a client that opened their timeline sees messages live; other
|
|
2844
|
+
// internal agents (branch-name/git-metadata generators) stay silent.
|
|
2845
|
+
// agent_state above is always filtered, so neither clutters the sidebar.
|
|
2846
|
+
if (agent?.internal && !agent.observable) {
|
|
2847
|
+
continue;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
subscriber.callback(event);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
async normalizeConfig(config, options = {}) {
|
|
2855
|
+
const normalized = { ...config };
|
|
2856
|
+
// Always resolve cwd to absolute path for consistent history file lookup
|
|
2857
|
+
if (normalized.cwd) {
|
|
2858
|
+
normalized.cwd = resolve(normalized.cwd);
|
|
2859
|
+
try {
|
|
2860
|
+
const cwdStats = await stat(normalized.cwd);
|
|
2861
|
+
if (!cwdStats.isDirectory()) {
|
|
2862
|
+
throw new Error(`Working directory is not a directory: ${normalized.cwd}`);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
catch (error) {
|
|
2866
|
+
if (error instanceof Error &&
|
|
2867
|
+
"code" in error &&
|
|
2868
|
+
error.code === "ENOENT") {
|
|
2869
|
+
throw new Error(`Working directory does not exist: ${normalized.cwd}`, { cause: error });
|
|
2870
|
+
}
|
|
2871
|
+
if (error instanceof Error) {
|
|
2872
|
+
throw error;
|
|
2873
|
+
}
|
|
2874
|
+
throw new Error(`Failed to access working directory: ${normalized.cwd}`, { cause: error });
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
if (typeof normalized.model === "string") {
|
|
2878
|
+
const trimmed = normalized.model.trim();
|
|
2879
|
+
normalized.model = trimmed.length > 0 && trimmed !== "default" ? trimmed : undefined;
|
|
2880
|
+
}
|
|
2881
|
+
const shouldResolveDefaultModel = options.resolveDefaultModel ?? true;
|
|
2882
|
+
if (shouldResolveDefaultModel && !normalized.model) {
|
|
2883
|
+
const defaultModelId = await this.resolveDefaultModelId(normalized);
|
|
2884
|
+
if (defaultModelId) {
|
|
2885
|
+
normalized.model = defaultModelId;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
if (!normalized.modeId) {
|
|
2889
|
+
try {
|
|
2890
|
+
normalized.modeId =
|
|
2891
|
+
getAgentProviderDefinition(normalized.provider).defaultModeId ?? undefined;
|
|
2892
|
+
}
|
|
2893
|
+
catch {
|
|
2894
|
+
// Unknown provider
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
return normalized;
|
|
2898
|
+
}
|
|
2899
|
+
async resolveDefaultModelId(config) {
|
|
2900
|
+
const client = this.clients.get(config.provider);
|
|
2901
|
+
if (!client) {
|
|
2902
|
+
return undefined;
|
|
2903
|
+
}
|
|
2904
|
+
try {
|
|
2905
|
+
const catalog = await client.fetchCatalog({
|
|
2906
|
+
scope: "workspace",
|
|
2907
|
+
cwd: config.cwd,
|
|
2908
|
+
force: false,
|
|
2909
|
+
});
|
|
2910
|
+
return (catalog.models.find((model) => model.isDefault) ?? catalog.models[0])?.id;
|
|
2911
|
+
}
|
|
2912
|
+
catch {
|
|
2913
|
+
// Provider may not support model listing — leave model undefined.
|
|
2914
|
+
return undefined;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
async prepareSessionConfig(config, agentId) {
|
|
2918
|
+
const storedConfig = await this.normalizeConfig(stripInternalOttoMcpServer(config));
|
|
2919
|
+
const launchConfig = this.applyDaemonAppendSystemPrompt(withRuntimeOttoMcpServer({
|
|
2920
|
+
config: storedConfig,
|
|
2921
|
+
agentId,
|
|
2922
|
+
mcpBaseUrl: this.mcpBaseUrl,
|
|
2923
|
+
mcpAuthToken: this.mcpAuthToken,
|
|
2924
|
+
}));
|
|
2925
|
+
return { storedConfig, launchConfig };
|
|
2926
|
+
}
|
|
2927
|
+
applyDaemonAppendSystemPrompt(config) {
|
|
2928
|
+
const daemonAppendSystemPrompt = this.appendSystemPrompt.trim();
|
|
2929
|
+
const next = { ...config };
|
|
2930
|
+
delete next.daemonAppendSystemPrompt;
|
|
2931
|
+
// A personality with respectGlobalAppendPrompt === false owns its whole
|
|
2932
|
+
// system prompt — the daemon-global append must not stack on top of it.
|
|
2933
|
+
const suppressGlobalAppend = config.personalitySnapshot?.respectGlobalAppendPrompt === false;
|
|
2934
|
+
return daemonAppendSystemPrompt && !suppressGlobalAppend
|
|
2935
|
+
? {
|
|
2936
|
+
...next,
|
|
2937
|
+
daemonAppendSystemPrompt,
|
|
2938
|
+
}
|
|
2939
|
+
: next;
|
|
2940
|
+
}
|
|
2941
|
+
async buildLaunchContext(agentId, client, env) {
|
|
2942
|
+
const context = {
|
|
2943
|
+
agentId,
|
|
2944
|
+
env: {
|
|
2945
|
+
...env,
|
|
2946
|
+
OTTO_AGENT_ID: agentId,
|
|
2947
|
+
},
|
|
2948
|
+
};
|
|
2949
|
+
if (this.ottoToolsEnabled &&
|
|
2950
|
+
client.capabilities.supportsNativeOttoTools &&
|
|
2951
|
+
this.ottoToolCatalogFactory) {
|
|
2952
|
+
context.ottoTools = await this.ottoToolCatalogFactory({ callerAgentId: agentId });
|
|
2953
|
+
}
|
|
2954
|
+
return context;
|
|
2955
|
+
}
|
|
2956
|
+
resolveProviderLaunchConfig(launchConfig, launchContext) {
|
|
2957
|
+
return launchContext.ottoTools ? stripInternalOttoMcpServer(launchConfig) : launchConfig;
|
|
2958
|
+
}
|
|
2959
|
+
async requireAvailableClient(options) {
|
|
2960
|
+
const client = this.clients.get(options.provider);
|
|
2961
|
+
if (!client) {
|
|
2962
|
+
const configuredProviders = this.getConfiguredProviderIds();
|
|
2963
|
+
throw new Error(`Unknown provider '${options.provider}'. Configured providers: ${formatProviderList(configuredProviders)}.`);
|
|
2964
|
+
}
|
|
2965
|
+
let unavailableReason = null;
|
|
2966
|
+
try {
|
|
2967
|
+
const available = await client.isAvailable();
|
|
2968
|
+
if (available) {
|
|
2969
|
+
return client;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
catch (error) {
|
|
2973
|
+
unavailableReason = error instanceof Error ? error.message : String(error);
|
|
2974
|
+
}
|
|
2975
|
+
const availableProviders = (await this.listProviderAvailability())
|
|
2976
|
+
.filter((entry) => entry.available)
|
|
2977
|
+
.map((entry) => entry.provider);
|
|
2978
|
+
const providerList = formatProviderList(availableProviders);
|
|
2979
|
+
const reason = unavailableReason ? ` Reason: ${unavailableReason}.` : "";
|
|
2980
|
+
throw new Error(`Provider '${options.provider}' is not available.${reason} Available providers: ${providerList}. Use one of those providers, or install/configure '${options.provider}'.`);
|
|
2981
|
+
}
|
|
2982
|
+
requireEnabledProvider(provider) {
|
|
2983
|
+
if (this.providerEnabled.get(provider) === false) {
|
|
2984
|
+
throw new Error(`Provider '${provider}' is disabled`);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
getConfiguredProviderIds() {
|
|
2988
|
+
return Array.from(new Set([...this.providerEnabled.keys(), ...this.clients.keys()]));
|
|
2989
|
+
}
|
|
2990
|
+
requireClient(provider) {
|
|
2991
|
+
const client = this.clients.get(provider);
|
|
2992
|
+
if (!client) {
|
|
2993
|
+
throw new Error(`No client registered for provider '${provider}'`);
|
|
2994
|
+
}
|
|
2995
|
+
return client;
|
|
2996
|
+
}
|
|
2997
|
+
async archiveNativeSessionBestEffort(provider, persistence) {
|
|
2998
|
+
if (!persistence)
|
|
2999
|
+
return;
|
|
3000
|
+
const client = this.clients.get(provider);
|
|
3001
|
+
if (!client?.archiveNativeSession)
|
|
3002
|
+
return;
|
|
3003
|
+
try {
|
|
3004
|
+
await client.archiveNativeSession(persistence);
|
|
3005
|
+
}
|
|
3006
|
+
catch (error) {
|
|
3007
|
+
this.logger.warn({ error, provider, sessionId: persistence.sessionId }, "Failed to archive native session (best-effort)");
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
async unarchiveNativeSession(provider, persistence) {
|
|
3011
|
+
if (!persistence)
|
|
3012
|
+
return;
|
|
3013
|
+
const client = this.clients.get(provider);
|
|
3014
|
+
if (!client?.unarchiveNativeSession)
|
|
3015
|
+
return;
|
|
3016
|
+
await client.unarchiveNativeSession(persistence);
|
|
3017
|
+
}
|
|
3018
|
+
requireAgent(id) {
|
|
3019
|
+
const normalizedId = validateAgentId(id, "requireAgent");
|
|
3020
|
+
const agent = this.agents.get(normalizedId);
|
|
3021
|
+
if (!agent) {
|
|
3022
|
+
throw new Error(`Unknown agent '${normalizedId}'`);
|
|
3023
|
+
}
|
|
3024
|
+
return agent;
|
|
3025
|
+
}
|
|
3026
|
+
requireSessionAgent(id) {
|
|
3027
|
+
const agent = this.requireAgent(id);
|
|
3028
|
+
if (agent.session === null) {
|
|
3029
|
+
throw new Error(`Agent '${agent.id}' has no managed session`);
|
|
3030
|
+
}
|
|
3031
|
+
return agent;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
export function commandMayHaveChangedExternalState(command) {
|
|
3035
|
+
const normalized = command.toLowerCase();
|
|
3036
|
+
// Commands that operate on remote state and do NOT trigger local file
|
|
3037
|
+
// watchers. Local git mutations (commit, checkout, merge, rebase, reset,
|
|
3038
|
+
// pull) are already caught by watchers on .git/HEAD and refs/heads/.
|
|
3039
|
+
return (
|
|
3040
|
+
// GitHub PR operations (merge, close, create, edit, comment, review)
|
|
3041
|
+
/\bgh\s+pr\s+(merge|close|create|edit|comment|review)\b/.test(normalized) ||
|
|
3042
|
+
// Pushes to remote — local refs unchanged, but remote state (PR checks,
|
|
3043
|
+
// mergeable status) may shift immediately after.
|
|
3044
|
+
/\bgit\s+push\b/.test(normalized) ||
|
|
3045
|
+
// Fetches update refs/remotes/ which our watchers do not watch, so
|
|
3046
|
+
// ahead/behind counts can drift stale until the next refresh.
|
|
3047
|
+
/\bgit\s+fetch\b/.test(normalized));
|
|
3048
|
+
}
|
|
3049
|
+
//# sourceMappingURL=agent-manager.js.map
|