@seawork/server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +23 -0
- package/README.md +107 -0
- package/dist/scripts/dev-runner.js +27 -0
- package/dist/scripts/dev-runner.js.map +1 -0
- package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
- package/dist/scripts/supervisor-entrypoint.js +123 -0
- package/dist/scripts/supervisor-entrypoint.js.map +1 -0
- package/dist/scripts/supervisor.js +148 -0
- package/dist/scripts/supervisor.js.map +1 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts +8 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.js +161 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.js.map +1 -0
- package/dist/server/client/daemon-client-transport-types.d.ts +34 -0
- package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport-types.js +2 -0
- package/dist/server/client/daemon-client-transport-types.js.map +1 -0
- package/dist/server/client/daemon-client-transport-utils.d.ts +9 -0
- package/dist/server/client/daemon-client-transport-utils.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport-utils.js +121 -0
- package/dist/server/client/daemon-client-transport-utils.js.map +1 -0
- package/dist/server/client/daemon-client-transport.d.ts +5 -0
- package/dist/server/client/daemon-client-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport.js +4 -0
- package/dist/server/client/daemon-client-transport.js.map +1 -0
- package/dist/server/client/daemon-client-websocket-transport.d.ts +7 -0
- package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-websocket-transport.js +119 -0
- package/dist/server/client/daemon-client-websocket-transport.js.map +1 -0
- package/dist/server/client/daemon-client.d.ts +697 -0
- package/dist/server/client/daemon-client.d.ts.map +1 -0
- package/dist/server/client/daemon-client.js +2885 -0
- package/dist/server/client/daemon-client.js.map +1 -0
- package/dist/server/server/agent/activity-curator.d.ts +8 -0
- package/dist/server/server/agent/activity-curator.d.ts.map +1 -0
- package/dist/server/server/agent/activity-curator.js +243 -0
- package/dist/server/server/agent/activity-curator.js.map +1 -0
- package/dist/server/server/agent/agent-management-mcp.d.ts +41 -0
- package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -0
- package/dist/server/server/agent/agent-management-mcp.js +767 -0
- package/dist/server/server/agent/agent-management-mcp.js.map +1 -0
- package/dist/server/server/agent/agent-manager.d.ts +287 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -0
- package/dist/server/server/agent/agent-manager.js +1956 -0
- package/dist/server/server/agent/agent-manager.js.map +1 -0
- package/dist/server/server/agent/agent-metadata-generator.d.ts +29 -0
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -0
- package/dist/server/server/agent/agent-metadata-generator.js +161 -0
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -0
- package/dist/server/server/agent/agent-projections.d.ts +17 -0
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -0
- package/dist/server/server/agent/agent-projections.js +280 -0
- package/dist/server/server/agent/agent-projections.js.map +1 -0
- package/dist/server/server/agent/agent-response-loop.d.ts +60 -0
- package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -0
- package/dist/server/server/agent/agent-response-loop.js +304 -0
- package/dist/server/server/agent/agent-response-loop.js.map +1 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +470 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -0
- package/dist/server/server/agent/agent-sdk-types.js +12 -0
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -0
- package/dist/server/server/agent/agent-storage.d.ts +336 -0
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -0
- package/dist/server/server/agent/agent-storage.js +304 -0
- package/dist/server/server/agent/agent-storage.js.map +1 -0
- 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/audio-utils.d.ts +3 -0
- package/dist/server/server/agent/audio-utils.d.ts.map +1 -0
- package/dist/server/server/agent/audio-utils.js +19 -0
- package/dist/server/server/agent/audio-utils.js.map +1 -0
- package/dist/server/server/agent/dictation-debug.d.ts +13 -0
- package/dist/server/server/agent/dictation-debug.d.ts.map +1 -0
- package/dist/server/server/agent/dictation-debug.js +50 -0
- package/dist/server/server/agent/dictation-debug.js.map +1 -0
- package/dist/server/server/agent/llm-openai.d.ts +7 -0
- package/dist/server/server/agent/llm-openai.d.ts.map +1 -0
- package/dist/server/server/agent/llm-openai.js +8 -0
- package/dist/server/server/agent/llm-openai.js.map +1 -0
- package/dist/server/server/agent/mcp-server.d.ts +33 -0
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -0
- package/dist/server/server/agent/mcp-server.js +1279 -0
- package/dist/server/server/agent/mcp-server.js.map +1 -0
- package/dist/server/server/agent/mcp-shared.d.ts +251 -0
- package/dist/server/server/agent/mcp-shared.d.ts.map +1 -0
- package/dist/server/server/agent/mcp-shared.js +242 -0
- package/dist/server/server/agent/mcp-shared.js.map +1 -0
- package/dist/server/server/agent/model-resolver.d.ts +11 -0
- package/dist/server/server/agent/model-resolver.d.ts.map +1 -0
- package/dist/server/server/agent/model-resolver.js +21 -0
- package/dist/server/server/agent/model-resolver.js.map +1 -0
- package/dist/server/server/agent/orchestrator-instructions.d.ts +7 -0
- package/dist/server/server/agent/orchestrator-instructions.d.ts.map +1 -0
- package/dist/server/server/agent/orchestrator-instructions.js +51 -0
- package/dist/server/server/agent/orchestrator-instructions.js.map +1 -0
- package/dist/server/server/agent/orchestrator.d.ts +12 -0
- package/dist/server/server/agent/orchestrator.d.ts.map +1 -0
- package/dist/server/server/agent/orchestrator.js +12 -0
- package/dist/server/server/agent/orchestrator.js.map +1 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts.map +1 -0
- package/dist/server/server/agent/pcm16-resampler.js +63 -0
- package/dist/server/server/agent/pcm16-resampler.js.map +1 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +138 -0
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -0
- package/dist/server/server/agent/provider-launch-config.js +80 -0
- package/dist/server/server/agent/provider-launch-config.js.map +1 -0
- package/dist/server/server/agent/provider-manifest.d.ts +29 -0
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -0
- package/dist/server/server/agent/provider-manifest.js +157 -0
- package/dist/server/server/agent/provider-manifest.js.map +1 -0
- package/dist/server/server/agent/provider-registry.d.ts +19 -0
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -0
- package/dist/server/server/agent/provider-registry.js +58 -0
- package/dist/server/server/agent/provider-registry.js.map +1 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +27 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +181 -0
- package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +202 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/acp-agent.js +1650 -0
- package/dist/server/server/agent/providers/acp-agent.js.map +1 -0
- package/dist/server/server/agent/providers/claude/claude-models.d.ts +8 -0
- package/dist/server/server/agent/providers/claude/claude-models.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/claude-models.js +63 -0
- package/dist/server/server/agent/providers/claude/claude-models.js.map +1 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
- package/dist/server/server/agent/providers/claude/partial-json.js.map +1 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +20 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +230 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js.map +1 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +55 -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 +267 -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 +3 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +121 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +252 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -0
- package/dist/server/server/agent/providers/claude-agent.d.ts +44 -0
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude-agent.js +3452 -0
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +15 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +762 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +200 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js +3474 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts.map +1 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js +45 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js.map +1 -0
- package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts +9 -0
- package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -0
- package/dist/server/server/agent/providers/codex-rollout-timeline.js +544 -0
- package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +16 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js +95 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js.map +1 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +17 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts.map +1 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +56 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js.map +1 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +39 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +144 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +121 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/opencode-agent.js +1649 -0
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -0
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts +28 -0
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/pi-acp-agent.js +302 -0
- package/dist/server/server/agent/providers/pi-acp-agent.js.map +1 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +57 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +1745 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +686 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +18 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +115 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -0
- package/dist/server/server/agent/recordings-debug.d.ts +3 -0
- package/dist/server/server/agent/recordings-debug.d.ts.map +1 -0
- package/dist/server/server/agent/recordings-debug.js +19 -0
- package/dist/server/server/agent/recordings-debug.js.map +1 -0
- package/dist/server/server/agent/stt-debug.d.ts +10 -0
- package/dist/server/server/agent/stt-debug.d.ts.map +1 -0
- package/dist/server/server/agent/stt-debug.js +33 -0
- package/dist/server/server/agent/stt-debug.js.map +1 -0
- package/dist/server/server/agent/stt-manager.d.ts +33 -0
- package/dist/server/server/agent/stt-manager.d.ts.map +1 -0
- package/dist/server/server/agent/stt-manager.js +232 -0
- package/dist/server/server/agent/stt-manager.js.map +1 -0
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.d.ts.map +1 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-append.js.map +1 -0
- package/dist/server/server/agent/timeline-projection.d.ts +39 -0
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -0
- package/dist/server/server/agent/timeline-projection.js +215 -0
- package/dist/server/server/agent/timeline-projection.js.map +1 -0
- package/dist/server/server/agent/tool-name-normalization.d.ts +9 -0
- package/dist/server/server/agent/tool-name-normalization.d.ts.map +1 -0
- package/dist/server/server/agent/tool-name-normalization.js +82 -0
- package/dist/server/server/agent/tool-name-normalization.js.map +1 -0
- package/dist/server/server/agent/tts-debug.d.ts +8 -0
- package/dist/server/server/agent/tts-debug.d.ts.map +1 -0
- package/dist/server/server/agent/tts-debug.js +24 -0
- package/dist/server/server/agent/tts-debug.js.map +1 -0
- package/dist/server/server/agent/tts-manager.d.ts +41 -0
- package/dist/server/server/agent/tts-manager.d.ts.map +1 -0
- package/dist/server/server/agent/tts-manager.js +374 -0
- package/dist/server/server/agent/tts-manager.js.map +1 -0
- package/dist/server/server/agent/wait-for-agent-tracker.d.ts +15 -0
- package/dist/server/server/agent/wait-for-agent-tracker.d.ts.map +1 -0
- package/dist/server/server/agent/wait-for-agent-tracker.js +53 -0
- package/dist/server/server/agent/wait-for-agent-tracker.js.map +1 -0
- package/dist/server/server/agent-attention-policy.d.ts +20 -0
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -0
- package/dist/server/server/agent-attention-policy.js +40 -0
- package/dist/server/server/agent-attention-policy.js.map +1 -0
- package/dist/server/server/allowed-hosts.d.ts +13 -0
- package/dist/server/server/allowed-hosts.d.ts.map +1 -0
- package/dist/server/server/allowed-hosts.js +94 -0
- package/dist/server/server/allowed-hosts.js.map +1 -0
- package/dist/server/server/bootstrap.d.ts +74 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -0
- package/dist/server/server/bootstrap.js +537 -0
- package/dist/server/server/bootstrap.js.map +1 -0
- package/dist/server/server/chat/chat-mentions.d.ts +31 -0
- package/dist/server/server/chat/chat-mentions.d.ts.map +1 -0
- package/dist/server/server/chat/chat-mentions.js +71 -0
- package/dist/server/server/chat/chat-mentions.js.map +1 -0
- package/dist/server/server/chat/chat-rpc-schemas.d.ts +728 -0
- package/dist/server/server/chat/chat-rpc-schemas.d.ts.map +1 -0
- package/dist/server/server/chat/chat-rpc-schemas.js +103 -0
- package/dist/server/server/chat/chat-rpc-schemas.js.map +1 -0
- package/dist/server/server/chat/chat-service.d.ts +74 -0
- package/dist/server/server/chat/chat-service.d.ts.map +1 -0
- package/dist/server/server/chat/chat-service.js +330 -0
- package/dist/server/server/chat/chat-service.js.map +1 -0
- package/dist/server/server/chat/chat-types.d.ts +75 -0
- package/dist/server/server/chat/chat-types.d.ts.map +1 -0
- package/dist/server/server/chat/chat-types.js +22 -0
- package/dist/server/server/chat/chat-types.js.map +1 -0
- package/dist/server/server/checkout-diff-manager.d.ts +45 -0
- package/dist/server/server/checkout-diff-manager.d.ts.map +1 -0
- package/dist/server/server/checkout-diff-manager.js +374 -0
- package/dist/server/server/checkout-diff-manager.js.map +1 -0
- package/dist/server/server/checkout-git-utils.d.ts +9 -0
- package/dist/server/server/checkout-git-utils.d.ts.map +1 -0
- package/dist/server/server/checkout-git-utils.js +37 -0
- package/dist/server/server/checkout-git-utils.js.map +1 -0
- 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/config.d.ts +14 -0
- package/dist/server/server/config.d.ts.map +1 -0
- package/dist/server/server/config.js +96 -0
- package/dist/server/server/config.js.map +1 -0
- package/dist/server/server/connection-offer.d.ts +19 -0
- package/dist/server/server/connection-offer.d.ts.map +1 -0
- package/dist/server/server/connection-offer.js +59 -0
- package/dist/server/server/connection-offer.js.map +1 -0
- package/dist/server/server/daemon-config-store.d.ts +23 -0
- package/dist/server/server/daemon-config-store.d.ts.map +1 -0
- package/dist/server/server/daemon-config-store.js +114 -0
- package/dist/server/server/daemon-config-store.js.map +1 -0
- package/dist/server/server/daemon-keypair.d.ts +8 -0
- package/dist/server/server/daemon-keypair.d.ts.map +1 -0
- package/dist/server/server/daemon-keypair.js +40 -0
- package/dist/server/server/daemon-keypair.js.map +1 -0
- package/dist/server/server/daemon-version.d.ts +5 -0
- package/dist/server/server/daemon-version.d.ts.map +1 -0
- package/dist/server/server/daemon-version.js +22 -0
- package/dist/server/server/daemon-version.js.map +1 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +85 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts.map +1 -0
- package/dist/server/server/dictation/dictation-stream-manager.js +571 -0
- package/dist/server/server/dictation/dictation-stream-manager.js.map +1 -0
- package/dist/server/server/editor-targets.d.ts +18 -0
- package/dist/server/server/editor-targets.d.ts.map +1 -0
- package/dist/server/server/editor-targets.js +113 -0
- package/dist/server/server/editor-targets.js.map +1 -0
- package/dist/server/server/exports.d.ts +19 -0
- package/dist/server/server/exports.d.ts.map +1 -0
- package/dist/server/server/exports.js +21 -0
- package/dist/server/server/exports.js.map +1 -0
- package/dist/server/server/file-download/token-store.d.ts +24 -0
- package/dist/server/server/file-download/token-store.d.ts.map +1 -0
- package/dist/server/server/file-download/token-store.js +40 -0
- package/dist/server/server/file-download/token-store.js.map +1 -0
- package/dist/server/server/file-explorer/service.d.ts +41 -0
- package/dist/server/server/file-explorer/service.d.ts.map +1 -0
- package/dist/server/server/file-explorer/service.js +194 -0
- package/dist/server/server/file-explorer/service.js.map +1 -0
- package/dist/server/server/index.d.ts +2 -0
- package/dist/server/server/index.d.ts.map +1 -0
- package/dist/server/server/index.js +176 -0
- package/dist/server/server/index.js.map +1 -0
- package/dist/server/server/json-utils.d.ts +11 -0
- package/dist/server/server/json-utils.d.ts.map +1 -0
- package/dist/server/server/json-utils.js +45 -0
- package/dist/server/server/json-utils.js.map +1 -0
- package/dist/server/server/logger.d.ts +33 -0
- package/dist/server/server/logger.d.ts.map +1 -0
- package/dist/server/server/logger.js +195 -0
- package/dist/server/server/logger.js.map +1 -0
- package/dist/server/server/loop/rpc-schemas.d.ts +2937 -0
- package/dist/server/server/loop/rpc-schemas.d.ts.map +1 -0
- package/dist/server/server/loop/rpc-schemas.js +159 -0
- package/dist/server/server/loop/rpc-schemas.js.map +1 -0
- package/dist/server/server/loop-service.d.ts +520 -0
- package/dist/server/server/loop-service.d.ts.map +1 -0
- package/dist/server/server/loop-service.js +739 -0
- package/dist/server/server/loop-service.js.map +1 -0
- package/dist/server/server/messages.d.ts +9 -0
- package/dist/server/server/messages.d.ts.map +1 -0
- package/dist/server/server/messages.js +29 -0
- package/dist/server/server/messages.js.map +1 -0
- package/dist/server/server/package-version.d.ts +13 -0
- package/dist/server/server/package-version.d.ts.map +1 -0
- package/dist/server/server/package-version.js +46 -0
- package/dist/server/server/package-version.js.map +1 -0
- package/dist/server/server/pairing-offer.d.ts +16 -0
- package/dist/server/server/pairing-offer.d.ts.map +1 -0
- package/dist/server/server/pairing-offer.js +45 -0
- package/dist/server/server/pairing-offer.js.map +1 -0
- package/dist/server/server/pairing-qr.d.ts +7 -0
- package/dist/server/server/pairing-qr.d.ts.map +1 -0
- package/dist/server/server/pairing-qr.js +45 -0
- package/dist/server/server/pairing-qr.js.map +1 -0
- package/dist/server/server/path-utils.d.ts +3 -0
- package/dist/server/server/path-utils.d.ts.map +1 -0
- package/dist/server/server/path-utils.js +20 -0
- package/dist/server/server/path-utils.js.map +1 -0
- package/dist/server/server/persisted-config.d.ts +625 -0
- package/dist/server/server/persisted-config.d.ts.map +1 -0
- package/dist/server/server/persisted-config.js +265 -0
- package/dist/server/server/persisted-config.js.map +1 -0
- package/dist/server/server/persistence-hooks.d.ts +24 -0
- package/dist/server/server/persistence-hooks.d.ts.map +1 -0
- package/dist/server/server/persistence-hooks.js +60 -0
- package/dist/server/server/persistence-hooks.js.map +1 -0
- package/dist/server/server/pid-lock.d.ts +29 -0
- package/dist/server/server/pid-lock.d.ts.map +1 -0
- package/dist/server/server/pid-lock.js +148 -0
- package/dist/server/server/pid-lock.js.map +1 -0
- package/dist/server/server/push/push-service.d.ts +22 -0
- package/dist/server/server/push/push-service.d.ts.map +1 -0
- package/dist/server/server/push/push-service.js +69 -0
- package/dist/server/server/push/push-service.js.map +1 -0
- package/dist/server/server/push/token-store.d.ts +18 -0
- package/dist/server/server/push/token-store.d.ts.map +1 -0
- package/dist/server/server/push/token-store.js +70 -0
- package/dist/server/server/push/token-store.js.map +1 -0
- package/dist/server/server/relay-transport.d.ts +23 -0
- package/dist/server/server/relay-transport.d.ts.map +1 -0
- package/dist/server/server/relay-transport.js +461 -0
- package/dist/server/server/relay-transport.js.map +1 -0
- package/dist/server/server/schedule/cron.d.ts +4 -0
- package/dist/server/server/schedule/cron.d.ts.map +1 -0
- package/dist/server/server/schedule/cron.js +103 -0
- package/dist/server/server/schedule/cron.js.map +1 -0
- package/dist/server/server/schedule/rpc-schemas.d.ts +2773 -0
- package/dist/server/server/schedule/rpc-schemas.d.ts.map +1 -0
- package/dist/server/server/schedule/rpc-schemas.js +112 -0
- package/dist/server/server/schedule/rpc-schemas.js.map +1 -0
- package/dist/server/server/schedule/service.d.ts +39 -0
- package/dist/server/server/schedule/service.d.ts.map +1 -0
- package/dist/server/server/schedule/service.js +410 -0
- package/dist/server/server/schedule/service.js.map +1 -0
- package/dist/server/server/schedule/store.d.ts +13 -0
- package/dist/server/server/schedule/store.d.ts.map +1 -0
- package/dist/server/server/schedule/store.js +56 -0
- package/dist/server/server/schedule/store.js.map +1 -0
- package/dist/server/server/schedule/types.d.ts +710 -0
- package/dist/server/server/schedule/types.d.ts.map +1 -0
- package/dist/server/server/schedule/types.js +73 -0
- package/dist/server/server/schedule/types.js.map +1 -0
- package/dist/server/server/seawork-home.d.ts +2 -0
- package/dist/server/server/seawork-home.d.ts.map +1 -0
- package/dist/server/server/seawork-home.js +19 -0
- package/dist/server/server/seawork-home.js.map +1 -0
- package/dist/server/server/server-id.d.ts +17 -0
- package/dist/server/server/server-id.d.ts.map +1 -0
- package/dist/server/server/server-id.js +63 -0
- package/dist/server/server/server-id.js.map +1 -0
- package/dist/server/server/session.d.ts +510 -0
- package/dist/server/server/session.d.ts.map +1 -0
- package/dist/server/server/session.js +6414 -0
- package/dist/server/server/session.js.map +1 -0
- package/dist/server/server/speech/audio.d.ts +10 -0
- package/dist/server/server/speech/audio.d.ts.map +1 -0
- package/dist/server/server/speech/audio.js +101 -0
- package/dist/server/server/speech/audio.js.map +1 -0
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.d.ts.map +1 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/provider-resolver.js.map +1 -0
- package/dist/server/server/speech/providers/local/config.d.ts +25 -0
- package/dist/server/server/speech/providers/local/config.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/config.js +74 -0
- package/dist/server/server/speech/providers/local/config.js.map +1 -0
- package/dist/server/server/speech/providers/local/models.d.ts +11 -0
- package/dist/server/server/speech/providers/local/models.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/models.js +17 -0
- package/dist/server/server/speech/providers/local/models.js.map +1 -0
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts +24 -0
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js +436 -0
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/runtime.js +247 -0
- package/dist/server/server/speech/providers/local/runtime.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +117 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +166 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +15 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +165 -0
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts +37 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.d.ts +7 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +11 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +102 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +135 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +131 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.d.ts +23 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js +110 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts +23 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js +138 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +108 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js.map +1 -0
- package/dist/server/server/speech/providers/openai/config.d.ts +22 -0
- package/dist/server/server/speech/providers/openai/config.d.ts.map +1 -0
- package/dist/server/server/speech/providers/openai/config.js +80 -0
- package/dist/server/server/speech/providers/openai/config.js.map +1 -0
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +42 -0
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts.map +1 -0
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +168 -0
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.js.map +1 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts.map +1 -0
- package/dist/server/server/speech/providers/openai/runtime.js +112 -0
- package/dist/server/server/speech/providers/openai/runtime.js.map +1 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts +22 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts.map +1 -0
- package/dist/server/server/speech/providers/openai/stt.js +206 -0
- package/dist/server/server/speech/providers/openai/stt.js.map +1 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts +18 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts.map +1 -0
- package/dist/server/server/speech/providers/openai/tts.js +46 -0
- package/dist/server/server/speech/providers/openai/tts.js.map +1 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts.map +1 -0
- package/dist/server/server/speech/speech-config-resolver.js +104 -0
- package/dist/server/server/speech/speech-config-resolver.js.map +1 -0
- package/dist/server/server/speech/speech-provider.d.ts +59 -0
- package/dist/server/server/speech/speech-provider.d.ts.map +1 -0
- package/dist/server/server/speech/speech-provider.js +2 -0
- package/dist/server/server/speech/speech-provider.js.map +1 -0
- package/dist/server/server/speech/speech-runtime.d.ts +43 -0
- package/dist/server/server/speech/speech-runtime.d.ts.map +1 -0
- package/dist/server/server/speech/speech-runtime.js +560 -0
- package/dist/server/server/speech/speech-runtime.js.map +1 -0
- package/dist/server/server/speech/speech-types.d.ts +24 -0
- package/dist/server/server/speech/speech-types.d.ts.map +1 -0
- package/dist/server/server/speech/speech-types.js +8 -0
- package/dist/server/server/speech/speech-types.js.map +1 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts.map +1 -0
- package/dist/server/server/speech/turn-detection-provider.js +2 -0
- package/dist/server/server/speech/turn-detection-provider.js.map +1 -0
- package/dist/server/server/types.d.ts +5 -0
- package/dist/server/server/types.d.ts.map +1 -0
- package/dist/server/server/types.js +3 -0
- package/dist/server/server/types.js.map +1 -0
- package/dist/server/server/utils/diff-highlighter.d.ts +60 -0
- package/dist/server/server/utils/diff-highlighter.d.ts.map +1 -0
- package/dist/server/server/utils/diff-highlighter.js +257 -0
- package/dist/server/server/utils/diff-highlighter.js.map +1 -0
- package/dist/server/server/voice/fixed-duration-pcm-ring-buffer.d.ts +16 -0
- package/dist/server/server/voice/fixed-duration-pcm-ring-buffer.d.ts.map +1 -0
- package/dist/server/server/voice/fixed-duration-pcm-ring-buffer.js +35 -0
- package/dist/server/server/voice/fixed-duration-pcm-ring-buffer.js.map +1 -0
- package/dist/server/server/voice/voice-turn-controller.d.ts +34 -0
- package/dist/server/server/voice/voice-turn-controller.d.ts.map +1 -0
- package/dist/server/server/voice/voice-turn-controller.js +160 -0
- package/dist/server/server/voice/voice-turn-controller.js.map +1 -0
- package/dist/server/server/voice-config.d.ts +15 -0
- package/dist/server/server/voice-config.d.ts.map +1 -0
- package/dist/server/server/voice-config.js +54 -0
- package/dist/server/server/voice-config.js.map +1 -0
- package/dist/server/server/voice-permission-policy.d.ts +4 -0
- package/dist/server/server/voice-permission-policy.d.ts.map +1 -0
- package/dist/server/server/voice-permission-policy.js +13 -0
- package/dist/server/server/voice-permission-policy.js.map +1 -0
- package/dist/server/server/voice-types.d.ts +12 -0
- package/dist/server/server/voice-types.d.ts.map +1 -0
- package/dist/server/server/voice-types.js +2 -0
- package/dist/server/server/voice-types.js.map +1 -0
- package/dist/server/server/websocket-server.d.ts +122 -0
- package/dist/server/server/websocket-server.d.ts.map +1 -0
- package/dist/server/server/websocket-server.js +1092 -0
- package/dist/server/server/websocket-server.js.map +1 -0
- package/dist/server/server/workspace-git-service.d.ts +108 -0
- package/dist/server/server/workspace-git-service.d.ts.map +1 -0
- package/dist/server/server/workspace-git-service.js +404 -0
- package/dist/server/server/workspace-git-service.js.map +1 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts +11 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -0
- package/dist/server/server/workspace-registry-bootstrap.js +100 -0
- package/dist/server/server/workspace-registry-bootstrap.js.map +1 -0
- package/dist/server/server/workspace-registry-model.d.ts +33 -0
- package/dist/server/server/workspace-registry-model.d.ts.map +1 -0
- package/dist/server/server/workspace-registry-model.js +167 -0
- package/dist/server/server/workspace-registry-model.js.map +1 -0
- package/dist/server/server/workspace-registry.d.ts +130 -0
- package/dist/server/server/workspace-registry.d.ts.map +1 -0
- package/dist/server/server/workspace-registry.js +151 -0
- package/dist/server/server/workspace-registry.js.map +1 -0
- package/dist/server/server/worktree-bootstrap.d.ts +29 -0
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -0
- package/dist/server/server/worktree-bootstrap.js +508 -0
- package/dist/server/server/worktree-bootstrap.js.map +1 -0
- package/dist/server/server/worktree-session.d.ts +131 -0
- package/dist/server/server/worktree-session.d.ts.map +1 -0
- package/dist/server/server/worktree-session.js +487 -0
- package/dist/server/server/worktree-session.js.map +1 -0
- package/dist/server/shared/agent-attention-notification.d.ts +40 -0
- package/dist/server/shared/agent-attention-notification.d.ts.map +1 -0
- package/dist/server/shared/agent-attention-notification.js +130 -0
- package/dist/server/shared/agent-attention-notification.js.map +1 -0
- package/dist/server/shared/agent-lifecycle.d.ts +3 -0
- package/dist/server/shared/agent-lifecycle.d.ts.map +1 -0
- package/dist/server/shared/agent-lifecycle.js +8 -0
- package/dist/server/shared/agent-lifecycle.js.map +1 -0
- package/dist/server/shared/connection-offer.d.ts +62 -0
- package/dist/server/shared/connection-offer.d.ts.map +1 -0
- package/dist/server/shared/connection-offer.js +17 -0
- package/dist/server/shared/connection-offer.js.map +1 -0
- package/dist/server/shared/daemon-endpoints.d.ts +28 -0
- package/dist/server/shared/daemon-endpoints.d.ts.map +1 -0
- package/dist/server/shared/daemon-endpoints.js +122 -0
- package/dist/server/shared/daemon-endpoints.js.map +1 -0
- package/dist/server/shared/literal-union.d.ts +2 -0
- package/dist/server/shared/literal-union.d.ts.map +1 -0
- package/dist/server/shared/literal-union.js +2 -0
- package/dist/server/shared/literal-union.js.map +1 -0
- package/dist/server/shared/messages.d.ts +81816 -0
- package/dist/server/shared/messages.d.ts.map +1 -0
- package/dist/server/shared/messages.js +2553 -0
- package/dist/server/shared/messages.js.map +1 -0
- package/dist/server/shared/path-utils.d.ts +2 -0
- package/dist/server/shared/path-utils.d.ts.map +1 -0
- package/dist/server/shared/path-utils.js +16 -0
- package/dist/server/shared/path-utils.js.map +1 -0
- package/dist/server/shared/terminal-stream-protocol.d.ts +36 -0
- package/dist/server/shared/terminal-stream-protocol.d.ts.map +1 -0
- package/dist/server/shared/terminal-stream-protocol.js +99 -0
- package/dist/server/shared/terminal-stream-protocol.js.map +1 -0
- package/dist/server/shared/tool-call-display.d.ts +11 -0
- package/dist/server/shared/tool-call-display.d.ts.map +1 -0
- package/dist/server/shared/tool-call-display.js +133 -0
- package/dist/server/shared/tool-call-display.js.map +1 -0
- package/dist/server/terminal/terminal-manager.d.ts +30 -0
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -0
- package/dist/server/terminal/terminal-manager.js +136 -0
- package/dist/server/terminal/terminal-manager.js.map +1 -0
- package/dist/server/terminal/terminal.d.ts +68 -0
- package/dist/server/terminal/terminal.d.ts.map +1 -0
- package/dist/server/terminal/terminal.js +384 -0
- package/dist/server/terminal/terminal.js.map +1 -0
- package/dist/server/utils/checkout-git.d.ts +180 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -0
- package/dist/server/utils/checkout-git.js +1621 -0
- package/dist/server/utils/checkout-git.js.map +1 -0
- package/dist/server/utils/directory-suggestions.d.ts +24 -0
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -0
- package/dist/server/utils/directory-suggestions.js +671 -0
- package/dist/server/utils/directory-suggestions.js.map +1 -0
- package/dist/server/utils/executable.d.ts +33 -0
- package/dist/server/utils/executable.d.ts.map +1 -0
- package/dist/server/utils/executable.js +134 -0
- package/dist/server/utils/executable.js.map +1 -0
- package/dist/server/utils/path.d.ts +5 -0
- package/dist/server/utils/path.d.ts.map +1 -0
- package/dist/server/utils/path.js +15 -0
- package/dist/server/utils/path.js.map +1 -0
- package/dist/server/utils/project-icon.d.ts +39 -0
- package/dist/server/utils/project-icon.d.ts.map +1 -0
- package/dist/server/utils/project-icon.js +389 -0
- package/dist/server/utils/project-icon.js.map +1 -0
- package/dist/server/utils/spawn.d.ts +13 -0
- package/dist/server/utils/spawn.d.ts.map +1 -0
- package/dist/server/utils/spawn.js +33 -0
- package/dist/server/utils/spawn.js.map +1 -0
- package/dist/server/utils/worktree-metadata.d.ts +47 -0
- package/dist/server/utils/worktree-metadata.d.ts.map +1 -0
- package/dist/server/utils/worktree-metadata.js +116 -0
- package/dist/server/utils/worktree-metadata.js.map +1 -0
- package/dist/server/utils/worktree.d.ts +144 -0
- package/dist/server/utils/worktree.d.ts.map +1 -0
- package/dist/server/utils/worktree.js +745 -0
- package/dist/server/utils/worktree.js.map +1 -0
- package/dist/src/server/pid-lock.js +148 -0
- package/dist/src/server/pid-lock.js.map +1 -0
- package/dist/src/server/seawork-home.js +19 -0
- package/dist/src/server/seawork-home.js.map +1 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -0
- package/package.json +117 -0
- package/src/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
|
@@ -0,0 +1,3452 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { promises } from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
9
|
+
import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./claude/tool-call-mapper.js";
|
|
10
|
+
import { mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./claude/task-notification-tool-call.js";
|
|
11
|
+
import { getClaudeModels, normalizeClaudeRuntimeModelId } from "./claude/claude-models.js";
|
|
12
|
+
import { parsePartialJsonObject } from "./claude/partial-json.js";
|
|
13
|
+
import { ClaudeSidechainTracker } from "./claude/sidechain-tracker.js";
|
|
14
|
+
import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
|
|
15
|
+
import { applyProviderEnv } from "../provider-launch-config.js";
|
|
16
|
+
import { findExecutable } from "../../../utils/executable.js";
|
|
17
|
+
import { spawnProcess } from "../../../utils/spawn.js";
|
|
18
|
+
import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
|
|
19
|
+
const fsPromises = promises;
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
const CLAUDE_SETTING_SOURCES = ["user", "project"];
|
|
22
|
+
const CLAUDE_CAPABILITIES = {
|
|
23
|
+
supportsStreaming: true,
|
|
24
|
+
supportsSessionPersistence: true,
|
|
25
|
+
supportsDynamicModes: true,
|
|
26
|
+
supportsMcpServers: true,
|
|
27
|
+
supportsReasoningStream: true,
|
|
28
|
+
supportsToolInvocations: true,
|
|
29
|
+
};
|
|
30
|
+
const DEFAULT_MODES = [
|
|
31
|
+
{
|
|
32
|
+
id: "default",
|
|
33
|
+
label: "Always Ask",
|
|
34
|
+
description: "Prompts for permission the first time a tool is used",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "acceptEdits",
|
|
38
|
+
label: "Accept File Edits",
|
|
39
|
+
description: "Automatically approves edit-focused tools without prompting",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "plan",
|
|
43
|
+
label: "Plan Mode",
|
|
44
|
+
description: "Analyze the codebase without executing tools or edits",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "bypassPermissions",
|
|
48
|
+
label: "Bypass",
|
|
49
|
+
description: "Skip all permission prompts (use with caution)",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const VALID_CLAUDE_MODES = new Set(DEFAULT_MODES.map((mode) => mode.id));
|
|
53
|
+
const REWIND_COMMAND_NAME = "rewind";
|
|
54
|
+
const REWIND_COMMAND = {
|
|
55
|
+
name: REWIND_COMMAND_NAME,
|
|
56
|
+
description: "Rewind tracked files to a previous user message",
|
|
57
|
+
argumentHint: "[user_message_uuid]",
|
|
58
|
+
};
|
|
59
|
+
const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
|
|
60
|
+
const INTERRUPT_PLACEHOLDER_PATTERN = /^\[Request interrupted by user(?:[^\]]*)\]$/;
|
|
61
|
+
const NO_RESPONSE_REQUESTED_PLACEHOLDER = "No response requested.";
|
|
62
|
+
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;
|
|
63
|
+
function resolveClaudeSpawnCommand(spawnOptions, runtimeSettings) {
|
|
64
|
+
const commandConfig = runtimeSettings?.command;
|
|
65
|
+
if (!commandConfig || commandConfig.mode === "default") {
|
|
66
|
+
return {
|
|
67
|
+
command: spawnOptions.command,
|
|
68
|
+
args: [...spawnOptions.args],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (commandConfig.mode === "append") {
|
|
72
|
+
return {
|
|
73
|
+
command: spawnOptions.command,
|
|
74
|
+
args: [...spawnOptions.args, ...(commandConfig.args ?? [])],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
command: commandConfig.argv[0],
|
|
79
|
+
args: [...commandConfig.argv.slice(1), ...spawnOptions.args],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function applyRuntimeSettingsToClaudeOptions(options, runtimeSettings, launchEnv) {
|
|
83
|
+
return {
|
|
84
|
+
...options,
|
|
85
|
+
spawnClaudeCodeProcess: (spawnOptions) => {
|
|
86
|
+
const resolved = resolveClaudeSpawnCommand(spawnOptions, runtimeSettings);
|
|
87
|
+
// When the SDK passes a default JS runtime ("node"/"bun"), replace it with
|
|
88
|
+
// process.execPath — the actual node binary running the daemon. This avoids
|
|
89
|
+
// PATH lookup failures in the managed runtime bundle.
|
|
90
|
+
// When the SDK passes a native binary path (from pathToClaudeCodeExecutable)
|
|
91
|
+
// or the user overrides the command via runtime settings, use that directly.
|
|
92
|
+
const isDefaultRuntime = resolved.command === "node" || resolved.command === "bun";
|
|
93
|
+
const command = isDefaultRuntime ? process.execPath : resolved.command;
|
|
94
|
+
const child = spawnProcess(command, resolved.args, {
|
|
95
|
+
cwd: spawnOptions.cwd,
|
|
96
|
+
env: {
|
|
97
|
+
...applyProviderEnv(spawnOptions.env, runtimeSettings),
|
|
98
|
+
...(launchEnv ?? {}),
|
|
99
|
+
},
|
|
100
|
+
signal: spawnOptions.signal,
|
|
101
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
102
|
+
// Bypass cmd.exe on Windows: the SDK passes --mcp-config with inline JSON
|
|
103
|
+
// containing double quotes, which cmd.exe mangles (strips quotes, breaks parsing).
|
|
104
|
+
// The command is always a resolved binary path, so shell routing is unnecessary.
|
|
105
|
+
shell: false,
|
|
106
|
+
});
|
|
107
|
+
if (typeof options.stderr === "function") {
|
|
108
|
+
child.stderr?.on("data", (chunk) => {
|
|
109
|
+
options.stderr?.(chunk.toString());
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return child;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function isClaudeThinkingEffort(value) {
|
|
117
|
+
return value === "low" || value === "medium" || value === "high" || value === "max";
|
|
118
|
+
}
|
|
119
|
+
const MAX_RECENT_STDERR_CHARS = 4000;
|
|
120
|
+
const STDERR_FLUSH_WAIT_MS = 150;
|
|
121
|
+
const STDERR_FLUSH_POLL_INTERVAL_MS = 10;
|
|
122
|
+
function summarizeClaudeOptionsForLog(options) {
|
|
123
|
+
const systemPromptRaw = options.systemPrompt;
|
|
124
|
+
const systemPromptSummary = (() => {
|
|
125
|
+
if (!systemPromptRaw) {
|
|
126
|
+
return { mode: "none", preset: null };
|
|
127
|
+
}
|
|
128
|
+
if (typeof systemPromptRaw === "string") {
|
|
129
|
+
return { mode: "string", preset: null };
|
|
130
|
+
}
|
|
131
|
+
const prompt = systemPromptRaw;
|
|
132
|
+
const promptType = typeof prompt.type === "string" ? prompt.type : "custom";
|
|
133
|
+
return {
|
|
134
|
+
mode: promptType === "preset" ? "preset" : "custom",
|
|
135
|
+
preset: typeof prompt.preset === "string" && prompt.preset.length > 0 ? prompt.preset : null,
|
|
136
|
+
};
|
|
137
|
+
})();
|
|
138
|
+
const mcpServerNames = options.mcpServers ? Object.keys(options.mcpServers).sort() : [];
|
|
139
|
+
return {
|
|
140
|
+
cwd: typeof options.cwd === "string" ? options.cwd : null,
|
|
141
|
+
permissionMode: typeof options.permissionMode === "string" ? options.permissionMode : null,
|
|
142
|
+
model: typeof options.model === "string" ? options.model : null,
|
|
143
|
+
includePartialMessages: options.includePartialMessages === true,
|
|
144
|
+
settingSources: Array.isArray(options.settingSources) ? options.settingSources : [],
|
|
145
|
+
enableFileCheckpointing: options.enableFileCheckpointing === true,
|
|
146
|
+
hasResume: typeof options.resume === "string" && options.resume.length > 0,
|
|
147
|
+
maxThinkingTokens: typeof options.maxThinkingTokens === "number" ? options.maxThinkingTokens : null,
|
|
148
|
+
hasEnv: !!options.env,
|
|
149
|
+
envKeyCount: Object.keys(options.env ?? {}).length,
|
|
150
|
+
hasMcpServers: mcpServerNames.length > 0,
|
|
151
|
+
mcpServerNames,
|
|
152
|
+
systemPromptMode: systemPromptSummary.mode,
|
|
153
|
+
systemPromptPreset: systemPromptSummary.preset,
|
|
154
|
+
hasCanUseTool: typeof options.canUseTool === "function",
|
|
155
|
+
hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
|
|
156
|
+
hasStderrHandler: typeof options.stderr === "function",
|
|
157
|
+
pathToClaudeCodeExecutable: typeof options.pathToClaudeCodeExecutable === "string"
|
|
158
|
+
? options.pathToClaudeCodeExecutable
|
|
159
|
+
: null,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function isToolResultTextBlock(value) {
|
|
163
|
+
return (!!value &&
|
|
164
|
+
typeof value === "object" &&
|
|
165
|
+
value.type === "text" &&
|
|
166
|
+
typeof value.text === "string");
|
|
167
|
+
}
|
|
168
|
+
function normalizeForDeterministicString(value, seen) {
|
|
169
|
+
if (value === null ||
|
|
170
|
+
typeof value === "string" ||
|
|
171
|
+
typeof value === "number" ||
|
|
172
|
+
typeof value === "boolean") {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === "bigint") {
|
|
176
|
+
return value.toString();
|
|
177
|
+
}
|
|
178
|
+
if (typeof value === "function") {
|
|
179
|
+
return "[function]";
|
|
180
|
+
}
|
|
181
|
+
if (typeof value === "symbol") {
|
|
182
|
+
return value.toString();
|
|
183
|
+
}
|
|
184
|
+
if (typeof value === "undefined") {
|
|
185
|
+
return "[undefined]";
|
|
186
|
+
}
|
|
187
|
+
if (Array.isArray(value)) {
|
|
188
|
+
return value.map((entry) => normalizeForDeterministicString(entry, seen));
|
|
189
|
+
}
|
|
190
|
+
if (typeof value === "object") {
|
|
191
|
+
const objectValue = value;
|
|
192
|
+
if (seen.has(objectValue)) {
|
|
193
|
+
return "[circular]";
|
|
194
|
+
}
|
|
195
|
+
seen.add(objectValue);
|
|
196
|
+
const record = value;
|
|
197
|
+
const normalized = {};
|
|
198
|
+
for (const key of Object.keys(record).sort()) {
|
|
199
|
+
normalized[key] = normalizeForDeterministicString(record[key], seen);
|
|
200
|
+
}
|
|
201
|
+
seen.delete(objectValue);
|
|
202
|
+
return normalized;
|
|
203
|
+
}
|
|
204
|
+
return String(value);
|
|
205
|
+
}
|
|
206
|
+
function deterministicStringify(value) {
|
|
207
|
+
if (typeof value === "undefined") {
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const normalized = normalizeForDeterministicString(value, new WeakSet());
|
|
212
|
+
if (typeof normalized === "string") {
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
215
|
+
return JSON.stringify(normalized);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
try {
|
|
219
|
+
return String(value);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return "[unserializable]";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function coerceToolResultContentToString(content) {
|
|
227
|
+
if (typeof content === "string") {
|
|
228
|
+
return content;
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
|
|
231
|
+
return content.map((block) => block.text).join("");
|
|
232
|
+
}
|
|
233
|
+
return deterministicStringify(content);
|
|
234
|
+
}
|
|
235
|
+
function normalizeClaudeTranscriptText(value) {
|
|
236
|
+
if (typeof value !== "string") {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const normalized = value.trim();
|
|
240
|
+
return normalized.length > 0 ? normalized : null;
|
|
241
|
+
}
|
|
242
|
+
function isClaudeInterruptPlaceholderText(value) {
|
|
243
|
+
const normalized = normalizeClaudeTranscriptText(value);
|
|
244
|
+
return normalized !== null && INTERRUPT_PLACEHOLDER_PATTERN.test(normalized);
|
|
245
|
+
}
|
|
246
|
+
function isClaudeNoResponsePlaceholderText(value) {
|
|
247
|
+
return normalizeClaudeTranscriptText(value) === NO_RESPONSE_REQUESTED_PLACEHOLDER;
|
|
248
|
+
}
|
|
249
|
+
const LOCAL_COMMAND_STDOUT_PATTERN = /^\s*<local-command-stdout>[\s\S]*<\/local-command-stdout>\s*$/;
|
|
250
|
+
function isClaudeLocalCommandStdout(value) {
|
|
251
|
+
const normalized = normalizeClaudeTranscriptText(value);
|
|
252
|
+
return normalized !== null && LOCAL_COMMAND_STDOUT_PATTERN.test(normalized);
|
|
253
|
+
}
|
|
254
|
+
function isClaudeTranscriptNoiseText(value) {
|
|
255
|
+
return (isClaudeInterruptPlaceholderText(value) ||
|
|
256
|
+
isClaudeNoResponsePlaceholderText(value) ||
|
|
257
|
+
isClaudeLocalCommandStdout(value));
|
|
258
|
+
}
|
|
259
|
+
function collectClaudeTextContentParts(content) {
|
|
260
|
+
if (typeof content === "string") {
|
|
261
|
+
const normalized = normalizeClaudeTranscriptText(content);
|
|
262
|
+
return normalized ? [normalized] : [];
|
|
263
|
+
}
|
|
264
|
+
if (!Array.isArray(content)) {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
const parts = [];
|
|
268
|
+
for (const block of content) {
|
|
269
|
+
if (!block || typeof block !== "object") {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const text = normalizeClaudeTranscriptText(block.text);
|
|
273
|
+
if (text) {
|
|
274
|
+
parts.push(text);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const input = normalizeClaudeTranscriptText(block.input);
|
|
278
|
+
if (input) {
|
|
279
|
+
parts.push(input);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return parts;
|
|
283
|
+
}
|
|
284
|
+
function isClaudeTranscriptNoiseContent(content) {
|
|
285
|
+
const parts = collectClaudeTextContentParts(content);
|
|
286
|
+
return parts.length > 0 && parts.every((part) => isClaudeTranscriptNoiseText(part));
|
|
287
|
+
}
|
|
288
|
+
export function extractUserMessageText(content) {
|
|
289
|
+
if (typeof content === "string") {
|
|
290
|
+
const normalized = content.trim();
|
|
291
|
+
if (!normalized || isClaudeTranscriptNoiseText(normalized)) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return normalized;
|
|
295
|
+
}
|
|
296
|
+
if (!Array.isArray(content)) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
const parts = [];
|
|
300
|
+
for (const block of content) {
|
|
301
|
+
if (!block || typeof block !== "object") {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const text = typeof block.text === "string" ? block.text : undefined;
|
|
305
|
+
if (text && text.trim()) {
|
|
306
|
+
const trimmed = text.trim();
|
|
307
|
+
if (!isClaudeTranscriptNoiseText(trimmed)) {
|
|
308
|
+
parts.push(trimmed);
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const input = typeof block.input === "string" ? block.input : undefined;
|
|
313
|
+
if (input && input.trim()) {
|
|
314
|
+
const trimmed = input.trim();
|
|
315
|
+
if (!isClaudeTranscriptNoiseText(trimmed)) {
|
|
316
|
+
parts.push(trimmed);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (parts.length === 0) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
const combined = parts.join("\n\n").trim();
|
|
324
|
+
return combined.length > 0 ? combined : null;
|
|
325
|
+
}
|
|
326
|
+
function isMetadata(value) {
|
|
327
|
+
return typeof value === "object" && value !== null;
|
|
328
|
+
}
|
|
329
|
+
function readTrimmedString(value) {
|
|
330
|
+
if (typeof value !== "string") {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
const trimmed = value.trim();
|
|
334
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
335
|
+
}
|
|
336
|
+
function isMcpServerConfig(value) {
|
|
337
|
+
if (!isMetadata(value)) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
const type = value.type;
|
|
341
|
+
if (type === "stdio") {
|
|
342
|
+
return typeof value.command === "string";
|
|
343
|
+
}
|
|
344
|
+
if (type === "http" || type === "sse") {
|
|
345
|
+
return typeof value.url === "string";
|
|
346
|
+
}
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
function isMcpServersRecord(value) {
|
|
350
|
+
if (!isMetadata(value)) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
for (const config of Object.values(value)) {
|
|
354
|
+
if (!isMcpServerConfig(config)) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
function isPermissionMode(value) {
|
|
361
|
+
return typeof value === "string" && VALID_CLAUDE_MODES.has(value);
|
|
362
|
+
}
|
|
363
|
+
function coerceSessionMetadata(metadata) {
|
|
364
|
+
if (!isMetadata(metadata)) {
|
|
365
|
+
return {};
|
|
366
|
+
}
|
|
367
|
+
const result = {};
|
|
368
|
+
if (metadata.provider === "claude" || metadata.provider === "codex") {
|
|
369
|
+
result.provider = metadata.provider;
|
|
370
|
+
}
|
|
371
|
+
if (typeof metadata.cwd === "string") {
|
|
372
|
+
result.cwd = metadata.cwd;
|
|
373
|
+
}
|
|
374
|
+
if (typeof metadata.modeId === "string") {
|
|
375
|
+
result.modeId = metadata.modeId;
|
|
376
|
+
}
|
|
377
|
+
if (typeof metadata.model === "string") {
|
|
378
|
+
result.model = metadata.model;
|
|
379
|
+
}
|
|
380
|
+
if (typeof metadata.title === "string" || metadata.title === null) {
|
|
381
|
+
result.title = metadata.title;
|
|
382
|
+
}
|
|
383
|
+
if (typeof metadata.approvalPolicy === "string") {
|
|
384
|
+
result.approvalPolicy = metadata.approvalPolicy;
|
|
385
|
+
}
|
|
386
|
+
if (typeof metadata.sandboxMode === "string") {
|
|
387
|
+
result.sandboxMode = metadata.sandboxMode;
|
|
388
|
+
}
|
|
389
|
+
if (typeof metadata.networkAccess === "boolean") {
|
|
390
|
+
result.networkAccess = metadata.networkAccess;
|
|
391
|
+
}
|
|
392
|
+
if (typeof metadata.webSearch === "boolean") {
|
|
393
|
+
result.webSearch = metadata.webSearch;
|
|
394
|
+
}
|
|
395
|
+
if (isMetadata(metadata.extra)) {
|
|
396
|
+
const extra = {};
|
|
397
|
+
if (isMetadata(metadata.extra.codex)) {
|
|
398
|
+
extra.codex = metadata.extra.codex;
|
|
399
|
+
}
|
|
400
|
+
if (isClaudeExtra(metadata.extra.claude)) {
|
|
401
|
+
extra.claude = metadata.extra.claude;
|
|
402
|
+
}
|
|
403
|
+
if (extra.codex || extra.claude) {
|
|
404
|
+
result.extra = extra;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (typeof metadata.systemPrompt === "string") {
|
|
408
|
+
result.systemPrompt = metadata.systemPrompt;
|
|
409
|
+
}
|
|
410
|
+
if (isMcpServersRecord(metadata.mcpServers)) {
|
|
411
|
+
result.mcpServers = metadata.mcpServers;
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
function toClaudeSdkMcpConfig(config) {
|
|
416
|
+
switch (config.type) {
|
|
417
|
+
case "stdio":
|
|
418
|
+
return {
|
|
419
|
+
type: "stdio",
|
|
420
|
+
command: config.command,
|
|
421
|
+
args: config.args,
|
|
422
|
+
env: config.env,
|
|
423
|
+
};
|
|
424
|
+
case "http":
|
|
425
|
+
return {
|
|
426
|
+
type: "http",
|
|
427
|
+
url: config.url,
|
|
428
|
+
headers: config.headers,
|
|
429
|
+
};
|
|
430
|
+
case "sse":
|
|
431
|
+
return {
|
|
432
|
+
type: "sse",
|
|
433
|
+
url: config.url,
|
|
434
|
+
headers: config.headers,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function isClaudeContentChunk(value) {
|
|
439
|
+
return isMetadata(value) && typeof value.type === "string";
|
|
440
|
+
}
|
|
441
|
+
function isClaudeExtra(value) {
|
|
442
|
+
return isMetadata(value);
|
|
443
|
+
}
|
|
444
|
+
function isPermissionUpdate(value) {
|
|
445
|
+
if (!isMetadata(value)) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
const type = value.type;
|
|
449
|
+
if (type !== "addRules" && type !== "replaceRules" && type !== "removeRules") {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
const rules = value.rules;
|
|
453
|
+
const behavior = value.behavior;
|
|
454
|
+
const destination = value.destination;
|
|
455
|
+
return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
|
|
456
|
+
}
|
|
457
|
+
function resolvePermissionKind(toolName, input) {
|
|
458
|
+
if (toolName === "ExitPlanMode")
|
|
459
|
+
return "plan";
|
|
460
|
+
if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
|
|
461
|
+
return "question";
|
|
462
|
+
}
|
|
463
|
+
return "tool";
|
|
464
|
+
}
|
|
465
|
+
function getClaudeModeLabel(modeId) {
|
|
466
|
+
return DEFAULT_MODES.find((mode) => mode.id === modeId)?.label ?? modeId;
|
|
467
|
+
}
|
|
468
|
+
function buildClaudePlanPermissionActions(resumeMode) {
|
|
469
|
+
const actions = [
|
|
470
|
+
{
|
|
471
|
+
id: "reject",
|
|
472
|
+
label: "Reject",
|
|
473
|
+
behavior: "deny",
|
|
474
|
+
variant: "danger",
|
|
475
|
+
intent: "dismiss",
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "implement",
|
|
479
|
+
label: "Implement",
|
|
480
|
+
behavior: "allow",
|
|
481
|
+
variant: "primary",
|
|
482
|
+
intent: "implement",
|
|
483
|
+
},
|
|
484
|
+
];
|
|
485
|
+
if (resumeMode === "bypassPermissions") {
|
|
486
|
+
actions.push({
|
|
487
|
+
id: "implement_resume",
|
|
488
|
+
label: `Implement with ${getClaudeModeLabel(resumeMode)}`,
|
|
489
|
+
behavior: "allow",
|
|
490
|
+
variant: "secondary",
|
|
491
|
+
intent: "implement_resume",
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return actions;
|
|
495
|
+
}
|
|
496
|
+
class TimelineAssembler {
|
|
497
|
+
constructor() {
|
|
498
|
+
this.messages = new Map();
|
|
499
|
+
this.finalizedMessageIds = new Set();
|
|
500
|
+
this.activeMessageByRun = new Map();
|
|
501
|
+
this.syntheticMessageCounter = 0;
|
|
502
|
+
}
|
|
503
|
+
consume(input) {
|
|
504
|
+
if (input.message.type === "assistant") {
|
|
505
|
+
return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
|
|
506
|
+
}
|
|
507
|
+
if (input.message.type === "stream_event") {
|
|
508
|
+
return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
|
|
509
|
+
}
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
consumeAssistantMessage(message, runId, messageIdHint) {
|
|
513
|
+
const messageId = this.readMessageIdFromAssistantMessage(message) ??
|
|
514
|
+
messageIdHint ??
|
|
515
|
+
this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
|
|
516
|
+
if (!messageId) {
|
|
517
|
+
return [];
|
|
518
|
+
}
|
|
519
|
+
if (this.finalizedMessageIds.has(messageId)) {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
const state = this.ensureMessageState(messageId, runId);
|
|
523
|
+
const fragments = this.extractFragments(message.message?.content);
|
|
524
|
+
return this.applyAbsoluteFragments(state, fragments);
|
|
525
|
+
}
|
|
526
|
+
consumeStreamEvent(message, runId, messageIdHint) {
|
|
527
|
+
const event = message.event;
|
|
528
|
+
const eventType = readTrimmedString(event.type);
|
|
529
|
+
const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
|
|
530
|
+
if (eventType === "message_start") {
|
|
531
|
+
const messageId = this.resolveMessageId({
|
|
532
|
+
runId,
|
|
533
|
+
createIfMissing: true,
|
|
534
|
+
messageId: streamEventMessageId,
|
|
535
|
+
});
|
|
536
|
+
if (!messageId) {
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
this.ensureMessageState(messageId, runId);
|
|
540
|
+
return [];
|
|
541
|
+
}
|
|
542
|
+
if (eventType === "message_stop") {
|
|
543
|
+
const messageId = this.resolveMessageId({
|
|
544
|
+
runId,
|
|
545
|
+
createIfMissing: false,
|
|
546
|
+
messageId: streamEventMessageId,
|
|
547
|
+
});
|
|
548
|
+
if (!messageId) {
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
return this.finalizeMessage(messageId, runId);
|
|
552
|
+
}
|
|
553
|
+
if (eventType === "content_block_start") {
|
|
554
|
+
return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
|
|
555
|
+
}
|
|
556
|
+
if (eventType === "content_block_delta") {
|
|
557
|
+
return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
|
|
558
|
+
}
|
|
559
|
+
return [];
|
|
560
|
+
}
|
|
561
|
+
consumeDeltaContent(content, runId, messageIdHint) {
|
|
562
|
+
const fragments = this.extractFragments(content);
|
|
563
|
+
if (fragments.length === 0) {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
const messageId = this.resolveMessageId({
|
|
567
|
+
runId,
|
|
568
|
+
createIfMissing: true,
|
|
569
|
+
messageId: messageIdHint,
|
|
570
|
+
});
|
|
571
|
+
if (!messageId) {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
574
|
+
const state = this.ensureMessageState(messageId, runId);
|
|
575
|
+
return this.appendFragments(state, fragments);
|
|
576
|
+
}
|
|
577
|
+
appendFragments(state, fragments) {
|
|
578
|
+
for (const fragment of fragments) {
|
|
579
|
+
if (fragment.kind === "assistant") {
|
|
580
|
+
state.assistantText += fragment.text;
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
state.reasoningText += fragment.text;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return this.emitNewContent(state);
|
|
587
|
+
}
|
|
588
|
+
applyAbsoluteFragments(state, fragments) {
|
|
589
|
+
const assistantText = fragments
|
|
590
|
+
.filter((fragment) => fragment.kind === "assistant")
|
|
591
|
+
.map((fragment) => fragment.text)
|
|
592
|
+
.join("");
|
|
593
|
+
const reasoningText = fragments
|
|
594
|
+
.filter((fragment) => fragment.kind === "reasoning")
|
|
595
|
+
.map((fragment) => fragment.text)
|
|
596
|
+
.join("");
|
|
597
|
+
if (assistantText.length > 0) {
|
|
598
|
+
if (!assistantText.startsWith(state.assistantText)) {
|
|
599
|
+
state.emittedAssistantLength = 0;
|
|
600
|
+
}
|
|
601
|
+
state.assistantText = assistantText;
|
|
602
|
+
}
|
|
603
|
+
if (reasoningText.length > 0) {
|
|
604
|
+
if (!reasoningText.startsWith(state.reasoningText)) {
|
|
605
|
+
state.emittedReasoningLength = 0;
|
|
606
|
+
}
|
|
607
|
+
state.reasoningText = reasoningText;
|
|
608
|
+
}
|
|
609
|
+
return this.emitNewContent(state);
|
|
610
|
+
}
|
|
611
|
+
finalizeMessage(messageId, runId) {
|
|
612
|
+
const state = this.messages.get(messageId);
|
|
613
|
+
if (!state) {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
state.stopped = true;
|
|
617
|
+
const items = this.emitNewContent(state);
|
|
618
|
+
if (runId && this.activeMessageByRun.get(runId) === messageId) {
|
|
619
|
+
this.activeMessageByRun.delete(runId);
|
|
620
|
+
}
|
|
621
|
+
this.finalizedMessageIds.add(messageId);
|
|
622
|
+
this.messages.delete(messageId);
|
|
623
|
+
return items;
|
|
624
|
+
}
|
|
625
|
+
emitNewContent(state) {
|
|
626
|
+
const items = [];
|
|
627
|
+
const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
|
|
628
|
+
if (nextAssistantText.length > 0 &&
|
|
629
|
+
nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER &&
|
|
630
|
+
!isClaudeTranscriptNoiseText(nextAssistantText)) {
|
|
631
|
+
state.emittedAssistantLength = state.assistantText.length;
|
|
632
|
+
items.push({ type: "assistant_message", text: nextAssistantText });
|
|
633
|
+
}
|
|
634
|
+
const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
|
|
635
|
+
if (nextReasoningText.length > 0) {
|
|
636
|
+
state.emittedReasoningLength = state.reasoningText.length;
|
|
637
|
+
items.push({ type: "reasoning", text: nextReasoningText });
|
|
638
|
+
}
|
|
639
|
+
return items;
|
|
640
|
+
}
|
|
641
|
+
ensureMessageState(messageId, runId) {
|
|
642
|
+
const existing = this.messages.get(messageId);
|
|
643
|
+
if (existing) {
|
|
644
|
+
existing.stopped = false;
|
|
645
|
+
if (runId) {
|
|
646
|
+
this.activeMessageByRun.set(runId, messageId);
|
|
647
|
+
}
|
|
648
|
+
return existing;
|
|
649
|
+
}
|
|
650
|
+
const created = {
|
|
651
|
+
id: messageId,
|
|
652
|
+
assistantText: "",
|
|
653
|
+
reasoningText: "",
|
|
654
|
+
emittedAssistantLength: 0,
|
|
655
|
+
emittedReasoningLength: 0,
|
|
656
|
+
stopped: false,
|
|
657
|
+
};
|
|
658
|
+
this.messages.set(messageId, created);
|
|
659
|
+
if (runId) {
|
|
660
|
+
this.activeMessageByRun.set(runId, messageId);
|
|
661
|
+
}
|
|
662
|
+
return created;
|
|
663
|
+
}
|
|
664
|
+
resolveMessageId(input) {
|
|
665
|
+
if (input.messageId) {
|
|
666
|
+
return input.messageId;
|
|
667
|
+
}
|
|
668
|
+
if (input.runId) {
|
|
669
|
+
const active = this.activeMessageByRun.get(input.runId);
|
|
670
|
+
if (active) {
|
|
671
|
+
return active;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (!input.createIfMissing) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
|
|
678
|
+
if (input.runId) {
|
|
679
|
+
this.activeMessageByRun.set(input.runId, synthetic);
|
|
680
|
+
}
|
|
681
|
+
return synthetic;
|
|
682
|
+
}
|
|
683
|
+
extractFragments(content) {
|
|
684
|
+
if (typeof content === "string") {
|
|
685
|
+
if (content.length === 0) {
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
return [{ kind: "assistant", text: content }];
|
|
689
|
+
}
|
|
690
|
+
const blocks = Array.isArray(content) ? content : [content];
|
|
691
|
+
const fragments = [];
|
|
692
|
+
for (const rawBlock of blocks) {
|
|
693
|
+
if (!isClaudeContentChunk(rawBlock)) {
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
|
|
697
|
+
typeof rawBlock.text === "string" &&
|
|
698
|
+
rawBlock.text.length > 0) {
|
|
699
|
+
fragments.push({ kind: "assistant", text: rawBlock.text });
|
|
700
|
+
}
|
|
701
|
+
if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
|
|
702
|
+
typeof rawBlock.thinking === "string" &&
|
|
703
|
+
rawBlock.thinking.length > 0) {
|
|
704
|
+
fragments.push({ kind: "reasoning", text: rawBlock.thinking });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return fragments;
|
|
708
|
+
}
|
|
709
|
+
readMessageIdFromAssistantMessage(message) {
|
|
710
|
+
const candidate = message;
|
|
711
|
+
return (readTrimmedString(candidate.message_id) ?? readTrimmedString(candidate.message?.id) ?? null);
|
|
712
|
+
}
|
|
713
|
+
readMessageIdFromStreamEvent(event) {
|
|
714
|
+
const message = event.message;
|
|
715
|
+
return readTrimmedString(event.message_id) ?? readTrimmedString(message?.id) ?? null;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function isSyntheticUserEntry(entry) {
|
|
719
|
+
if (!entry || typeof entry !== "object") {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
const candidate = entry;
|
|
723
|
+
return candidate.isSynthetic === true || candidate.isMeta === true;
|
|
724
|
+
}
|
|
725
|
+
export function readEventIdentifiers(message) {
|
|
726
|
+
const root = message;
|
|
727
|
+
const messageType = readTrimmedString(root.type);
|
|
728
|
+
const streamEvent = root.event;
|
|
729
|
+
const streamEventMessage = streamEvent?.message;
|
|
730
|
+
const messageContainer = root.message;
|
|
731
|
+
return {
|
|
732
|
+
taskId: readTrimmedString(root.task_id) ??
|
|
733
|
+
readTrimmedString(streamEvent?.task_id) ??
|
|
734
|
+
readTrimmedString(streamEventMessage?.task_id) ??
|
|
735
|
+
readTrimmedString(messageContainer?.task_id) ??
|
|
736
|
+
null,
|
|
737
|
+
parentMessageId: readTrimmedString(root.parent_message_id) ??
|
|
738
|
+
readTrimmedString(streamEvent?.parent_message_id) ??
|
|
739
|
+
readTrimmedString(streamEventMessage?.parent_message_id) ??
|
|
740
|
+
readTrimmedString(messageContainer?.parent_message_id) ??
|
|
741
|
+
null,
|
|
742
|
+
messageId: readTrimmedString(root.message_id) ??
|
|
743
|
+
readTrimmedString(streamEvent?.message_id) ??
|
|
744
|
+
readTrimmedString(streamEventMessage?.id) ??
|
|
745
|
+
readTrimmedString(streamEventMessage?.message_id) ??
|
|
746
|
+
readTrimmedString(messageContainer?.id) ??
|
|
747
|
+
readTrimmedString(messageContainer?.message_id) ??
|
|
748
|
+
(messageType === "user" ? readTrimmedString(root.uuid) : null) ??
|
|
749
|
+
null,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const claudeDebug = process.env.SEAWORK_CLAUDE_DEBUG === "1";
|
|
753
|
+
export class ClaudeAgentClient {
|
|
754
|
+
constructor(options) {
|
|
755
|
+
this.provider = "claude";
|
|
756
|
+
this.capabilities = CLAUDE_CAPABILITIES;
|
|
757
|
+
this.defaults = options.defaults;
|
|
758
|
+
this.logger = options.logger.child({ module: "agent", provider: "claude" });
|
|
759
|
+
this.runtimeSettings = options.runtimeSettings;
|
|
760
|
+
this.queryFactory = options.queryFactory ?? query;
|
|
761
|
+
}
|
|
762
|
+
async createSession(config, launchContext) {
|
|
763
|
+
const claudeConfig = this.assertConfig(config);
|
|
764
|
+
return new ClaudeAgentSession(claudeConfig, {
|
|
765
|
+
defaults: this.defaults,
|
|
766
|
+
runtimeSettings: this.runtimeSettings,
|
|
767
|
+
launchEnv: launchContext?.env,
|
|
768
|
+
logger: this.logger,
|
|
769
|
+
queryFactory: this.queryFactory,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
async resumeSession(handle, overrides, launchContext) {
|
|
773
|
+
const metadata = coerceSessionMetadata(handle.metadata);
|
|
774
|
+
const merged = { ...metadata, ...overrides };
|
|
775
|
+
if (!merged.cwd) {
|
|
776
|
+
throw new Error("Claude resume requires the original working directory in metadata");
|
|
777
|
+
}
|
|
778
|
+
const mergedConfig = { ...merged, provider: "claude", cwd: merged.cwd };
|
|
779
|
+
const claudeConfig = this.assertConfig(mergedConfig);
|
|
780
|
+
return new ClaudeAgentSession(claudeConfig, {
|
|
781
|
+
defaults: this.defaults,
|
|
782
|
+
runtimeSettings: this.runtimeSettings,
|
|
783
|
+
handle,
|
|
784
|
+
launchEnv: launchContext?.env,
|
|
785
|
+
logger: this.logger,
|
|
786
|
+
queryFactory: this.queryFactory,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
async listModels(_options) {
|
|
790
|
+
return getClaudeModels();
|
|
791
|
+
}
|
|
792
|
+
async listPersistedAgents(options) {
|
|
793
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
794
|
+
const projectsRoot = path.join(configDir, "projects");
|
|
795
|
+
if (!(await pathExists(projectsRoot))) {
|
|
796
|
+
return [];
|
|
797
|
+
}
|
|
798
|
+
const limit = options?.limit ?? 20;
|
|
799
|
+
const candidates = await collectRecentClaudeSessions(projectsRoot, limit * 3);
|
|
800
|
+
const descriptors = [];
|
|
801
|
+
for (const candidate of candidates) {
|
|
802
|
+
const descriptor = await parseClaudeSessionDescriptor(candidate.path, candidate.mtime);
|
|
803
|
+
if (descriptor) {
|
|
804
|
+
descriptors.push(descriptor);
|
|
805
|
+
}
|
|
806
|
+
if (descriptors.length >= limit) {
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return descriptors;
|
|
811
|
+
}
|
|
812
|
+
async isAvailable() {
|
|
813
|
+
const command = this.runtimeSettings?.command;
|
|
814
|
+
if (command?.mode === "replace") {
|
|
815
|
+
return fs.existsSync(command.argv[0]);
|
|
816
|
+
}
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
async getDiagnostic() {
|
|
820
|
+
try {
|
|
821
|
+
const resolvedBinary = (await findExecutable("claude")) ?? "not found";
|
|
822
|
+
const available = await this.isAvailable();
|
|
823
|
+
const version = await resolveClaudeVersion(this.runtimeSettings);
|
|
824
|
+
let modelsValue = "Not checked";
|
|
825
|
+
let status = formatDiagnosticStatus(available);
|
|
826
|
+
if (available) {
|
|
827
|
+
try {
|
|
828
|
+
const models = await this.listModels();
|
|
829
|
+
modelsValue = String(models.length);
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
modelsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
|
|
833
|
+
status = formatDiagnosticStatus(available, {
|
|
834
|
+
source: "model fetch",
|
|
835
|
+
cause: error,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
diagnostic: formatProviderDiagnostic("Claude Code", [
|
|
841
|
+
{ label: "Binary", value: resolvedBinary },
|
|
842
|
+
...(version ? [{ label: "Version", value: version }] : []),
|
|
843
|
+
{ label: "Models", value: modelsValue },
|
|
844
|
+
{ label: "Status", value: status },
|
|
845
|
+
]),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
catch (error) {
|
|
849
|
+
return {
|
|
850
|
+
diagnostic: formatProviderDiagnosticError("Claude Code", error),
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
assertConfig(config) {
|
|
855
|
+
if (config.provider !== "claude") {
|
|
856
|
+
throw new Error(`ClaudeAgentClient received config for provider '${config.provider}'`);
|
|
857
|
+
}
|
|
858
|
+
return { ...config, provider: "claude" };
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async function resolveClaudeVersion(runtimeSettings) {
|
|
862
|
+
const command = runtimeSettings?.command;
|
|
863
|
+
try {
|
|
864
|
+
if (command?.mode === "replace") {
|
|
865
|
+
const { stdout } = await execFileAsync(command.argv[0], [...command.argv.slice(1), "--version"], {
|
|
866
|
+
encoding: "utf8",
|
|
867
|
+
timeout: 5000,
|
|
868
|
+
windowsHide: true,
|
|
869
|
+
});
|
|
870
|
+
return stdout.trim() || null;
|
|
871
|
+
}
|
|
872
|
+
const executable = await findExecutable("claude");
|
|
873
|
+
if (!executable) {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
const { stdout } = await execFileAsync(executable, ["--version"], {
|
|
877
|
+
encoding: "utf8",
|
|
878
|
+
timeout: 5000,
|
|
879
|
+
windowsHide: true,
|
|
880
|
+
});
|
|
881
|
+
return stdout.trim() || null;
|
|
882
|
+
}
|
|
883
|
+
catch {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function extractContextWindowSize(modelUsage) {
|
|
888
|
+
if (!modelUsage || typeof modelUsage !== "object") {
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
let maxContextWindow;
|
|
892
|
+
for (const value of Object.values(modelUsage)) {
|
|
893
|
+
if (!value || typeof value !== "object") {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const contextWindow = value.contextWindow;
|
|
897
|
+
if (typeof contextWindow !== "number" ||
|
|
898
|
+
!Number.isFinite(contextWindow) ||
|
|
899
|
+
contextWindow <= 0) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow);
|
|
903
|
+
}
|
|
904
|
+
return maxContextWindow;
|
|
905
|
+
}
|
|
906
|
+
function readUsageTotalTokens(usage) {
|
|
907
|
+
if (!usage || typeof usage !== "object") {
|
|
908
|
+
return undefined;
|
|
909
|
+
}
|
|
910
|
+
const totalTokens = usage.total_tokens;
|
|
911
|
+
if (typeof totalTokens !== "number" || !Number.isFinite(totalTokens) || totalTokens < 0) {
|
|
912
|
+
return undefined;
|
|
913
|
+
}
|
|
914
|
+
return totalTokens;
|
|
915
|
+
}
|
|
916
|
+
function readContextWindowUsedTokensFromTaskProgress(message) {
|
|
917
|
+
return readUsageTotalTokens(message.usage);
|
|
918
|
+
}
|
|
919
|
+
function readUsageFromTaskNotification(message) {
|
|
920
|
+
return readUsageTotalTokens(message.usage);
|
|
921
|
+
}
|
|
922
|
+
function readStreamRequestInputTokens(event) {
|
|
923
|
+
const messageUsage = event.message?.usage;
|
|
924
|
+
if (!messageUsage || typeof messageUsage !== "object") {
|
|
925
|
+
return undefined;
|
|
926
|
+
}
|
|
927
|
+
const usage = messageUsage;
|
|
928
|
+
const inputTokens = typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
|
|
929
|
+
? usage.input_tokens
|
|
930
|
+
: undefined;
|
|
931
|
+
const cacheCreationInputTokens = typeof usage.cache_creation_input_tokens === "number" &&
|
|
932
|
+
Number.isFinite(usage.cache_creation_input_tokens)
|
|
933
|
+
? usage.cache_creation_input_tokens
|
|
934
|
+
: 0;
|
|
935
|
+
const cacheReadInputTokens = typeof usage.cache_read_input_tokens === "number" &&
|
|
936
|
+
Number.isFinite(usage.cache_read_input_tokens)
|
|
937
|
+
? usage.cache_read_input_tokens
|
|
938
|
+
: 0;
|
|
939
|
+
if (typeof inputTokens !== "number" || inputTokens < 0) {
|
|
940
|
+
return undefined;
|
|
941
|
+
}
|
|
942
|
+
return inputTokens + cacheCreationInputTokens + cacheReadInputTokens;
|
|
943
|
+
}
|
|
944
|
+
function readStreamRequestOutputTokens(event) {
|
|
945
|
+
const outputTokens = event.usage?.output_tokens;
|
|
946
|
+
if (typeof outputTokens !== "number" || !Number.isFinite(outputTokens) || outputTokens < 0) {
|
|
947
|
+
return undefined;
|
|
948
|
+
}
|
|
949
|
+
return outputTokens;
|
|
950
|
+
}
|
|
951
|
+
class ClaudeAgentSession {
|
|
952
|
+
constructor(config, options) {
|
|
953
|
+
this.provider = "claude";
|
|
954
|
+
this.capabilities = CLAUDE_CAPABILITIES;
|
|
955
|
+
this.query = null;
|
|
956
|
+
this.input = null;
|
|
957
|
+
this.planResumeMode = null;
|
|
958
|
+
this.availableModes = DEFAULT_MODES;
|
|
959
|
+
this.toolUseCache = new Map();
|
|
960
|
+
this.toolUseIndexToId = new Map();
|
|
961
|
+
this.toolUseInputBuffers = new Map();
|
|
962
|
+
this.pendingPermissions = new Map();
|
|
963
|
+
this.activeForegroundTurnId = null;
|
|
964
|
+
this.autonomousTurn = null;
|
|
965
|
+
this.subscribers = new Set();
|
|
966
|
+
this.timelineAssembler = new TimelineAssembler();
|
|
967
|
+
this.sidechainTracker = new ClaudeSidechainTracker({
|
|
968
|
+
getToolInput: (toolUseId) => this.toolUseCache.get(toolUseId)?.input ?? null,
|
|
969
|
+
});
|
|
970
|
+
this.persistedHistory = [];
|
|
971
|
+
this.historyPending = false;
|
|
972
|
+
this.turnState = "idle";
|
|
973
|
+
this.nextTurnOrdinal = 1;
|
|
974
|
+
this.cancelCurrentTurn = null;
|
|
975
|
+
this.cachedRuntimeInfo = null;
|
|
976
|
+
this.lastOptionsModel = null;
|
|
977
|
+
this.lastRuntimeModel = null;
|
|
978
|
+
this.compacting = false;
|
|
979
|
+
this.queryPumpPromise = null;
|
|
980
|
+
this.queryRestartNeeded = false;
|
|
981
|
+
this.pendingInterruptAbort = false;
|
|
982
|
+
this.lastForegroundPromptText = null;
|
|
983
|
+
this.foregroundHasVisibleActivity = false;
|
|
984
|
+
this.userMessageIds = [];
|
|
985
|
+
this.recentStderr = "";
|
|
986
|
+
this.closed = false;
|
|
987
|
+
this.handlePermissionRequest = async (toolName, input, options) => {
|
|
988
|
+
const requestId = `permission-${randomUUID()}`;
|
|
989
|
+
const kind = resolvePermissionKind(toolName, input);
|
|
990
|
+
const metadata = {};
|
|
991
|
+
if (options.toolUseID) {
|
|
992
|
+
metadata.toolUseId = options.toolUseID;
|
|
993
|
+
}
|
|
994
|
+
if (toolName === "ExitPlanMode" && typeof input.plan === "string") {
|
|
995
|
+
metadata.planText = input.plan;
|
|
996
|
+
}
|
|
997
|
+
const toolDetail = kind === "tool"
|
|
998
|
+
? mapClaudeRunningToolCall({
|
|
999
|
+
name: toolName,
|
|
1000
|
+
callId: options.toolUseID ?? requestId,
|
|
1001
|
+
input,
|
|
1002
|
+
output: null,
|
|
1003
|
+
})?.detail
|
|
1004
|
+
: undefined;
|
|
1005
|
+
const request = {
|
|
1006
|
+
id: requestId,
|
|
1007
|
+
provider: "claude",
|
|
1008
|
+
name: toolName,
|
|
1009
|
+
kind,
|
|
1010
|
+
input,
|
|
1011
|
+
detail: toolDetail,
|
|
1012
|
+
suggestions: options.suggestions?.map((suggestion) => ({ ...suggestion })),
|
|
1013
|
+
actions: kind === "plan" ? buildClaudePlanPermissionActions(this.planResumeMode) : undefined,
|
|
1014
|
+
metadata: Object.keys(metadata).length ? metadata : undefined,
|
|
1015
|
+
};
|
|
1016
|
+
this.pushEvent({ type: "permission_requested", provider: "claude", request });
|
|
1017
|
+
return await new Promise((resolve, reject) => {
|
|
1018
|
+
const cleanupFns = [];
|
|
1019
|
+
const cleanup = () => {
|
|
1020
|
+
while (cleanupFns.length) {
|
|
1021
|
+
const fn = cleanupFns.pop();
|
|
1022
|
+
try {
|
|
1023
|
+
fn?.();
|
|
1024
|
+
}
|
|
1025
|
+
catch {
|
|
1026
|
+
// ignore cleanup errors
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const abortHandler = () => {
|
|
1031
|
+
this.pendingPermissions.delete(requestId);
|
|
1032
|
+
cleanup();
|
|
1033
|
+
reject(new Error("Permission request aborted"));
|
|
1034
|
+
};
|
|
1035
|
+
if (options?.signal) {
|
|
1036
|
+
if (options.signal.aborted) {
|
|
1037
|
+
abortHandler();
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
1041
|
+
cleanupFns.push(() => options.signal?.removeEventListener("abort", abortHandler));
|
|
1042
|
+
}
|
|
1043
|
+
this.pendingPermissions.set(requestId, {
|
|
1044
|
+
request,
|
|
1045
|
+
resolve,
|
|
1046
|
+
reject,
|
|
1047
|
+
cleanup,
|
|
1048
|
+
});
|
|
1049
|
+
});
|
|
1050
|
+
};
|
|
1051
|
+
this.config = config;
|
|
1052
|
+
this.launchEnv = options.launchEnv;
|
|
1053
|
+
this.defaults = options.defaults;
|
|
1054
|
+
this.runtimeSettings = options.runtimeSettings;
|
|
1055
|
+
this.logger = options.logger;
|
|
1056
|
+
this.queryFactory = options.queryFactory ?? query;
|
|
1057
|
+
const handle = options.handle;
|
|
1058
|
+
if (handle) {
|
|
1059
|
+
if (!handle.sessionId) {
|
|
1060
|
+
throw new Error("Cannot resume: persistence handle has no sessionId");
|
|
1061
|
+
}
|
|
1062
|
+
this.claudeSessionId = handle.sessionId;
|
|
1063
|
+
this.persistence = handle;
|
|
1064
|
+
this.loadPersistedHistory(handle.sessionId);
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
this.claudeSessionId = null;
|
|
1068
|
+
this.persistence = null;
|
|
1069
|
+
}
|
|
1070
|
+
// Validate mode if provided
|
|
1071
|
+
if (config.modeId && !VALID_CLAUDE_MODES.has(config.modeId)) {
|
|
1072
|
+
const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
|
|
1073
|
+
throw new Error(`Invalid mode '${config.modeId}' for Claude provider. Valid modes: ${validModesList}`);
|
|
1074
|
+
}
|
|
1075
|
+
this.currentMode = isPermissionMode(config.modeId) ? config.modeId : "default";
|
|
1076
|
+
if (this.currentMode !== "plan") {
|
|
1077
|
+
this.planResumeMode = this.currentMode;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
get id() {
|
|
1081
|
+
return this.claudeSessionId;
|
|
1082
|
+
}
|
|
1083
|
+
async getRuntimeInfo() {
|
|
1084
|
+
if (this.cachedRuntimeInfo) {
|
|
1085
|
+
return { ...this.cachedRuntimeInfo };
|
|
1086
|
+
}
|
|
1087
|
+
const info = {
|
|
1088
|
+
provider: "claude",
|
|
1089
|
+
sessionId: this.claudeSessionId,
|
|
1090
|
+
model: this.lastOptionsModel,
|
|
1091
|
+
modeId: this.currentMode ?? null,
|
|
1092
|
+
...(this.lastRuntimeModel
|
|
1093
|
+
? {
|
|
1094
|
+
extra: {
|
|
1095
|
+
runtimeModel: this.lastRuntimeModel,
|
|
1096
|
+
},
|
|
1097
|
+
}
|
|
1098
|
+
: {}),
|
|
1099
|
+
};
|
|
1100
|
+
this.cachedRuntimeInfo = info;
|
|
1101
|
+
return { ...info };
|
|
1102
|
+
}
|
|
1103
|
+
async run(prompt, options) {
|
|
1104
|
+
const timeline = [];
|
|
1105
|
+
let finalText = "";
|
|
1106
|
+
let usage;
|
|
1107
|
+
let turnId = null;
|
|
1108
|
+
const bufferedEvents = [];
|
|
1109
|
+
let settled = false;
|
|
1110
|
+
let resolveCompletion;
|
|
1111
|
+
let rejectCompletion;
|
|
1112
|
+
const processEvent = (event) => {
|
|
1113
|
+
if (settled) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const eventTurnId = event.turnId;
|
|
1117
|
+
if (turnId && eventTurnId && eventTurnId !== turnId) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (event.type === "timeline") {
|
|
1121
|
+
timeline.push(event.item);
|
|
1122
|
+
if (event.item.type === "assistant_message") {
|
|
1123
|
+
if (!finalText) {
|
|
1124
|
+
finalText = event.item.text;
|
|
1125
|
+
}
|
|
1126
|
+
else if (event.item.text.startsWith(finalText)) {
|
|
1127
|
+
finalText = event.item.text;
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
finalText += event.item.text;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (event.type === "turn_completed") {
|
|
1136
|
+
usage = event.usage;
|
|
1137
|
+
settled = true;
|
|
1138
|
+
resolveCompletion();
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (event.type === "turn_failed") {
|
|
1142
|
+
settled = true;
|
|
1143
|
+
rejectCompletion(new Error(event.error));
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (event.type === "turn_canceled") {
|
|
1147
|
+
settled = true;
|
|
1148
|
+
resolveCompletion();
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
const completion = new Promise((resolve, reject) => {
|
|
1152
|
+
resolveCompletion = resolve;
|
|
1153
|
+
rejectCompletion = reject;
|
|
1154
|
+
});
|
|
1155
|
+
const unsubscribe = this.subscribe((event) => {
|
|
1156
|
+
if (!turnId) {
|
|
1157
|
+
bufferedEvents.push(event);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
processEvent(event);
|
|
1161
|
+
});
|
|
1162
|
+
try {
|
|
1163
|
+
const result = await this.startTurn(prompt, options);
|
|
1164
|
+
turnId = result.turnId;
|
|
1165
|
+
for (const event of bufferedEvents) {
|
|
1166
|
+
processEvent(event);
|
|
1167
|
+
}
|
|
1168
|
+
if (!settled) {
|
|
1169
|
+
await completion;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
finally {
|
|
1173
|
+
unsubscribe();
|
|
1174
|
+
}
|
|
1175
|
+
this.cachedRuntimeInfo = {
|
|
1176
|
+
provider: "claude",
|
|
1177
|
+
sessionId: this.claudeSessionId,
|
|
1178
|
+
model: this.lastOptionsModel,
|
|
1179
|
+
modeId: this.currentMode ?? null,
|
|
1180
|
+
};
|
|
1181
|
+
if (!this.claudeSessionId) {
|
|
1182
|
+
throw new Error("Session ID not set after run completed");
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
sessionId: this.claudeSessionId,
|
|
1186
|
+
finalText,
|
|
1187
|
+
usage,
|
|
1188
|
+
timeline,
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
async startTurn(prompt, _options) {
|
|
1192
|
+
if (this.closed) {
|
|
1193
|
+
throw new Error("Claude session is closed");
|
|
1194
|
+
}
|
|
1195
|
+
if (this.activeForegroundTurnId) {
|
|
1196
|
+
throw new Error("A foreground turn is already active");
|
|
1197
|
+
}
|
|
1198
|
+
const slashCommand = this.resolveSlashCommandInvocation(prompt);
|
|
1199
|
+
if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
|
|
1200
|
+
const turnId = this.createTurnId("foreground");
|
|
1201
|
+
this.activeForegroundTurnId = turnId;
|
|
1202
|
+
this.transitionTurnState("foreground", "rewind command");
|
|
1203
|
+
void this.executeRewindTurn(turnId, slashCommand);
|
|
1204
|
+
return { turnId };
|
|
1205
|
+
}
|
|
1206
|
+
if (this.autonomousTurn) {
|
|
1207
|
+
this.completeAutonomousTurn();
|
|
1208
|
+
}
|
|
1209
|
+
const sdkMessage = this.toSdkUserMessage(prompt);
|
|
1210
|
+
this.lastForegroundPromptText = this.extractPromptText(prompt);
|
|
1211
|
+
const turnId = this.createTurnId("foreground");
|
|
1212
|
+
this.activeForegroundTurnId = turnId;
|
|
1213
|
+
this.foregroundHasVisibleActivity = false;
|
|
1214
|
+
this.transitionTurnState("foreground", "foreground turn started");
|
|
1215
|
+
this.clearRecentStderr();
|
|
1216
|
+
let cancelIssued = false;
|
|
1217
|
+
const requestCancel = () => {
|
|
1218
|
+
if (cancelIssued) {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
cancelIssued = true;
|
|
1222
|
+
if (this.cancelCurrentTurn === requestCancel) {
|
|
1223
|
+
this.cancelCurrentTurn = null;
|
|
1224
|
+
}
|
|
1225
|
+
this.rejectAllPendingPermissions(new Error("Permission request aborted"));
|
|
1226
|
+
this.finishForegroundTurn({
|
|
1227
|
+
type: "turn_canceled",
|
|
1228
|
+
provider: "claude",
|
|
1229
|
+
reason: "Interrupted",
|
|
1230
|
+
});
|
|
1231
|
+
void this.interruptActiveTurn().catch((error) => {
|
|
1232
|
+
this.logger.warn({ err: error }, "Failed to interrupt during cancel");
|
|
1233
|
+
});
|
|
1234
|
+
};
|
|
1235
|
+
this.cancelCurrentTurn = requestCancel;
|
|
1236
|
+
this.notifySubscribers({ type: "turn_started", provider: "claude" });
|
|
1237
|
+
try {
|
|
1238
|
+
await this.ensureQuery();
|
|
1239
|
+
if (!this.input) {
|
|
1240
|
+
throw new Error("Claude session input stream not initialized");
|
|
1241
|
+
}
|
|
1242
|
+
this.startQueryPump();
|
|
1243
|
+
this.input.push(sdkMessage);
|
|
1244
|
+
}
|
|
1245
|
+
catch (error) {
|
|
1246
|
+
this.finishForegroundTurn(this.buildTurnFailedEvent(error instanceof Error ? error.message : "Claude stream failed"));
|
|
1247
|
+
}
|
|
1248
|
+
return { turnId };
|
|
1249
|
+
}
|
|
1250
|
+
subscribe(callback) {
|
|
1251
|
+
this.subscribers.add(callback);
|
|
1252
|
+
return () => {
|
|
1253
|
+
this.subscribers.delete(callback);
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
async interrupt() {
|
|
1257
|
+
if (this.cancelCurrentTurn) {
|
|
1258
|
+
this.cancelCurrentTurn();
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (this.autonomousTurn) {
|
|
1262
|
+
this.flushPendingToolCalls();
|
|
1263
|
+
this.completeAutonomousTurn();
|
|
1264
|
+
}
|
|
1265
|
+
await this.interruptActiveTurn();
|
|
1266
|
+
}
|
|
1267
|
+
async *streamHistory() {
|
|
1268
|
+
if (!this.historyPending || this.persistedHistory.length === 0) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const history = this.persistedHistory;
|
|
1272
|
+
this.persistedHistory = [];
|
|
1273
|
+
this.historyPending = false;
|
|
1274
|
+
for (const item of history) {
|
|
1275
|
+
yield { type: "timeline", item, provider: "claude" };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
async getAvailableModes() {
|
|
1279
|
+
return this.availableModes;
|
|
1280
|
+
}
|
|
1281
|
+
async getCurrentMode() {
|
|
1282
|
+
return this.currentMode ?? null;
|
|
1283
|
+
}
|
|
1284
|
+
async setMode(modeId) {
|
|
1285
|
+
// Validate mode
|
|
1286
|
+
if (!VALID_CLAUDE_MODES.has(modeId)) {
|
|
1287
|
+
const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
|
|
1288
|
+
throw new Error(`Invalid mode '${modeId}' for Claude provider. Valid modes: ${validModesList}`);
|
|
1289
|
+
}
|
|
1290
|
+
const normalized = isPermissionMode(modeId) ? modeId : "default";
|
|
1291
|
+
const previousMode = this.currentMode;
|
|
1292
|
+
const query = await this.ensureQuery();
|
|
1293
|
+
await query.setPermissionMode(normalized);
|
|
1294
|
+
if (normalized === "plan") {
|
|
1295
|
+
if (previousMode !== "plan") {
|
|
1296
|
+
this.planResumeMode = previousMode;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
this.planResumeMode = normalized;
|
|
1301
|
+
}
|
|
1302
|
+
this.currentMode = normalized;
|
|
1303
|
+
}
|
|
1304
|
+
async setModel(modelId) {
|
|
1305
|
+
const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
|
|
1306
|
+
const query = await this.ensureQuery();
|
|
1307
|
+
await query.setModel(normalizedModelId ?? undefined);
|
|
1308
|
+
this.config.model = normalizedModelId ?? undefined;
|
|
1309
|
+
this.lastOptionsModel = normalizedModelId ?? this.lastOptionsModel;
|
|
1310
|
+
this.lastRuntimeModel = null;
|
|
1311
|
+
this.cachedRuntimeInfo = null;
|
|
1312
|
+
// Model change affects persistence metadata, so invalidate cached handle.
|
|
1313
|
+
this.persistence = null;
|
|
1314
|
+
}
|
|
1315
|
+
async setThinkingOption(thinkingOptionId) {
|
|
1316
|
+
const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
|
|
1317
|
+
? thinkingOptionId
|
|
1318
|
+
: null;
|
|
1319
|
+
if (!normalizedThinkingOptionId || normalizedThinkingOptionId === "default") {
|
|
1320
|
+
this.config.thinkingOptionId = undefined;
|
|
1321
|
+
}
|
|
1322
|
+
else if (isClaudeThinkingEffort(normalizedThinkingOptionId)) {
|
|
1323
|
+
this.config.thinkingOptionId = normalizedThinkingOptionId;
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
throw new Error(`Unknown thinking option: ${normalizedThinkingOptionId}`);
|
|
1327
|
+
}
|
|
1328
|
+
this.queryRestartNeeded = true;
|
|
1329
|
+
}
|
|
1330
|
+
getPendingPermissions() {
|
|
1331
|
+
return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
|
|
1332
|
+
}
|
|
1333
|
+
async respondToPermission(requestId, response) {
|
|
1334
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
1335
|
+
if (!pending) {
|
|
1336
|
+
throw new Error(`No pending permission request with id '${requestId}'`);
|
|
1337
|
+
}
|
|
1338
|
+
this.pendingPermissions.delete(requestId);
|
|
1339
|
+
pending.cleanup?.();
|
|
1340
|
+
if (response.behavior === "allow") {
|
|
1341
|
+
if (pending.request.kind === "plan") {
|
|
1342
|
+
const selectedActionId = response.selectedActionId;
|
|
1343
|
+
const shouldResumePriorMode = selectedActionId === "implement_resume" && this.planResumeMode === "bypassPermissions";
|
|
1344
|
+
const targetMode = shouldResumePriorMode
|
|
1345
|
+
? "bypassPermissions"
|
|
1346
|
+
: "acceptEdits";
|
|
1347
|
+
await this.setMode(targetMode);
|
|
1348
|
+
this.pushToolCall(mapClaudeCompletedToolCall({
|
|
1349
|
+
name: "plan_approval",
|
|
1350
|
+
callId: pending.request.id,
|
|
1351
|
+
input: pending.request.input ?? null,
|
|
1352
|
+
output: {
|
|
1353
|
+
approved: true,
|
|
1354
|
+
actionId: selectedActionId ?? "implement",
|
|
1355
|
+
},
|
|
1356
|
+
}));
|
|
1357
|
+
}
|
|
1358
|
+
const result = {
|
|
1359
|
+
behavior: "allow",
|
|
1360
|
+
updatedInput: response.updatedInput ?? pending.request.input ?? {},
|
|
1361
|
+
updatedPermissions: this.normalizePermissionUpdates(response.updatedPermissions),
|
|
1362
|
+
};
|
|
1363
|
+
pending.resolve(result);
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
if (pending.request.kind === "tool") {
|
|
1367
|
+
this.pushToolCall(mapClaudeFailedToolCall({
|
|
1368
|
+
name: pending.request.name,
|
|
1369
|
+
callId: (typeof pending.request.metadata?.toolUseId === "string"
|
|
1370
|
+
? pending.request.metadata.toolUseId
|
|
1371
|
+
: null) ?? pending.request.id,
|
|
1372
|
+
input: pending.request.input ?? null,
|
|
1373
|
+
output: null,
|
|
1374
|
+
error: { message: response.message ?? "Permission denied" },
|
|
1375
|
+
}));
|
|
1376
|
+
}
|
|
1377
|
+
const result = {
|
|
1378
|
+
behavior: "deny",
|
|
1379
|
+
message: response.message ?? "Permission request denied",
|
|
1380
|
+
interrupt: response.interrupt,
|
|
1381
|
+
};
|
|
1382
|
+
pending.resolve(result);
|
|
1383
|
+
}
|
|
1384
|
+
this.pushEvent({
|
|
1385
|
+
type: "permission_resolved",
|
|
1386
|
+
provider: "claude",
|
|
1387
|
+
requestId,
|
|
1388
|
+
resolution: response,
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
describePersistence() {
|
|
1392
|
+
if (this.persistence) {
|
|
1393
|
+
return this.persistence;
|
|
1394
|
+
}
|
|
1395
|
+
if (!this.claudeSessionId) {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
this.persistence = {
|
|
1399
|
+
provider: "claude",
|
|
1400
|
+
sessionId: this.claudeSessionId,
|
|
1401
|
+
nativeHandle: this.claudeSessionId,
|
|
1402
|
+
metadata: { ...this.config },
|
|
1403
|
+
};
|
|
1404
|
+
return this.persistence;
|
|
1405
|
+
}
|
|
1406
|
+
async close() {
|
|
1407
|
+
this.logger.trace({
|
|
1408
|
+
claudeSessionId: this.claudeSessionId,
|
|
1409
|
+
turnState: this.turnState,
|
|
1410
|
+
hasQuery: Boolean(this.query),
|
|
1411
|
+
hasInput: Boolean(this.input),
|
|
1412
|
+
hasActiveForegroundTurnId: Boolean(this.activeForegroundTurnId),
|
|
1413
|
+
}, "Claude session close: start");
|
|
1414
|
+
this.closed = true;
|
|
1415
|
+
this.rejectAllPendingPermissions(new Error("Claude session closed"));
|
|
1416
|
+
this.cancelCurrentTurn?.();
|
|
1417
|
+
this.subscribers.clear();
|
|
1418
|
+
this.activeForegroundTurnId = null;
|
|
1419
|
+
this.autonomousTurn = null;
|
|
1420
|
+
this.cancelCurrentTurn = null;
|
|
1421
|
+
this.turnState = "idle";
|
|
1422
|
+
this.sidechainTracker.clear();
|
|
1423
|
+
this.input?.end();
|
|
1424
|
+
this.query?.close?.();
|
|
1425
|
+
await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
|
|
1426
|
+
await this.awaitWithTimeout(this.query?.return?.(), "close query return");
|
|
1427
|
+
this.query = null;
|
|
1428
|
+
this.input = null;
|
|
1429
|
+
this.logger.trace({ claudeSessionId: this.claudeSessionId, turnState: this.turnState }, "Claude session close: completed");
|
|
1430
|
+
}
|
|
1431
|
+
async listCommands() {
|
|
1432
|
+
const q = await this.ensureQuery();
|
|
1433
|
+
const commands = await q.supportedCommands();
|
|
1434
|
+
const commandMap = new Map();
|
|
1435
|
+
for (const cmd of commands) {
|
|
1436
|
+
if (!commandMap.has(cmd.name)) {
|
|
1437
|
+
commandMap.set(cmd.name, {
|
|
1438
|
+
name: cmd.name,
|
|
1439
|
+
description: cmd.description,
|
|
1440
|
+
argumentHint: cmd.argumentHint,
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (!commandMap.has(REWIND_COMMAND_NAME)) {
|
|
1445
|
+
commandMap.set(REWIND_COMMAND_NAME, REWIND_COMMAND);
|
|
1446
|
+
}
|
|
1447
|
+
return Array.from(commandMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
1448
|
+
}
|
|
1449
|
+
resolveSlashCommandInvocation(prompt) {
|
|
1450
|
+
if (typeof prompt !== "string") {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
const parsed = this.parseSlashCommandInput(prompt);
|
|
1454
|
+
if (!parsed) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
return parsed.commandName === REWIND_COMMAND_NAME ? parsed : null;
|
|
1458
|
+
}
|
|
1459
|
+
parseSlashCommandInput(text) {
|
|
1460
|
+
const trimmed = text.trim();
|
|
1461
|
+
if (!trimmed.startsWith("/") || trimmed.length <= 1) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
const withoutPrefix = trimmed.slice(1);
|
|
1465
|
+
const firstWhitespaceIdx = withoutPrefix.search(/\s/);
|
|
1466
|
+
const commandName = firstWhitespaceIdx === -1 ? withoutPrefix : withoutPrefix.slice(0, firstWhitespaceIdx);
|
|
1467
|
+
if (!commandName || commandName.includes("/")) {
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
const rawArgs = firstWhitespaceIdx === -1 ? "" : withoutPrefix.slice(firstWhitespaceIdx + 1).trim();
|
|
1471
|
+
return rawArgs.length > 0
|
|
1472
|
+
? { commandName, args: rawArgs, rawInput: trimmed }
|
|
1473
|
+
: { commandName, rawInput: trimmed };
|
|
1474
|
+
}
|
|
1475
|
+
buildRewindSuccessMessage(targetUserMessageId, rewindResult) {
|
|
1476
|
+
const fileCount = Array.isArray(rewindResult.filesChanged)
|
|
1477
|
+
? rewindResult.filesChanged.length
|
|
1478
|
+
: undefined;
|
|
1479
|
+
const stats = [];
|
|
1480
|
+
if (typeof fileCount === "number") {
|
|
1481
|
+
stats.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
|
|
1482
|
+
}
|
|
1483
|
+
if (typeof rewindResult.insertions === "number") {
|
|
1484
|
+
stats.push(`${rewindResult.insertions} insertions`);
|
|
1485
|
+
}
|
|
1486
|
+
if (typeof rewindResult.deletions === "number") {
|
|
1487
|
+
stats.push(`${rewindResult.deletions} deletions`);
|
|
1488
|
+
}
|
|
1489
|
+
if (stats.length > 0) {
|
|
1490
|
+
return `Rewound tracked files to message ${targetUserMessageId} (${stats.join(", ")}).`;
|
|
1491
|
+
}
|
|
1492
|
+
return `Rewound tracked files to message ${targetUserMessageId}.`;
|
|
1493
|
+
}
|
|
1494
|
+
async attemptRewind(args) {
|
|
1495
|
+
if (typeof args === "string" && args.trim().length > 0) {
|
|
1496
|
+
const candidate = args.trim().split(/\s+/)[0] ?? "";
|
|
1497
|
+
if (!UUID_PATTERN.test(candidate)) {
|
|
1498
|
+
return {
|
|
1499
|
+
messageId: null,
|
|
1500
|
+
error: "Invalid message UUID. Usage: /rewind <user_message_uuid> or /rewind",
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
const rewindResult = await this.rewindFilesOnce(candidate);
|
|
1504
|
+
if (rewindResult.canRewind) {
|
|
1505
|
+
return { messageId: candidate, result: rewindResult };
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
messageId: null,
|
|
1509
|
+
error: rewindResult.error ?? `No file checkpoint found for message ${candidate}.`,
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
const candidates = this.getRewindCandidateUserMessageIds();
|
|
1513
|
+
if (candidates.length === 0) {
|
|
1514
|
+
return {
|
|
1515
|
+
messageId: null,
|
|
1516
|
+
error: "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
let lastError;
|
|
1520
|
+
for (const candidate of candidates) {
|
|
1521
|
+
try {
|
|
1522
|
+
const rewindResult = await this.rewindFilesOnce(candidate);
|
|
1523
|
+
if (rewindResult.canRewind) {
|
|
1524
|
+
return { messageId: candidate, result: rewindResult };
|
|
1525
|
+
}
|
|
1526
|
+
if (rewindResult.error) {
|
|
1527
|
+
lastError = rewindResult.error;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
catch (error) {
|
|
1531
|
+
lastError = error instanceof Error ? error.message : "Failed to rewind tracked files.";
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
messageId: null,
|
|
1536
|
+
error: lastError ?? "No rewind checkpoints are currently available for this session.",
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
async rewindFilesOnce(messageId) {
|
|
1540
|
+
try {
|
|
1541
|
+
const query = await this.ensureFreshQuery();
|
|
1542
|
+
return await query.rewindFiles(messageId, { dryRun: false });
|
|
1543
|
+
}
|
|
1544
|
+
catch (error) {
|
|
1545
|
+
// The Claude SDK transport can close after a rewind call.
|
|
1546
|
+
// If that happens, mark the query stale so a follow-up attempt uses a fresh query.
|
|
1547
|
+
this.queryRestartNeeded = true;
|
|
1548
|
+
throw error;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
async ensureFreshQuery() {
|
|
1552
|
+
if (this.query) {
|
|
1553
|
+
this.queryRestartNeeded = true;
|
|
1554
|
+
}
|
|
1555
|
+
return this.ensureQuery();
|
|
1556
|
+
}
|
|
1557
|
+
getRewindCandidateUserMessageIds() {
|
|
1558
|
+
const candidates = [];
|
|
1559
|
+
const pushUnique = (value) => {
|
|
1560
|
+
if (typeof value === "string" && value.length > 0 && !candidates.includes(value)) {
|
|
1561
|
+
candidates.push(value);
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
const historyIds = this.readUserMessageIdsFromHistoryFile();
|
|
1565
|
+
for (let idx = historyIds.length - 1; idx >= 0; idx -= 1) {
|
|
1566
|
+
pushUnique(historyIds[idx]);
|
|
1567
|
+
}
|
|
1568
|
+
for (let idx = this.persistedHistory.length - 1; idx >= 0; idx -= 1) {
|
|
1569
|
+
const item = this.persistedHistory[idx];
|
|
1570
|
+
if (item?.type === "user_message") {
|
|
1571
|
+
pushUnique(item.messageId);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
for (let idx = this.userMessageIds.length - 1; idx >= 0; idx -= 1) {
|
|
1575
|
+
pushUnique(this.userMessageIds[idx]);
|
|
1576
|
+
}
|
|
1577
|
+
return candidates;
|
|
1578
|
+
}
|
|
1579
|
+
readUserMessageIdsFromHistoryFile() {
|
|
1580
|
+
if (!this.claudeSessionId) {
|
|
1581
|
+
return [];
|
|
1582
|
+
}
|
|
1583
|
+
const historyPath = this.resolveHistoryPath(this.claudeSessionId);
|
|
1584
|
+
if (!historyPath || !fs.existsSync(historyPath)) {
|
|
1585
|
+
return [];
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
const ids = [];
|
|
1589
|
+
const content = fs.readFileSync(historyPath, "utf8");
|
|
1590
|
+
for (const line of content.split(/\n+/)) {
|
|
1591
|
+
const trimmed = line.trim();
|
|
1592
|
+
if (!trimmed)
|
|
1593
|
+
continue;
|
|
1594
|
+
try {
|
|
1595
|
+
const entry = JSON.parse(trimmed);
|
|
1596
|
+
if (entry?.type === "user" && typeof entry.uuid === "string") {
|
|
1597
|
+
ids.push(entry.uuid);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
catch {
|
|
1601
|
+
// ignore malformed lines
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return ids;
|
|
1605
|
+
}
|
|
1606
|
+
catch {
|
|
1607
|
+
return [];
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
rememberUserMessageId(messageId) {
|
|
1611
|
+
if (typeof messageId !== "string" || messageId.length === 0) {
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const last = this.userMessageIds[this.userMessageIds.length - 1];
|
|
1615
|
+
if (last === messageId) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
this.userMessageIds.push(messageId);
|
|
1619
|
+
}
|
|
1620
|
+
async ensureQuery() {
|
|
1621
|
+
if (this.query && !this.queryRestartNeeded) {
|
|
1622
|
+
return this.query;
|
|
1623
|
+
}
|
|
1624
|
+
if (this.queryRestartNeeded && this.query) {
|
|
1625
|
+
const oldQuery = this.query;
|
|
1626
|
+
const oldInput = this.input;
|
|
1627
|
+
// Null out query/input BEFORE awaiting the old iterator's return so the
|
|
1628
|
+
// old pump sees this.query !== activeQuery and skips failActiveTurns.
|
|
1629
|
+
this.query = null;
|
|
1630
|
+
this.input = null;
|
|
1631
|
+
this.queryPumpPromise = null;
|
|
1632
|
+
this.queryRestartNeeded = false;
|
|
1633
|
+
// Reset session identity for explicit restarts so the new query starts
|
|
1634
|
+
// a fresh session rather than resuming the previous one.
|
|
1635
|
+
this.claudeSessionId = null;
|
|
1636
|
+
oldInput?.end();
|
|
1637
|
+
oldQuery.close?.();
|
|
1638
|
+
try {
|
|
1639
|
+
await oldQuery.return?.();
|
|
1640
|
+
}
|
|
1641
|
+
catch {
|
|
1642
|
+
/* ignore */
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
// When the pump died unexpectedly (query became null, e.g. after a session
|
|
1646
|
+
// ID overwrite error), preserve claudeSessionId so buildOptions() passes
|
|
1647
|
+
// resume: sessionId and the new query auto-resumes the previous session.
|
|
1648
|
+
// For explicit restarts above, claudeSessionId was already cleared.
|
|
1649
|
+
this.persistence = null;
|
|
1650
|
+
const input = createAsyncMessageInput();
|
|
1651
|
+
const options = await this.buildOptions();
|
|
1652
|
+
this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
|
|
1653
|
+
this.input = input;
|
|
1654
|
+
this.query = this.queryFactory({ prompt: input.iterable, options });
|
|
1655
|
+
// Do not kick off background control-plane queries here. Methods like
|
|
1656
|
+
// supportedCommands()/setPermissionMode() may execute immediately after
|
|
1657
|
+
// ensureQuery() (for listCommands()/setMode()), and sharing the same query
|
|
1658
|
+
// control plane can cause those calls to wait behind supportedModels().
|
|
1659
|
+
return this.query;
|
|
1660
|
+
}
|
|
1661
|
+
async awaitWithTimeout(promise, label) {
|
|
1662
|
+
if (!promise) {
|
|
1663
|
+
this.logger.trace({ label }, "Claude query operation skipped (no promise)");
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const startedAt = Date.now();
|
|
1667
|
+
this.logger.trace({ label }, "Claude query operation wait start");
|
|
1668
|
+
try {
|
|
1669
|
+
await Promise.race([
|
|
1670
|
+
promise,
|
|
1671
|
+
new Promise((_, reject) => {
|
|
1672
|
+
setTimeout(() => reject(new Error("timeout")), 3000);
|
|
1673
|
+
}),
|
|
1674
|
+
]);
|
|
1675
|
+
this.logger.trace({ label, durationMs: Date.now() - startedAt }, "Claude query operation settled");
|
|
1676
|
+
}
|
|
1677
|
+
catch (error) {
|
|
1678
|
+
this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
async buildOptions() {
|
|
1682
|
+
const thinkingOptionId = this.config.thinkingOptionId && this.config.thinkingOptionId !== "default"
|
|
1683
|
+
? this.config.thinkingOptionId
|
|
1684
|
+
: undefined;
|
|
1685
|
+
let thinking;
|
|
1686
|
+
let effort;
|
|
1687
|
+
if (thinkingOptionId && isClaudeThinkingEffort(thinkingOptionId)) {
|
|
1688
|
+
thinking = { type: "adaptive" };
|
|
1689
|
+
effort = thinkingOptionId;
|
|
1690
|
+
}
|
|
1691
|
+
const appendedSystemPrompt = [
|
|
1692
|
+
getOrchestratorModeInstructions(),
|
|
1693
|
+
this.config.systemPrompt?.trim(),
|
|
1694
|
+
]
|
|
1695
|
+
.filter((entry) => typeof entry === "string" && entry.length > 0)
|
|
1696
|
+
.join("\n\n");
|
|
1697
|
+
const claudeBinary = await findExecutable("claude");
|
|
1698
|
+
this.logger.debug({
|
|
1699
|
+
claudeBinary,
|
|
1700
|
+
pathEnvKey: process.env["Path"] !== undefined
|
|
1701
|
+
? "Path"
|
|
1702
|
+
: process.env["PATH"] !== undefined
|
|
1703
|
+
? "PATH"
|
|
1704
|
+
: null,
|
|
1705
|
+
pathIncludesClaudeLocalBin: (process.env["Path"] ?? process.env["PATH"] ?? "")
|
|
1706
|
+
.toLowerCase()
|
|
1707
|
+
.includes("\\.local\\bin"),
|
|
1708
|
+
}, "Resolved Claude executable");
|
|
1709
|
+
const base = {
|
|
1710
|
+
cwd: this.config.cwd,
|
|
1711
|
+
includePartialMessages: true,
|
|
1712
|
+
permissionMode: this.currentMode,
|
|
1713
|
+
// Dynamic mode switching can recreate the underlying Claude query. Keep the
|
|
1714
|
+
// bypass launch capability available so later setPermissionMode("bypassPermissions")
|
|
1715
|
+
// calls do not fail after a model/thinking/rewind-driven restart.
|
|
1716
|
+
allowDangerouslySkipPermissions: true,
|
|
1717
|
+
agents: this.defaults?.agents,
|
|
1718
|
+
canUseTool: this.handlePermissionRequest,
|
|
1719
|
+
...(claudeBinary ? { pathToClaudeCodeExecutable: claudeBinary } : {}),
|
|
1720
|
+
// Use Claude Code preset system prompt and load CLAUDE.md files
|
|
1721
|
+
// Append provider-agnostic system prompt and orchestrator instructions for agents.
|
|
1722
|
+
systemPrompt: {
|
|
1723
|
+
type: "preset",
|
|
1724
|
+
preset: "claude_code",
|
|
1725
|
+
append: appendedSystemPrompt,
|
|
1726
|
+
},
|
|
1727
|
+
settingSources: CLAUDE_SETTING_SOURCES,
|
|
1728
|
+
stderr: (data) => {
|
|
1729
|
+
this.captureStderr(data);
|
|
1730
|
+
this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
|
|
1731
|
+
},
|
|
1732
|
+
env: {
|
|
1733
|
+
...process.env,
|
|
1734
|
+
// Increase MCP timeouts for long-running tool calls (10 minutes)
|
|
1735
|
+
MCP_TIMEOUT: "600000",
|
|
1736
|
+
MCP_TOOL_TIMEOUT: "600000",
|
|
1737
|
+
...(this.launchEnv ?? {}),
|
|
1738
|
+
},
|
|
1739
|
+
// Required for provider-level /rewind support.
|
|
1740
|
+
enableFileCheckpointing: true,
|
|
1741
|
+
// If we have a session ID from a previous query (e.g., after interrupt),
|
|
1742
|
+
// resume that session to continue the conversation history.
|
|
1743
|
+
...(this.claudeSessionId ? { resume: this.claudeSessionId } : {}),
|
|
1744
|
+
...(thinking ? { thinking } : {}),
|
|
1745
|
+
...(effort ? { effort } : {}),
|
|
1746
|
+
...this.config.extra?.claude,
|
|
1747
|
+
};
|
|
1748
|
+
if (this.config.mcpServers) {
|
|
1749
|
+
base.mcpServers = this.normalizeMcpServers(this.config.mcpServers);
|
|
1750
|
+
}
|
|
1751
|
+
if (this.config.model) {
|
|
1752
|
+
base.model = this.config.model;
|
|
1753
|
+
}
|
|
1754
|
+
this.lastOptionsModel = base.model ?? null;
|
|
1755
|
+
if (this.claudeSessionId) {
|
|
1756
|
+
base.resume = this.claudeSessionId;
|
|
1757
|
+
}
|
|
1758
|
+
return this.applyRuntimeSettings(base);
|
|
1759
|
+
}
|
|
1760
|
+
applyRuntimeSettings(options) {
|
|
1761
|
+
return applyRuntimeSettingsToClaudeOptions(options, this.runtimeSettings, this.launchEnv);
|
|
1762
|
+
}
|
|
1763
|
+
normalizeMcpServers(servers) {
|
|
1764
|
+
const result = {};
|
|
1765
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
1766
|
+
result[name] = toClaudeSdkMcpConfig(config);
|
|
1767
|
+
}
|
|
1768
|
+
return result;
|
|
1769
|
+
}
|
|
1770
|
+
toSdkUserMessage(prompt) {
|
|
1771
|
+
const content = [];
|
|
1772
|
+
if (Array.isArray(prompt)) {
|
|
1773
|
+
for (const chunk of prompt) {
|
|
1774
|
+
if (chunk.type === "text") {
|
|
1775
|
+
content.push({ type: "text", text: chunk.text });
|
|
1776
|
+
}
|
|
1777
|
+
else if (chunk.type === "image") {
|
|
1778
|
+
content.push({
|
|
1779
|
+
type: "image",
|
|
1780
|
+
source: {
|
|
1781
|
+
type: "base64",
|
|
1782
|
+
media_type: chunk.mimeType,
|
|
1783
|
+
data: chunk.data,
|
|
1784
|
+
},
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
content.push({ type: "text", text: prompt });
|
|
1791
|
+
}
|
|
1792
|
+
const messageId = randomUUID();
|
|
1793
|
+
this.rememberUserMessageId(messageId);
|
|
1794
|
+
return {
|
|
1795
|
+
type: "user",
|
|
1796
|
+
message: {
|
|
1797
|
+
role: "user",
|
|
1798
|
+
content,
|
|
1799
|
+
},
|
|
1800
|
+
parent_tool_use_id: null,
|
|
1801
|
+
uuid: messageId,
|
|
1802
|
+
session_id: this.claudeSessionId ?? "",
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
transitionTurnState(next, reason) {
|
|
1806
|
+
if (this.turnState === next) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
|
|
1810
|
+
this.turnState = next;
|
|
1811
|
+
}
|
|
1812
|
+
syncTurnState(reason) {
|
|
1813
|
+
if (this.activeForegroundTurnId) {
|
|
1814
|
+
this.transitionTurnState("foreground", reason);
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (this.autonomousTurn) {
|
|
1818
|
+
this.transitionTurnState("autonomous", reason);
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
this.transitionTurnState("idle", reason);
|
|
1822
|
+
}
|
|
1823
|
+
isAbortError(message) {
|
|
1824
|
+
const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
|
|
1825
|
+
return errors.some((e) => /\baborted\b/i.test(e));
|
|
1826
|
+
}
|
|
1827
|
+
buildTurnFailedEvent(errorMessage) {
|
|
1828
|
+
const normalized = errorMessage.trim() || "Claude run failed";
|
|
1829
|
+
const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
|
|
1830
|
+
const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
|
|
1831
|
+
const diagnostic = this.getRecentStderrDiagnostic();
|
|
1832
|
+
return {
|
|
1833
|
+
type: "turn_failed",
|
|
1834
|
+
provider: "claude",
|
|
1835
|
+
error: normalized,
|
|
1836
|
+
...(code ? { code } : {}),
|
|
1837
|
+
...(diagnostic ? { diagnostic } : {}),
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
captureStderr(data) {
|
|
1841
|
+
const text = data.trim();
|
|
1842
|
+
if (!text) {
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
|
|
1846
|
+
this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
|
|
1847
|
+
}
|
|
1848
|
+
clearRecentStderr() {
|
|
1849
|
+
this.recentStderr = "";
|
|
1850
|
+
}
|
|
1851
|
+
getRecentStderrDiagnostic() {
|
|
1852
|
+
return this.recentStderr.trim() || undefined;
|
|
1853
|
+
}
|
|
1854
|
+
async awaitRecentStderrAfterProcessExit(error) {
|
|
1855
|
+
if (this.getRecentStderrDiagnostic()) {
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
const message = typeof error === "string" ? error : error instanceof Error ? error.message : "";
|
|
1859
|
+
if (!/\bprocess exited with code\b/i.test(message) &&
|
|
1860
|
+
!/\bterminated by signal\b/i.test(message)) {
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
const startedAt = Date.now();
|
|
1864
|
+
while (!this.closed && !this.getRecentStderrDiagnostic()) {
|
|
1865
|
+
if (Date.now() - startedAt >= STDERR_FLUSH_WAIT_MS) {
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
await new Promise((resolve) => setTimeout(resolve, STDERR_FLUSH_POLL_INTERVAL_MS));
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
createTurnId(owner) {
|
|
1872
|
+
return `${owner}-turn-${this.nextTurnOrdinal++}`;
|
|
1873
|
+
}
|
|
1874
|
+
isTerminalTurnEvent(event) {
|
|
1875
|
+
return (event.type === "turn_completed" ||
|
|
1876
|
+
event.type === "turn_failed" ||
|
|
1877
|
+
event.type === "turn_canceled");
|
|
1878
|
+
}
|
|
1879
|
+
extractPromptText(prompt) {
|
|
1880
|
+
if (typeof prompt === "string") {
|
|
1881
|
+
return prompt;
|
|
1882
|
+
}
|
|
1883
|
+
const textParts = prompt
|
|
1884
|
+
.filter((block) => block.type === "text")
|
|
1885
|
+
.map((block) => block.text);
|
|
1886
|
+
return textParts.length > 0 ? textParts.join("\n") : null;
|
|
1887
|
+
}
|
|
1888
|
+
async executeRewindTurn(_turnId, invocation) {
|
|
1889
|
+
this.notifySubscribers({ type: "turn_started", provider: "claude" });
|
|
1890
|
+
try {
|
|
1891
|
+
const rewindAttempt = await this.attemptRewind(invocation.args);
|
|
1892
|
+
if (!rewindAttempt.messageId || !rewindAttempt.result) {
|
|
1893
|
+
this.finishForegroundTurn({
|
|
1894
|
+
type: "turn_failed",
|
|
1895
|
+
provider: "claude",
|
|
1896
|
+
error: rewindAttempt.error ??
|
|
1897
|
+
"No prior user message available to rewind. Use /rewind <user_message_uuid>.",
|
|
1898
|
+
});
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
this.notifySubscribers({
|
|
1902
|
+
type: "timeline",
|
|
1903
|
+
provider: "claude",
|
|
1904
|
+
item: {
|
|
1905
|
+
type: "assistant_message",
|
|
1906
|
+
text: this.buildRewindSuccessMessage(rewindAttempt.messageId, rewindAttempt.result),
|
|
1907
|
+
},
|
|
1908
|
+
});
|
|
1909
|
+
this.finishForegroundTurn({ type: "turn_completed", provider: "claude" });
|
|
1910
|
+
}
|
|
1911
|
+
catch (error) {
|
|
1912
|
+
this.finishForegroundTurn({
|
|
1913
|
+
type: "turn_failed",
|
|
1914
|
+
provider: "claude",
|
|
1915
|
+
error: error instanceof Error ? error.message : "Failed to rewind tracked files",
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
shouldRecoverInterruptedQueryAbort(error, consecutiveRecoveries) {
|
|
1920
|
+
if (consecutiveRecoveries >= 3) {
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
const message = typeof error === "string"
|
|
1924
|
+
? error
|
|
1925
|
+
: error instanceof Error
|
|
1926
|
+
? `${error.message}\n${error.stack ?? ""}`
|
|
1927
|
+
: JSON.stringify(error);
|
|
1928
|
+
return message.toLowerCase().includes("request was aborted");
|
|
1929
|
+
}
|
|
1930
|
+
finishForegroundTurn(event) {
|
|
1931
|
+
if (event.type === "turn_failed" || event.type === "turn_canceled") {
|
|
1932
|
+
this.flushPendingToolCalls();
|
|
1933
|
+
}
|
|
1934
|
+
this.notifySubscribers(event);
|
|
1935
|
+
this.activeForegroundTurnId = null;
|
|
1936
|
+
this.lastForegroundPromptText = null;
|
|
1937
|
+
this.cancelCurrentTurn = null;
|
|
1938
|
+
this.syncTurnState("foreground turn terminal");
|
|
1939
|
+
}
|
|
1940
|
+
dispatchEvents(events) {
|
|
1941
|
+
let terminalSeen = false;
|
|
1942
|
+
for (const event of events) {
|
|
1943
|
+
this.notifySubscribers(event);
|
|
1944
|
+
terminalSeen || (terminalSeen = this.isTerminalTurnEvent(event));
|
|
1945
|
+
}
|
|
1946
|
+
if (terminalSeen) {
|
|
1947
|
+
if (this.activeForegroundTurnId) {
|
|
1948
|
+
this.activeForegroundTurnId = null;
|
|
1949
|
+
this.lastForegroundPromptText = null;
|
|
1950
|
+
this.cancelCurrentTurn = null;
|
|
1951
|
+
this.syncTurnState("foreground turn terminal");
|
|
1952
|
+
}
|
|
1953
|
+
else if (this.autonomousTurn) {
|
|
1954
|
+
this.autonomousTurn = null;
|
|
1955
|
+
this.syncTurnState("autonomous turn terminal");
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
startAutonomousTurn() {
|
|
1960
|
+
if (this.autonomousTurn) {
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
this.autonomousTurn = {
|
|
1964
|
+
id: this.createTurnId("autonomous"),
|
|
1965
|
+
};
|
|
1966
|
+
this.notifySubscribers({ type: "turn_started", provider: "claude" });
|
|
1967
|
+
this.syncTurnState("autonomous turn started");
|
|
1968
|
+
}
|
|
1969
|
+
completeAutonomousTurn() {
|
|
1970
|
+
if (!this.autonomousTurn) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
this.notifySubscribers({ type: "turn_completed", provider: "claude" });
|
|
1974
|
+
this.autonomousTurn = null;
|
|
1975
|
+
this.syncTurnState("autonomous turn completed");
|
|
1976
|
+
}
|
|
1977
|
+
failActiveTurns(errorMessage) {
|
|
1978
|
+
const failure = this.buildTurnFailedEvent(errorMessage);
|
|
1979
|
+
this.flushPendingToolCalls();
|
|
1980
|
+
if (this.activeForegroundTurnId) {
|
|
1981
|
+
this.finishForegroundTurn(failure);
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
if (this.autonomousTurn) {
|
|
1985
|
+
this.dispatchEvents([failure]);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
startQueryPump() {
|
|
1989
|
+
if (this.closed || this.queryPumpPromise) {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
const pump = this.runQueryPump().catch((error) => {
|
|
1993
|
+
this.logger.trace({ err: error }, "Claude query pump exited unexpectedly");
|
|
1994
|
+
});
|
|
1995
|
+
this.queryPumpPromise = pump;
|
|
1996
|
+
pump.finally(() => {
|
|
1997
|
+
if (this.queryPumpPromise === pump) {
|
|
1998
|
+
this.queryPumpPromise = null;
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
async runQueryPump() {
|
|
2003
|
+
let activeQuery;
|
|
2004
|
+
try {
|
|
2005
|
+
activeQuery = await this.ensureQuery();
|
|
2006
|
+
}
|
|
2007
|
+
catch (error) {
|
|
2008
|
+
this.logger.trace({ err: error }, "Failed to initialize Claude query pump");
|
|
2009
|
+
this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
let consecutiveInterruptAbortRecoveries = 0;
|
|
2013
|
+
try {
|
|
2014
|
+
while (!this.closed && this.query === activeQuery) {
|
|
2015
|
+
try {
|
|
2016
|
+
for await (const message of activeQuery) {
|
|
2017
|
+
if (claudeDebug) {
|
|
2018
|
+
this.logger.trace({
|
|
2019
|
+
claudeSessionId: this.claudeSessionId,
|
|
2020
|
+
messageType: message.type,
|
|
2021
|
+
messageSubtype: "subtype" in message ? message.subtype : undefined,
|
|
2022
|
+
messageUuid: "uuid" in message ? message.uuid : undefined,
|
|
2023
|
+
}, "Claude query pump: raw SDK message");
|
|
2024
|
+
}
|
|
2025
|
+
consecutiveInterruptAbortRecoveries = 0;
|
|
2026
|
+
if (await this.handleMissingResumedConversation(message, activeQuery)) {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
this.routeSdkMessageFromPump(message);
|
|
2030
|
+
}
|
|
2031
|
+
if (!this.closed && this.query === activeQuery) {
|
|
2032
|
+
this.failActiveTurns("Claude stream ended before terminal result");
|
|
2033
|
+
}
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
catch (error) {
|
|
2037
|
+
if (!this.closed &&
|
|
2038
|
+
this.query === activeQuery &&
|
|
2039
|
+
this.shouldRecoverInterruptedQueryAbort(error, consecutiveInterruptAbortRecoveries)) {
|
|
2040
|
+
consecutiveInterruptAbortRecoveries += 1;
|
|
2041
|
+
this.logger.debug({ recoveries: consecutiveInterruptAbortRecoveries }, "Recovering Claude query pump after interrupt abort");
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (!this.closed && this.query === activeQuery) {
|
|
2045
|
+
await this.awaitRecentStderrAfterProcessExit(error);
|
|
2046
|
+
this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
|
|
2047
|
+
}
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
finally {
|
|
2053
|
+
if (this.query === activeQuery) {
|
|
2054
|
+
this.query = null;
|
|
2055
|
+
this.input = null;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
routeSdkMessageFromPump(message) {
|
|
2060
|
+
// Suppress stale results from interrupted requests. The cancel path already
|
|
2061
|
+
// emitted the terminal event; this result is leftover from the killed API
|
|
2062
|
+
// request. Consume the flag on ANY result so it doesn't linger.
|
|
2063
|
+
if (message.type === "result" && this.pendingInterruptAbort) {
|
|
2064
|
+
this.pendingInterruptAbort = false;
|
|
2065
|
+
if (message.subtype !== "success") {
|
|
2066
|
+
this.logger.debug("Suppressing stale non-success result from interrupted request");
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
if (message.type === "result" && message.subtype !== "success" && this.isAbortError(message)) {
|
|
2071
|
+
this.logger.debug("Suppressing abort result by content");
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const isForeground = Boolean(this.activeForegroundTurnId);
|
|
2075
|
+
const assistantishMessage = message.type === "assistant" ||
|
|
2076
|
+
message.type === "stream_event" ||
|
|
2077
|
+
message.type === "tool_progress" ||
|
|
2078
|
+
(message.type === "system" && message.subtype === "task_notification");
|
|
2079
|
+
if (!isForeground && assistantishMessage) {
|
|
2080
|
+
this.startAutonomousTurn();
|
|
2081
|
+
}
|
|
2082
|
+
if (!isForeground && !this.autonomousTurn && message.type === "result") {
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? null;
|
|
2086
|
+
const identifiers = readEventIdentifiers(message);
|
|
2087
|
+
if (claudeDebug) {
|
|
2088
|
+
this.logger.trace({
|
|
2089
|
+
claudeSessionId: this.claudeSessionId,
|
|
2090
|
+
messageType: message.type,
|
|
2091
|
+
turnId,
|
|
2092
|
+
}, "Claude query pump: SDK message");
|
|
2093
|
+
}
|
|
2094
|
+
const messageEvents = this.translateMessageToEvents(message, {
|
|
2095
|
+
suppressAssistantText: true,
|
|
2096
|
+
suppressReasoning: true,
|
|
2097
|
+
});
|
|
2098
|
+
const assistantTimelineEvents = this.timelineAssembler
|
|
2099
|
+
.consume({
|
|
2100
|
+
message,
|
|
2101
|
+
runId: turnId,
|
|
2102
|
+
messageIdHint: identifiers.messageId,
|
|
2103
|
+
})
|
|
2104
|
+
.map((item) => ({
|
|
2105
|
+
type: "timeline",
|
|
2106
|
+
item,
|
|
2107
|
+
provider: "claude",
|
|
2108
|
+
}));
|
|
2109
|
+
// User message dedup: suppress echoed user messages that match the foreground prompt
|
|
2110
|
+
const filteredMessageEvents = messageEvents.filter((event) => {
|
|
2111
|
+
if (event.type === "timeline" &&
|
|
2112
|
+
event.item.type === "user_message" &&
|
|
2113
|
+
this.activeForegroundTurnId &&
|
|
2114
|
+
this.lastForegroundPromptText) {
|
|
2115
|
+
if (event.item.text.trim() === this.lastForegroundPromptText.trim()) {
|
|
2116
|
+
return false;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return true;
|
|
2120
|
+
});
|
|
2121
|
+
const events = [...filteredMessageEvents, ...assistantTimelineEvents];
|
|
2122
|
+
if (events.length === 0) {
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
if (this.pendingInterruptAbort &&
|
|
2126
|
+
message.type === "result" &&
|
|
2127
|
+
events.some((event) => event.type === "turn_completed" || event.type === "turn_failed") &&
|
|
2128
|
+
(!this.activeForegroundTurnId || !this.foregroundHasVisibleActivity)) {
|
|
2129
|
+
this.pendingInterruptAbort = false;
|
|
2130
|
+
this.logger.debug("Suppressing stale Claude interrupt terminal result");
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (this.activeForegroundTurnId &&
|
|
2134
|
+
events.some((event) => event.type === "timeline" ||
|
|
2135
|
+
event.type === "permission_requested" ||
|
|
2136
|
+
event.type === "permission_resolved")) {
|
|
2137
|
+
this.foregroundHasVisibleActivity = true;
|
|
2138
|
+
}
|
|
2139
|
+
this.dispatchEvents(events);
|
|
2140
|
+
}
|
|
2141
|
+
async handleMissingResumedConversation(message, query) {
|
|
2142
|
+
const staleResumeError = this.readMissingResumedConversationError(message);
|
|
2143
|
+
if (!staleResumeError) {
|
|
2144
|
+
return false;
|
|
2145
|
+
}
|
|
2146
|
+
this.logger.warn({
|
|
2147
|
+
claudeSessionId: this.claudeSessionId,
|
|
2148
|
+
error: staleResumeError,
|
|
2149
|
+
}, "Claude resumed session no longer exists; invalidating persisted session");
|
|
2150
|
+
this.failActiveTurns(staleResumeError);
|
|
2151
|
+
this.input?.end();
|
|
2152
|
+
await this.awaitWithTimeout(query.return?.(), "query pump return on missing resumed conversation");
|
|
2153
|
+
if (this.query === query) {
|
|
2154
|
+
this.query = null;
|
|
2155
|
+
this.input = null;
|
|
2156
|
+
}
|
|
2157
|
+
this.claudeSessionId = null;
|
|
2158
|
+
this.persistence = null;
|
|
2159
|
+
this.persistedHistory = [];
|
|
2160
|
+
this.historyPending = false;
|
|
2161
|
+
this.cachedRuntimeInfo = null;
|
|
2162
|
+
this.queryRestartNeeded = false;
|
|
2163
|
+
this.autonomousTurn = null;
|
|
2164
|
+
this.activeForegroundTurnId = null;
|
|
2165
|
+
this.syncTurnState("missing resumed conversation");
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
2168
|
+
async interruptActiveTurn() {
|
|
2169
|
+
const queryToInterrupt = this.query;
|
|
2170
|
+
if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
|
|
2171
|
+
this.logger.trace("interruptActiveTurn: no query to interrupt");
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
this.pendingInterruptAbort = true;
|
|
2175
|
+
try {
|
|
2176
|
+
await this.awaitWithTimeout(queryToInterrupt.interrupt(), "interruptActiveTurn query.interrupt()");
|
|
2177
|
+
}
|
|
2178
|
+
catch (error) {
|
|
2179
|
+
this.logger.warn({ err: error }, "Failed to interrupt active turn");
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
translateMessageToEvents(message, options) {
|
|
2183
|
+
const parentToolUseId = "parent_tool_use_id" in message
|
|
2184
|
+
? message.parent_tool_use_id
|
|
2185
|
+
: null;
|
|
2186
|
+
if (parentToolUseId) {
|
|
2187
|
+
return this.sidechainTracker.handleMessage(message, parentToolUseId);
|
|
2188
|
+
}
|
|
2189
|
+
const events = [];
|
|
2190
|
+
const fallbackThreadSessionId = this.captureSessionIdFromMessage(message);
|
|
2191
|
+
if (fallbackThreadSessionId) {
|
|
2192
|
+
events.push({
|
|
2193
|
+
type: "thread_started",
|
|
2194
|
+
provider: "claude",
|
|
2195
|
+
sessionId: fallbackThreadSessionId,
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
switch (message.type) {
|
|
2199
|
+
case "system":
|
|
2200
|
+
if (message.subtype === "init") {
|
|
2201
|
+
const threadSessionId = this.handleSystemMessage(message);
|
|
2202
|
+
if (threadSessionId) {
|
|
2203
|
+
events.push({
|
|
2204
|
+
type: "thread_started",
|
|
2205
|
+
provider: "claude",
|
|
2206
|
+
sessionId: threadSessionId,
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
else if (message.subtype === "status") {
|
|
2211
|
+
const status = message.status;
|
|
2212
|
+
if (status === "compacting") {
|
|
2213
|
+
this.compacting = true;
|
|
2214
|
+
events.push({
|
|
2215
|
+
type: "timeline",
|
|
2216
|
+
item: { type: "compaction", status: "loading" },
|
|
2217
|
+
provider: "claude",
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
else if (message.subtype === "compact_boundary") {
|
|
2222
|
+
const compactMetadata = readCompactionMetadata(message);
|
|
2223
|
+
events.push({
|
|
2224
|
+
type: "timeline",
|
|
2225
|
+
item: {
|
|
2226
|
+
type: "compaction",
|
|
2227
|
+
status: "completed",
|
|
2228
|
+
trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
|
|
2229
|
+
preTokens: compactMetadata?.preTokens,
|
|
2230
|
+
},
|
|
2231
|
+
provider: "claude",
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
else if (message.subtype === "task_notification") {
|
|
2235
|
+
const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
|
|
2236
|
+
if (taskNotificationItem) {
|
|
2237
|
+
events.push({
|
|
2238
|
+
type: "timeline",
|
|
2239
|
+
item: taskNotificationItem,
|
|
2240
|
+
provider: "claude",
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
const usage = readUsageFromTaskNotification(message);
|
|
2244
|
+
if (typeof usage === "number") {
|
|
2245
|
+
this.lastContextWindowUsedTokens = usage;
|
|
2246
|
+
events.push(this.createUsageUpdatedEvent(usage));
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
else if (message.subtype === "task_progress") {
|
|
2250
|
+
this.lastContextWindowUsedTokens =
|
|
2251
|
+
readContextWindowUsedTokensFromTaskProgress(message) ??
|
|
2252
|
+
this.lastContextWindowUsedTokens;
|
|
2253
|
+
if (typeof this.lastContextWindowUsedTokens === "number") {
|
|
2254
|
+
events.push(this.createUsageUpdatedEvent(this.lastContextWindowUsedTokens));
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
break;
|
|
2258
|
+
case "user": {
|
|
2259
|
+
if (isSyntheticUserEntry(message)) {
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
if (this.compacting) {
|
|
2263
|
+
this.compacting = false;
|
|
2264
|
+
break;
|
|
2265
|
+
}
|
|
2266
|
+
const messageId = typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
|
|
2267
|
+
this.rememberUserMessageId(messageId);
|
|
2268
|
+
const content = message.message?.content;
|
|
2269
|
+
const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
|
|
2270
|
+
content,
|
|
2271
|
+
messageId,
|
|
2272
|
+
});
|
|
2273
|
+
if (taskNotificationItem) {
|
|
2274
|
+
events.push({
|
|
2275
|
+
type: "timeline",
|
|
2276
|
+
item: taskNotificationItem,
|
|
2277
|
+
provider: "claude",
|
|
2278
|
+
});
|
|
2279
|
+
break;
|
|
2280
|
+
}
|
|
2281
|
+
if (typeof content === "string" && content.length > 0) {
|
|
2282
|
+
if (!isClaudeTranscriptNoiseText(content)) {
|
|
2283
|
+
events.push({
|
|
2284
|
+
type: "timeline",
|
|
2285
|
+
item: {
|
|
2286
|
+
type: "user_message",
|
|
2287
|
+
text: content,
|
|
2288
|
+
...(messageId ? { messageId } : {}),
|
|
2289
|
+
},
|
|
2290
|
+
provider: "claude",
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
else if (Array.isArray(content)) {
|
|
2295
|
+
const timelineItems = this.mapBlocksToTimeline(content, {
|
|
2296
|
+
textMessageType: "user_message",
|
|
2297
|
+
});
|
|
2298
|
+
for (const item of timelineItems) {
|
|
2299
|
+
if (item.type === "user_message" && messageId && !item.messageId) {
|
|
2300
|
+
events.push({
|
|
2301
|
+
type: "timeline",
|
|
2302
|
+
item: { ...item, messageId },
|
|
2303
|
+
provider: "claude",
|
|
2304
|
+
});
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
events.push({ type: "timeline", item, provider: "claude" });
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
case "assistant": {
|
|
2313
|
+
const timelineItems = this.mapBlocksToTimeline(message.message.content, {
|
|
2314
|
+
suppressAssistantText: options?.suppressAssistantText ?? false,
|
|
2315
|
+
suppressReasoning: options?.suppressReasoning ?? false,
|
|
2316
|
+
});
|
|
2317
|
+
for (const item of timelineItems) {
|
|
2318
|
+
events.push({ type: "timeline", item, provider: "claude" });
|
|
2319
|
+
}
|
|
2320
|
+
break;
|
|
2321
|
+
}
|
|
2322
|
+
case "stream_event": {
|
|
2323
|
+
const usageUpdatedEvent = this.trackStreamEventUsage(message.event);
|
|
2324
|
+
if (usageUpdatedEvent) {
|
|
2325
|
+
events.push(usageUpdatedEvent);
|
|
2326
|
+
}
|
|
2327
|
+
const timelineItems = this.mapPartialEvent(message.event, {
|
|
2328
|
+
suppressAssistantText: options?.suppressAssistantText ?? false,
|
|
2329
|
+
suppressReasoning: options?.suppressReasoning ?? false,
|
|
2330
|
+
});
|
|
2331
|
+
for (const item of timelineItems) {
|
|
2332
|
+
events.push({ type: "timeline", item, provider: "claude" });
|
|
2333
|
+
}
|
|
2334
|
+
break;
|
|
2335
|
+
}
|
|
2336
|
+
case "result": {
|
|
2337
|
+
const usage = this.convertUsage(message, message.modelUsage);
|
|
2338
|
+
if (message.subtype === "success") {
|
|
2339
|
+
events.push({ type: "turn_completed", provider: "claude", usage });
|
|
2340
|
+
}
|
|
2341
|
+
else {
|
|
2342
|
+
const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
|
|
2343
|
+
? message.errors.join("\n")
|
|
2344
|
+
: "Claude run failed";
|
|
2345
|
+
events.push(this.buildTurnFailedEvent(errorMessage));
|
|
2346
|
+
}
|
|
2347
|
+
break;
|
|
2348
|
+
}
|
|
2349
|
+
default:
|
|
2350
|
+
break;
|
|
2351
|
+
}
|
|
2352
|
+
return events;
|
|
2353
|
+
}
|
|
2354
|
+
captureSessionIdFromMessage(message) {
|
|
2355
|
+
const msg = message;
|
|
2356
|
+
const sessionIdRaw = typeof msg.session_id === "string"
|
|
2357
|
+
? msg.session_id
|
|
2358
|
+
: typeof msg.sessionId === "string"
|
|
2359
|
+
? msg.sessionId
|
|
2360
|
+
: typeof msg.session?.id === "string"
|
|
2361
|
+
? msg.session.id
|
|
2362
|
+
: "";
|
|
2363
|
+
const sessionId = sessionIdRaw.trim();
|
|
2364
|
+
if (!sessionId) {
|
|
2365
|
+
return null;
|
|
2366
|
+
}
|
|
2367
|
+
if (this.claudeSessionId === null) {
|
|
2368
|
+
this.claudeSessionId = sessionId;
|
|
2369
|
+
this.persistence = null;
|
|
2370
|
+
return sessionId;
|
|
2371
|
+
}
|
|
2372
|
+
if (this.claudeSessionId === sessionId) {
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
// Session ID changed mid-stream (e.g. a hook caused Claude to restart
|
|
2376
|
+
// with a new session). Accept the new ID and continue — the turn should
|
|
2377
|
+
// not be failed just because the underlying subprocess cycled.
|
|
2378
|
+
this.logger.warn({ existingSessionId: this.claudeSessionId, newSessionId: sessionId }, "Claude session ID changed in message; accepting new session");
|
|
2379
|
+
this.claudeSessionId = sessionId;
|
|
2380
|
+
this.persistence = null;
|
|
2381
|
+
return sessionId;
|
|
2382
|
+
}
|
|
2383
|
+
handleSystemMessage(message) {
|
|
2384
|
+
if (message.subtype !== "init") {
|
|
2385
|
+
return null;
|
|
2386
|
+
}
|
|
2387
|
+
const msg = message;
|
|
2388
|
+
const newSessionIdRaw = typeof msg.session_id === "string"
|
|
2389
|
+
? msg.session_id
|
|
2390
|
+
: typeof msg.sessionId === "string"
|
|
2391
|
+
? msg.sessionId
|
|
2392
|
+
: typeof msg.session?.id === "string"
|
|
2393
|
+
? msg.session.id
|
|
2394
|
+
: "";
|
|
2395
|
+
const newSessionId = newSessionIdRaw.trim();
|
|
2396
|
+
if (!newSessionId) {
|
|
2397
|
+
return null;
|
|
2398
|
+
}
|
|
2399
|
+
const existingSessionId = this.claudeSessionId;
|
|
2400
|
+
let threadStartedSessionId = null;
|
|
2401
|
+
if (existingSessionId === null) {
|
|
2402
|
+
this.claudeSessionId = newSessionId;
|
|
2403
|
+
threadStartedSessionId = newSessionId;
|
|
2404
|
+
this.logger.debug({ sessionId: newSessionId }, "Claude session ID set for the first time");
|
|
2405
|
+
}
|
|
2406
|
+
else if (existingSessionId === newSessionId) {
|
|
2407
|
+
this.logger.debug({ sessionId: newSessionId }, "Claude session ID unchanged (same value)");
|
|
2408
|
+
}
|
|
2409
|
+
else {
|
|
2410
|
+
// Session ID changed in an init message (e.g. a hook restarted Claude
|
|
2411
|
+
// with a new session mid-turn). Accept the new ID and continue.
|
|
2412
|
+
this.logger.warn({ existingSessionId, newSessionId }, "Claude session ID changed in init message; accepting new session");
|
|
2413
|
+
this.claudeSessionId = newSessionId;
|
|
2414
|
+
threadStartedSessionId = newSessionId;
|
|
2415
|
+
}
|
|
2416
|
+
this.availableModes = DEFAULT_MODES;
|
|
2417
|
+
this.currentMode = message.permissionMode;
|
|
2418
|
+
if (this.currentMode !== "plan") {
|
|
2419
|
+
this.planResumeMode = this.currentMode;
|
|
2420
|
+
}
|
|
2421
|
+
this.persistence = null;
|
|
2422
|
+
if (message.model) {
|
|
2423
|
+
const normalizedRuntimeModel = normalizeClaudeRuntimeModelId(message.model);
|
|
2424
|
+
this.logger.debug({ runtimeModel: message.model, normalizedRuntimeModel }, "Captured runtime model from SDK init");
|
|
2425
|
+
if (normalizedRuntimeModel) {
|
|
2426
|
+
this.lastOptionsModel = normalizedRuntimeModel;
|
|
2427
|
+
}
|
|
2428
|
+
else if (!this.lastOptionsModel) {
|
|
2429
|
+
this.lastOptionsModel = this.config.model ?? null;
|
|
2430
|
+
}
|
|
2431
|
+
this.lastRuntimeModel = message.model;
|
|
2432
|
+
this.cachedRuntimeInfo = null;
|
|
2433
|
+
}
|
|
2434
|
+
return threadStartedSessionId;
|
|
2435
|
+
}
|
|
2436
|
+
readMissingResumedConversationError(message) {
|
|
2437
|
+
if (message.type !== "result" || message.subtype !== "error_during_execution") {
|
|
2438
|
+
return null;
|
|
2439
|
+
}
|
|
2440
|
+
if (!this.claudeSessionId) {
|
|
2441
|
+
return null;
|
|
2442
|
+
}
|
|
2443
|
+
const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
|
|
2444
|
+
for (const entry of errors) {
|
|
2445
|
+
if (typeof entry !== "string") {
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
const match = entry.match(/^No conversation found with session ID:\s*(.+)$/);
|
|
2449
|
+
if (!match) {
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
if (match[1]?.trim() === this.claudeSessionId) {
|
|
2453
|
+
return entry.trim();
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
convertUsage(message, modelUsage) {
|
|
2459
|
+
if (!message.usage) {
|
|
2460
|
+
return undefined;
|
|
2461
|
+
}
|
|
2462
|
+
const usage = {
|
|
2463
|
+
inputTokens: message.usage.input_tokens,
|
|
2464
|
+
cachedInputTokens: message.usage.cache_read_input_tokens,
|
|
2465
|
+
outputTokens: message.usage.output_tokens,
|
|
2466
|
+
totalCostUsd: message.total_cost_usd,
|
|
2467
|
+
};
|
|
2468
|
+
const contextWindowMaxTokens = extractContextWindowSize(modelUsage ?? message.modelUsage);
|
|
2469
|
+
if (contextWindowMaxTokens !== undefined) {
|
|
2470
|
+
this.lastContextWindowMaxTokens = contextWindowMaxTokens;
|
|
2471
|
+
usage.contextWindowMaxTokens = contextWindowMaxTokens;
|
|
2472
|
+
}
|
|
2473
|
+
else if (this.lastContextWindowMaxTokens !== undefined) {
|
|
2474
|
+
usage.contextWindowMaxTokens = this.lastContextWindowMaxTokens;
|
|
2475
|
+
}
|
|
2476
|
+
if (typeof this.lastContextWindowUsedTokens === "number") {
|
|
2477
|
+
// task_progress.total_tokens is the accurate context window fill level.
|
|
2478
|
+
// Prefer it over result.usage which contains accumulated session totals.
|
|
2479
|
+
usage.contextWindowUsedTokens = this.lastContextWindowUsedTokens;
|
|
2480
|
+
}
|
|
2481
|
+
else if (typeof this.lastStreamRequestInputTokens === "number" &&
|
|
2482
|
+
typeof this.lastStreamRequestOutputTokens === "number") {
|
|
2483
|
+
usage.contextWindowUsedTokens =
|
|
2484
|
+
this.lastStreamRequestInputTokens + this.lastStreamRequestOutputTokens;
|
|
2485
|
+
}
|
|
2486
|
+
else if (message.usage) {
|
|
2487
|
+
// Fallback: derive from result.usage when no task_progress has been
|
|
2488
|
+
// received yet. These values are accumulated across all API calls, but
|
|
2489
|
+
// for the first turn they equal the per-call values so the estimate is
|
|
2490
|
+
// reasonable. Once a task_progress arrives it takes over permanently.
|
|
2491
|
+
const usageWithCacheCreation = message.usage;
|
|
2492
|
+
const derived = (message.usage.input_tokens ?? 0) +
|
|
2493
|
+
(usageWithCacheCreation.cache_creation_input_tokens ?? 0) +
|
|
2494
|
+
(message.usage.cache_read_input_tokens ?? 0) +
|
|
2495
|
+
(message.usage.output_tokens ?? 0);
|
|
2496
|
+
if (Number.isFinite(derived) && derived > 0) {
|
|
2497
|
+
usage.contextWindowUsedTokens = derived;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return usage;
|
|
2501
|
+
}
|
|
2502
|
+
createUsageUpdatedEvent(contextWindowUsedTokens) {
|
|
2503
|
+
const usage = {
|
|
2504
|
+
contextWindowUsedTokens,
|
|
2505
|
+
};
|
|
2506
|
+
if (this.lastContextWindowMaxTokens !== undefined) {
|
|
2507
|
+
usage.contextWindowMaxTokens = this.lastContextWindowMaxTokens;
|
|
2508
|
+
}
|
|
2509
|
+
return {
|
|
2510
|
+
type: "usage_updated",
|
|
2511
|
+
provider: "claude",
|
|
2512
|
+
usage,
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
trackStreamEventUsage(event) {
|
|
2516
|
+
if (!event || typeof event !== "object") {
|
|
2517
|
+
return null;
|
|
2518
|
+
}
|
|
2519
|
+
const streamEvent = event;
|
|
2520
|
+
const eventType = readTrimmedString(streamEvent.type);
|
|
2521
|
+
if (eventType === "message_start") {
|
|
2522
|
+
const inputTokens = readStreamRequestInputTokens(streamEvent);
|
|
2523
|
+
if (typeof inputTokens !== "number") {
|
|
2524
|
+
return null;
|
|
2525
|
+
}
|
|
2526
|
+
this.lastStreamRequestInputTokens = inputTokens;
|
|
2527
|
+
this.lastStreamRequestOutputTokens = 0;
|
|
2528
|
+
}
|
|
2529
|
+
else if (eventType === "message_delta") {
|
|
2530
|
+
const outputTokens = readStreamRequestOutputTokens(streamEvent);
|
|
2531
|
+
if (typeof outputTokens !== "number") {
|
|
2532
|
+
return null;
|
|
2533
|
+
}
|
|
2534
|
+
this.lastStreamRequestOutputTokens = outputTokens;
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
if (typeof this.lastStreamRequestInputTokens !== "number" ||
|
|
2540
|
+
typeof this.lastStreamRequestOutputTokens !== "number") {
|
|
2541
|
+
return null;
|
|
2542
|
+
}
|
|
2543
|
+
return this.createUsageUpdatedEvent(this.lastStreamRequestInputTokens + this.lastStreamRequestOutputTokens);
|
|
2544
|
+
}
|
|
2545
|
+
enqueueTimeline(item) {
|
|
2546
|
+
this.pushEvent({ type: "timeline", item, provider: "claude" });
|
|
2547
|
+
}
|
|
2548
|
+
flushPendingToolCalls() {
|
|
2549
|
+
for (const [id, entry] of this.toolUseCache) {
|
|
2550
|
+
if (entry.started) {
|
|
2551
|
+
this.pushToolCall(mapClaudeCanceledToolCall({
|
|
2552
|
+
name: entry.name,
|
|
2553
|
+
callId: id,
|
|
2554
|
+
input: entry.input ?? null,
|
|
2555
|
+
output: null,
|
|
2556
|
+
}));
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
this.toolUseCache.clear();
|
|
2560
|
+
this.sidechainTracker.clear();
|
|
2561
|
+
}
|
|
2562
|
+
pushToolCall(item, target) {
|
|
2563
|
+
if (!item) {
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
if (target) {
|
|
2567
|
+
target.push(item);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
this.enqueueTimeline(item);
|
|
2571
|
+
}
|
|
2572
|
+
pushEvent(event) {
|
|
2573
|
+
this.notifySubscribers(event);
|
|
2574
|
+
}
|
|
2575
|
+
notifySubscribers(event) {
|
|
2576
|
+
const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id;
|
|
2577
|
+
const tagged = turnId ? { ...event, turnId } : event;
|
|
2578
|
+
for (const callback of this.subscribers) {
|
|
2579
|
+
try {
|
|
2580
|
+
callback(tagged);
|
|
2581
|
+
}
|
|
2582
|
+
catch (error) {
|
|
2583
|
+
this.logger.warn({ err: error }, "Subscriber callback threw");
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
normalizePermissionUpdates(updates) {
|
|
2588
|
+
if (!updates || updates.length === 0) {
|
|
2589
|
+
return undefined;
|
|
2590
|
+
}
|
|
2591
|
+
const normalized = updates.filter(isPermissionUpdate);
|
|
2592
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
2593
|
+
}
|
|
2594
|
+
rejectAllPendingPermissions(error) {
|
|
2595
|
+
for (const [id, pending] of this.pendingPermissions) {
|
|
2596
|
+
pending.cleanup?.();
|
|
2597
|
+
pending.reject(error);
|
|
2598
|
+
this.pendingPermissions.delete(id);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
loadPersistedHistory(sessionId) {
|
|
2602
|
+
try {
|
|
2603
|
+
const historyPath = this.resolveHistoryPath(sessionId);
|
|
2604
|
+
if (!historyPath || !fs.existsSync(historyPath)) {
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
this.ingestPersistedHistory(fs.readFileSync(historyPath, "utf8"));
|
|
2608
|
+
}
|
|
2609
|
+
catch (error) {
|
|
2610
|
+
// ignore history load failures
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
ingestPersistedHistory(content) {
|
|
2614
|
+
if (!content) {
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const timeline = [];
|
|
2618
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2619
|
+
this.ingestPersistedHistoryLine(line, timeline);
|
|
2620
|
+
}
|
|
2621
|
+
if (timeline.length > 0) {
|
|
2622
|
+
this.persistedHistory = [...this.persistedHistory, ...timeline];
|
|
2623
|
+
this.historyPending = true;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
ingestPersistedHistoryLine(line, timeline) {
|
|
2627
|
+
const trimmed = line.trim();
|
|
2628
|
+
if (!trimmed) {
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
let entry;
|
|
2632
|
+
try {
|
|
2633
|
+
entry = JSON.parse(trimmed);
|
|
2634
|
+
}
|
|
2635
|
+
catch {
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
if (entry.isSidechain) {
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
if (entry.type === "user" && typeof entry.uuid === "string") {
|
|
2642
|
+
this.rememberUserMessageId(entry.uuid);
|
|
2643
|
+
}
|
|
2644
|
+
const items = this.convertHistoryEntry(entry);
|
|
2645
|
+
if (items.length > 0) {
|
|
2646
|
+
timeline.push(...items);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
resolveHistoryPath(sessionId) {
|
|
2650
|
+
const cwd = this.config.cwd;
|
|
2651
|
+
if (!cwd)
|
|
2652
|
+
return null;
|
|
2653
|
+
// Match Claude CLI's path sanitization: replace slashes, dots, and underscores with dashes
|
|
2654
|
+
const sanitized = cwd.replace(/[\\/\.]/g, "-").replace(/_/g, "-");
|
|
2655
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
2656
|
+
const dir = path.join(configDir, "projects", sanitized);
|
|
2657
|
+
return path.join(dir, `${sessionId}.jsonl`);
|
|
2658
|
+
}
|
|
2659
|
+
convertHistoryEntry(entry) {
|
|
2660
|
+
return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
|
|
2661
|
+
}
|
|
2662
|
+
// Maps Claude content blocks into AgentTimelineItems.
|
|
2663
|
+
//
|
|
2664
|
+
// textMessageType controls what type text blocks emit:
|
|
2665
|
+
// - "assistant_message" (default): one item per text block (streaming granularity)
|
|
2666
|
+
// - "user_message": coalesces all text blocks into a single user_message
|
|
2667
|
+
// (matches extractUserMessageText semantics: trim each block, join with "\n\n")
|
|
2668
|
+
//
|
|
2669
|
+
// suppressAssistantText only applies when textMessageType is "assistant_message" — user text
|
|
2670
|
+
// must never be suppressed since the TimelineAssembler only handles assistant text.
|
|
2671
|
+
//
|
|
2672
|
+
// NOTE: convertClaudeHistoryEntry uses extractUserMessageText directly instead of this function
|
|
2673
|
+
// for user entries. Both paths must produce equivalent user_message items.
|
|
2674
|
+
mapBlocksToTimeline(content, options) {
|
|
2675
|
+
const textMessageType = options?.textMessageType ?? "assistant_message";
|
|
2676
|
+
const suppressText = textMessageType === "assistant_message" && (options?.suppressAssistantText ?? false);
|
|
2677
|
+
const suppressReasoning = options?.suppressReasoning ?? false;
|
|
2678
|
+
if (typeof content === "string") {
|
|
2679
|
+
if (!content ||
|
|
2680
|
+
content === INTERRUPT_TOOL_USE_PLACEHOLDER ||
|
|
2681
|
+
isClaudeTranscriptNoiseText(content)) {
|
|
2682
|
+
return [];
|
|
2683
|
+
}
|
|
2684
|
+
if (suppressText) {
|
|
2685
|
+
return [];
|
|
2686
|
+
}
|
|
2687
|
+
return [{ type: textMessageType, text: content }];
|
|
2688
|
+
}
|
|
2689
|
+
const items = [];
|
|
2690
|
+
// User SDK entries can arrive as multiple text blocks, but Seawork treats them as one message.
|
|
2691
|
+
const userTextParts = [];
|
|
2692
|
+
for (const block of content) {
|
|
2693
|
+
switch (block.type) {
|
|
2694
|
+
case "text":
|
|
2695
|
+
case "text_delta":
|
|
2696
|
+
if (block.text &&
|
|
2697
|
+
block.text !== INTERRUPT_TOOL_USE_PLACEHOLDER &&
|
|
2698
|
+
!isClaudeTranscriptNoiseText(block.text)) {
|
|
2699
|
+
if (textMessageType === "user_message") {
|
|
2700
|
+
const trimmed = block.text.trim();
|
|
2701
|
+
if (trimmed) {
|
|
2702
|
+
userTextParts.push(trimmed);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
else if (!suppressText) {
|
|
2706
|
+
items.push({ type: "assistant_message", text: block.text });
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
break;
|
|
2710
|
+
case "thinking":
|
|
2711
|
+
case "thinking_delta":
|
|
2712
|
+
if (block.thinking) {
|
|
2713
|
+
if (!suppressReasoning) {
|
|
2714
|
+
items.push({ type: "reasoning", text: block.thinking });
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
break;
|
|
2718
|
+
case "tool_use":
|
|
2719
|
+
case "server_tool_use":
|
|
2720
|
+
case "mcp_tool_use": {
|
|
2721
|
+
this.handleToolUseStart(block, items);
|
|
2722
|
+
break;
|
|
2723
|
+
}
|
|
2724
|
+
case "tool_result":
|
|
2725
|
+
case "mcp_tool_result":
|
|
2726
|
+
case "web_fetch_tool_result":
|
|
2727
|
+
case "web_search_tool_result":
|
|
2728
|
+
case "code_execution_tool_result":
|
|
2729
|
+
case "bash_code_execution_tool_result":
|
|
2730
|
+
case "text_editor_code_execution_tool_result": {
|
|
2731
|
+
this.handleToolResult(block, items);
|
|
2732
|
+
break;
|
|
2733
|
+
}
|
|
2734
|
+
default:
|
|
2735
|
+
break;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (textMessageType === "user_message" && userTextParts.length > 0) {
|
|
2739
|
+
items.unshift({
|
|
2740
|
+
type: "user_message",
|
|
2741
|
+
text: userTextParts.join("\n\n"),
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
return items;
|
|
2745
|
+
}
|
|
2746
|
+
handleToolUseStart(block, items) {
|
|
2747
|
+
const entry = this.upsertToolUseEntry(block);
|
|
2748
|
+
if (!entry) {
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
if (entry.started) {
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
entry.started = true;
|
|
2755
|
+
this.toolUseCache.set(entry.id, entry);
|
|
2756
|
+
this.pushToolCall(mapClaudeRunningToolCall({
|
|
2757
|
+
name: entry.name,
|
|
2758
|
+
callId: entry.id,
|
|
2759
|
+
input: entry.input ?? this.normalizeToolInput(block.input) ?? null,
|
|
2760
|
+
output: null,
|
|
2761
|
+
}), items);
|
|
2762
|
+
}
|
|
2763
|
+
handleToolResult(block, items) {
|
|
2764
|
+
const entry = typeof block.tool_use_id === "string" ? this.toolUseCache.get(block.tool_use_id) : undefined;
|
|
2765
|
+
const toolName = entry?.name ?? block.tool_name ?? "tool";
|
|
2766
|
+
const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
|
|
2767
|
+
? block.tool_use_id
|
|
2768
|
+
: (entry?.id ?? null);
|
|
2769
|
+
// Extract output from block.content (SDK always returns content in string form)
|
|
2770
|
+
const output = this.buildToolOutput(block, entry);
|
|
2771
|
+
if (block.is_error) {
|
|
2772
|
+
this.pushToolCall(mapClaudeFailedToolCall({
|
|
2773
|
+
name: toolName,
|
|
2774
|
+
callId,
|
|
2775
|
+
input: entry?.input ?? null,
|
|
2776
|
+
output: output ?? null,
|
|
2777
|
+
error: block,
|
|
2778
|
+
}), items);
|
|
2779
|
+
}
|
|
2780
|
+
else {
|
|
2781
|
+
this.pushToolCall(mapClaudeCompletedToolCall({
|
|
2782
|
+
name: toolName,
|
|
2783
|
+
callId,
|
|
2784
|
+
input: entry?.input ?? null,
|
|
2785
|
+
output: output ?? null,
|
|
2786
|
+
}), items);
|
|
2787
|
+
}
|
|
2788
|
+
if (typeof block.tool_use_id === "string") {
|
|
2789
|
+
this.toolUseCache.delete(block.tool_use_id);
|
|
2790
|
+
this.sidechainTracker.delete(block.tool_use_id);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
buildToolOutput(block, entry) {
|
|
2794
|
+
if (block.is_error) {
|
|
2795
|
+
return undefined;
|
|
2796
|
+
}
|
|
2797
|
+
const server = entry?.server ?? block.server ?? "tool";
|
|
2798
|
+
const tool = entry?.name ?? block.tool_name ?? "tool";
|
|
2799
|
+
const content = coerceToolResultContentToString(block.content);
|
|
2800
|
+
const input = entry?.input;
|
|
2801
|
+
// Build structured result based on tool type
|
|
2802
|
+
const structured = this.buildStructuredToolResult(server, tool, content, input);
|
|
2803
|
+
if (structured) {
|
|
2804
|
+
return structured;
|
|
2805
|
+
}
|
|
2806
|
+
// Fallback format - try to parse JSON first
|
|
2807
|
+
const result = {};
|
|
2808
|
+
if (content.length > 0) {
|
|
2809
|
+
try {
|
|
2810
|
+
// If content is a JSON string, parse it
|
|
2811
|
+
result.output = JSON.parse(content);
|
|
2812
|
+
}
|
|
2813
|
+
catch {
|
|
2814
|
+
// If not JSON, return unchanged (no extra wrapping)
|
|
2815
|
+
result.output = content;
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
// Preserve file changes tracked during tool execution
|
|
2819
|
+
if (entry?.files?.length) {
|
|
2820
|
+
result.files = entry.files;
|
|
2821
|
+
}
|
|
2822
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
2823
|
+
}
|
|
2824
|
+
buildStructuredToolResult(server, tool, output, input) {
|
|
2825
|
+
const normalizedServer = server.toLowerCase();
|
|
2826
|
+
const normalizedTool = tool.toLowerCase();
|
|
2827
|
+
// Command execution tools
|
|
2828
|
+
if (normalizedServer.includes("bash") ||
|
|
2829
|
+
normalizedServer.includes("shell") ||
|
|
2830
|
+
normalizedServer.includes("command") ||
|
|
2831
|
+
normalizedTool.includes("bash") ||
|
|
2832
|
+
normalizedTool.includes("shell") ||
|
|
2833
|
+
normalizedTool.includes("command") ||
|
|
2834
|
+
(input && (typeof input.command === "string" || Array.isArray(input.command)))) {
|
|
2835
|
+
const command = this.extractCommandText(input ?? {}) ?? "command";
|
|
2836
|
+
return {
|
|
2837
|
+
type: "command",
|
|
2838
|
+
command,
|
|
2839
|
+
output,
|
|
2840
|
+
cwd: typeof input?.cwd === "string" ? input.cwd : undefined,
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
// File write tools (new files or complete replacements)
|
|
2844
|
+
if (normalizedTool.includes("write") ||
|
|
2845
|
+
normalizedTool === "write_file" ||
|
|
2846
|
+
normalizedTool === "create_file") {
|
|
2847
|
+
if (input && typeof input.file_path === "string") {
|
|
2848
|
+
return {
|
|
2849
|
+
type: "file_write",
|
|
2850
|
+
filePath: input.file_path,
|
|
2851
|
+
oldContent: "",
|
|
2852
|
+
newContent: typeof input.content === "string" ? input.content : output,
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
// File edit/patch tools
|
|
2857
|
+
if (normalizedTool.includes("edit") ||
|
|
2858
|
+
normalizedTool.includes("patch") ||
|
|
2859
|
+
normalizedTool === "apply_patch" ||
|
|
2860
|
+
normalizedTool === "apply_diff") {
|
|
2861
|
+
if (input && typeof input.file_path === "string") {
|
|
2862
|
+
// Support both old_str/new_str and old_string/new_string parameter names
|
|
2863
|
+
const oldContent = typeof input.old_str === "string"
|
|
2864
|
+
? input.old_str
|
|
2865
|
+
: typeof input.old_string === "string"
|
|
2866
|
+
? input.old_string
|
|
2867
|
+
: undefined;
|
|
2868
|
+
const newContent = typeof input.new_str === "string"
|
|
2869
|
+
? input.new_str
|
|
2870
|
+
: typeof input.new_string === "string"
|
|
2871
|
+
? input.new_string
|
|
2872
|
+
: undefined;
|
|
2873
|
+
return {
|
|
2874
|
+
type: "file_edit",
|
|
2875
|
+
filePath: input.file_path,
|
|
2876
|
+
diff: typeof input.patch === "string"
|
|
2877
|
+
? input.patch
|
|
2878
|
+
: typeof input.diff === "string"
|
|
2879
|
+
? input.diff
|
|
2880
|
+
: undefined,
|
|
2881
|
+
oldContent,
|
|
2882
|
+
newContent,
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
// File read tools
|
|
2887
|
+
if (normalizedTool.includes("read") ||
|
|
2888
|
+
normalizedTool === "read_file" ||
|
|
2889
|
+
normalizedTool === "view_file") {
|
|
2890
|
+
if (input && typeof input.file_path === "string") {
|
|
2891
|
+
return {
|
|
2892
|
+
type: "file_read",
|
|
2893
|
+
filePath: input.file_path,
|
|
2894
|
+
content: output,
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return undefined;
|
|
2899
|
+
}
|
|
2900
|
+
mapPartialEvent(event, options) {
|
|
2901
|
+
if (event.type === "content_block_start") {
|
|
2902
|
+
const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
|
|
2903
|
+
if (block?.type === "tool_use" &&
|
|
2904
|
+
typeof event.index === "number" &&
|
|
2905
|
+
typeof block.id === "string") {
|
|
2906
|
+
this.toolUseIndexToId.set(event.index, block.id);
|
|
2907
|
+
this.toolUseInputBuffers.delete(block.id);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
else if (event.type === "content_block_delta") {
|
|
2911
|
+
const delta = isClaudeContentChunk(event.delta) ? event.delta : null;
|
|
2912
|
+
if (delta?.type === "input_json_delta") {
|
|
2913
|
+
const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : undefined;
|
|
2914
|
+
this.handleToolInputDelta(event.index, partialJson);
|
|
2915
|
+
return [];
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
else if (event.type === "content_block_stop" && typeof event.index === "number") {
|
|
2919
|
+
const toolId = this.toolUseIndexToId.get(event.index);
|
|
2920
|
+
if (toolId) {
|
|
2921
|
+
this.toolUseIndexToId.delete(event.index);
|
|
2922
|
+
this.toolUseInputBuffers.delete(toolId);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
switch (event.type) {
|
|
2926
|
+
case "content_block_start":
|
|
2927
|
+
return isClaudeContentChunk(event.content_block)
|
|
2928
|
+
? this.mapBlocksToTimeline([event.content_block], {
|
|
2929
|
+
suppressAssistantText: options?.suppressAssistantText,
|
|
2930
|
+
suppressReasoning: options?.suppressReasoning,
|
|
2931
|
+
})
|
|
2932
|
+
: [];
|
|
2933
|
+
case "content_block_delta":
|
|
2934
|
+
return isClaudeContentChunk(event.delta)
|
|
2935
|
+
? this.mapBlocksToTimeline([event.delta], {
|
|
2936
|
+
suppressAssistantText: options?.suppressAssistantText,
|
|
2937
|
+
suppressReasoning: options?.suppressReasoning,
|
|
2938
|
+
})
|
|
2939
|
+
: [];
|
|
2940
|
+
default:
|
|
2941
|
+
return [];
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
upsertToolUseEntry(block) {
|
|
2945
|
+
const id = typeof block.id === "string" ? block.id : undefined;
|
|
2946
|
+
if (!id) {
|
|
2947
|
+
return null;
|
|
2948
|
+
}
|
|
2949
|
+
const existing = this.toolUseCache.get(id) ??
|
|
2950
|
+
{
|
|
2951
|
+
id,
|
|
2952
|
+
name: typeof block.name === "string" && block.name.length > 0 ? block.name : "tool",
|
|
2953
|
+
server: typeof block.server === "string" && block.server.length > 0
|
|
2954
|
+
? block.server
|
|
2955
|
+
: typeof block.name === "string" && block.name.length > 0
|
|
2956
|
+
? block.name
|
|
2957
|
+
: "tool",
|
|
2958
|
+
classification: "generic",
|
|
2959
|
+
started: false,
|
|
2960
|
+
};
|
|
2961
|
+
if (typeof block.name === "string" && block.name.length > 0) {
|
|
2962
|
+
existing.name = block.name;
|
|
2963
|
+
}
|
|
2964
|
+
if (typeof block.server === "string" && block.server.length > 0) {
|
|
2965
|
+
existing.server = block.server;
|
|
2966
|
+
}
|
|
2967
|
+
else if (!existing.server) {
|
|
2968
|
+
existing.server = existing.name;
|
|
2969
|
+
}
|
|
2970
|
+
if (block.type === "tool_use" ||
|
|
2971
|
+
block.type === "mcp_tool_use" ||
|
|
2972
|
+
block.type === "server_tool_use") {
|
|
2973
|
+
const input = this.normalizeToolInput(block.input);
|
|
2974
|
+
if (input) {
|
|
2975
|
+
this.applyToolInput(existing, input);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
this.toolUseCache.set(id, existing);
|
|
2979
|
+
return existing;
|
|
2980
|
+
}
|
|
2981
|
+
handleToolInputDelta(index, partialJson) {
|
|
2982
|
+
if (typeof index !== "number" || typeof partialJson !== "string") {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
const toolId = this.toolUseIndexToId.get(index);
|
|
2986
|
+
if (!toolId) {
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const buffer = (this.toolUseInputBuffers.get(toolId) ?? "") + partialJson;
|
|
2990
|
+
this.toolUseInputBuffers.set(toolId, buffer);
|
|
2991
|
+
const entry = this.toolUseCache.get(toolId);
|
|
2992
|
+
const parsed = parsePartialJsonObject(buffer);
|
|
2993
|
+
if (!entry || !parsed) {
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
const normalized = this.normalizeToolInput(parsed.value);
|
|
2997
|
+
if (!normalized) {
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
if (!parsed.complete && Object.keys(normalized).length === 0) {
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
if (this.areToolInputsEqual(entry.input ?? undefined, normalized)) {
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
this.applyToolInput(entry, normalized);
|
|
3007
|
+
this.toolUseCache.set(toolId, entry);
|
|
3008
|
+
this.pushToolCall(mapClaudeRunningToolCall({
|
|
3009
|
+
name: entry.name,
|
|
3010
|
+
callId: toolId,
|
|
3011
|
+
input: normalized,
|
|
3012
|
+
output: null,
|
|
3013
|
+
}));
|
|
3014
|
+
}
|
|
3015
|
+
normalizeToolInput(input) {
|
|
3016
|
+
if (!isMetadata(input)) {
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
return input;
|
|
3020
|
+
}
|
|
3021
|
+
areToolInputsEqual(left, right) {
|
|
3022
|
+
if (!left) {
|
|
3023
|
+
return false;
|
|
3024
|
+
}
|
|
3025
|
+
const leftKeys = Object.keys(left);
|
|
3026
|
+
const rightKeys = Object.keys(right);
|
|
3027
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
3028
|
+
return false;
|
|
3029
|
+
}
|
|
3030
|
+
return rightKeys.every((key) => left[key] === right[key]);
|
|
3031
|
+
}
|
|
3032
|
+
applyToolInput(entry, input) {
|
|
3033
|
+
entry.input = input;
|
|
3034
|
+
if (this.isCommandTool(entry.name, input)) {
|
|
3035
|
+
entry.classification = "command";
|
|
3036
|
+
entry.commandText = this.extractCommandText(input) ?? entry.commandText;
|
|
3037
|
+
}
|
|
3038
|
+
else {
|
|
3039
|
+
const files = this.extractFileChanges(input);
|
|
3040
|
+
if (files?.length) {
|
|
3041
|
+
entry.classification = "file_change";
|
|
3042
|
+
entry.files = files;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
isCommandTool(name, input) {
|
|
3047
|
+
const normalized = name.toLowerCase();
|
|
3048
|
+
if (normalized.includes("bash") ||
|
|
3049
|
+
normalized.includes("shell") ||
|
|
3050
|
+
normalized.includes("terminal") ||
|
|
3051
|
+
normalized.includes("command")) {
|
|
3052
|
+
return true;
|
|
3053
|
+
}
|
|
3054
|
+
if (typeof input.command === "string" || Array.isArray(input.command)) {
|
|
3055
|
+
return true;
|
|
3056
|
+
}
|
|
3057
|
+
return false;
|
|
3058
|
+
}
|
|
3059
|
+
extractCommandText(input) {
|
|
3060
|
+
const command = input.command;
|
|
3061
|
+
if (typeof command === "string" && command.length > 0) {
|
|
3062
|
+
return command;
|
|
3063
|
+
}
|
|
3064
|
+
if (Array.isArray(command)) {
|
|
3065
|
+
const tokens = command.filter((value) => typeof value === "string");
|
|
3066
|
+
if (tokens.length > 0) {
|
|
3067
|
+
return tokens.join(" ");
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (typeof input.description === "string" && input.description.length > 0) {
|
|
3071
|
+
return input.description;
|
|
3072
|
+
}
|
|
3073
|
+
return undefined;
|
|
3074
|
+
}
|
|
3075
|
+
extractFileChanges(input) {
|
|
3076
|
+
if (typeof input.file_path === "string" && input.file_path.length > 0) {
|
|
3077
|
+
const relative = this.relativizePath(input.file_path);
|
|
3078
|
+
if (relative) {
|
|
3079
|
+
return [{ path: relative, kind: this.detectFileKind(input.file_path) }];
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
if (typeof input.patch === "string" && input.patch.length > 0) {
|
|
3083
|
+
const files = this.parsePatchFileList(input.patch);
|
|
3084
|
+
if (files.length > 0) {
|
|
3085
|
+
return files.map((entry) => ({
|
|
3086
|
+
path: this.relativizePath(entry.path) ?? entry.path,
|
|
3087
|
+
kind: entry.kind,
|
|
3088
|
+
}));
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
if (Array.isArray(input.files)) {
|
|
3092
|
+
const files = [];
|
|
3093
|
+
for (const value of input.files) {
|
|
3094
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3095
|
+
files.push({
|
|
3096
|
+
path: this.relativizePath(value) ?? value,
|
|
3097
|
+
kind: this.detectFileKind(value),
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (files.length > 0) {
|
|
3102
|
+
return files;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
return undefined;
|
|
3106
|
+
}
|
|
3107
|
+
detectFileKind(filePath) {
|
|
3108
|
+
try {
|
|
3109
|
+
return fs.existsSync(filePath) ? "update" : "add";
|
|
3110
|
+
}
|
|
3111
|
+
catch {
|
|
3112
|
+
return "update";
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
relativizePath(target) {
|
|
3116
|
+
if (!target) {
|
|
3117
|
+
return undefined;
|
|
3118
|
+
}
|
|
3119
|
+
const cwd = this.config.cwd;
|
|
3120
|
+
if (cwd && target.startsWith(cwd)) {
|
|
3121
|
+
const relative = path.relative(cwd, target);
|
|
3122
|
+
return relative.length > 0 ? relative : path.basename(target);
|
|
3123
|
+
}
|
|
3124
|
+
return target;
|
|
3125
|
+
}
|
|
3126
|
+
parsePatchFileList(patch) {
|
|
3127
|
+
const files = [];
|
|
3128
|
+
const seen = new Set();
|
|
3129
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
3130
|
+
const trimmed = line.trim();
|
|
3131
|
+
let kind = null;
|
|
3132
|
+
let parsedPath = null;
|
|
3133
|
+
if (trimmed.startsWith("*** Add File:")) {
|
|
3134
|
+
kind = "add";
|
|
3135
|
+
parsedPath = trimmed.replace("*** Add File:", "").trim();
|
|
3136
|
+
}
|
|
3137
|
+
else if (trimmed.startsWith("*** Delete File:")) {
|
|
3138
|
+
kind = "delete";
|
|
3139
|
+
parsedPath = trimmed.replace("*** Delete File:", "").trim();
|
|
3140
|
+
}
|
|
3141
|
+
else if (trimmed.startsWith("*** Update File:")) {
|
|
3142
|
+
kind = "update";
|
|
3143
|
+
parsedPath = trimmed.replace("*** Update File:", "").trim();
|
|
3144
|
+
}
|
|
3145
|
+
if (kind && parsedPath && !seen.has(`${kind}:${parsedPath}`)) {
|
|
3146
|
+
seen.add(`${kind}:${parsedPath}`);
|
|
3147
|
+
files.push({ path: parsedPath, kind });
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
return files;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
function hasToolLikeBlock(block) {
|
|
3154
|
+
if (!block || typeof block !== "object") {
|
|
3155
|
+
return false;
|
|
3156
|
+
}
|
|
3157
|
+
const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
|
|
3158
|
+
return type.includes("tool");
|
|
3159
|
+
}
|
|
3160
|
+
function readCompactionMetadata(source) {
|
|
3161
|
+
const candidates = [source.compact_metadata, source.compactMetadata, source.compactionMetadata];
|
|
3162
|
+
for (const candidate of candidates) {
|
|
3163
|
+
if (!candidate || typeof candidate !== "object") {
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
const metadata = candidate;
|
|
3167
|
+
const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
|
|
3168
|
+
const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
|
|
3169
|
+
const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
|
|
3170
|
+
return { trigger, preTokens };
|
|
3171
|
+
}
|
|
3172
|
+
return null;
|
|
3173
|
+
}
|
|
3174
|
+
function normalizeHistoryBlocks(content) {
|
|
3175
|
+
if (Array.isArray(content)) {
|
|
3176
|
+
const blocks = content.filter((entry) => isClaudeContentChunk(entry));
|
|
3177
|
+
return blocks.length > 0 ? blocks : null;
|
|
3178
|
+
}
|
|
3179
|
+
if (isClaudeContentChunk(content)) {
|
|
3180
|
+
return [content];
|
|
3181
|
+
}
|
|
3182
|
+
return null;
|
|
3183
|
+
}
|
|
3184
|
+
export function convertClaudeHistoryEntry(entry, mapBlocks) {
|
|
3185
|
+
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
|
3186
|
+
const compactMetadata = readCompactionMetadata(entry);
|
|
3187
|
+
return [
|
|
3188
|
+
{
|
|
3189
|
+
type: "compaction",
|
|
3190
|
+
status: "completed",
|
|
3191
|
+
trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
|
|
3192
|
+
preTokens: compactMetadata?.preTokens,
|
|
3193
|
+
},
|
|
3194
|
+
];
|
|
3195
|
+
}
|
|
3196
|
+
const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
|
|
3197
|
+
if (taskNotificationItem) {
|
|
3198
|
+
return [taskNotificationItem];
|
|
3199
|
+
}
|
|
3200
|
+
if (entry.isCompactSummary) {
|
|
3201
|
+
return [];
|
|
3202
|
+
}
|
|
3203
|
+
if (entry.type === "user" && isSyntheticUserEntry(entry)) {
|
|
3204
|
+
return [];
|
|
3205
|
+
}
|
|
3206
|
+
const message = entry?.message;
|
|
3207
|
+
if (!message || !("content" in message)) {
|
|
3208
|
+
return [];
|
|
3209
|
+
}
|
|
3210
|
+
const content = message.content;
|
|
3211
|
+
if ((entry.type === "user" || entry.type === "assistant") &&
|
|
3212
|
+
isClaudeTranscriptNoiseContent(content)) {
|
|
3213
|
+
return [];
|
|
3214
|
+
}
|
|
3215
|
+
const normalizedBlocks = normalizeHistoryBlocks(content);
|
|
3216
|
+
const contentValue = typeof content === "string" ? content : normalizedBlocks;
|
|
3217
|
+
const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
|
|
3218
|
+
const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
|
|
3219
|
+
? entry.uuid
|
|
3220
|
+
: null;
|
|
3221
|
+
if (entry.type === "user") {
|
|
3222
|
+
const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
|
|
3223
|
+
content,
|
|
3224
|
+
messageId: userMessageId,
|
|
3225
|
+
});
|
|
3226
|
+
if (taskNotificationItem) {
|
|
3227
|
+
return [taskNotificationItem];
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
const timeline = [];
|
|
3231
|
+
if (entry.type === "user") {
|
|
3232
|
+
const text = extractUserMessageText(content);
|
|
3233
|
+
if (text) {
|
|
3234
|
+
timeline.push({
|
|
3235
|
+
type: "user_message",
|
|
3236
|
+
text,
|
|
3237
|
+
...(userMessageId ? { messageId: userMessageId } : {}),
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
if (hasToolBlock && normalizedBlocks) {
|
|
3242
|
+
const mapped = mapBlocks(normalizedBlocks);
|
|
3243
|
+
if (entry.type === "user") {
|
|
3244
|
+
const toolItems = mapped.filter((item) => item.type === "tool_call");
|
|
3245
|
+
return timeline.length ? [...timeline, ...toolItems] : toolItems;
|
|
3246
|
+
}
|
|
3247
|
+
return mapped;
|
|
3248
|
+
}
|
|
3249
|
+
if (entry.type === "assistant" && contentValue) {
|
|
3250
|
+
return mapBlocks(contentValue);
|
|
3251
|
+
}
|
|
3252
|
+
return timeline;
|
|
3253
|
+
}
|
|
3254
|
+
function createAsyncMessageInput() {
|
|
3255
|
+
const queue = [];
|
|
3256
|
+
const resolvers = [];
|
|
3257
|
+
let closed = false;
|
|
3258
|
+
return {
|
|
3259
|
+
push(item) {
|
|
3260
|
+
if (closed) {
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
const resolve = resolvers.shift();
|
|
3264
|
+
if (resolve) {
|
|
3265
|
+
resolve({ value: item, done: false });
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
queue.push(item);
|
|
3269
|
+
},
|
|
3270
|
+
end() {
|
|
3271
|
+
closed = true;
|
|
3272
|
+
while (resolvers.length > 0) {
|
|
3273
|
+
const resolve = resolvers.shift();
|
|
3274
|
+
resolve?.({ value: undefined, done: true });
|
|
3275
|
+
}
|
|
3276
|
+
},
|
|
3277
|
+
iterable: {
|
|
3278
|
+
[Symbol.asyncIterator]() {
|
|
3279
|
+
return {
|
|
3280
|
+
next: () => {
|
|
3281
|
+
if (queue.length > 0) {
|
|
3282
|
+
const value = queue.shift();
|
|
3283
|
+
if (value !== undefined) {
|
|
3284
|
+
return Promise.resolve({ value, done: false });
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
if (closed) {
|
|
3288
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
3289
|
+
}
|
|
3290
|
+
return new Promise((resolve) => {
|
|
3291
|
+
resolvers.push(resolve);
|
|
3292
|
+
});
|
|
3293
|
+
},
|
|
3294
|
+
};
|
|
3295
|
+
},
|
|
3296
|
+
},
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
async function pathExists(target) {
|
|
3300
|
+
try {
|
|
3301
|
+
await fsPromises.access(target);
|
|
3302
|
+
return true;
|
|
3303
|
+
}
|
|
3304
|
+
catch {
|
|
3305
|
+
return false;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
async function collectRecentClaudeSessions(root, limit) {
|
|
3309
|
+
let projectDirs;
|
|
3310
|
+
try {
|
|
3311
|
+
projectDirs = await fsPromises.readdir(root);
|
|
3312
|
+
}
|
|
3313
|
+
catch {
|
|
3314
|
+
return [];
|
|
3315
|
+
}
|
|
3316
|
+
const candidates = [];
|
|
3317
|
+
for (const dirName of projectDirs) {
|
|
3318
|
+
const projectPath = path.join(root, dirName);
|
|
3319
|
+
let stats;
|
|
3320
|
+
try {
|
|
3321
|
+
stats = await fsPromises.stat(projectPath);
|
|
3322
|
+
}
|
|
3323
|
+
catch {
|
|
3324
|
+
continue;
|
|
3325
|
+
}
|
|
3326
|
+
if (!stats.isDirectory()) {
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
let files;
|
|
3330
|
+
try {
|
|
3331
|
+
files = await fsPromises.readdir(projectPath);
|
|
3332
|
+
}
|
|
3333
|
+
catch {
|
|
3334
|
+
continue;
|
|
3335
|
+
}
|
|
3336
|
+
for (const file of files) {
|
|
3337
|
+
if (!file.endsWith(".jsonl")) {
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3340
|
+
const fullPath = path.join(projectPath, file);
|
|
3341
|
+
try {
|
|
3342
|
+
const fileStats = await fsPromises.stat(fullPath);
|
|
3343
|
+
candidates.push({ path: fullPath, mtime: fileStats.mtime });
|
|
3344
|
+
}
|
|
3345
|
+
catch {
|
|
3346
|
+
// ignore stat errors for individual files
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
return candidates.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).slice(0, limit);
|
|
3351
|
+
}
|
|
3352
|
+
async function parseClaudeSessionDescriptor(filePath, mtime) {
|
|
3353
|
+
let content;
|
|
3354
|
+
try {
|
|
3355
|
+
content = await fsPromises.readFile(filePath, "utf8");
|
|
3356
|
+
}
|
|
3357
|
+
catch {
|
|
3358
|
+
return null;
|
|
3359
|
+
}
|
|
3360
|
+
let sessionId = null;
|
|
3361
|
+
let cwd = null;
|
|
3362
|
+
let title = null;
|
|
3363
|
+
const timeline = [];
|
|
3364
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
3365
|
+
const line = rawLine.trim();
|
|
3366
|
+
if (!line)
|
|
3367
|
+
continue;
|
|
3368
|
+
let entry;
|
|
3369
|
+
try {
|
|
3370
|
+
entry = JSON.parse(line);
|
|
3371
|
+
}
|
|
3372
|
+
catch {
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3375
|
+
if (entry?.isSidechain) {
|
|
3376
|
+
continue;
|
|
3377
|
+
}
|
|
3378
|
+
if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3381
|
+
if (!sessionId && typeof entry.sessionId === "string") {
|
|
3382
|
+
sessionId = entry.sessionId;
|
|
3383
|
+
}
|
|
3384
|
+
if (!cwd && typeof entry.cwd === "string") {
|
|
3385
|
+
cwd = entry.cwd;
|
|
3386
|
+
}
|
|
3387
|
+
if (entry.type === "user" && entry.message) {
|
|
3388
|
+
const text = extractClaudeUserText(entry.message);
|
|
3389
|
+
if (text) {
|
|
3390
|
+
if (!title) {
|
|
3391
|
+
title = text;
|
|
3392
|
+
}
|
|
3393
|
+
timeline.push({ type: "user_message", text });
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
else if (entry.type === "assistant" && entry.message) {
|
|
3397
|
+
const text = extractClaudeUserText(entry.message);
|
|
3398
|
+
if (text) {
|
|
3399
|
+
timeline.push({ type: "assistant_message", text });
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
if (sessionId && cwd && title) {
|
|
3403
|
+
break;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
if (!sessionId || !cwd) {
|
|
3407
|
+
return null;
|
|
3408
|
+
}
|
|
3409
|
+
const persistence = {
|
|
3410
|
+
provider: "claude",
|
|
3411
|
+
sessionId,
|
|
3412
|
+
nativeHandle: sessionId,
|
|
3413
|
+
metadata: {
|
|
3414
|
+
provider: "claude",
|
|
3415
|
+
cwd,
|
|
3416
|
+
},
|
|
3417
|
+
};
|
|
3418
|
+
return {
|
|
3419
|
+
provider: "claude",
|
|
3420
|
+
sessionId,
|
|
3421
|
+
cwd,
|
|
3422
|
+
title: (title ?? "").trim() || `Claude session ${sessionId.slice(0, 8)}`,
|
|
3423
|
+
lastActivityAt: mtime,
|
|
3424
|
+
persistence,
|
|
3425
|
+
timeline,
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
function extractClaudeUserText(message) {
|
|
3429
|
+
if (!message) {
|
|
3430
|
+
return null;
|
|
3431
|
+
}
|
|
3432
|
+
if (typeof message.content === "string") {
|
|
3433
|
+
const normalized = message.content.trim();
|
|
3434
|
+
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
|
3435
|
+
}
|
|
3436
|
+
if (typeof message.text === "string") {
|
|
3437
|
+
const normalized = message.text.trim();
|
|
3438
|
+
return normalized && !isClaudeTranscriptNoiseText(normalized) ? normalized : null;
|
|
3439
|
+
}
|
|
3440
|
+
if (Array.isArray(message.content)) {
|
|
3441
|
+
for (const block of message.content) {
|
|
3442
|
+
if (block && typeof block.text === "string") {
|
|
3443
|
+
const normalized = block.text.trim();
|
|
3444
|
+
if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
|
|
3445
|
+
return normalized;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
return null;
|
|
3451
|
+
}
|
|
3452
|
+
//# sourceMappingURL=claude-agent.js.map
|