@getpaseo/server 0.1.15 → 0.1.17
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/dist/scripts/daemon-runner.js +53 -14
- package/dist/scripts/daemon-runner.js.map +1 -1
- package/dist/scripts/dev-runner.js +9 -16
- package/dist/scripts/dev-runner.js.map +1 -1
- package/dist/scripts/supervisor.js +40 -13
- package/dist/scripts/supervisor.js.map +1 -1
- package/dist/server/client/daemon-client.d.ts +63 -6
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +436 -92
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +13 -1
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +404 -39
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +13 -4
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts +5 -0
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +24 -0
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-response-loop.js +1 -1
- package/dist/server/server/agent/agent-response-loop.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +20 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js +11 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +20 -6
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +43 -72
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/agent-title-limits.d.ts +3 -0
- package/dist/server/server/agent/agent-title-limits.d.ts.map +1 -0
- package/dist/server/server/agent/agent-title-limits.js +3 -0
- package/dist/server/server/agent/agent-title-limits.js.map +1 -0
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts +29 -0
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/model-catalog.js +70 -0
- package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +44 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +250 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +17 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +2 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts +10 -3
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +1702 -330
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +81 -28
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +50 -9
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +207 -176
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +55 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
- package/dist/server/server/agent/timeline-projection.d.ts +20 -0
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
- package/dist/server/server/agent/timeline-projection.js +73 -0
- package/dist/server/server/agent/timeline-projection.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +15 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +27 -4
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/client-message-id.d.ts +3 -0
- package/dist/server/server/client-message-id.d.ts.map +1 -0
- package/dist/server/server/client-message-id.js +12 -0
- package/dist/server/server/client-message-id.js.map +1 -0
- package/dist/server/server/file-download/token-store.d.ts +0 -1
- package/dist/server/server/file-download/token-store.d.ts.map +1 -1
- package/dist/server/server/file-download/token-store.js.map +1 -1
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +56 -36
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/index.js +85 -29
- package/dist/server/server/index.js.map +1 -1
- package/dist/server/server/logger.d.ts +24 -3
- package/dist/server/server/logger.d.ts.map +1 -1
- package/dist/server/server/logger.js +157 -21
- package/dist/server/server/logger.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +94 -8
- package/dist/server/server/persisted-config.d.ts.map +1 -1
- package/dist/server/server/persisted-config.js +25 -3
- package/dist/server/server/persisted-config.js.map +1 -1
- package/dist/server/server/persistence-hooks.js +1 -1
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/pid-lock.d.ts +6 -2
- package/dist/server/server/pid-lock.d.ts.map +1 -1
- package/dist/server/server/pid-lock.js +7 -10
- package/dist/server/server/pid-lock.js.map +1 -1
- package/dist/server/server/relay-transport.js +28 -28
- package/dist/server/server/relay-transport.js.map +1 -1
- package/dist/server/server/session.d.ts +60 -4
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +854 -190
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +24 -5
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +400 -77
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +45 -2
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/shared/daemon-endpoints.d.ts +9 -1
- package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
- package/dist/server/shared/daemon-endpoints.js +18 -3
- package/dist/server/shared/daemon-endpoints.js.map +1 -1
- package/dist/server/shared/messages.d.ts +4432 -380
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +139 -6
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/tool-call-display.d.ts.map +1 -1
- package/dist/server/shared/tool-call-display.js +7 -0
- package/dist/server/shared/tool-call-display.js.map +1 -1
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/terminal-manager.js +1 -13
- package/dist/server/terminal/terminal-manager.js.map +1 -1
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +29 -5
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/worktree.d.ts +1 -0
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +17 -2
- package/dist/server/utils/worktree.js.map +1 -1
- package/dist/src/server/agent/activity-curator.js +228 -0
- package/dist/src/server/agent/activity-curator.js.map +1 -0
- package/dist/src/server/agent/agent-manager.js +1712 -0
- package/dist/src/server/agent/agent-manager.js.map +1 -0
- package/dist/src/server/agent/agent-metadata-generator.js +163 -0
- package/dist/src/server/agent/agent-metadata-generator.js.map +1 -0
- package/dist/src/server/agent/agent-projections.js +262 -0
- package/dist/src/server/agent/agent-projections.js.map +1 -0
- package/dist/src/server/agent/agent-response-loop.js +304 -0
- package/dist/src/server/agent/agent-response-loop.js.map +1 -0
- package/dist/src/server/agent/agent-sdk-types.js +12 -0
- package/dist/src/server/agent/agent-sdk-types.js.map +1 -0
- package/dist/src/server/agent/agent-storage.js +299 -0
- package/dist/src/server/agent/agent-storage.js.map +1 -0
- package/dist/src/server/agent/agent-title-limits.js +3 -0
- package/dist/src/server/agent/agent-title-limits.js.map +1 -0
- package/dist/src/server/agent/audio-utils.js +19 -0
- package/dist/src/server/agent/audio-utils.js.map +1 -0
- package/dist/src/server/agent/dictation-debug.js +50 -0
- package/dist/src/server/agent/dictation-debug.js.map +1 -0
- package/dist/src/server/agent/mcp-server.js +787 -0
- package/dist/src/server/agent/mcp-server.js.map +1 -0
- package/dist/src/server/agent/orchestrator-instructions.js +51 -0
- package/dist/src/server/agent/orchestrator-instructions.js.map +1 -0
- package/dist/src/server/agent/pcm16-resampler.js +63 -0
- package/dist/src/server/agent/pcm16-resampler.js.map +1 -0
- package/dist/src/server/agent/provider-launch-config.js +83 -0
- package/dist/src/server/agent/provider-launch-config.js.map +1 -0
- package/dist/src/server/agent/provider-manifest.js +97 -0
- package/dist/src/server/agent/provider-manifest.js.map +1 -0
- package/dist/src/server/agent/provider-registry.js +45 -0
- package/dist/src/server/agent/provider-registry.js.map +1 -0
- package/dist/src/server/agent/providers/claude/model-catalog.js +70 -0
- package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -0
- package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +250 -0
- package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -0
- package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +109 -0
- package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
- package/dist/src/server/agent/providers/claude/tool-call-mapper.js +238 -0
- package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
- package/dist/src/server/agent/providers/claude-agent.js +3747 -0
- package/dist/src/server/agent/providers/claude-agent.js.map +1 -0
- package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
- package/dist/src/server/agent/providers/codex/tool-call-mapper.js +720 -0
- package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
- package/dist/src/server/agent/providers/codex-app-server-agent.js +2601 -0
- package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -0
- package/dist/src/server/agent/providers/codex-rollout-timeline.js +487 -0
- package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -0
- package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
- package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
- package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +151 -0
- package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
- package/dist/src/server/agent/providers/opencode-agent.js +905 -0
- package/dist/src/server/agent/providers/opencode-agent.js.map +1 -0
- package/dist/src/server/agent/providers/tool-call-detail-primitives.js +552 -0
- package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
- package/dist/src/server/agent/providers/tool-call-mapper-utils.js +109 -0
- package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
- package/dist/src/server/agent/recordings-debug.js +19 -0
- package/dist/src/server/agent/recordings-debug.js.map +1 -0
- package/dist/src/server/agent/stt-debug.js +33 -0
- package/dist/src/server/agent/stt-debug.js.map +1 -0
- package/dist/src/server/agent/stt-manager.js +233 -0
- package/dist/src/server/agent/stt-manager.js.map +1 -0
- package/dist/src/server/agent/timeline-append.js +27 -0
- package/dist/src/server/agent/timeline-append.js.map +1 -0
- package/dist/src/server/agent/timeline-projection.js +215 -0
- package/dist/src/server/agent/timeline-projection.js.map +1 -0
- package/dist/src/server/agent/tool-name-normalization.js +45 -0
- package/dist/src/server/agent/tool-name-normalization.js.map +1 -0
- package/dist/src/server/agent/tts-debug.js +24 -0
- package/dist/src/server/agent/tts-debug.js.map +1 -0
- package/dist/src/server/agent/tts-manager.js +249 -0
- package/dist/src/server/agent/tts-manager.js.map +1 -0
- package/dist/src/server/agent/wait-for-agent-tracker.js +53 -0
- package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -0
- package/dist/src/server/agent-attention-policy.js +40 -0
- package/dist/src/server/agent-attention-policy.js.map +1 -0
- package/dist/src/server/allowed-hosts.js +94 -0
- package/dist/src/server/allowed-hosts.js.map +1 -0
- package/dist/src/server/bootstrap.js +498 -0
- package/dist/src/server/bootstrap.js.map +1 -0
- package/dist/src/server/client-message-id.js +12 -0
- package/dist/src/server/client-message-id.js.map +1 -0
- package/dist/src/server/config.js +84 -0
- package/dist/src/server/config.js.map +1 -0
- package/dist/src/server/connection-offer.js +60 -0
- package/dist/src/server/connection-offer.js.map +1 -0
- package/dist/src/server/daemon-keypair.js +40 -0
- package/dist/src/server/daemon-keypair.js.map +1 -0
- package/dist/src/server/daemon-version.js +22 -0
- package/dist/src/server/daemon-version.js.map +1 -0
- package/dist/src/server/dictation/dictation-stream-manager.js +568 -0
- package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -0
- package/dist/src/server/file-download/token-store.js +40 -0
- package/dist/src/server/file-download/token-store.js.map +1 -0
- package/dist/src/server/file-explorer/service.js +183 -0
- package/dist/src/server/file-explorer/service.js.map +1 -0
- package/dist/src/server/json-utils.js +45 -0
- package/dist/src/server/json-utils.js.map +1 -0
- package/dist/src/server/messages.js +29 -0
- package/dist/src/server/messages.js.map +1 -0
- package/dist/src/server/package-version.js +47 -0
- package/dist/src/server/package-version.js.map +1 -0
- package/dist/src/server/paseo-home.js +19 -0
- package/dist/src/server/paseo-home.js.map +1 -0
- package/dist/src/server/path-utils.js +20 -0
- package/dist/src/server/path-utils.js.map +1 -0
- package/dist/src/server/persisted-config.js +259 -0
- package/dist/src/server/persisted-config.js.map +1 -0
- package/dist/src/server/persistence-hooks.js +60 -0
- package/dist/src/server/persistence-hooks.js.map +1 -0
- package/dist/src/server/pid-lock.js +126 -0
- package/dist/src/server/pid-lock.js.map +1 -0
- package/dist/src/server/push/push-service.js +68 -0
- package/dist/src/server/push/push-service.js.map +1 -0
- package/dist/src/server/push/token-store.js +70 -0
- package/dist/src/server/push/token-store.js.map +1 -0
- package/dist/src/server/relay-transport.js +457 -0
- package/dist/src/server/relay-transport.js.map +1 -0
- package/dist/src/server/server-id.js +63 -0
- package/dist/src/server/server-id.js.map +1 -0
- package/dist/src/server/session.js +5947 -0
- package/dist/src/server/session.js.map +1 -0
- package/dist/src/server/speech/audio.js +101 -0
- package/dist/src/server/speech/audio.js.map +1 -0
- package/dist/src/server/speech/provider-resolver.js +7 -0
- package/dist/src/server/speech/provider-resolver.js.map +1 -0
- package/dist/src/server/speech/providers/local/config.js +83 -0
- package/dist/src/server/speech/providers/local/config.js.map +1 -0
- package/dist/src/server/speech/providers/local/models.js +17 -0
- package/dist/src/server/speech/providers/local/models.js.map +1 -0
- package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +422 -0
- package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
- package/dist/src/server/speech/providers/local/runtime.js +253 -0
- package/dist/src/server/speech/providers/local/runtime.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +166 -0
- package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +165 -0
- package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +68 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +79 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +131 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +132 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +112 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +140 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +95 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
- package/dist/src/server/speech/providers/openai/config.js +99 -0
- package/dist/src/server/speech/providers/openai/config.js.map +1 -0
- package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +165 -0
- package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
- package/dist/src/server/speech/providers/openai/runtime.js +114 -0
- package/dist/src/server/speech/providers/openai/runtime.js.map +1 -0
- package/dist/src/server/speech/providers/openai/stt.js +208 -0
- package/dist/src/server/speech/providers/openai/stt.js.map +1 -0
- package/dist/src/server/speech/providers/openai/tts.js +46 -0
- package/dist/src/server/speech/providers/openai/tts.js.map +1 -0
- package/dist/src/server/speech/speech-config-resolver.js +85 -0
- package/dist/src/server/speech/speech-config-resolver.js.map +1 -0
- package/dist/src/server/speech/speech-provider.js +2 -0
- package/dist/src/server/speech/speech-provider.js.map +1 -0
- package/dist/src/server/speech/speech-runtime.js +497 -0
- package/dist/src/server/speech/speech-runtime.js.map +1 -0
- package/dist/src/server/speech/speech-types.js +8 -0
- package/dist/src/server/speech/speech-types.js.map +1 -0
- package/dist/src/server/utils/diff-highlighter.js +244 -0
- package/dist/src/server/utils/diff-highlighter.js.map +1 -0
- package/dist/src/server/utils/syntax-highlighter.js +145 -0
- package/dist/src/server/utils/syntax-highlighter.js.map +1 -0
- package/dist/src/server/voice-config.js +51 -0
- package/dist/src/server/voice-config.js.map +1 -0
- package/dist/src/server/voice-mcp-bridge-command.js +31 -0
- package/dist/src/server/voice-mcp-bridge-command.js.map +1 -0
- package/dist/src/server/voice-mcp-bridge.js +109 -0
- package/dist/src/server/voice-mcp-bridge.js.map +1 -0
- package/dist/src/server/voice-permission-policy.js +13 -0
- package/dist/src/server/voice-permission-policy.js.map +1 -0
- package/dist/src/server/voice-types.js +2 -0
- package/dist/src/server/voice-types.js.map +1 -0
- package/dist/src/server/websocket-server.js +967 -0
- package/dist/src/server/websocket-server.js.map +1 -0
- package/dist/src/server/worktree-bootstrap.js +497 -0
- package/dist/src/server/worktree-bootstrap.js.map +1 -0
- package/dist/src/shared/agent-attention-notification.js +130 -0
- package/dist/src/shared/agent-attention-notification.js.map +1 -0
- package/dist/src/shared/agent-lifecycle.js +8 -0
- package/dist/src/shared/agent-lifecycle.js.map +1 -0
- package/dist/src/shared/binary-mux.js +114 -0
- package/dist/src/shared/binary-mux.js.map +1 -0
- package/dist/src/shared/connection-offer.js +17 -0
- package/dist/src/shared/connection-offer.js.map +1 -0
- package/dist/src/shared/daemon-endpoints.js +113 -0
- package/dist/src/shared/daemon-endpoints.js.map +1 -0
- package/dist/src/shared/messages.js +2001 -0
- package/dist/src/shared/messages.js.map +1 -0
- package/dist/src/shared/path-utils.js +16 -0
- package/dist/src/shared/path-utils.js.map +1 -0
- package/dist/src/shared/tool-call-display.js +93 -0
- package/dist/src/shared/tool-call-display.js.map +1 -0
- package/dist/src/terminal/terminal-manager.js +136 -0
- package/dist/src/terminal/terminal-manager.js.map +1 -0
- package/dist/src/terminal/terminal.js +410 -0
- package/dist/src/terminal/terminal.js.map +1 -0
- package/dist/src/utils/checkout-git.js +1397 -0
- package/dist/src/utils/checkout-git.js.map +1 -0
- package/dist/src/utils/directory-suggestions.js +655 -0
- package/dist/src/utils/directory-suggestions.js.map +1 -0
- package/dist/src/utils/path.js +15 -0
- package/dist/src/utils/path.js.map +1 -0
- package/dist/src/utils/project-icon.js +391 -0
- package/dist/src/utils/project-icon.js.map +1 -0
- package/dist/src/utils/worktree-metadata.js +116 -0
- package/dist/src/utils/worktree-metadata.js.map +1 -0
- package/dist/src/utils/worktree.js +741 -0
- package/dist/src/utils/worktree.js.map +1 -0
- package/package.json +15 -7
|
@@ -6,22 +6,16 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
8
8
|
import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./claude/tool-call-mapper.js";
|
|
9
|
+
import { isTaskNotificationUserContent, mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./claude/task-notification-tool-call.js";
|
|
10
|
+
import { buildClaudeModelFamilyAliases, buildClaudeSelectableModelIds, listClaudeCatalogModels, } from "./claude/model-catalog.js";
|
|
11
|
+
import { buildToolCallDisplayModel } from "../../../shared/tool-call-display.js";
|
|
9
12
|
import { applyProviderEnv, isProviderCommandAvailable, } from "../provider-launch-config.js";
|
|
10
13
|
import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
|
|
11
14
|
const fsPromises = promises;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return fallback;
|
|
17
|
-
// Prefer concrete versioned labels from description (e.g. "Opus 4.6",
|
|
18
|
-
// "Sonnet 4.5"), especially when displayName is generic like
|
|
19
|
-
// "Default (recommended)".
|
|
20
|
-
if (/\d/.test(prefix)) {
|
|
21
|
-
return prefix;
|
|
22
|
-
}
|
|
23
|
-
return fallback;
|
|
24
|
-
}
|
|
15
|
+
const CLAUDE_SETTING_SOURCES = [
|
|
16
|
+
"user",
|
|
17
|
+
"project",
|
|
18
|
+
];
|
|
25
19
|
function normalizeModelIdCandidate(modelId) {
|
|
26
20
|
if (typeof modelId !== "string") {
|
|
27
21
|
return null;
|
|
@@ -36,6 +30,28 @@ function pickSupportedModelId(supportedModelIds, candidate) {
|
|
|
36
30
|
}
|
|
37
31
|
return supportedModelIds.has(normalizedCandidate) ? normalizedCandidate : null;
|
|
38
32
|
}
|
|
33
|
+
function inferClaudeModelFamilyFromText(text) {
|
|
34
|
+
if (typeof text !== "string") {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const lowerText = text.toLowerCase();
|
|
38
|
+
if (lowerText.includes("sonnet")) {
|
|
39
|
+
return "sonnet";
|
|
40
|
+
}
|
|
41
|
+
if (lowerText.includes("opus")) {
|
|
42
|
+
return "opus";
|
|
43
|
+
}
|
|
44
|
+
if (lowerText.includes("haiku")) {
|
|
45
|
+
return "haiku";
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function pickFamilyAliasModelId(familyAliases, family) {
|
|
50
|
+
if (!familyAliases) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return normalizeModelIdCandidate(familyAliases.get(family) ?? null);
|
|
54
|
+
}
|
|
39
55
|
export function normalizeClaudeRuntimeModelId(options) {
|
|
40
56
|
const runtimeModel = options.runtimeModelId.trim();
|
|
41
57
|
if (!runtimeModel) {
|
|
@@ -45,31 +61,43 @@ export function normalizeClaudeRuntimeModelId(options) {
|
|
|
45
61
|
if (!supportedModelIds || supportedModelIds.size === 0) {
|
|
46
62
|
return runtimeModel;
|
|
47
63
|
}
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
if (supportedModelIds.has(runtimeModel)) {
|
|
65
|
+
return runtimeModel;
|
|
66
|
+
}
|
|
67
|
+
const runtimeFamily = inferClaudeModelFamilyFromText(runtimeModel);
|
|
68
|
+
const familyAlias = runtimeFamily
|
|
69
|
+
? pickFamilyAliasModelId(options.supportedModelFamilyAliases, runtimeFamily)
|
|
70
|
+
: null;
|
|
71
|
+
if (runtimeFamily === "sonnet") {
|
|
50
72
|
const explicitSonnet = pickSupportedModelId(supportedModelIds, "sonnet");
|
|
51
73
|
if (explicitSonnet) {
|
|
52
74
|
return explicitSonnet;
|
|
53
75
|
}
|
|
76
|
+
if (familyAlias && supportedModelIds.has(familyAlias)) {
|
|
77
|
+
return familyAlias;
|
|
78
|
+
}
|
|
54
79
|
const defaultAlias = pickSupportedModelId(supportedModelIds, "default");
|
|
55
80
|
if (defaultAlias) {
|
|
56
81
|
return defaultAlias;
|
|
57
82
|
}
|
|
58
83
|
}
|
|
59
|
-
if (
|
|
84
|
+
if (runtimeFamily === "opus") {
|
|
60
85
|
const alias = pickSupportedModelId(supportedModelIds, "opus");
|
|
61
86
|
if (alias) {
|
|
62
87
|
return alias;
|
|
63
88
|
}
|
|
89
|
+
if (familyAlias && supportedModelIds.has(familyAlias)) {
|
|
90
|
+
return familyAlias;
|
|
91
|
+
}
|
|
64
92
|
}
|
|
65
|
-
if (
|
|
93
|
+
if (runtimeFamily === "haiku") {
|
|
66
94
|
const alias = pickSupportedModelId(supportedModelIds, "haiku");
|
|
67
95
|
if (alias) {
|
|
68
96
|
return alias;
|
|
69
97
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
if (familyAlias && supportedModelIds.has(familyAlias)) {
|
|
99
|
+
return familyAlias;
|
|
100
|
+
}
|
|
73
101
|
}
|
|
74
102
|
const configuredModelId = pickSupportedModelId(supportedModelIds, options.configuredModelId);
|
|
75
103
|
if (configuredModelId) {
|
|
@@ -79,6 +107,15 @@ export function normalizeClaudeRuntimeModelId(options) {
|
|
|
79
107
|
if (currentModelId) {
|
|
80
108
|
return currentModelId;
|
|
81
109
|
}
|
|
110
|
+
// If Claude reports a concrete family ID we can't map directly, prefer the
|
|
111
|
+
// provider default alias for unconfigured sessions so UI model/thinking state
|
|
112
|
+
// can still reconcile against the current model catalog.
|
|
113
|
+
const defaultAlias = pickSupportedModelId(supportedModelIds, "default");
|
|
114
|
+
const hasConfiguredModel = normalizeModelIdCandidate(options.configuredModelId) !== null;
|
|
115
|
+
const hasCurrentModel = normalizeModelIdCandidate(options.currentModelId) !== null;
|
|
116
|
+
if (runtimeFamily && defaultAlias && !hasConfiguredModel && !hasCurrentModel) {
|
|
117
|
+
return defaultAlias;
|
|
118
|
+
}
|
|
82
119
|
return runtimeModel;
|
|
83
120
|
}
|
|
84
121
|
const CLAUDE_CAPABILITIES = {
|
|
@@ -118,6 +155,7 @@ const REWIND_COMMAND = {
|
|
|
118
155
|
description: "Rewind tracked files to a previous user message",
|
|
119
156
|
argumentHint: "[user_message_uuid]",
|
|
120
157
|
};
|
|
158
|
+
const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
|
|
121
159
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
122
160
|
function resolveClaudeBinary() {
|
|
123
161
|
try {
|
|
@@ -150,6 +188,149 @@ function resolveClaudeSpawnCommand(spawnOptions, runtimeSettings) {
|
|
|
150
188
|
args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
|
|
151
189
|
};
|
|
152
190
|
}
|
|
191
|
+
function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings) {
|
|
192
|
+
const hasEnvOverrides = Object.keys(runtimeSettings?.env ?? {}).length > 0;
|
|
193
|
+
const commandMode = runtimeSettings?.command?.mode;
|
|
194
|
+
const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
|
|
195
|
+
if (!needsCustomSpawn) {
|
|
196
|
+
return options;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
...options,
|
|
200
|
+
spawnClaudeCodeProcess: (spawnOptions) => {
|
|
201
|
+
const resolved = resolveClaudeSpawnCommand(spawnOptions, runtimeSettings);
|
|
202
|
+
return spawn(resolved.command, resolved.args, {
|
|
203
|
+
cwd: spawnOptions.cwd,
|
|
204
|
+
env: applyProviderEnv(spawnOptions.env, runtimeSettings),
|
|
205
|
+
signal: spawnOptions.signal,
|
|
206
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const MAX_RECENT_STDERR_CHARS = 4000;
|
|
212
|
+
function summarizeClaudeOptionsForLog(options) {
|
|
213
|
+
const systemPromptRaw = options.systemPrompt;
|
|
214
|
+
const systemPromptSummary = (() => {
|
|
215
|
+
if (!systemPromptRaw) {
|
|
216
|
+
return { mode: "none", preset: null };
|
|
217
|
+
}
|
|
218
|
+
if (typeof systemPromptRaw === "string") {
|
|
219
|
+
return { mode: "string", preset: null };
|
|
220
|
+
}
|
|
221
|
+
const prompt = systemPromptRaw;
|
|
222
|
+
const promptType = typeof prompt.type === "string" ? prompt.type : "custom";
|
|
223
|
+
return {
|
|
224
|
+
mode: promptType === "preset"
|
|
225
|
+
? "preset"
|
|
226
|
+
: "custom",
|
|
227
|
+
preset: typeof prompt.preset === "string" && prompt.preset.length > 0
|
|
228
|
+
? prompt.preset
|
|
229
|
+
: null,
|
|
230
|
+
};
|
|
231
|
+
})();
|
|
232
|
+
const mcpServerNames = options.mcpServers
|
|
233
|
+
? Object.keys(options.mcpServers).sort()
|
|
234
|
+
: [];
|
|
235
|
+
return {
|
|
236
|
+
cwd: typeof options.cwd === "string" ? options.cwd : null,
|
|
237
|
+
permissionMode: typeof options.permissionMode === "string"
|
|
238
|
+
? options.permissionMode
|
|
239
|
+
: null,
|
|
240
|
+
model: typeof options.model === "string" ? options.model : null,
|
|
241
|
+
includePartialMessages: options.includePartialMessages === true,
|
|
242
|
+
settingSources: Array.isArray(options.settingSources)
|
|
243
|
+
? options.settingSources
|
|
244
|
+
: [],
|
|
245
|
+
enableFileCheckpointing: options.enableFileCheckpointing === true,
|
|
246
|
+
hasResume: typeof options.resume === "string" && options.resume.length > 0,
|
|
247
|
+
maxThinkingTokens: typeof options.maxThinkingTokens === "number"
|
|
248
|
+
? options.maxThinkingTokens
|
|
249
|
+
: null,
|
|
250
|
+
hasEnv: !!options.env,
|
|
251
|
+
envKeyCount: Object.keys(options.env ?? {}).length,
|
|
252
|
+
hasMcpServers: mcpServerNames.length > 0,
|
|
253
|
+
mcpServerNames,
|
|
254
|
+
systemPromptMode: systemPromptSummary.mode,
|
|
255
|
+
systemPromptPreset: systemPromptSummary.preset,
|
|
256
|
+
hasCanUseTool: typeof options.canUseTool === "function",
|
|
257
|
+
hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
|
|
258
|
+
hasStderrHandler: typeof options.stderr === "function",
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function isToolResultTextBlock(value) {
|
|
262
|
+
return (!!value &&
|
|
263
|
+
typeof value === "object" &&
|
|
264
|
+
value.type === "text" &&
|
|
265
|
+
typeof value.text === "string");
|
|
266
|
+
}
|
|
267
|
+
function normalizeForDeterministicString(value, seen) {
|
|
268
|
+
if (value === null ||
|
|
269
|
+
typeof value === "string" ||
|
|
270
|
+
typeof value === "number" ||
|
|
271
|
+
typeof value === "boolean") {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
if (typeof value === "bigint") {
|
|
275
|
+
return value.toString();
|
|
276
|
+
}
|
|
277
|
+
if (typeof value === "function") {
|
|
278
|
+
return "[function]";
|
|
279
|
+
}
|
|
280
|
+
if (typeof value === "symbol") {
|
|
281
|
+
return value.toString();
|
|
282
|
+
}
|
|
283
|
+
if (typeof value === "undefined") {
|
|
284
|
+
return "[undefined]";
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(value)) {
|
|
287
|
+
return value.map((entry) => normalizeForDeterministicString(entry, seen));
|
|
288
|
+
}
|
|
289
|
+
if (typeof value === "object") {
|
|
290
|
+
const objectValue = value;
|
|
291
|
+
if (seen.has(objectValue)) {
|
|
292
|
+
return "[circular]";
|
|
293
|
+
}
|
|
294
|
+
seen.add(objectValue);
|
|
295
|
+
const record = value;
|
|
296
|
+
const normalized = {};
|
|
297
|
+
for (const key of Object.keys(record).sort()) {
|
|
298
|
+
normalized[key] = normalizeForDeterministicString(record[key], seen);
|
|
299
|
+
}
|
|
300
|
+
seen.delete(objectValue);
|
|
301
|
+
return normalized;
|
|
302
|
+
}
|
|
303
|
+
return String(value);
|
|
304
|
+
}
|
|
305
|
+
function deterministicStringify(value) {
|
|
306
|
+
if (typeof value === "undefined") {
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const normalized = normalizeForDeterministicString(value, new WeakSet());
|
|
311
|
+
if (typeof normalized === "string") {
|
|
312
|
+
return normalized;
|
|
313
|
+
}
|
|
314
|
+
return JSON.stringify(normalized);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
try {
|
|
318
|
+
return String(value);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return "[unserializable]";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function coerceToolResultContentToString(content) {
|
|
326
|
+
if (typeof content === "string") {
|
|
327
|
+
return content;
|
|
328
|
+
}
|
|
329
|
+
if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
|
|
330
|
+
return content.map((block) => block.text).join("");
|
|
331
|
+
}
|
|
332
|
+
return deterministicStringify(content);
|
|
333
|
+
}
|
|
153
334
|
export function extractUserMessageText(content) {
|
|
154
335
|
if (typeof content === "string") {
|
|
155
336
|
const normalized = content.trim();
|
|
@@ -179,10 +360,18 @@ export function extractUserMessageText(content) {
|
|
|
179
360
|
const combined = parts.join("\n\n").trim();
|
|
180
361
|
return combined.length > 0 ? combined : null;
|
|
181
362
|
}
|
|
182
|
-
const
|
|
363
|
+
const MAX_SUB_AGENT_LOG_ENTRIES = 200;
|
|
364
|
+
const MAX_SUB_AGENT_SUMMARY_CHARS = 160;
|
|
183
365
|
function isMetadata(value) {
|
|
184
366
|
return typeof value === "object" && value !== null;
|
|
185
367
|
}
|
|
368
|
+
function readTrimmedString(value) {
|
|
369
|
+
if (typeof value !== "string") {
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
const trimmed = value.trim();
|
|
373
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
374
|
+
}
|
|
186
375
|
function isMcpServerConfig(value) {
|
|
187
376
|
if (!isMetadata(value)) {
|
|
188
377
|
return false;
|
|
@@ -312,6 +501,440 @@ function resolvePermissionKind(toolName, input) {
|
|
|
312
501
|
}
|
|
313
502
|
return "tool";
|
|
314
503
|
}
|
|
504
|
+
const ACTIVE_RUN_STATES = new Set([
|
|
505
|
+
"queued",
|
|
506
|
+
"awaiting_response",
|
|
507
|
+
"streaming",
|
|
508
|
+
"finalizing",
|
|
509
|
+
]);
|
|
510
|
+
class RunTracker {
|
|
511
|
+
constructor() {
|
|
512
|
+
this.runs = new Map();
|
|
513
|
+
this.runByTaskId = new Map();
|
|
514
|
+
this.runByParentMessageId = new Map();
|
|
515
|
+
this.runByMessageId = new Map();
|
|
516
|
+
}
|
|
517
|
+
createRun(input) {
|
|
518
|
+
const run = {
|
|
519
|
+
id: input.id,
|
|
520
|
+
owner: input.owner,
|
|
521
|
+
queue: input.queue,
|
|
522
|
+
state: "queued",
|
|
523
|
+
promptReplaySeen: input.promptReplaySeen ?? true,
|
|
524
|
+
taskIds: new Set(),
|
|
525
|
+
parentMessageIds: new Set(),
|
|
526
|
+
messageIds: new Set(),
|
|
527
|
+
};
|
|
528
|
+
this.runs.set(run.id, run);
|
|
529
|
+
return run;
|
|
530
|
+
}
|
|
531
|
+
getRun(runId) {
|
|
532
|
+
return this.runs.get(runId) ?? null;
|
|
533
|
+
}
|
|
534
|
+
getForegroundRun() {
|
|
535
|
+
for (const run of this.runs.values()) {
|
|
536
|
+
if (run.owner === "foreground" && this.isActive(run.state)) {
|
|
537
|
+
return run;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
listActiveRuns(owner) {
|
|
543
|
+
const runs = [];
|
|
544
|
+
for (const run of this.runs.values()) {
|
|
545
|
+
if (!this.isActive(run.state)) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (owner && run.owner !== owner) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
runs.push(run);
|
|
552
|
+
}
|
|
553
|
+
return runs;
|
|
554
|
+
}
|
|
555
|
+
hasActiveRuns(owner) {
|
|
556
|
+
for (const run of this.runs.values()) {
|
|
557
|
+
if (!this.isActive(run.state)) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (owner && run.owner !== owner) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
getLatestActiveRun(owner) {
|
|
568
|
+
let latest = null;
|
|
569
|
+
for (const run of this.runs.values()) {
|
|
570
|
+
if (!this.isActive(run.state)) {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (owner && run.owner !== owner) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
latest = run;
|
|
577
|
+
}
|
|
578
|
+
return latest;
|
|
579
|
+
}
|
|
580
|
+
isRunActive(run) {
|
|
581
|
+
if (!run) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
return this.isActive(run.state);
|
|
585
|
+
}
|
|
586
|
+
resolveByIdentifiers(identifiers) {
|
|
587
|
+
if (identifiers.taskId) {
|
|
588
|
+
const run = this.resolveMappedRun(this.runByTaskId, identifiers.taskId);
|
|
589
|
+
if (run) {
|
|
590
|
+
return { run, reason: "task_id" };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (identifiers.parentMessageId) {
|
|
594
|
+
const run = this.resolveMappedRun(this.runByParentMessageId, identifiers.parentMessageId);
|
|
595
|
+
if (run) {
|
|
596
|
+
return { run, reason: "parent_message_id" };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (identifiers.messageId) {
|
|
600
|
+
const run = this.resolveMappedRun(this.runByMessageId, identifiers.messageId);
|
|
601
|
+
if (run) {
|
|
602
|
+
return { run, reason: "message_id" };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return { run: null, reason: "metadata" };
|
|
606
|
+
}
|
|
607
|
+
bindIdentifiers(run, identifiers) {
|
|
608
|
+
if (identifiers.taskId) {
|
|
609
|
+
run.taskIds.add(identifiers.taskId);
|
|
610
|
+
this.runByTaskId.set(identifiers.taskId, run.id);
|
|
611
|
+
}
|
|
612
|
+
if (identifiers.parentMessageId) {
|
|
613
|
+
run.parentMessageIds.add(identifiers.parentMessageId);
|
|
614
|
+
this.runByParentMessageId.set(identifiers.parentMessageId, run.id);
|
|
615
|
+
}
|
|
616
|
+
if (identifiers.messageId) {
|
|
617
|
+
run.messageIds.add(identifiers.messageId);
|
|
618
|
+
this.runByMessageId.set(identifiers.messageId, run.id);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
transition(run, nextState) {
|
|
622
|
+
run.state = nextState;
|
|
623
|
+
}
|
|
624
|
+
complete(run, terminalState) {
|
|
625
|
+
run.state = terminalState;
|
|
626
|
+
this.clearRunIndex(run);
|
|
627
|
+
}
|
|
628
|
+
deriveLifecycle(pendingPermissionCount) {
|
|
629
|
+
for (const run of this.runs.values()) {
|
|
630
|
+
if (this.isActive(run.state)) {
|
|
631
|
+
return "running";
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (pendingPermissionCount > 0) {
|
|
635
|
+
return "permission";
|
|
636
|
+
}
|
|
637
|
+
for (const run of this.runs.values()) {
|
|
638
|
+
if (run.state === "error") {
|
|
639
|
+
return "error";
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return "idle";
|
|
643
|
+
}
|
|
644
|
+
resolveMappedRun(mapping, identifier) {
|
|
645
|
+
const runId = mapping.get(identifier);
|
|
646
|
+
if (!runId) {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
const run = this.runs.get(runId);
|
|
650
|
+
if (!run || !this.isActive(run.state)) {
|
|
651
|
+
mapping.delete(identifier);
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
return run;
|
|
655
|
+
}
|
|
656
|
+
clearRunIndex(run) {
|
|
657
|
+
for (const taskId of run.taskIds) {
|
|
658
|
+
this.runByTaskId.delete(taskId);
|
|
659
|
+
}
|
|
660
|
+
for (const parentMessageId of run.parentMessageIds) {
|
|
661
|
+
this.runByParentMessageId.delete(parentMessageId);
|
|
662
|
+
}
|
|
663
|
+
for (const messageId of run.messageIds) {
|
|
664
|
+
this.runByMessageId.delete(messageId);
|
|
665
|
+
}
|
|
666
|
+
run.taskIds.clear();
|
|
667
|
+
run.parentMessageIds.clear();
|
|
668
|
+
run.messageIds.clear();
|
|
669
|
+
}
|
|
670
|
+
isActive(state) {
|
|
671
|
+
return ACTIVE_RUN_STATES.has(state);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
class TimelineAssembler {
|
|
675
|
+
constructor() {
|
|
676
|
+
this.messages = new Map();
|
|
677
|
+
this.activeMessageByRun = new Map();
|
|
678
|
+
this.syntheticMessageCounter = 0;
|
|
679
|
+
}
|
|
680
|
+
consume(input) {
|
|
681
|
+
if (input.message.type === "assistant") {
|
|
682
|
+
return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
|
|
683
|
+
}
|
|
684
|
+
if (input.message.type === "stream_event") {
|
|
685
|
+
return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
|
|
686
|
+
}
|
|
687
|
+
return [];
|
|
688
|
+
}
|
|
689
|
+
consumeAssistantMessage(message, runId, messageIdHint) {
|
|
690
|
+
const messageId = this.readMessageIdFromAssistantMessage(message) ??
|
|
691
|
+
messageIdHint ??
|
|
692
|
+
this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
|
|
693
|
+
if (!messageId) {
|
|
694
|
+
return [];
|
|
695
|
+
}
|
|
696
|
+
const state = this.ensureMessageState(messageId, runId);
|
|
697
|
+
const fragments = this.extractFragments(message.message?.content);
|
|
698
|
+
return this.applyAbsoluteFragments(state, fragments);
|
|
699
|
+
}
|
|
700
|
+
consumeStreamEvent(message, runId, messageIdHint) {
|
|
701
|
+
const event = message.event;
|
|
702
|
+
const eventType = readTrimmedString(event.type);
|
|
703
|
+
const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
|
|
704
|
+
if (eventType === "message_start") {
|
|
705
|
+
const messageId = this.resolveMessageId({
|
|
706
|
+
runId,
|
|
707
|
+
createIfMissing: true,
|
|
708
|
+
messageId: streamEventMessageId,
|
|
709
|
+
});
|
|
710
|
+
if (!messageId) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
this.ensureMessageState(messageId, runId);
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
if (eventType === "message_stop") {
|
|
717
|
+
const messageId = this.resolveMessageId({
|
|
718
|
+
runId,
|
|
719
|
+
createIfMissing: false,
|
|
720
|
+
messageId: streamEventMessageId,
|
|
721
|
+
});
|
|
722
|
+
if (!messageId) {
|
|
723
|
+
return [];
|
|
724
|
+
}
|
|
725
|
+
return this.finalizeMessage(messageId, runId);
|
|
726
|
+
}
|
|
727
|
+
if (eventType === "content_block_start") {
|
|
728
|
+
return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
|
|
729
|
+
}
|
|
730
|
+
if (eventType === "content_block_delta") {
|
|
731
|
+
return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
|
|
732
|
+
}
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
consumeDeltaContent(content, runId, messageIdHint) {
|
|
736
|
+
const fragments = this.extractFragments(content);
|
|
737
|
+
if (fragments.length === 0) {
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
const messageId = this.resolveMessageId({
|
|
741
|
+
runId,
|
|
742
|
+
createIfMissing: true,
|
|
743
|
+
messageId: messageIdHint,
|
|
744
|
+
});
|
|
745
|
+
if (!messageId) {
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
const state = this.ensureMessageState(messageId, runId);
|
|
749
|
+
return this.appendFragments(state, fragments);
|
|
750
|
+
}
|
|
751
|
+
appendFragments(state, fragments) {
|
|
752
|
+
for (const fragment of fragments) {
|
|
753
|
+
if (fragment.kind === "assistant") {
|
|
754
|
+
state.assistantText += fragment.text;
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
state.reasoningText += fragment.text;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return this.emitNewContent(state);
|
|
761
|
+
}
|
|
762
|
+
applyAbsoluteFragments(state, fragments) {
|
|
763
|
+
const assistantText = fragments
|
|
764
|
+
.filter((fragment) => fragment.kind === "assistant")
|
|
765
|
+
.map((fragment) => fragment.text)
|
|
766
|
+
.join("");
|
|
767
|
+
const reasoningText = fragments
|
|
768
|
+
.filter((fragment) => fragment.kind === "reasoning")
|
|
769
|
+
.map((fragment) => fragment.text)
|
|
770
|
+
.join("");
|
|
771
|
+
if (assistantText.length > 0) {
|
|
772
|
+
if (!assistantText.startsWith(state.assistantText)) {
|
|
773
|
+
state.emittedAssistantLength = 0;
|
|
774
|
+
}
|
|
775
|
+
state.assistantText = assistantText;
|
|
776
|
+
}
|
|
777
|
+
if (reasoningText.length > 0) {
|
|
778
|
+
if (!reasoningText.startsWith(state.reasoningText)) {
|
|
779
|
+
state.emittedReasoningLength = 0;
|
|
780
|
+
}
|
|
781
|
+
state.reasoningText = reasoningText;
|
|
782
|
+
}
|
|
783
|
+
return this.emitNewContent(state);
|
|
784
|
+
}
|
|
785
|
+
finalizeMessage(messageId, runId) {
|
|
786
|
+
const state = this.messages.get(messageId);
|
|
787
|
+
if (!state) {
|
|
788
|
+
return [];
|
|
789
|
+
}
|
|
790
|
+
state.stopped = true;
|
|
791
|
+
const items = this.emitNewContent(state);
|
|
792
|
+
if (runId && this.activeMessageByRun.get(runId) === messageId) {
|
|
793
|
+
this.activeMessageByRun.delete(runId);
|
|
794
|
+
}
|
|
795
|
+
return items;
|
|
796
|
+
}
|
|
797
|
+
emitNewContent(state) {
|
|
798
|
+
const items = [];
|
|
799
|
+
const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
|
|
800
|
+
if (nextAssistantText.length > 0 &&
|
|
801
|
+
nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER) {
|
|
802
|
+
state.emittedAssistantLength = state.assistantText.length;
|
|
803
|
+
items.push({ type: "assistant_message", text: nextAssistantText });
|
|
804
|
+
}
|
|
805
|
+
const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
|
|
806
|
+
if (nextReasoningText.length > 0) {
|
|
807
|
+
state.emittedReasoningLength = state.reasoningText.length;
|
|
808
|
+
items.push({ type: "reasoning", text: nextReasoningText });
|
|
809
|
+
}
|
|
810
|
+
return items;
|
|
811
|
+
}
|
|
812
|
+
ensureMessageState(messageId, runId) {
|
|
813
|
+
const existing = this.messages.get(messageId);
|
|
814
|
+
if (existing) {
|
|
815
|
+
existing.stopped = false;
|
|
816
|
+
if (runId) {
|
|
817
|
+
this.activeMessageByRun.set(runId, messageId);
|
|
818
|
+
}
|
|
819
|
+
return existing;
|
|
820
|
+
}
|
|
821
|
+
const created = {
|
|
822
|
+
id: messageId,
|
|
823
|
+
assistantText: "",
|
|
824
|
+
reasoningText: "",
|
|
825
|
+
emittedAssistantLength: 0,
|
|
826
|
+
emittedReasoningLength: 0,
|
|
827
|
+
stopped: false,
|
|
828
|
+
};
|
|
829
|
+
this.messages.set(messageId, created);
|
|
830
|
+
if (runId) {
|
|
831
|
+
this.activeMessageByRun.set(runId, messageId);
|
|
832
|
+
}
|
|
833
|
+
return created;
|
|
834
|
+
}
|
|
835
|
+
resolveMessageId(input) {
|
|
836
|
+
if (input.messageId) {
|
|
837
|
+
return input.messageId;
|
|
838
|
+
}
|
|
839
|
+
if (input.runId) {
|
|
840
|
+
const active = this.activeMessageByRun.get(input.runId);
|
|
841
|
+
if (active) {
|
|
842
|
+
return active;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (!input.createIfMissing) {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
|
|
849
|
+
if (input.runId) {
|
|
850
|
+
this.activeMessageByRun.set(input.runId, synthetic);
|
|
851
|
+
}
|
|
852
|
+
return synthetic;
|
|
853
|
+
}
|
|
854
|
+
extractFragments(content) {
|
|
855
|
+
if (typeof content === "string") {
|
|
856
|
+
if (content.length === 0) {
|
|
857
|
+
return [];
|
|
858
|
+
}
|
|
859
|
+
return [{ kind: "assistant", text: content }];
|
|
860
|
+
}
|
|
861
|
+
const blocks = Array.isArray(content) ? content : [content];
|
|
862
|
+
const fragments = [];
|
|
863
|
+
for (const rawBlock of blocks) {
|
|
864
|
+
if (!isClaudeContentChunk(rawBlock)) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
|
|
868
|
+
typeof rawBlock.text === "string" &&
|
|
869
|
+
rawBlock.text.length > 0) {
|
|
870
|
+
fragments.push({ kind: "assistant", text: rawBlock.text });
|
|
871
|
+
}
|
|
872
|
+
if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
|
|
873
|
+
typeof rawBlock.thinking === "string" &&
|
|
874
|
+
rawBlock.thinking.length > 0) {
|
|
875
|
+
fragments.push({ kind: "reasoning", text: rawBlock.thinking });
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return fragments;
|
|
879
|
+
}
|
|
880
|
+
readMessageIdFromAssistantMessage(message) {
|
|
881
|
+
const candidate = message;
|
|
882
|
+
return readTrimmedString(candidate.message_id) ??
|
|
883
|
+
readTrimmedString(candidate.message?.id) ??
|
|
884
|
+
null;
|
|
885
|
+
}
|
|
886
|
+
readMessageIdFromStreamEvent(event) {
|
|
887
|
+
const message = event.message;
|
|
888
|
+
return (readTrimmedString(event.message_id) ??
|
|
889
|
+
readTrimmedString(message?.id) ??
|
|
890
|
+
null);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function isMetadataOnlySdkMessage(message) {
|
|
894
|
+
if (message.type === "system") {
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
if (message.type !== "user") {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
if (isSyntheticUserEntry(message)) {
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
return isTaskNotificationUserContent(message.message?.content);
|
|
904
|
+
}
|
|
905
|
+
function isSyntheticUserEntry(entry) {
|
|
906
|
+
if (!entry || typeof entry !== "object") {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
return entry.isSynthetic === true;
|
|
910
|
+
}
|
|
911
|
+
export function readEventIdentifiers(message) {
|
|
912
|
+
const root = message;
|
|
913
|
+
const messageType = readTrimmedString(root.type);
|
|
914
|
+
const streamEvent = root.event;
|
|
915
|
+
const streamEventMessage = streamEvent?.message;
|
|
916
|
+
const messageContainer = root.message;
|
|
917
|
+
return {
|
|
918
|
+
taskId: readTrimmedString(root.task_id) ??
|
|
919
|
+
readTrimmedString(streamEvent?.task_id) ??
|
|
920
|
+
readTrimmedString(streamEventMessage?.task_id) ??
|
|
921
|
+
readTrimmedString(messageContainer?.task_id) ??
|
|
922
|
+
null,
|
|
923
|
+
parentMessageId: readTrimmedString(root.parent_message_id) ??
|
|
924
|
+
readTrimmedString(streamEvent?.parent_message_id) ??
|
|
925
|
+
readTrimmedString(streamEventMessage?.parent_message_id) ??
|
|
926
|
+
readTrimmedString(messageContainer?.parent_message_id) ??
|
|
927
|
+
null,
|
|
928
|
+
messageId: readTrimmedString(root.message_id) ??
|
|
929
|
+
readTrimmedString(streamEvent?.message_id) ??
|
|
930
|
+
readTrimmedString(streamEventMessage?.id) ??
|
|
931
|
+
readTrimmedString(streamEventMessage?.message_id) ??
|
|
932
|
+
readTrimmedString(messageContainer?.id) ??
|
|
933
|
+
readTrimmedString(messageContainer?.message_id) ??
|
|
934
|
+
(messageType === "user" ? readTrimmedString(root.uuid) : null) ??
|
|
935
|
+
null,
|
|
936
|
+
};
|
|
937
|
+
}
|
|
315
938
|
export class ClaudeAgentClient {
|
|
316
939
|
constructor(options) {
|
|
317
940
|
this.provider = "claude";
|
|
@@ -326,26 +949,6 @@ export class ClaudeAgentClient {
|
|
|
326
949
|
this.claudePath = null;
|
|
327
950
|
}
|
|
328
951
|
}
|
|
329
|
-
applyRuntimeSettings(options) {
|
|
330
|
-
const hasEnvOverrides = Object.keys(this.runtimeSettings?.env ?? {}).length > 0;
|
|
331
|
-
const commandMode = this.runtimeSettings?.command?.mode;
|
|
332
|
-
const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
|
|
333
|
-
if (!needsCustomSpawn) {
|
|
334
|
-
return options;
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
...options,
|
|
338
|
-
spawnClaudeCodeProcess: (spawnOptions) => {
|
|
339
|
-
const resolved = resolveClaudeSpawnCommand(spawnOptions, this.runtimeSettings);
|
|
340
|
-
return spawn(resolved.command, resolved.args, {
|
|
341
|
-
cwd: spawnOptions.cwd,
|
|
342
|
-
env: applyProviderEnv(spawnOptions.env, this.runtimeSettings),
|
|
343
|
-
signal: spawnOptions.signal,
|
|
344
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
345
|
-
});
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
952
|
async createSession(config) {
|
|
350
953
|
const claudeConfig = this.assertConfig(config);
|
|
351
954
|
return new ClaudeAgentSession(claudeConfig, {
|
|
@@ -371,45 +974,8 @@ export class ClaudeAgentClient {
|
|
|
371
974
|
logger: this.logger,
|
|
372
975
|
});
|
|
373
976
|
}
|
|
374
|
-
async listModels(
|
|
375
|
-
|
|
376
|
-
const claudeOptions = {
|
|
377
|
-
cwd: options?.cwd ?? process.cwd(),
|
|
378
|
-
permissionMode: "plan",
|
|
379
|
-
includePartialMessages: false,
|
|
380
|
-
...(this.claudePath ? { pathToClaudeCodeExecutable: this.claudePath } : {}),
|
|
381
|
-
};
|
|
382
|
-
const claudeQuery = query({
|
|
383
|
-
prompt,
|
|
384
|
-
options: this.applyRuntimeSettings(claudeOptions),
|
|
385
|
-
});
|
|
386
|
-
try {
|
|
387
|
-
const models = await claudeQuery.supportedModels();
|
|
388
|
-
return models.map((model) => ({
|
|
389
|
-
provider: "claude",
|
|
390
|
-
id: model.value,
|
|
391
|
-
label: normalizeClaudeModelLabel(model),
|
|
392
|
-
description: model.description,
|
|
393
|
-
thinkingOptions: [
|
|
394
|
-
{ id: "off", label: "Off", isDefault: true },
|
|
395
|
-
{ id: "on", label: "On" },
|
|
396
|
-
],
|
|
397
|
-
defaultThinkingOptionId: "off",
|
|
398
|
-
metadata: {
|
|
399
|
-
description: model.description,
|
|
400
|
-
},
|
|
401
|
-
}));
|
|
402
|
-
}
|
|
403
|
-
finally {
|
|
404
|
-
if (typeof claudeQuery.return === "function") {
|
|
405
|
-
try {
|
|
406
|
-
await claudeQuery.return();
|
|
407
|
-
}
|
|
408
|
-
catch {
|
|
409
|
-
// ignore shutdown errors
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
977
|
+
async listModels(_options) {
|
|
978
|
+
return listClaudeCatalogModels();
|
|
413
979
|
}
|
|
414
980
|
async listPersistedAgents(options) {
|
|
415
981
|
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
@@ -456,28 +1022,32 @@ class ClaudeAgentSession {
|
|
|
456
1022
|
this.toolUseIndexToId = new Map();
|
|
457
1023
|
this.toolUseInputBuffers = new Map();
|
|
458
1024
|
this.pendingPermissions = new Map();
|
|
459
|
-
this.
|
|
1025
|
+
this.activeForegroundTurn = null;
|
|
1026
|
+
this.liveEventQueue = new Pushable();
|
|
1027
|
+
this.runTracker = new RunTracker();
|
|
1028
|
+
this.timelineAssembler = new TimelineAssembler();
|
|
460
1029
|
this.persistedHistory = [];
|
|
461
1030
|
this.historyPending = false;
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
1031
|
+
this.turnState = "idle";
|
|
1032
|
+
this.preReplayMetadataSeen = false;
|
|
1033
|
+
this.pendingAutonomousWakeReservations = 0;
|
|
1034
|
+
this.nextRunOrdinal = 1;
|
|
466
1035
|
this.cancelCurrentTurn = null;
|
|
467
|
-
// Track the pending interrupt promise so we can await it in processPrompt
|
|
468
|
-
// This ensures the interrupt's response is consumed before we call query.next()
|
|
469
1036
|
this.pendingInterruptPromise = null;
|
|
470
|
-
// Track the current turn ID and active turn promise to serialize concurrent stream() calls
|
|
471
|
-
// and prevent race conditions where two processPrompt() loops run against the same query
|
|
472
|
-
this.currentTurnId = 0;
|
|
473
1037
|
this.activeTurnPromise = null;
|
|
474
1038
|
this.cachedRuntimeInfo = null;
|
|
475
1039
|
this.lastOptionsModel = null;
|
|
476
|
-
this.selectableModelIds =
|
|
1040
|
+
this.selectableModelIds = buildClaudeSelectableModelIds();
|
|
1041
|
+
this.selectableModelFamilyAliases = buildClaudeModelFamilyAliases();
|
|
477
1042
|
this.activeSidechains = new Map();
|
|
478
1043
|
this.compacting = false;
|
|
1044
|
+
this.queryPumpPromise = null;
|
|
479
1045
|
this.queryRestartNeeded = false;
|
|
480
1046
|
this.userMessageIds = [];
|
|
1047
|
+
this.localUserMessageIds = new Set();
|
|
1048
|
+
this.suppressLocalReplayActivity = false;
|
|
1049
|
+
this.recentStderr = "";
|
|
1050
|
+
this.closed = false;
|
|
481
1051
|
this.handlePermissionRequest = async (toolName, input, options) => {
|
|
482
1052
|
const requestId = `permission-${randomUUID()}`;
|
|
483
1053
|
const kind = resolvePermissionKind(toolName, input);
|
|
@@ -520,19 +1090,6 @@ class ClaudeAgentSession {
|
|
|
520
1090
|
}
|
|
521
1091
|
}
|
|
522
1092
|
};
|
|
523
|
-
const timeout = setTimeout(() => {
|
|
524
|
-
this.pendingPermissions.delete(requestId);
|
|
525
|
-
cleanup();
|
|
526
|
-
const error = new Error("Permission request timed out");
|
|
527
|
-
this.pushEvent({
|
|
528
|
-
type: "permission_resolved",
|
|
529
|
-
provider: "claude",
|
|
530
|
-
requestId,
|
|
531
|
-
resolution: { behavior: "deny", message: "timeout" },
|
|
532
|
-
});
|
|
533
|
-
reject(error);
|
|
534
|
-
}, DEFAULT_PERMISSION_TIMEOUT_MS);
|
|
535
|
-
cleanupFns.push(() => clearTimeout(timeout));
|
|
536
1093
|
const abortHandler = () => {
|
|
537
1094
|
this.pendingPermissions.delete(requestId);
|
|
538
1095
|
cleanup();
|
|
@@ -640,81 +1197,125 @@ class ClaudeAgentSession {
|
|
|
640
1197
|
}
|
|
641
1198
|
async *stream(prompt, options) {
|
|
642
1199
|
void options;
|
|
643
|
-
// Increment turn ID to invalidate any in-flight processPrompt() loops from previous turns.
|
|
644
|
-
// This prevents race conditions where an interrupted turn's events get mixed with the new turn.
|
|
645
|
-
const turnId = ++this.currentTurnId;
|
|
646
|
-
// Cancel the previous turn if one exists. The caller of interrupt() is responsible
|
|
647
|
-
// for awaiting completion - the new turn just signals cancellation and proceeds.
|
|
648
1200
|
if (this.cancelCurrentTurn) {
|
|
649
1201
|
this.cancelCurrentTurn();
|
|
650
1202
|
}
|
|
651
|
-
|
|
652
|
-
this.
|
|
1203
|
+
this.suppressLocalReplayActivity = false;
|
|
1204
|
+
this.pendingAutonomousWakeReservations = 0;
|
|
653
1205
|
const slashCommand = this.resolveSlashCommandInvocation(prompt);
|
|
654
1206
|
if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
|
|
655
1207
|
yield* this.streamRewindCommand(slashCommand);
|
|
656
1208
|
return;
|
|
657
1209
|
}
|
|
1210
|
+
await this.awaitPendingInterruptPromise();
|
|
1211
|
+
if (this.turnState === "autonomous" &&
|
|
1212
|
+
this.runTracker.hasActiveRuns("autonomous")) {
|
|
1213
|
+
await this.transitionAutonomousToForeground();
|
|
1214
|
+
}
|
|
658
1215
|
const sdkMessage = this.toSdkUserMessage(prompt);
|
|
659
1216
|
const queue = new Pushable();
|
|
660
|
-
this.
|
|
1217
|
+
const run = this.createRun("foreground", queue);
|
|
1218
|
+
this.runTracker.bindIdentifiers(run, {
|
|
1219
|
+
taskId: null,
|
|
1220
|
+
parentMessageId: null,
|
|
1221
|
+
messageId: typeof sdkMessage.uuid === "string" ? sdkMessage.uuid : null,
|
|
1222
|
+
});
|
|
1223
|
+
const foregroundTurn = {
|
|
1224
|
+
runId: run.id,
|
|
1225
|
+
queue,
|
|
1226
|
+
};
|
|
1227
|
+
this.activeForegroundTurn = foregroundTurn;
|
|
1228
|
+
this.preReplayMetadataSeen = false;
|
|
1229
|
+
this.transitionTurnState("foreground", "foreground stream started");
|
|
1230
|
+
this.clearRecentStderr();
|
|
661
1231
|
let finishedNaturally = false;
|
|
662
1232
|
let cancelIssued = false;
|
|
1233
|
+
let queueDrainedWithoutTerminal = false;
|
|
1234
|
+
const turnPromise = Promise.resolve();
|
|
1235
|
+
this.activeTurnPromise = turnPromise;
|
|
663
1236
|
const requestCancel = () => {
|
|
664
1237
|
if (cancelIssued) {
|
|
665
1238
|
return;
|
|
666
1239
|
}
|
|
667
1240
|
cancelIssued = true;
|
|
668
|
-
this.
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1241
|
+
if (this.activeForegroundTurn?.runId === run.id) {
|
|
1242
|
+
this.activeForegroundTurn = null;
|
|
1243
|
+
}
|
|
1244
|
+
if (this.cancelCurrentTurn === requestCancel) {
|
|
1245
|
+
this.cancelCurrentTurn = null;
|
|
1246
|
+
}
|
|
1247
|
+
this.rejectAllPendingPermissions(new Error("Permission request aborted"));
|
|
1248
|
+
this.cancelRun(run, {
|
|
676
1249
|
type: "turn_canceled",
|
|
677
1250
|
provider: "claude",
|
|
678
1251
|
reason: "Interrupted",
|
|
679
1252
|
});
|
|
680
|
-
|
|
1253
|
+
this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
|
|
1254
|
+
this.logger.warn({ err: error }, "Failed to interrupt during cancel");
|
|
1255
|
+
});
|
|
681
1256
|
};
|
|
682
1257
|
this.cancelCurrentTurn = requestCancel;
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1258
|
+
try {
|
|
1259
|
+
await this.ensureQuery();
|
|
1260
|
+
if (!this.input) {
|
|
1261
|
+
throw new Error("Claude session input stream not initialized");
|
|
1262
|
+
}
|
|
1263
|
+
this.startQueryPump();
|
|
1264
|
+
this.input.push(sdkMessage);
|
|
1265
|
+
}
|
|
1266
|
+
catch (error) {
|
|
1267
|
+
this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
|
|
1268
|
+
finishedNaturally = true;
|
|
1269
|
+
}
|
|
689
1270
|
try {
|
|
690
1271
|
for await (const event of queue) {
|
|
691
|
-
|
|
692
|
-
if (event.type === "turn_completed" ||
|
|
1272
|
+
const isTerminalEvent = event.type === "turn_completed" ||
|
|
693
1273
|
event.type === "turn_failed" ||
|
|
694
|
-
event.type === "turn_canceled"
|
|
1274
|
+
event.type === "turn_canceled";
|
|
1275
|
+
if (isTerminalEvent) {
|
|
695
1276
|
finishedNaturally = true;
|
|
1277
|
+
}
|
|
1278
|
+
yield event;
|
|
1279
|
+
if (isTerminalEvent) {
|
|
696
1280
|
break;
|
|
697
1281
|
}
|
|
698
1282
|
}
|
|
1283
|
+
if (!finishedNaturally && !cancelIssued) {
|
|
1284
|
+
queueDrainedWithoutTerminal = true;
|
|
1285
|
+
}
|
|
699
1286
|
}
|
|
700
1287
|
finally {
|
|
701
|
-
if (!finishedNaturally && !cancelIssued) {
|
|
1288
|
+
if (!finishedNaturally && !cancelIssued && !queueDrainedWithoutTerminal) {
|
|
702
1289
|
requestCancel();
|
|
703
1290
|
}
|
|
704
|
-
if (this.
|
|
705
|
-
this.
|
|
1291
|
+
if (this.activeForegroundTurn === foregroundTurn) {
|
|
1292
|
+
this.activeForegroundTurn = null;
|
|
706
1293
|
}
|
|
707
1294
|
if (this.cancelCurrentTurn === requestCancel) {
|
|
708
1295
|
this.cancelCurrentTurn = null;
|
|
709
1296
|
}
|
|
710
|
-
|
|
711
|
-
if (this.activeTurnPromise === forwardPromise) {
|
|
1297
|
+
if (this.activeTurnPromise === turnPromise) {
|
|
712
1298
|
this.activeTurnPromise = null;
|
|
713
1299
|
}
|
|
714
1300
|
}
|
|
715
1301
|
}
|
|
716
1302
|
async interrupt() {
|
|
717
|
-
this.cancelCurrentTurn
|
|
1303
|
+
if (this.cancelCurrentTurn) {
|
|
1304
|
+
this.cancelCurrentTurn();
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const autonomousRuns = this.runTracker.listActiveRuns("autonomous");
|
|
1308
|
+
if (autonomousRuns.length > 0) {
|
|
1309
|
+
this.flushPendingToolCalls();
|
|
1310
|
+
for (const run of autonomousRuns) {
|
|
1311
|
+
this.emitRunEvent(run, {
|
|
1312
|
+
type: "turn_canceled",
|
|
1313
|
+
provider: "claude",
|
|
1314
|
+
reason: "Interrupted",
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
await this.interruptActiveTurn();
|
|
718
1319
|
}
|
|
719
1320
|
async *streamHistory() {
|
|
720
1321
|
if (!this.historyPending || this.persistedHistory.length === 0) {
|
|
@@ -727,6 +1328,14 @@ class ClaudeAgentSession {
|
|
|
727
1328
|
yield { type: "timeline", item, provider: "claude" };
|
|
728
1329
|
}
|
|
729
1330
|
}
|
|
1331
|
+
async *streamLiveEvents() {
|
|
1332
|
+
if (this.claudeSessionId) {
|
|
1333
|
+
this.startQueryPump();
|
|
1334
|
+
}
|
|
1335
|
+
for await (const event of this.liveEventQueue) {
|
|
1336
|
+
yield event;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
730
1339
|
async getAvailableModes() {
|
|
731
1340
|
return this.availableModes;
|
|
732
1341
|
}
|
|
@@ -841,12 +1450,30 @@ class ClaudeAgentSession {
|
|
|
841
1450
|
return this.persistence;
|
|
842
1451
|
}
|
|
843
1452
|
async close() {
|
|
1453
|
+
this.logger.trace({
|
|
1454
|
+
claudeSessionId: this.claudeSessionId,
|
|
1455
|
+
turnState: this.turnState,
|
|
1456
|
+
hasQuery: Boolean(this.query),
|
|
1457
|
+
hasInput: Boolean(this.input),
|
|
1458
|
+
hasActiveForegroundTurn: Boolean(this.activeForegroundTurn),
|
|
1459
|
+
}, "Claude session close: start");
|
|
1460
|
+
this.closed = true;
|
|
844
1461
|
this.rejectAllPendingPermissions(new Error("Claude session closed"));
|
|
1462
|
+
this.cancelCurrentTurn?.();
|
|
1463
|
+
this.activeForegroundTurn?.queue.end();
|
|
1464
|
+
this.activeForegroundTurn = null;
|
|
1465
|
+
this.cancelCurrentTurn = null;
|
|
1466
|
+
this.turnState = "idle";
|
|
1467
|
+
this.suppressLocalReplayActivity = false;
|
|
1468
|
+
this.pendingAutonomousWakeReservations = 0;
|
|
1469
|
+
this.liveEventQueue.end();
|
|
1470
|
+
this.activeTurnPromise = null;
|
|
845
1471
|
this.input?.end();
|
|
846
|
-
await this.query?.interrupt?.();
|
|
847
|
-
await this.query?.return?.();
|
|
1472
|
+
await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
|
|
1473
|
+
await this.awaitWithTimeout(this.query?.return?.(), "close query return");
|
|
848
1474
|
this.query = null;
|
|
849
1475
|
this.input = null;
|
|
1476
|
+
this.logger.trace({ claudeSessionId: this.claudeSessionId, turnState: this.turnState }, "Claude session close: completed");
|
|
850
1477
|
}
|
|
851
1478
|
async listCommands() {
|
|
852
1479
|
const q = await this.ensureQuery();
|
|
@@ -1081,20 +1708,6 @@ class ClaudeAgentSession {
|
|
|
1081
1708
|
}
|
|
1082
1709
|
this.userMessageIds.push(messageId);
|
|
1083
1710
|
}
|
|
1084
|
-
async primeSelectableModelIds(query) {
|
|
1085
|
-
try {
|
|
1086
|
-
const models = await query.supportedModels();
|
|
1087
|
-
const ids = models
|
|
1088
|
-
.map((model) => model.value?.trim())
|
|
1089
|
-
.filter((id) => typeof id === "string" && id.length > 0);
|
|
1090
|
-
this.selectableModelIds = new Set(ids);
|
|
1091
|
-
this.logger.debug({ modelIds: ids }, "Primed Claude selectable model IDs");
|
|
1092
|
-
}
|
|
1093
|
-
catch (error) {
|
|
1094
|
-
this.selectableModelIds = null;
|
|
1095
|
-
this.logger.warn({ err: error }, "Failed to prime Claude selectable model IDs");
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
1711
|
async ensureQuery() {
|
|
1099
1712
|
if (this.query && !this.queryRestartNeeded) {
|
|
1100
1713
|
return this.query;
|
|
@@ -1111,13 +1724,35 @@ class ClaudeAgentSession {
|
|
|
1111
1724
|
}
|
|
1112
1725
|
const input = new Pushable();
|
|
1113
1726
|
const options = this.buildOptions();
|
|
1114
|
-
this.logger.debug({ options }, "claude query");
|
|
1727
|
+
this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
|
|
1115
1728
|
this.input = input;
|
|
1116
1729
|
this.query = query({ prompt: input, options });
|
|
1117
|
-
|
|
1118
|
-
|
|
1730
|
+
// Do not kick off background control-plane queries here. Methods like
|
|
1731
|
+
// supportedCommands()/setPermissionMode() may execute immediately after
|
|
1732
|
+
// ensureQuery() (for listCommands()/setMode()), and sharing the same query
|
|
1733
|
+
// control plane can cause those calls to wait behind supportedModels().
|
|
1119
1734
|
return this.query;
|
|
1120
1735
|
}
|
|
1736
|
+
async awaitWithTimeout(promise, label) {
|
|
1737
|
+
if (!promise) {
|
|
1738
|
+
this.logger.trace({ label }, "Claude query operation skipped (no promise)");
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
const startedAt = Date.now();
|
|
1742
|
+
this.logger.trace({ label }, "Claude query operation wait start");
|
|
1743
|
+
try {
|
|
1744
|
+
await Promise.race([
|
|
1745
|
+
promise,
|
|
1746
|
+
new Promise((_, reject) => {
|
|
1747
|
+
setTimeout(() => reject(new Error("timeout")), 3000);
|
|
1748
|
+
}),
|
|
1749
|
+
]);
|
|
1750
|
+
this.logger.trace({ label, durationMs: Date.now() - startedAt }, "Claude query operation settled");
|
|
1751
|
+
}
|
|
1752
|
+
catch (error) {
|
|
1753
|
+
this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1121
1756
|
buildOptions() {
|
|
1122
1757
|
const configuredThinkingOptionId = this.config.thinkingOptionId;
|
|
1123
1758
|
const thinkingOptionId = configuredThinkingOptionId && configuredThinkingOptionId !== "default"
|
|
@@ -1150,8 +1785,9 @@ class ClaudeAgentSession {
|
|
|
1150
1785
|
preset: "claude_code",
|
|
1151
1786
|
append: appendedSystemPrompt,
|
|
1152
1787
|
},
|
|
1153
|
-
settingSources:
|
|
1788
|
+
settingSources: CLAUDE_SETTING_SOURCES,
|
|
1154
1789
|
stderr: (data) => {
|
|
1790
|
+
this.captureStderr(data);
|
|
1155
1791
|
this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
|
|
1156
1792
|
},
|
|
1157
1793
|
env: {
|
|
@@ -1181,24 +1817,7 @@ class ClaudeAgentSession {
|
|
|
1181
1817
|
return this.applyRuntimeSettings(base);
|
|
1182
1818
|
}
|
|
1183
1819
|
applyRuntimeSettings(options) {
|
|
1184
|
-
|
|
1185
|
-
const commandMode = this.runtimeSettings?.command?.mode;
|
|
1186
|
-
const needsCustomSpawn = hasEnvOverrides || commandMode === "append" || commandMode === "replace";
|
|
1187
|
-
if (!needsCustomSpawn) {
|
|
1188
|
-
return options;
|
|
1189
|
-
}
|
|
1190
|
-
return {
|
|
1191
|
-
...options,
|
|
1192
|
-
spawnClaudeCodeProcess: (spawnOptions) => {
|
|
1193
|
-
const resolved = resolveClaudeSpawnCommand(spawnOptions, this.runtimeSettings);
|
|
1194
|
-
return spawn(resolved.command, resolved.args, {
|
|
1195
|
-
cwd: spawnOptions.cwd,
|
|
1196
|
-
env: applyProviderEnv(spawnOptions.env, this.runtimeSettings),
|
|
1197
|
-
signal: spawnOptions.signal,
|
|
1198
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1199
|
-
});
|
|
1200
|
-
},
|
|
1201
|
-
};
|
|
1820
|
+
return applyRuntimeSettingsToClaudeOptions(options, this.runtimeSettings);
|
|
1202
1821
|
}
|
|
1203
1822
|
normalizeMcpServers(servers) {
|
|
1204
1823
|
const result = {};
|
|
@@ -1226,191 +1845,828 @@ class ClaudeAgentSession {
|
|
|
1226
1845
|
}
|
|
1227
1846
|
}
|
|
1228
1847
|
}
|
|
1229
|
-
else {
|
|
1230
|
-
content.push({ type: "text", text: prompt });
|
|
1848
|
+
else {
|
|
1849
|
+
content.push({ type: "text", text: prompt });
|
|
1850
|
+
}
|
|
1851
|
+
const messageId = randomUUID();
|
|
1852
|
+
this.rememberUserMessageId(messageId);
|
|
1853
|
+
this.localUserMessageIds.add(messageId);
|
|
1854
|
+
return {
|
|
1855
|
+
type: "user",
|
|
1856
|
+
message: {
|
|
1857
|
+
role: "user",
|
|
1858
|
+
content,
|
|
1859
|
+
},
|
|
1860
|
+
parent_tool_use_id: null,
|
|
1861
|
+
uuid: messageId,
|
|
1862
|
+
session_id: this.claudeSessionId ?? "",
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
async awaitPendingInterruptPromise() {
|
|
1866
|
+
if (!this.pendingInterruptPromise) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
await this.pendingInterruptPromise;
|
|
1870
|
+
this.pendingInterruptPromise = null;
|
|
1871
|
+
}
|
|
1872
|
+
createRun(owner, queue) {
|
|
1873
|
+
const runId = `${owner}-run-${this.nextRunOrdinal++}`;
|
|
1874
|
+
const run = this.runTracker.createRun({
|
|
1875
|
+
id: runId,
|
|
1876
|
+
owner,
|
|
1877
|
+
queue,
|
|
1878
|
+
promptReplaySeen: owner === "autonomous",
|
|
1879
|
+
});
|
|
1880
|
+
this.logger.debug({ runId, owner, state: run.state }, "Created Claude run");
|
|
1881
|
+
return run;
|
|
1882
|
+
}
|
|
1883
|
+
transitionTurnState(next, reason) {
|
|
1884
|
+
if (this.turnState === next) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
|
|
1888
|
+
this.turnState = next;
|
|
1889
|
+
}
|
|
1890
|
+
transitionTurnStateFromActiveRuns(reason) {
|
|
1891
|
+
if (this.runTracker.hasActiveRuns("foreground")) {
|
|
1892
|
+
this.transitionTurnState("foreground", reason);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (this.runTracker.hasActiveRuns("autonomous")) {
|
|
1896
|
+
this.transitionTurnState("autonomous", reason);
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
this.transitionTurnState("idle", reason);
|
|
1900
|
+
}
|
|
1901
|
+
failRun(run, errorMessage) {
|
|
1902
|
+
this.emitRunEvent(run, this.buildTurnFailedEvent(errorMessage));
|
|
1903
|
+
}
|
|
1904
|
+
buildTurnFailedEvent(errorMessage) {
|
|
1905
|
+
const normalized = errorMessage.trim() || "Claude run failed";
|
|
1906
|
+
const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
|
|
1907
|
+
const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
|
|
1908
|
+
const diagnostic = this.getRecentStderrDiagnostic();
|
|
1909
|
+
return {
|
|
1910
|
+
type: "turn_failed",
|
|
1911
|
+
provider: "claude",
|
|
1912
|
+
error: normalized,
|
|
1913
|
+
...(code ? { code } : {}),
|
|
1914
|
+
...(diagnostic ? { diagnostic } : {}),
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
captureStderr(data) {
|
|
1918
|
+
const text = data.trim();
|
|
1919
|
+
if (!text) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
|
|
1923
|
+
this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
|
|
1924
|
+
}
|
|
1925
|
+
clearRecentStderr() {
|
|
1926
|
+
this.recentStderr = "";
|
|
1927
|
+
}
|
|
1928
|
+
getRecentStderrDiagnostic() {
|
|
1929
|
+
const text = this.recentStderr.trim();
|
|
1930
|
+
return text.length > 0 ? text : undefined;
|
|
1931
|
+
}
|
|
1932
|
+
cancelRun(run, event) {
|
|
1933
|
+
this.flushPendingToolCalls();
|
|
1934
|
+
this.emitRunEvent(run, event);
|
|
1935
|
+
}
|
|
1936
|
+
emitRunEvent(run, event) {
|
|
1937
|
+
if (event.type === "turn_started" ||
|
|
1938
|
+
event.type === "turn_completed" ||
|
|
1939
|
+
event.type === "turn_failed" ||
|
|
1940
|
+
event.type === "turn_canceled") {
|
|
1941
|
+
this.logger.trace({
|
|
1942
|
+
runId: run.id,
|
|
1943
|
+
owner: run.owner,
|
|
1944
|
+
runState: run.state,
|
|
1945
|
+
eventType: event.type,
|
|
1946
|
+
routedTo: run.owner === "foreground" && run.queue ? "foreground_queue" : "live_queue",
|
|
1947
|
+
}, "Claude run event emitted");
|
|
1948
|
+
}
|
|
1949
|
+
if (run.owner === "foreground" && run.queue) {
|
|
1950
|
+
run.queue.push(event);
|
|
1951
|
+
if (event.type === "turn_completed" ||
|
|
1952
|
+
event.type === "turn_failed" ||
|
|
1953
|
+
event.type === "turn_canceled") {
|
|
1954
|
+
run.queue.end();
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
else {
|
|
1958
|
+
this.liveEventQueue.push(event);
|
|
1959
|
+
}
|
|
1960
|
+
this.handleRunTerminalEvent(run, event);
|
|
1961
|
+
}
|
|
1962
|
+
handleRunTerminalEvent(run, event) {
|
|
1963
|
+
if (event.type === "turn_completed") {
|
|
1964
|
+
this.runTracker.complete(run, "completed");
|
|
1965
|
+
}
|
|
1966
|
+
else if (event.type === "turn_failed") {
|
|
1967
|
+
this.runTracker.complete(run, "error");
|
|
1968
|
+
}
|
|
1969
|
+
else if (event.type === "turn_canceled") {
|
|
1970
|
+
this.runTracker.complete(run, "interrupted");
|
|
1971
|
+
}
|
|
1972
|
+
else {
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
if (this.activeForegroundTurn?.runId === run.id) {
|
|
1976
|
+
this.activeForegroundTurn = null;
|
|
1977
|
+
this.preReplayMetadataSeen = false;
|
|
1978
|
+
}
|
|
1979
|
+
this.logger.trace({
|
|
1980
|
+
runId: run.id,
|
|
1981
|
+
owner: run.owner,
|
|
1982
|
+
eventType: event.type,
|
|
1983
|
+
runState: run.state,
|
|
1984
|
+
hasActiveForegroundTurn: Boolean(this.activeForegroundTurn),
|
|
1985
|
+
}, "Claude run terminal event handled");
|
|
1986
|
+
this.transitionTurnStateFromActiveRuns(`run ${run.id} terminal`);
|
|
1987
|
+
}
|
|
1988
|
+
async transitionAutonomousToForeground() {
|
|
1989
|
+
const autonomousRuns = this.runTracker.listActiveRuns("autonomous");
|
|
1990
|
+
if (autonomousRuns.length === 0) {
|
|
1991
|
+
this.transitionTurnStateFromActiveRuns("no autonomous runs to transition");
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
this.logger.debug({ runIds: autonomousRuns.map((run) => run.id) }, "Transitioning autonomous runs to foreground ownership");
|
|
1995
|
+
this.flushPendingToolCalls();
|
|
1996
|
+
for (const run of autonomousRuns) {
|
|
1997
|
+
this.emitRunEvent(run, {
|
|
1998
|
+
type: "turn_canceled",
|
|
1999
|
+
provider: "claude",
|
|
2000
|
+
reason: "Interrupted by foreground prompt",
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
this.pendingInterruptPromise = this.interruptActiveTurn().catch((error) => {
|
|
2004
|
+
this.logger.warn({ err: error }, "Failed to interrupt autonomous run during foreground transition");
|
|
2005
|
+
});
|
|
2006
|
+
await this.awaitPendingInterruptPromise();
|
|
2007
|
+
this.transitionTurnStateFromActiveRuns("autonomous interrupted for foreground");
|
|
2008
|
+
}
|
|
2009
|
+
routeMessage(normalized) {
|
|
2010
|
+
if (normalized.metadataOnly) {
|
|
2011
|
+
if (normalized.message.type === "user" &&
|
|
2012
|
+
isTaskNotificationUserContent(normalized.message.message?.content)) {
|
|
2013
|
+
this.reserveAutonomousWake("task_notification");
|
|
2014
|
+
}
|
|
2015
|
+
this.notePreReplayMetadata(normalized.message);
|
|
2016
|
+
return { run: null, reason: "metadata" };
|
|
2017
|
+
}
|
|
2018
|
+
const hasIdentifiers = Boolean(normalized.identifiers.taskId ||
|
|
2019
|
+
normalized.identifiers.parentMessageId ||
|
|
2020
|
+
normalized.identifiers.messageId);
|
|
2021
|
+
const byIdentifiers = this.runTracker.resolveByIdentifiers(normalized.identifiers);
|
|
2022
|
+
if (byIdentifiers.run) {
|
|
2023
|
+
return byIdentifiers;
|
|
2024
|
+
}
|
|
2025
|
+
if (this.turnState === "autonomous") {
|
|
2026
|
+
const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
|
|
2027
|
+
if (activeAutonomousRun) {
|
|
2028
|
+
return { run: activeAutonomousRun, reason: "unbound_autonomous" };
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
const foregroundRun = this.activeForegroundTurn
|
|
2032
|
+
? this.runTracker.getRun(this.activeForegroundTurn.runId)
|
|
2033
|
+
: null;
|
|
2034
|
+
// A previously unseen task_id during foreground ownership is deterministic
|
|
2035
|
+
// evidence of a distinct autonomous wake/run, not foreground response text.
|
|
2036
|
+
if (this.turnState === "foreground" &&
|
|
2037
|
+
foregroundRun &&
|
|
2038
|
+
normalized.identifiers.taskId) {
|
|
2039
|
+
const incomingTaskId = normalized.identifiers.taskId;
|
|
2040
|
+
// Foreground must claim its first task_id; otherwise early foreground
|
|
2041
|
+
// result events can be misrouted to autonomous fallback runs.
|
|
2042
|
+
if (foregroundRun.taskIds.size === 0) {
|
|
2043
|
+
if (foregroundRun.state !== "finalizing") {
|
|
2044
|
+
return { run: foregroundRun, reason: "foreground" };
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
else if (foregroundRun.taskIds.has(incomingTaskId)) {
|
|
2048
|
+
return { run: foregroundRun, reason: "foreground" };
|
|
2049
|
+
}
|
|
2050
|
+
const autonomousRun = this.createRun("autonomous", null);
|
|
2051
|
+
this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
|
|
2052
|
+
return { run: autonomousRun, reason: "task_id_new" };
|
|
2053
|
+
}
|
|
2054
|
+
if (this.turnState === "foreground" &&
|
|
2055
|
+
foregroundRun &&
|
|
2056
|
+
this.shouldPreferForegroundRun({
|
|
2057
|
+
run: foregroundRun,
|
|
2058
|
+
message: normalized.message,
|
|
2059
|
+
})) {
|
|
2060
|
+
return { run: foregroundRun, reason: "foreground" };
|
|
1231
2061
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
2062
|
+
if (!hasIdentifiers) {
|
|
2063
|
+
if (this.pendingAutonomousWakeReservations > 0) {
|
|
2064
|
+
const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_unbound");
|
|
2065
|
+
return {
|
|
2066
|
+
run: reservedAutonomousRun,
|
|
2067
|
+
reason: "unbound_autonomous",
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
const activeAutonomousRun = this.runTracker.getLatestActiveRun("autonomous");
|
|
2071
|
+
if (activeAutonomousRun) {
|
|
2072
|
+
return { run: activeAutonomousRun, reason: "unbound_autonomous" };
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (this.pendingAutonomousWakeReservations > 0) {
|
|
2076
|
+
const reservedAutonomousRun = this.claimOrCreateAutonomousRun("reservation_fallback");
|
|
2077
|
+
return { run: reservedAutonomousRun, reason: "fallback" };
|
|
2078
|
+
}
|
|
2079
|
+
const autonomousRun = this.createRun("autonomous", null);
|
|
2080
|
+
this.emitRunEvent(autonomousRun, { type: "turn_started", provider: "claude" });
|
|
2081
|
+
return { run: autonomousRun, reason: "fallback" };
|
|
2082
|
+
}
|
|
2083
|
+
shouldPreferForegroundRun(input) {
|
|
2084
|
+
const { run, message } = input;
|
|
2085
|
+
if (run.state === "completed" ||
|
|
2086
|
+
run.state === "interrupted" ||
|
|
2087
|
+
run.state === "error") {
|
|
2088
|
+
return false;
|
|
2089
|
+
}
|
|
2090
|
+
// Before prompt replay is observed, prefer foreground by default so the
|
|
2091
|
+
// first turn cannot be stranded in autonomous fallback. If metadata churn
|
|
2092
|
+
// was observed pre-replay, stay conservative and wait for replay.
|
|
2093
|
+
if (!run.promptReplaySeen) {
|
|
2094
|
+
if (this.isToolUseBoundaryStreamEvent(input.message)) {
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
// Keep pre-replay result events with the foreground run so stale result
|
|
2098
|
+
// bursts cannot consume autonomous wake reservations.
|
|
2099
|
+
if (message.type === "result") {
|
|
2100
|
+
return true;
|
|
2101
|
+
}
|
|
2102
|
+
if (message.type === "assistant" ||
|
|
2103
|
+
message.type === "stream_event" ||
|
|
2104
|
+
message.type === "tool_progress") {
|
|
2105
|
+
return !this.preReplayMetadataSeen;
|
|
2106
|
+
}
|
|
2107
|
+
return true;
|
|
2108
|
+
}
|
|
2109
|
+
if (run.state === "finalizing" &&
|
|
2110
|
+
(message.type === "assistant" || message.type === "stream_event")) {
|
|
2111
|
+
return false;
|
|
2112
|
+
}
|
|
2113
|
+
return true;
|
|
1244
2114
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
2115
|
+
isToolUseBoundaryStreamEvent(message) {
|
|
2116
|
+
if (message.type !== "stream_event") {
|
|
2117
|
+
return false;
|
|
2118
|
+
}
|
|
2119
|
+
const event = message.event;
|
|
2120
|
+
if (!event || event.type !== "message_delta") {
|
|
2121
|
+
return false;
|
|
1252
2122
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
2123
|
+
const delta = "delta" in event && event.delta && typeof event.delta === "object"
|
|
2124
|
+
? event.delta
|
|
2125
|
+
: null;
|
|
2126
|
+
return delta?.stop_reason === "tool_use";
|
|
2127
|
+
}
|
|
2128
|
+
notePreReplayMetadata(message) {
|
|
2129
|
+
if (this.turnState !== "foreground") {
|
|
1255
2130
|
return;
|
|
1256
2131
|
}
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
2132
|
+
const foregroundRun = this.activeForegroundTurn
|
|
2133
|
+
? this.runTracker.getRun(this.activeForegroundTurn.runId)
|
|
2134
|
+
: null;
|
|
2135
|
+
if (!foregroundRun || foregroundRun.promptReplaySeen) {
|
|
2136
|
+
return;
|
|
1260
2137
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
2138
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
this.preReplayMetadataSeen = true;
|
|
2142
|
+
}
|
|
2143
|
+
reserveAutonomousWake(reason) {
|
|
2144
|
+
this.pendingAutonomousWakeReservations += 1;
|
|
2145
|
+
this.logger.debug({
|
|
2146
|
+
reason,
|
|
2147
|
+
pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
|
|
2148
|
+
}, "Reserved autonomous wake");
|
|
2149
|
+
}
|
|
2150
|
+
claimOrCreateAutonomousRun(reason) {
|
|
2151
|
+
const existing = this.runTracker.getLatestActiveRun("autonomous");
|
|
2152
|
+
if (existing) {
|
|
2153
|
+
if (this.pendingAutonomousWakeReservations > 0) {
|
|
2154
|
+
this.pendingAutonomousWakeReservations -= 1;
|
|
2155
|
+
}
|
|
2156
|
+
this.logger.debug({
|
|
2157
|
+
reason,
|
|
2158
|
+
runId: existing.id,
|
|
2159
|
+
pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
|
|
2160
|
+
}, "Claimed autonomous wake reservation on existing run");
|
|
2161
|
+
return existing;
|
|
2162
|
+
}
|
|
2163
|
+
const run = this.createRun("autonomous", null);
|
|
2164
|
+
this.emitRunEvent(run, { type: "turn_started", provider: "claude" });
|
|
2165
|
+
if (this.pendingAutonomousWakeReservations > 0) {
|
|
2166
|
+
this.pendingAutonomousWakeReservations -= 1;
|
|
2167
|
+
}
|
|
2168
|
+
this.logger.debug({
|
|
2169
|
+
reason,
|
|
2170
|
+
runId: run.id,
|
|
2171
|
+
pendingAutonomousWakeReservations: this.pendingAutonomousWakeReservations,
|
|
2172
|
+
}, "Claimed autonomous wake reservation with new run");
|
|
2173
|
+
return run;
|
|
2174
|
+
}
|
|
2175
|
+
startQueryPump() {
|
|
2176
|
+
if (this.closed || this.queryPumpPromise) {
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
const pump = this.runQueryPump().catch((error) => {
|
|
2180
|
+
this.logger.warn({ err: error }, "Claude query pump exited unexpectedly");
|
|
2181
|
+
});
|
|
2182
|
+
this.queryPumpPromise = pump;
|
|
2183
|
+
pump.finally(() => {
|
|
2184
|
+
if (this.queryPumpPromise === pump) {
|
|
2185
|
+
this.queryPumpPromise = null;
|
|
1266
2186
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
async runQueryPump() {
|
|
2190
|
+
while (!this.closed) {
|
|
2191
|
+
if (!this.claudeSessionId && !this.activeForegroundTurn && !this.query) {
|
|
2192
|
+
await this.waitForLiveHistoryPoll();
|
|
2193
|
+
continue;
|
|
1270
2194
|
}
|
|
1271
|
-
|
|
2195
|
+
let q;
|
|
2196
|
+
try {
|
|
2197
|
+
q = await this.ensureQuery();
|
|
2198
|
+
}
|
|
2199
|
+
catch (error) {
|
|
2200
|
+
this.logger.warn({ err: error }, "Failed to initialize Claude query pump");
|
|
2201
|
+
await this.waitForLiveHistoryPoll();
|
|
1272
2202
|
continue;
|
|
1273
2203
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
2204
|
+
let next;
|
|
2205
|
+
try {
|
|
2206
|
+
next = await q.next();
|
|
2207
|
+
this.logger.info({ claudeSessionId: this.claudeSessionId, next }, "Claude query pump raw next()");
|
|
1277
2208
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
2209
|
+
catch (error) {
|
|
2210
|
+
this.logger.warn({ err: error }, "Claude query pump next() failed");
|
|
2211
|
+
for (const run of this.runTracker.listActiveRuns()) {
|
|
2212
|
+
this.failRun(run, error instanceof Error ? error.message : "Claude stream failed");
|
|
2213
|
+
}
|
|
2214
|
+
this.input?.end();
|
|
2215
|
+
await this.awaitWithTimeout(q.return?.(), "query pump return after failure");
|
|
2216
|
+
if (this.query === q) {
|
|
2217
|
+
this.query = null;
|
|
2218
|
+
this.input = null;
|
|
2219
|
+
}
|
|
2220
|
+
await this.waitForLiveHistoryPoll();
|
|
2221
|
+
continue;
|
|
1281
2222
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
try {
|
|
1293
|
-
for await (const sdkEvent of this.processPrompt(message, turnId)) {
|
|
1294
|
-
// Check if this turn has been superseded before pushing events
|
|
1295
|
-
if (this.currentTurnId !== turnId) {
|
|
1296
|
-
break;
|
|
2223
|
+
if (next.done) {
|
|
2224
|
+
this.logger.trace({
|
|
2225
|
+
claudeSessionId: this.claudeSessionId,
|
|
2226
|
+
activeRunCount: this.runTracker.listActiveRuns().length,
|
|
2227
|
+
}, "Claude query pump next() returned done");
|
|
2228
|
+
this.input?.end();
|
|
2229
|
+
await this.awaitWithTimeout(q.return?.(), "query pump return on done");
|
|
2230
|
+
if (this.query === q) {
|
|
2231
|
+
this.query = null;
|
|
2232
|
+
this.input = null;
|
|
1297
2233
|
}
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
completedNormally = true;
|
|
2234
|
+
const activeRuns = this.runTracker.listActiveRuns();
|
|
2235
|
+
if (activeRuns.length > 0) {
|
|
2236
|
+
for (const run of activeRuns) {
|
|
2237
|
+
this.failRun(run, "Claude stream ended before terminal result");
|
|
1303
2238
|
}
|
|
1304
2239
|
}
|
|
2240
|
+
await this.waitForLiveHistoryPoll();
|
|
2241
|
+
continue;
|
|
1305
2242
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
2243
|
+
const sdkMessage = next.value;
|
|
2244
|
+
if (!sdkMessage) {
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
try {
|
|
2248
|
+
this.routeSdkMessageFromPump(sdkMessage);
|
|
2249
|
+
}
|
|
2250
|
+
catch (error) {
|
|
2251
|
+
this.logger.warn({ err: error }, "Failed to route Claude SDK message from query pump");
|
|
1314
2252
|
}
|
|
1315
2253
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
2254
|
+
}
|
|
2255
|
+
routeSdkMessageFromPump(message) {
|
|
2256
|
+
if (this.shouldSuppressLocalReplayActivity(message)) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const identifiers = readEventIdentifiers(message);
|
|
2260
|
+
const metadataOnly = isMetadataOnlySdkMessage(message);
|
|
2261
|
+
const route = this.routeMessage({
|
|
2262
|
+
message,
|
|
2263
|
+
identifiers,
|
|
2264
|
+
metadataOnly,
|
|
2265
|
+
});
|
|
2266
|
+
const suppressTerminalEvents = this.shouldSuppressReplayResultTerminal({
|
|
2267
|
+
run: route.run,
|
|
2268
|
+
message,
|
|
2269
|
+
});
|
|
2270
|
+
this.logger.trace({
|
|
2271
|
+
claudeSessionId: this.claudeSessionId,
|
|
2272
|
+
messageType: message.type,
|
|
2273
|
+
routeReason: route.reason,
|
|
2274
|
+
runId: route.run?.id ?? null,
|
|
2275
|
+
runOwner: route.run?.owner ?? null,
|
|
2276
|
+
suppressTerminalEvents,
|
|
2277
|
+
metadataOnly,
|
|
2278
|
+
}, "Claude query pump routed SDK message");
|
|
2279
|
+
if (route.run) {
|
|
2280
|
+
this.transitionTurnStateFromActiveRuns(`routed via ${route.reason}`);
|
|
2281
|
+
this.runTracker.bindIdentifiers(route.run, identifiers);
|
|
2282
|
+
if (!suppressTerminalEvents) {
|
|
2283
|
+
this.updateRunLifecycleForMessage(route.run, message, identifiers);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
const messageEvents = this.translateMessageToEvents(message, {
|
|
2287
|
+
suppressAssistantText: true,
|
|
2288
|
+
suppressReasoning: true,
|
|
2289
|
+
suppressTerminalEvents,
|
|
2290
|
+
});
|
|
2291
|
+
const assistantTimelineItems = this.timelineAssembler.consume({
|
|
2292
|
+
message,
|
|
2293
|
+
runId: route.run?.id ?? null,
|
|
2294
|
+
messageIdHint: identifiers.messageId,
|
|
2295
|
+
});
|
|
2296
|
+
const assistantTimelineEvents = assistantTimelineItems.map((item) => ({
|
|
2297
|
+
type: "timeline",
|
|
2298
|
+
item,
|
|
2299
|
+
provider: "claude",
|
|
2300
|
+
}));
|
|
2301
|
+
const events = [...messageEvents, ...assistantTimelineEvents];
|
|
2302
|
+
if (events.length === 0) {
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
if (!route.run) {
|
|
2306
|
+
this.dispatchMetadataEvents(events);
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
for (const event of events) {
|
|
2310
|
+
this.emitRunEvent(route.run, event);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
shouldSuppressReplayResultTerminal(input) {
|
|
2314
|
+
const { run, message } = input;
|
|
2315
|
+
if (!run || run.owner !== "foreground" || message.type !== "result") {
|
|
2316
|
+
return false;
|
|
2317
|
+
}
|
|
2318
|
+
if (run.promptReplaySeen) {
|
|
2319
|
+
return false;
|
|
2320
|
+
}
|
|
2321
|
+
if (run.state === "streaming" || run.state === "finalizing") {
|
|
2322
|
+
return false;
|
|
2323
|
+
}
|
|
2324
|
+
const resultSubtype = "subtype" in message && typeof message.subtype === "string"
|
|
2325
|
+
? message.subtype
|
|
2326
|
+
: null;
|
|
2327
|
+
// Pre-replay success results are stale in practice (leftover from an
|
|
2328
|
+
// earlier query segment) and must not end the current foreground run.
|
|
2329
|
+
if (resultSubtype === "success") {
|
|
2330
|
+
this.logger.trace({
|
|
2331
|
+
runId: run.id,
|
|
2332
|
+
runOwner: run.owner,
|
|
2333
|
+
runState: run.state,
|
|
2334
|
+
promptReplaySeen: run.promptReplaySeen,
|
|
2335
|
+
resultSubtype,
|
|
2336
|
+
}, "Suppressing pre-replay foreground success result terminal event");
|
|
2337
|
+
return true;
|
|
2338
|
+
}
|
|
2339
|
+
// For non-success results, keep the metadata-churn guard to avoid
|
|
2340
|
+
// suppressing legitimate hard failures.
|
|
2341
|
+
return this.preReplayMetadataSeen;
|
|
2342
|
+
}
|
|
2343
|
+
dispatchMetadataEvents(events) {
|
|
2344
|
+
for (const event of events) {
|
|
2345
|
+
this.pushEvent(event);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
updateRunLifecycleForMessage(run, message, identifiers) {
|
|
2349
|
+
const previousState = run.state;
|
|
2350
|
+
if (message.type === "user" &&
|
|
2351
|
+
identifiers.messageId &&
|
|
2352
|
+
run.messageIds.has(identifiers.messageId)) {
|
|
2353
|
+
run.promptReplaySeen = true;
|
|
2354
|
+
this.preReplayMetadataSeen = false;
|
|
2355
|
+
}
|
|
2356
|
+
if (run.state === "queued") {
|
|
2357
|
+
this.runTracker.transition(run, "awaiting_response");
|
|
2358
|
+
}
|
|
2359
|
+
if (message.type === "assistant" ||
|
|
2360
|
+
message.type === "stream_event" ||
|
|
2361
|
+
message.type === "tool_progress") {
|
|
2362
|
+
this.runTracker.transition(run, "streaming");
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (message.type === "result") {
|
|
2366
|
+
this.runTracker.transition(run, "finalizing");
|
|
2367
|
+
}
|
|
2368
|
+
else {
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
if (run.state !== previousState) {
|
|
2372
|
+
this.logger.trace({
|
|
2373
|
+
runId: run.id,
|
|
2374
|
+
owner: run.owner,
|
|
2375
|
+
messageType: message.type,
|
|
2376
|
+
previousState,
|
|
2377
|
+
nextState: run.state,
|
|
2378
|
+
taskId: identifiers.taskId,
|
|
2379
|
+
parentMessageId: identifiers.parentMessageId,
|
|
2380
|
+
messageId: identifiers.messageId,
|
|
2381
|
+
}, "Updated Claude run lifecycle from SDK message");
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
shouldSuppressLocalReplayActivity(message) {
|
|
2385
|
+
const localReplay = this.isLocalReplayUserMessage(message);
|
|
2386
|
+
if (!this.activeForegroundTurn && localReplay) {
|
|
2387
|
+
this.suppressLocalReplayActivity = true;
|
|
2388
|
+
this.logger.debug({ uuid: message.uuid }, "Suppressing local replay user message from live pump");
|
|
2389
|
+
return true;
|
|
2390
|
+
}
|
|
2391
|
+
if (!this.suppressLocalReplayActivity) {
|
|
2392
|
+
return false;
|
|
2393
|
+
}
|
|
2394
|
+
// Suppress only replay scaffolding. Do not suppress autonomous
|
|
2395
|
+
// assistant/result events; otherwise task-notification replies can be dropped.
|
|
2396
|
+
if (localReplay) {
|
|
2397
|
+
return true;
|
|
2398
|
+
}
|
|
2399
|
+
if (message.type === "system") {
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
const identifiers = readEventIdentifiers(message);
|
|
2403
|
+
const hasIdentifiers = Boolean(identifiers.taskId || identifiers.parentMessageId || identifiers.messageId);
|
|
2404
|
+
if (message.type !== "user" && !hasIdentifiers) {
|
|
2405
|
+
if (this.pendingAutonomousWakeReservations > 0) {
|
|
2406
|
+
this.suppressLocalReplayActivity = false;
|
|
2407
|
+
return false;
|
|
1328
2408
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
2409
|
+
return true;
|
|
2410
|
+
}
|
|
2411
|
+
if (message.type === "user") {
|
|
2412
|
+
this.suppressLocalReplayActivity = false;
|
|
2413
|
+
return false;
|
|
1331
2414
|
}
|
|
2415
|
+
this.suppressLocalReplayActivity = false;
|
|
2416
|
+
return false;
|
|
2417
|
+
}
|
|
2418
|
+
isLocalReplayUserMessage(message) {
|
|
2419
|
+
if (message.type !== "user") {
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
const uuid = readTrimmedString(message.uuid);
|
|
2423
|
+
if (!uuid) {
|
|
2424
|
+
return false;
|
|
2425
|
+
}
|
|
2426
|
+
return this.localUserMessageIds.has(uuid);
|
|
1332
2427
|
}
|
|
1333
2428
|
async interruptActiveTurn() {
|
|
1334
2429
|
const queryToInterrupt = this.query;
|
|
1335
2430
|
if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
|
|
1336
|
-
this.logger.
|
|
2431
|
+
this.logger.debug("interruptActiveTurn: no query to interrupt");
|
|
1337
2432
|
return;
|
|
1338
2433
|
}
|
|
1339
2434
|
try {
|
|
1340
|
-
this.logger.
|
|
2435
|
+
this.logger.debug("interruptActiveTurn: calling query.interrupt()...");
|
|
1341
2436
|
const t0 = Date.now();
|
|
1342
2437
|
await queryToInterrupt.interrupt();
|
|
1343
|
-
this.logger.
|
|
2438
|
+
this.logger.debug({ durationMs: Date.now() - t0 }, "interruptActiveTurn: query.interrupt() returned");
|
|
1344
2439
|
// After interrupt(), the query iterator is done (returns done: true).
|
|
1345
2440
|
// Clear it so ensureQuery() creates a fresh query for the next turn.
|
|
1346
2441
|
// Also end the input stream and call return() to clean up the SDK process.
|
|
1347
2442
|
this.input?.end();
|
|
1348
|
-
this.logger.
|
|
2443
|
+
this.logger.debug("interruptActiveTurn: calling query.return()...");
|
|
1349
2444
|
const t1 = Date.now();
|
|
1350
2445
|
await queryToInterrupt.return?.();
|
|
1351
|
-
this.logger.
|
|
2446
|
+
this.logger.debug({ durationMs: Date.now() - t1 }, "interruptActiveTurn: query.return() returned");
|
|
1352
2447
|
this.query = null;
|
|
1353
2448
|
this.input = null;
|
|
1354
2449
|
this.queryRestartNeeded = false;
|
|
1355
2450
|
}
|
|
1356
2451
|
catch (error) {
|
|
1357
2452
|
this.logger.warn({ err: error }, "Failed to interrupt active turn");
|
|
2453
|
+
// If interrupt fails, the SDK iterator may remain in an indeterminate state.
|
|
2454
|
+
// Force a teardown/recreate path so the next turn cannot reuse stale query state.
|
|
2455
|
+
this.queryRestartNeeded = true;
|
|
1358
2456
|
}
|
|
1359
2457
|
}
|
|
1360
2458
|
handleSidechainMessage(message, parentToolUseId) {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
else if (message.type === "stream_event") {
|
|
1376
|
-
const event = message.event;
|
|
1377
|
-
if (event.type === "content_block_start") {
|
|
1378
|
-
const cb = isClaudeContentChunk(event.content_block) ? event.content_block : null;
|
|
1379
|
-
if (cb?.type === "tool_use" && typeof cb.name === "string") {
|
|
1380
|
-
toolName = cb.name;
|
|
1381
|
-
}
|
|
2459
|
+
const state = this.activeSidechains.get(parentToolUseId) ??
|
|
2460
|
+
{
|
|
2461
|
+
actions: [],
|
|
2462
|
+
actionKeys: [],
|
|
2463
|
+
nextActionIndex: 1,
|
|
2464
|
+
actionIndexByKey: new Map(),
|
|
2465
|
+
};
|
|
2466
|
+
this.activeSidechains.set(parentToolUseId, state);
|
|
2467
|
+
const contextUpdated = this.updateSubAgentContextFromTaskInput(state, parentToolUseId);
|
|
2468
|
+
const actionCandidates = this.extractSubAgentActionCandidates(message);
|
|
2469
|
+
let actionUpdated = false;
|
|
2470
|
+
for (const action of actionCandidates) {
|
|
2471
|
+
if (this.appendSubAgentAction(state, action)) {
|
|
2472
|
+
actionUpdated = true;
|
|
1382
2473
|
}
|
|
1383
2474
|
}
|
|
1384
|
-
|
|
1385
|
-
toolName = message.tool_name;
|
|
1386
|
-
}
|
|
1387
|
-
if (!toolName) {
|
|
1388
|
-
return [];
|
|
1389
|
-
}
|
|
1390
|
-
const prev = this.activeSidechains.get(parentToolUseId);
|
|
1391
|
-
if (prev === toolName) {
|
|
2475
|
+
if (!contextUpdated && !actionUpdated) {
|
|
1392
2476
|
return [];
|
|
1393
2477
|
}
|
|
1394
|
-
this.activeSidechains.set(parentToolUseId, toolName);
|
|
1395
2478
|
const toolCall = mapClaudeRunningToolCall({
|
|
1396
2479
|
name: "Task",
|
|
1397
2480
|
callId: parentToolUseId,
|
|
1398
2481
|
input: null,
|
|
1399
2482
|
output: null,
|
|
1400
|
-
metadata: { subAgentActivity: toolName },
|
|
1401
2483
|
});
|
|
1402
2484
|
if (!toolCall) {
|
|
1403
2485
|
return [];
|
|
1404
2486
|
}
|
|
2487
|
+
const detail = {
|
|
2488
|
+
type: "sub_agent",
|
|
2489
|
+
...(state.subAgentType ? { subAgentType: state.subAgentType } : {}),
|
|
2490
|
+
...(state.description ? { description: state.description } : {}),
|
|
2491
|
+
log: state.actions
|
|
2492
|
+
.map((action) => action.summary
|
|
2493
|
+
? `[${action.toolName}] ${action.summary}`
|
|
2494
|
+
: `[${action.toolName}]`)
|
|
2495
|
+
.join("\n"),
|
|
2496
|
+
actions: state.actions.map((action) => ({
|
|
2497
|
+
index: action.index,
|
|
2498
|
+
toolName: action.toolName,
|
|
2499
|
+
...(action.summary ? { summary: action.summary } : {}),
|
|
2500
|
+
})),
|
|
2501
|
+
};
|
|
1405
2502
|
return [
|
|
1406
2503
|
{
|
|
1407
2504
|
type: "timeline",
|
|
1408
|
-
item:
|
|
2505
|
+
item: {
|
|
2506
|
+
...toolCall,
|
|
2507
|
+
detail,
|
|
2508
|
+
},
|
|
1409
2509
|
provider: "claude",
|
|
1410
2510
|
},
|
|
1411
2511
|
];
|
|
1412
2512
|
}
|
|
1413
|
-
|
|
2513
|
+
updateSubAgentContextFromTaskInput(state, parentToolUseId) {
|
|
2514
|
+
const taskInput = this.toolUseCache.get(parentToolUseId)?.input;
|
|
2515
|
+
const nextSubAgentType = this.normalizeSubAgentText(taskInput?.subagent_type);
|
|
2516
|
+
const nextDescription = this.normalizeSubAgentText(taskInput?.description);
|
|
2517
|
+
let changed = false;
|
|
2518
|
+
if (nextSubAgentType && nextSubAgentType !== state.subAgentType) {
|
|
2519
|
+
state.subAgentType = nextSubAgentType;
|
|
2520
|
+
changed = true;
|
|
2521
|
+
}
|
|
2522
|
+
if (nextDescription && nextDescription !== state.description) {
|
|
2523
|
+
state.description = nextDescription;
|
|
2524
|
+
changed = true;
|
|
2525
|
+
}
|
|
2526
|
+
return changed;
|
|
2527
|
+
}
|
|
2528
|
+
normalizeSubAgentText(value) {
|
|
2529
|
+
const normalized = readTrimmedString(value)?.replace(/\s+/g, " ");
|
|
2530
|
+
if (!normalized) {
|
|
2531
|
+
return undefined;
|
|
2532
|
+
}
|
|
2533
|
+
if (normalized.length <= MAX_SUB_AGENT_SUMMARY_CHARS) {
|
|
2534
|
+
return normalized;
|
|
2535
|
+
}
|
|
2536
|
+
return `${normalized.slice(0, MAX_SUB_AGENT_SUMMARY_CHARS)}...`;
|
|
2537
|
+
}
|
|
2538
|
+
extractSubAgentActionCandidates(message) {
|
|
2539
|
+
if (message.type === "assistant") {
|
|
2540
|
+
const content = message.message?.content;
|
|
2541
|
+
if (!Array.isArray(content)) {
|
|
2542
|
+
return [];
|
|
2543
|
+
}
|
|
2544
|
+
const actions = [];
|
|
2545
|
+
for (const block of content) {
|
|
2546
|
+
if (!isClaudeContentChunk(block) ||
|
|
2547
|
+
!(block.type === "tool_use" ||
|
|
2548
|
+
block.type === "mcp_tool_use" ||
|
|
2549
|
+
block.type === "server_tool_use") ||
|
|
2550
|
+
typeof block.name !== "string") {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const key = readTrimmedString(block.id) ??
|
|
2554
|
+
`assistant:${block.name}:${actions.length}`;
|
|
2555
|
+
actions.push({
|
|
2556
|
+
key,
|
|
2557
|
+
toolName: block.name,
|
|
2558
|
+
input: block.input ?? null,
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
return actions;
|
|
2562
|
+
}
|
|
2563
|
+
if (message.type === "stream_event") {
|
|
2564
|
+
const event = message.event;
|
|
2565
|
+
if (event.type !== "content_block_start") {
|
|
2566
|
+
return [];
|
|
2567
|
+
}
|
|
2568
|
+
const block = isClaudeContentChunk(event.content_block)
|
|
2569
|
+
? event.content_block
|
|
2570
|
+
: null;
|
|
2571
|
+
if (!block ||
|
|
2572
|
+
!(block.type === "tool_use" ||
|
|
2573
|
+
block.type === "mcp_tool_use" ||
|
|
2574
|
+
block.type === "server_tool_use") ||
|
|
2575
|
+
typeof block.name !== "string") {
|
|
2576
|
+
return [];
|
|
2577
|
+
}
|
|
2578
|
+
const key = readTrimmedString(block.id) ??
|
|
2579
|
+
`stream:${block.name}:${typeof event.index === "number" ? event.index : 0}`;
|
|
2580
|
+
return [
|
|
2581
|
+
{
|
|
2582
|
+
key,
|
|
2583
|
+
toolName: block.name,
|
|
2584
|
+
input: block.input ?? null,
|
|
2585
|
+
},
|
|
2586
|
+
];
|
|
2587
|
+
}
|
|
2588
|
+
if (message.type === "tool_progress") {
|
|
2589
|
+
const toolName = readTrimmedString(message.tool_name);
|
|
2590
|
+
if (!toolName) {
|
|
2591
|
+
return [];
|
|
2592
|
+
}
|
|
2593
|
+
const key = readTrimmedString(message.tool_use_id) ?? `progress:${toolName}`;
|
|
2594
|
+
return [{ key, toolName, input: null }];
|
|
2595
|
+
}
|
|
2596
|
+
return [];
|
|
2597
|
+
}
|
|
2598
|
+
appendSubAgentAction(state, candidate) {
|
|
2599
|
+
const normalizedToolName = readTrimmedString(candidate.toolName);
|
|
2600
|
+
if (!normalizedToolName) {
|
|
2601
|
+
return false;
|
|
2602
|
+
}
|
|
2603
|
+
const summary = this.deriveSubAgentActionSummary(normalizedToolName, candidate.input);
|
|
2604
|
+
const existingIndex = state.actionIndexByKey.get(candidate.key);
|
|
2605
|
+
if (existingIndex !== undefined) {
|
|
2606
|
+
const existing = state.actions[existingIndex];
|
|
2607
|
+
if (!existing) {
|
|
2608
|
+
return false;
|
|
2609
|
+
}
|
|
2610
|
+
const nextSummary = existing.summary ?? summary;
|
|
2611
|
+
const unchanged = existing.toolName === normalizedToolName &&
|
|
2612
|
+
existing.summary === nextSummary;
|
|
2613
|
+
if (unchanged) {
|
|
2614
|
+
return false;
|
|
2615
|
+
}
|
|
2616
|
+
state.actions[existingIndex] = {
|
|
2617
|
+
...existing,
|
|
2618
|
+
toolName: normalizedToolName,
|
|
2619
|
+
...(nextSummary ? { summary: nextSummary } : {}),
|
|
2620
|
+
};
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
const nextEntry = {
|
|
2624
|
+
index: state.nextActionIndex,
|
|
2625
|
+
toolName: normalizedToolName,
|
|
2626
|
+
...(summary ? { summary } : {}),
|
|
2627
|
+
};
|
|
2628
|
+
state.nextActionIndex += 1;
|
|
2629
|
+
state.actions.push(nextEntry);
|
|
2630
|
+
state.actionKeys.push(candidate.key);
|
|
2631
|
+
this.trimSubAgentTail(state);
|
|
2632
|
+
this.rebuildSubAgentActionIndex(state);
|
|
2633
|
+
return true;
|
|
2634
|
+
}
|
|
2635
|
+
trimSubAgentTail(state) {
|
|
2636
|
+
while (state.actions.length > MAX_SUB_AGENT_LOG_ENTRIES) {
|
|
2637
|
+
state.actions.shift();
|
|
2638
|
+
state.actionKeys.shift();
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
rebuildSubAgentActionIndex(state) {
|
|
2642
|
+
state.actionIndexByKey.clear();
|
|
2643
|
+
for (let index = 0; index < state.actionKeys.length; index += 1) {
|
|
2644
|
+
const key = state.actionKeys[index];
|
|
2645
|
+
if (key) {
|
|
2646
|
+
state.actionIndexByKey.set(key, index);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
deriveSubAgentActionSummary(toolName, input) {
|
|
2651
|
+
const runningToolCall = mapClaudeRunningToolCall({
|
|
2652
|
+
name: toolName,
|
|
2653
|
+
callId: `sub-agent-summary-${toolName}`,
|
|
2654
|
+
input,
|
|
2655
|
+
output: null,
|
|
2656
|
+
});
|
|
2657
|
+
if (!runningToolCall) {
|
|
2658
|
+
return undefined;
|
|
2659
|
+
}
|
|
2660
|
+
const display = buildToolCallDisplayModel({
|
|
2661
|
+
name: runningToolCall.name,
|
|
2662
|
+
status: runningToolCall.status,
|
|
2663
|
+
error: runningToolCall.error,
|
|
2664
|
+
detail: runningToolCall.detail,
|
|
2665
|
+
metadata: runningToolCall.metadata,
|
|
2666
|
+
});
|
|
2667
|
+
return this.normalizeSubAgentText(display.summary);
|
|
2668
|
+
}
|
|
2669
|
+
translateMessageToEvents(message, options) {
|
|
1414
2670
|
const parentToolUseId = "parent_tool_use_id" in message
|
|
1415
2671
|
? message.parent_tool_use_id
|
|
1416
2672
|
: null;
|
|
@@ -1418,6 +2674,14 @@ class ClaudeAgentSession {
|
|
|
1418
2674
|
return this.handleSidechainMessage(message, parentToolUseId);
|
|
1419
2675
|
}
|
|
1420
2676
|
const events = [];
|
|
2677
|
+
const fallbackThreadSessionId = this.captureSessionIdFromMessage(message);
|
|
2678
|
+
if (fallbackThreadSessionId) {
|
|
2679
|
+
events.push({
|
|
2680
|
+
type: "thread_started",
|
|
2681
|
+
provider: "claude",
|
|
2682
|
+
sessionId: fallbackThreadSessionId,
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
1421
2685
|
switch (message.type) {
|
|
1422
2686
|
case "system":
|
|
1423
2687
|
if (message.subtype === "init") {
|
|
@@ -1442,20 +2706,33 @@ class ClaudeAgentSession {
|
|
|
1442
2706
|
}
|
|
1443
2707
|
}
|
|
1444
2708
|
else if (message.subtype === "compact_boundary") {
|
|
1445
|
-
const
|
|
2709
|
+
const compactMetadata = readCompactionMetadata(message);
|
|
1446
2710
|
events.push({
|
|
1447
2711
|
type: "timeline",
|
|
1448
2712
|
item: {
|
|
1449
2713
|
type: "compaction",
|
|
1450
2714
|
status: "completed",
|
|
1451
|
-
trigger:
|
|
1452
|
-
preTokens:
|
|
2715
|
+
trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
|
|
2716
|
+
preTokens: compactMetadata?.preTokens,
|
|
1453
2717
|
},
|
|
1454
2718
|
provider: "claude",
|
|
1455
2719
|
});
|
|
1456
2720
|
}
|
|
2721
|
+
else if (message.subtype === "task_notification") {
|
|
2722
|
+
const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
|
|
2723
|
+
if (taskNotificationItem) {
|
|
2724
|
+
events.push({
|
|
2725
|
+
type: "timeline",
|
|
2726
|
+
item: taskNotificationItem,
|
|
2727
|
+
provider: "claude",
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
1457
2731
|
break;
|
|
1458
2732
|
case "user": {
|
|
2733
|
+
if (isSyntheticUserEntry(message)) {
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
1459
2736
|
if (this.compacting) {
|
|
1460
2737
|
this.compacting = false;
|
|
1461
2738
|
break;
|
|
@@ -1465,6 +2742,18 @@ class ClaudeAgentSession {
|
|
|
1465
2742
|
: undefined;
|
|
1466
2743
|
this.rememberUserMessageId(messageId);
|
|
1467
2744
|
const content = message.message?.content;
|
|
2745
|
+
const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
|
|
2746
|
+
content,
|
|
2747
|
+
messageId,
|
|
2748
|
+
});
|
|
2749
|
+
if (taskNotificationItem) {
|
|
2750
|
+
events.push({
|
|
2751
|
+
type: "timeline",
|
|
2752
|
+
item: taskNotificationItem,
|
|
2753
|
+
provider: "claude",
|
|
2754
|
+
});
|
|
2755
|
+
break;
|
|
2756
|
+
}
|
|
1468
2757
|
if (typeof content === "string" && content.length > 0) {
|
|
1469
2758
|
// String content from user messages (e.g., local command output)
|
|
1470
2759
|
events.push({
|
|
@@ -1478,7 +2767,7 @@ class ClaudeAgentSession {
|
|
|
1478
2767
|
});
|
|
1479
2768
|
}
|
|
1480
2769
|
else if (Array.isArray(content)) {
|
|
1481
|
-
const timelineItems = this.mapBlocksToTimeline(content
|
|
2770
|
+
const timelineItems = this.mapBlocksToTimeline(content);
|
|
1482
2771
|
for (const item of timelineItems) {
|
|
1483
2772
|
if (item.type === "user_message" && messageId && !item.messageId) {
|
|
1484
2773
|
events.push({
|
|
@@ -1495,9 +2784,8 @@ class ClaudeAgentSession {
|
|
|
1495
2784
|
}
|
|
1496
2785
|
case "assistant": {
|
|
1497
2786
|
const timelineItems = this.mapBlocksToTimeline(message.message.content, {
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
suppressReasoning: turnContext.streamedReasoningThisTurn,
|
|
2787
|
+
suppressAssistantText: options?.suppressAssistantText ?? false,
|
|
2788
|
+
suppressReasoning: options?.suppressReasoning ?? false,
|
|
1501
2789
|
});
|
|
1502
2790
|
for (const item of timelineItems) {
|
|
1503
2791
|
events.push({ type: "timeline", item, provider: "claude" });
|
|
@@ -1505,13 +2793,19 @@ class ClaudeAgentSession {
|
|
|
1505
2793
|
break;
|
|
1506
2794
|
}
|
|
1507
2795
|
case "stream_event": {
|
|
1508
|
-
const timelineItems = this.mapPartialEvent(message.event,
|
|
2796
|
+
const timelineItems = this.mapPartialEvent(message.event, {
|
|
2797
|
+
suppressAssistantText: options?.suppressAssistantText ?? false,
|
|
2798
|
+
suppressReasoning: options?.suppressReasoning ?? false,
|
|
2799
|
+
});
|
|
1509
2800
|
for (const item of timelineItems) {
|
|
1510
2801
|
events.push({ type: "timeline", item, provider: "claude" });
|
|
1511
2802
|
}
|
|
1512
2803
|
break;
|
|
1513
2804
|
}
|
|
1514
2805
|
case "result": {
|
|
2806
|
+
if (options?.suppressTerminalEvents) {
|
|
2807
|
+
break;
|
|
2808
|
+
}
|
|
1515
2809
|
const usage = this.convertUsage(message);
|
|
1516
2810
|
if (message.subtype === "success") {
|
|
1517
2811
|
events.push({ type: "turn_completed", provider: "claude", usage });
|
|
@@ -1520,7 +2814,7 @@ class ClaudeAgentSession {
|
|
|
1520
2814
|
const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
|
|
1521
2815
|
? message.errors.join("\n")
|
|
1522
2816
|
: "Claude run failed";
|
|
1523
|
-
events.push(
|
|
2817
|
+
events.push(this.buildTurnFailedEvent(errorMessage));
|
|
1524
2818
|
}
|
|
1525
2819
|
break;
|
|
1526
2820
|
}
|
|
@@ -1529,6 +2823,31 @@ class ClaudeAgentSession {
|
|
|
1529
2823
|
}
|
|
1530
2824
|
return events;
|
|
1531
2825
|
}
|
|
2826
|
+
captureSessionIdFromMessage(message) {
|
|
2827
|
+
const msg = message;
|
|
2828
|
+
const sessionIdRaw = typeof msg.session_id === "string"
|
|
2829
|
+
? msg.session_id
|
|
2830
|
+
: typeof msg.sessionId === "string"
|
|
2831
|
+
? msg.sessionId
|
|
2832
|
+
: typeof msg.session?.id === "string"
|
|
2833
|
+
? msg.session.id
|
|
2834
|
+
: "";
|
|
2835
|
+
const sessionId = sessionIdRaw.trim();
|
|
2836
|
+
if (!sessionId) {
|
|
2837
|
+
return null;
|
|
2838
|
+
}
|
|
2839
|
+
if (this.claudeSessionId === null) {
|
|
2840
|
+
this.claudeSessionId = sessionId;
|
|
2841
|
+
this.persistence = null;
|
|
2842
|
+
return sessionId;
|
|
2843
|
+
}
|
|
2844
|
+
if (this.claudeSessionId === sessionId) {
|
|
2845
|
+
return null;
|
|
2846
|
+
}
|
|
2847
|
+
throw new Error(`CRITICAL: Claude session ID overwrite detected! ` +
|
|
2848
|
+
`Existing: ${this.claudeSessionId}, New: ${sessionId}. ` +
|
|
2849
|
+
`This indicates a session identity corruption bug.`);
|
|
2850
|
+
}
|
|
1532
2851
|
handleSystemMessage(message) {
|
|
1533
2852
|
if (message.subtype !== "init") {
|
|
1534
2853
|
return null;
|
|
@@ -1572,6 +2891,7 @@ class ClaudeAgentSession {
|
|
|
1572
2891
|
const normalizedModel = normalizeClaudeRuntimeModelId({
|
|
1573
2892
|
runtimeModelId: message.model,
|
|
1574
2893
|
supportedModelIds: this.selectableModelIds,
|
|
2894
|
+
supportedModelFamilyAliases: this.selectableModelFamilyAliases,
|
|
1575
2895
|
configuredModelId: this.config.model ?? null,
|
|
1576
2896
|
currentModelId: this.lastOptionsModel,
|
|
1577
2897
|
});
|
|
@@ -1608,6 +2928,7 @@ class ClaudeAgentSession {
|
|
|
1608
2928
|
}
|
|
1609
2929
|
}
|
|
1610
2930
|
this.toolUseCache.clear();
|
|
2931
|
+
this.activeSidechains.clear();
|
|
1611
2932
|
}
|
|
1612
2933
|
pushToolCall(item, target) {
|
|
1613
2934
|
if (!item) {
|
|
@@ -1620,9 +2941,19 @@ class ClaudeAgentSession {
|
|
|
1620
2941
|
this.enqueueTimeline(item);
|
|
1621
2942
|
}
|
|
1622
2943
|
pushEvent(event) {
|
|
1623
|
-
|
|
1624
|
-
|
|
2944
|
+
const foregroundTurn = this.activeForegroundTurn;
|
|
2945
|
+
if (foregroundTurn) {
|
|
2946
|
+
const run = this.runTracker.getRun(foregroundTurn.runId);
|
|
2947
|
+
if (run &&
|
|
2948
|
+
run.owner === "foreground" &&
|
|
2949
|
+
run.queue === foregroundTurn.queue &&
|
|
2950
|
+
this.runTracker.isRunActive(run)) {
|
|
2951
|
+
foregroundTurn.queue.push(event);
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
this.activeForegroundTurn = null;
|
|
1625
2955
|
}
|
|
2956
|
+
this.liveEventQueue.push(event);
|
|
1626
2957
|
}
|
|
1627
2958
|
normalizePermissionUpdates(updates) {
|
|
1628
2959
|
if (!updates || updates.length === 0) {
|
|
@@ -1638,6 +2969,9 @@ class ClaudeAgentSession {
|
|
|
1638
2969
|
this.pendingPermissions.delete(id);
|
|
1639
2970
|
}
|
|
1640
2971
|
}
|
|
2972
|
+
waitForLiveHistoryPoll() {
|
|
2973
|
+
return new Promise((resolve) => setTimeout(resolve, 250));
|
|
2974
|
+
}
|
|
1641
2975
|
loadPersistedHistory(sessionId) {
|
|
1642
2976
|
try {
|
|
1643
2977
|
const historyPath = this.resolveHistoryPath(sessionId);
|
|
@@ -1687,20 +3021,15 @@ class ClaudeAgentSession {
|
|
|
1687
3021
|
return path.join(dir, `${sessionId}.jsonl`);
|
|
1688
3022
|
}
|
|
1689
3023
|
convertHistoryEntry(entry) {
|
|
1690
|
-
return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content
|
|
3024
|
+
return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
|
|
1691
3025
|
}
|
|
1692
3026
|
mapBlocksToTimeline(content, options) {
|
|
1693
|
-
const context = options?.context ?? "live";
|
|
1694
|
-
const turnContext = options?.turnContext;
|
|
1695
3027
|
const suppressAssistant = options?.suppressAssistantText ?? false;
|
|
1696
3028
|
const suppressReasoning = options?.suppressReasoning ?? false;
|
|
1697
3029
|
if (typeof content === "string") {
|
|
1698
|
-
if (!content || content ===
|
|
3030
|
+
if (!content || content === INTERRUPT_TOOL_USE_PLACEHOLDER) {
|
|
1699
3031
|
return [];
|
|
1700
3032
|
}
|
|
1701
|
-
if (context === "live" && turnContext) {
|
|
1702
|
-
turnContext.streamedAssistantTextThisTurn = true;
|
|
1703
|
-
}
|
|
1704
3033
|
if (suppressAssistant) {
|
|
1705
3034
|
return [];
|
|
1706
3035
|
}
|
|
@@ -1711,10 +3040,7 @@ class ClaudeAgentSession {
|
|
|
1711
3040
|
switch (block.type) {
|
|
1712
3041
|
case "text":
|
|
1713
3042
|
case "text_delta":
|
|
1714
|
-
if (block.text && block.text !==
|
|
1715
|
-
if (context === "live" && turnContext) {
|
|
1716
|
-
turnContext.streamedAssistantTextThisTurn = true;
|
|
1717
|
-
}
|
|
3043
|
+
if (block.text && block.text !== INTERRUPT_TOOL_USE_PLACEHOLDER) {
|
|
1718
3044
|
if (!suppressAssistant) {
|
|
1719
3045
|
items.push({ type: "assistant_message", text: block.text });
|
|
1720
3046
|
}
|
|
@@ -1723,9 +3049,6 @@ class ClaudeAgentSession {
|
|
|
1723
3049
|
case "thinking":
|
|
1724
3050
|
case "thinking_delta":
|
|
1725
3051
|
if (block.thinking) {
|
|
1726
|
-
if (context === "live" && turnContext) {
|
|
1727
|
-
turnContext.streamedReasoningThisTurn = true;
|
|
1728
|
-
}
|
|
1729
3052
|
if (!suppressReasoning) {
|
|
1730
3053
|
items.push({ type: "reasoning", text: block.thinking });
|
|
1731
3054
|
}
|
|
@@ -1797,6 +3120,7 @@ class ClaudeAgentSession {
|
|
|
1797
3120
|
}
|
|
1798
3121
|
if (typeof block.tool_use_id === "string") {
|
|
1799
3122
|
this.toolUseCache.delete(block.tool_use_id);
|
|
3123
|
+
this.activeSidechains.delete(block.tool_use_id);
|
|
1800
3124
|
}
|
|
1801
3125
|
}
|
|
1802
3126
|
buildToolOutput(block, entry) {
|
|
@@ -1805,7 +3129,7 @@ class ClaudeAgentSession {
|
|
|
1805
3129
|
}
|
|
1806
3130
|
const server = entry?.server ?? block.server ?? "tool";
|
|
1807
3131
|
const tool = entry?.name ?? block.tool_name ?? "tool";
|
|
1808
|
-
const content =
|
|
3132
|
+
const content = coerceToolResultContentToString(block.content);
|
|
1809
3133
|
const input = entry?.input;
|
|
1810
3134
|
// Build structured result based on tool type
|
|
1811
3135
|
const structured = this.buildStructuredToolResult(server, tool, content, input);
|
|
@@ -1894,7 +3218,7 @@ class ClaudeAgentSession {
|
|
|
1894
3218
|
}
|
|
1895
3219
|
return undefined;
|
|
1896
3220
|
}
|
|
1897
|
-
mapPartialEvent(event,
|
|
3221
|
+
mapPartialEvent(event, options) {
|
|
1898
3222
|
if (event.type === "content_block_start") {
|
|
1899
3223
|
const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
|
|
1900
3224
|
if (block?.type === "tool_use" && typeof event.index === "number" && typeof block.id === "string") {
|
|
@@ -1920,10 +3244,18 @@ class ClaudeAgentSession {
|
|
|
1920
3244
|
switch (event.type) {
|
|
1921
3245
|
case "content_block_start":
|
|
1922
3246
|
return isClaudeContentChunk(event.content_block)
|
|
1923
|
-
? this.mapBlocksToTimeline([event.content_block], {
|
|
3247
|
+
? this.mapBlocksToTimeline([event.content_block], {
|
|
3248
|
+
suppressAssistantText: options?.suppressAssistantText,
|
|
3249
|
+
suppressReasoning: options?.suppressReasoning,
|
|
3250
|
+
})
|
|
1924
3251
|
: [];
|
|
1925
3252
|
case "content_block_delta":
|
|
1926
|
-
return isClaudeContentChunk(event.delta)
|
|
3253
|
+
return isClaudeContentChunk(event.delta)
|
|
3254
|
+
? this.mapBlocksToTimeline([event.delta], {
|
|
3255
|
+
suppressAssistantText: options?.suppressAssistantText,
|
|
3256
|
+
suppressReasoning: options?.suppressReasoning,
|
|
3257
|
+
})
|
|
3258
|
+
: [];
|
|
1927
3259
|
default:
|
|
1928
3260
|
return [];
|
|
1929
3261
|
}
|
|
@@ -2122,6 +3454,24 @@ function hasToolLikeBlock(block) {
|
|
|
2122
3454
|
const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
|
|
2123
3455
|
return type.includes("tool");
|
|
2124
3456
|
}
|
|
3457
|
+
function readCompactionMetadata(source) {
|
|
3458
|
+
const candidates = [
|
|
3459
|
+
source.compact_metadata,
|
|
3460
|
+
source.compactMetadata,
|
|
3461
|
+
source.compactionMetadata,
|
|
3462
|
+
];
|
|
3463
|
+
for (const candidate of candidates) {
|
|
3464
|
+
if (!candidate || typeof candidate !== "object") {
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
const metadata = candidate;
|
|
3468
|
+
const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
|
|
3469
|
+
const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
|
|
3470
|
+
const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
|
|
3471
|
+
return { trigger, preTokens };
|
|
3472
|
+
}
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
2125
3475
|
function normalizeHistoryBlocks(content) {
|
|
2126
3476
|
if (Array.isArray(content)) {
|
|
2127
3477
|
const blocks = content.filter((entry) => isClaudeContentChunk(entry));
|
|
@@ -2134,16 +3484,26 @@ function normalizeHistoryBlocks(content) {
|
|
|
2134
3484
|
}
|
|
2135
3485
|
export function convertClaudeHistoryEntry(entry, mapBlocks) {
|
|
2136
3486
|
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
|
3487
|
+
const compactMetadata = readCompactionMetadata(entry);
|
|
2137
3488
|
return [{
|
|
2138
3489
|
type: "compaction",
|
|
2139
3490
|
status: "completed",
|
|
2140
|
-
trigger:
|
|
2141
|
-
preTokens:
|
|
3491
|
+
trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
|
|
3492
|
+
preTokens: compactMetadata?.preTokens,
|
|
2142
3493
|
}];
|
|
2143
3494
|
}
|
|
3495
|
+
if (entry.type === "system") {
|
|
3496
|
+
const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
|
|
3497
|
+
if (taskNotificationItem) {
|
|
3498
|
+
return [taskNotificationItem];
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
2144
3501
|
if (entry.isCompactSummary) {
|
|
2145
3502
|
return [];
|
|
2146
3503
|
}
|
|
3504
|
+
if (entry.type === "user" && isSyntheticUserEntry(entry)) {
|
|
3505
|
+
return [];
|
|
3506
|
+
}
|
|
2147
3507
|
const message = entry?.message;
|
|
2148
3508
|
if (!message || !("content" in message)) {
|
|
2149
3509
|
return [];
|
|
@@ -2154,17 +3514,26 @@ export function convertClaudeHistoryEntry(entry, mapBlocks) {
|
|
|
2154
3514
|
? content
|
|
2155
3515
|
: normalizedBlocks;
|
|
2156
3516
|
const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
|
|
3517
|
+
const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
|
|
3518
|
+
? entry.uuid
|
|
3519
|
+
: null;
|
|
3520
|
+
if (entry.type === "user") {
|
|
3521
|
+
const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
|
|
3522
|
+
content,
|
|
3523
|
+
messageId: userMessageId,
|
|
3524
|
+
});
|
|
3525
|
+
if (taskNotificationItem) {
|
|
3526
|
+
return [taskNotificationItem];
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
2157
3529
|
const timeline = [];
|
|
2158
3530
|
if (entry.type === "user") {
|
|
2159
3531
|
const text = extractUserMessageText(content);
|
|
2160
3532
|
if (text) {
|
|
2161
|
-
const messageId = typeof entry.uuid === "string" && entry.uuid.length > 0
|
|
2162
|
-
? entry.uuid
|
|
2163
|
-
: undefined;
|
|
2164
3533
|
timeline.push({
|
|
2165
3534
|
type: "user_message",
|
|
2166
3535
|
text,
|
|
2167
|
-
...(
|
|
3536
|
+
...(userMessageId ? { messageId: userMessageId } : {}),
|
|
2168
3537
|
});
|
|
2169
3538
|
}
|
|
2170
3539
|
}
|
|
@@ -2306,6 +3675,9 @@ async function parseClaudeSessionDescriptor(filePath, mtime) {
|
|
|
2306
3675
|
if (entry?.isSidechain) {
|
|
2307
3676
|
continue;
|
|
2308
3677
|
}
|
|
3678
|
+
if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
2309
3681
|
if (!sessionId && typeof entry.sessionId === "string") {
|
|
2310
3682
|
sessionId = entry.sessionId;
|
|
2311
3683
|
}
|