@hyperdrive.bot/paseo-server 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +31 -0
- package/README.md +111 -0
- package/dist/scripts/dev-runner.js +37 -0
- package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
- package/dist/scripts/supervisor-entrypoint.js +141 -0
- package/dist/scripts/supervisor-log-config.js +31 -0
- package/dist/scripts/supervisor.js +252 -0
- package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
- package/dist/server/executable-resolution/executable-resolution.js +118 -0
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +65 -0
- package/dist/server/extensions/daemon-backend.d.ts +11 -0
- package/dist/server/extensions/daemon-backend.js +146 -0
- package/dist/server/extensions/extension-host-manager.d.ts +48 -0
- package/dist/server/extensions/extension-host-manager.js +141 -0
- package/dist/server/extensions/extension-loader.d.ts +11 -0
- package/dist/server/extensions/extension-loader.js +47 -0
- package/dist/server/extensions/extension-service.d.ts +17 -0
- package/dist/server/extensions/extension-service.js +76 -0
- package/dist/server/server/agent/activity-curator.d.ts +28 -0
- package/dist/server/server/agent/activity-curator.js +227 -0
- package/dist/server/server/agent/agent-archive.d.ts +11 -0
- package/dist/server/server/agent/agent-archive.js +16 -0
- package/dist/server/server/agent/agent-loading.d.ts +12 -0
- package/dist/server/server/agent/agent-loading.js +55 -0
- package/dist/server/server/agent/agent-manager.d.ts +462 -0
- package/dist/server/server/agent/agent-manager.js +2955 -0
- package/dist/server/server/agent/agent-projections.d.ts +26 -0
- package/dist/server/server/agent/agent-projections.js +404 -0
- package/dist/server/server/agent/agent-prompt.d.ts +76 -0
- package/dist/server/server/agent/agent-prompt.js +230 -0
- package/dist/server/server/agent/agent-response-loop.d.ts +67 -0
- package/dist/server/server/agent/agent-response-loop.js +326 -0
- package/dist/server/server/agent/agent-routes.d.ts +18 -0
- package/dist/server/server/agent/agent-routes.js +107 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +677 -0
- package/dist/server/server/agent/agent-sdk-types.js +22 -0
- package/dist/server/server/agent/agent-storage.d.ts +152 -0
- package/dist/server/server/agent/agent-storage.js +342 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +50 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +27 -0
- package/dist/server/server/agent/agent-timeline-store.js +230 -0
- package/dist/server/server/agent/audio-utils.d.ts +3 -0
- package/dist/server/server/agent/audio-utils.js +19 -0
- package/dist/server/server/agent/create-agent/create.d.ts +83 -0
- package/dist/server/server/agent/create-agent/create.js +289 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +54 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +152 -0
- package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
- package/dist/server/server/agent/create-agent-mode.js +62 -0
- package/dist/server/server/agent/create-agent-title.d.ts +10 -0
- package/dist/server/server/agent/create-agent-title.js +34 -0
- package/dist/server/server/agent/dictation-debug.d.ts +13 -0
- package/dist/server/server/agent/dictation-debug.js +50 -0
- package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
- package/dist/server/server/agent/foreground-run-state.js +160 -0
- package/dist/server/server/agent/import-sessions.d.ts +47 -0
- package/dist/server/server/agent/import-sessions.js +145 -0
- package/dist/server/server/agent/lifecycle-command.d.ts +73 -0
- package/dist/server/server/agent/lifecycle-command.js +92 -0
- package/dist/server/server/agent/mcp-server.d.ts +22 -0
- package/dist/server/server/agent/mcp-server.js +544 -0
- package/dist/server/server/agent/mcp-shared.d.ts +193 -0
- package/dist/server/server/agent/mcp-shared.js +197 -0
- package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
- package/dist/server/server/agent/pcm16-resampler.js +63 -0
- package/dist/server/server/agent/permission-response.d.ts +15 -0
- package/dist/server/server/agent/permission-response.js +14 -0
- package/dist/server/server/agent/prompt-attachments.d.ts +7 -0
- package/dist/server/server/agent/prompt-attachments.js +90 -0
- package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
- package/dist/server/server/agent/provider-history-timestamps.js +16 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +51 -0
- package/dist/server/server/agent/provider-launch-config.js +183 -0
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +49 -0
- package/dist/server/server/agent/provider-registry.js +578 -0
- package/dist/server/server/agent/provider-session-import.d.ts +10 -0
- package/dist/server/server/agent/provider-session-import.js +49 -0
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +137 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +694 -0
- package/dist/server/server/agent/providers/acp-agent.d.ts +354 -0
- package/dist/server/server/agent/providers/acp-agent.js +2438 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +341 -0
- package/dist/server/server/agent/providers/claude/agent.js +4606 -0
- package/dist/server/server/agent/providers/claude/background-shell-kill-keys.d.ts +32 -0
- package/dist/server/server/agent/providers/claude/background-shell-kill-keys.js +34 -0
- package/dist/server/server/agent/providers/claude/background-task-tracker.d.ts +49 -0
- package/dist/server/server/agent/providers/claude/background-task-tracker.js +119 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
- package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
- package/dist/server/server/agent/providers/claude/hook-handler.d.ts +37 -0
- package/dist/server/server/agent/providers/claude/hook-handler.js +193 -0
- package/dist/server/server/agent/providers/claude/hooks/event-translator.d.ts +49 -0
- package/dist/server/server/agent/providers/claude/hooks/event-translator.js +151 -0
- package/dist/server/server/agent/providers/claude/image-compression.d.ts +10 -0
- package/dist/server/server/agent/providers/claude/image-compression.js +32 -0
- package/dist/server/server/agent/providers/claude/model-manifest.d.ts +104 -0
- package/dist/server/server/agent/providers/claude/model-manifest.js +221 -0
- package/dist/server/server/agent/providers/claude/models.d.ts +11 -0
- package/dist/server/server/agent/providers/claude/models.js +97 -0
- package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
- package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
- package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
- package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
- package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +41 -0
- package/dist/server/server/agent/providers/claude/pty-session-launcher.js +131 -0
- package/dist/server/server/agent/providers/claude/resume-source.d.ts +11 -0
- package/dist/server/server/agent/providers/claude/resume-source.js +37 -0
- package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
- package/dist/server/server/agent/providers/claude/rewind.js +30 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +22 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +232 -0
- package/dist/server/server/agent/providers/claude/spawn-config-writer.d.ts +70 -0
- package/dist/server/server/agent/providers/claude/spawn-config-writer.js +229 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +47 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +292 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
- package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
- package/dist/server/server/agent/providers/claude/transport/claude-paths.d.ts +31 -0
- package/dist/server/server/agent/providers/claude/transport/claude-paths.js +63 -0
- package/dist/server/server/agent/providers/claude/transport/continue-flag.d.ts +31 -0
- package/dist/server/server/agent/providers/claude/transport/continue-flag.js +42 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +62 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +265 -0
- package/dist/server/server/agent/providers/claude/transport/pty.d.ts +50 -0
- package/dist/server/server/agent/providers/claude/transport/pty.js +211 -0
- package/dist/server/server/agent/providers/claude/transport/redact.d.ts +2 -0
- package/dist/server/server/agent/providers/claude/transport/redact.js +5 -0
- package/dist/server/server/agent/providers/claude/transport/sdk.d.ts +63 -0
- package/dist/server/server/agent/providers/claude/transport/sdk.js +207 -0
- package/dist/server/server/agent/providers/claude/transport/transcript-sdk-reader.d.ts +27 -0
- package/dist/server/server/agent/providers/claude/transport/transcript-sdk-reader.js +162 -0
- package/dist/server/server/agent/providers/claude/transport/transcript-tailer.d.ts +28 -0
- package/dist/server/server/agent/providers/claude/transport/transcript-tailer.js +166 -0
- package/dist/server/server/agent/providers/claude/transport/truncate-session.d.ts +41 -0
- package/dist/server/server/agent/providers/claude/transport/truncate-session.js +86 -0
- package/dist/server/server/agent/providers/claude/transport/types.d.ts +47 -0
- package/dist/server/server/agent/providers/claude/transport/types.js +2 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +90 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.js +262 -0
- package/dist/server/server/agent/providers/codex/rewind.d.ts +21 -0
- package/dist/server/server/agent/providers/codex/rewind.js +46 -0
- package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.d.ts +36 -0
- package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.js +217 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +830 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +308 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js +4514 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
- package/dist/server/server/agent/providers/codex-feature-definitions.js +45 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
- package/dist/server/server/agent/providers/copilot-acp-agent.js +187 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +15 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +18 -0
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +355 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +42 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +161 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +85 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +1010 -0
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
- package/dist/server/server/agent/providers/mock-slow-provider.js +39 -0
- package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/paths.js +7 -0
- package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +91 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +425 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +25 -0
- package/dist/server/server/agent/providers/opencode/test-server-manager.js +39 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.d.ts +92 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.js +268 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +286 -0
- package/dist/server/server/agent/providers/opencode-agent.js +2857 -0
- package/dist/server/server/agent/providers/pi/agent.d.ts +137 -0
- package/dist/server/server/agent/providers/pi/agent.js +1650 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
- package/dist/server/server/agent/providers/pi/cli-runtime.js +233 -0
- package/dist/server/server/agent/providers/pi/history-mapper.d.ts +9 -0
- package/dist/server/server/agent/providers/pi/history-mapper.js +140 -0
- package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
- package/dist/server/server/agent/providers/pi/rewind.js +8 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +208 -0
- package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
- package/dist/server/server/agent/providers/pi/runtime.d.ts +57 -0
- package/dist/server/server/agent/providers/pi/runtime.js +56 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
- package/dist/server/server/agent/providers/pi/session-descriptor.js +346 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +83 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +184 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +112 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.js +284 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
- package/dist/server/server/agent/providers/provider-image-output.js +111 -0
- package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
- package/dist/server/server/agent/providers/provider-runner.js +81 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +60 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
- package/dist/server/server/agent/recordings-debug.d.ts +3 -0
- package/dist/server/server/agent/recordings-debug.js +19 -0
- package/dist/server/server/agent/render-file-attachment.d.ts +29 -0
- package/dist/server/server/agent/render-file-attachment.js +41 -0
- package/dist/server/server/agent/resolve-resume-cwd.d.ts +14 -0
- package/dist/server/server/agent/resolve-resume-cwd.js +49 -0
- package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
- package/dist/server/server/agent/rewind/rewind.js +29 -0
- package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
- package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
- package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
- package/dist/server/server/agent/runtime-mcp-config.js +53 -0
- package/dist/server/server/agent/session-digest-generator.d.ts +37 -0
- package/dist/server/server/agent/session-digest-generator.js +115 -0
- package/dist/server/server/agent/session-digest.d.ts +35 -0
- package/dist/server/server/agent/session-digest.js +104 -0
- package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
- package/dist/server/server/agent/structured-generation-providers.js +236 -0
- package/dist/server/server/agent/stt-debug.d.ts +10 -0
- package/dist/server/server/agent/stt-debug.js +33 -0
- package/dist/server/server/agent/stt-manager.d.ts +38 -0
- package/dist/server/server/agent/stt-manager.js +246 -0
- package/dist/server/server/agent/system-prompt.d.ts +2 -0
- package/dist/server/server/agent/system-prompt.js +8 -0
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-projection.d.ts +71 -0
- package/dist/server/server/agent/timeline-projection.js +373 -0
- package/dist/server/server/agent/tools/paseo-tools.d.ts +54 -0
- package/dist/server/server/agent/tools/paseo-tools.js +2197 -0
- package/dist/server/server/agent/tools/types.d.ts +36 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/agent/tts-debug.d.ts +8 -0
- package/dist/server/server/agent/tts-debug.js +24 -0
- package/dist/server/server/agent/tts-manager.d.ts +41 -0
- package/dist/server/server/agent/tts-manager.js +375 -0
- package/dist/server/server/agent/wait-for-agent-tracker.d.ts +15 -0
- package/dist/server/server/agent/wait-for-agent-tracker.js +53 -0
- package/dist/server/server/agent-attention-policy.d.ts +29 -0
- package/dist/server/server/agent-attention-policy.js +36 -0
- package/dist/server/server/atomic-file.d.ts +3 -0
- package/dist/server/server/atomic-file.js +19 -0
- package/dist/server/server/auth.d.ts +38 -0
- package/dist/server/server/auth.js +143 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +92 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts +8 -0
- package/dist/server/server/auto-archive-on-merge/index.js +15 -0
- package/dist/server/server/bootstrap.d.ts +126 -0
- package/dist/server/server/bootstrap.js +1193 -0
- package/dist/server/server/browser-tools/broker.d.ts +45 -0
- package/dist/server/server/browser-tools/broker.js +390 -0
- package/dist/server/server/browser-tools/errors.d.ts +22 -0
- package/dist/server/server/browser-tools/errors.js +27 -0
- package/dist/server/server/browser-tools/policy.d.ts +10 -0
- package/dist/server/server/browser-tools/policy.js +16 -0
- package/dist/server/server/browser-tools/tools.d.ts +16 -0
- package/dist/server/server/browser-tools/tools.js +845 -0
- package/dist/server/server/chat/chat-mentions.d.ts +47 -0
- package/dist/server/server/chat/chat-mentions.js +113 -0
- package/dist/server/server/chat/chat-service.d.ts +78 -0
- package/dist/server/server/chat/chat-service.js +337 -0
- package/dist/server/server/checkout/status-projection.d.ts +19 -0
- package/dist/server/server/checkout/status-projection.js +102 -0
- package/dist/server/server/checkout-diff-manager.d.ts +41 -0
- package/dist/server/server/checkout-diff-manager.js +189 -0
- package/dist/server/server/checkout-git-utils.d.ts +11 -0
- package/dist/server/server/checkout-git-utils.js +34 -0
- package/dist/server/server/client-message-id.d.ts +3 -0
- package/dist/server/server/client-message-id.js +12 -0
- package/dist/server/server/config.d.ts +17 -0
- package/dist/server/server/config.js +320 -0
- package/dist/server/server/connection-offer.d.ts +20 -0
- package/dist/server/server/connection-offer.js +59 -0
- package/dist/server/server/daemon-config-store.d.ts +25 -0
- package/dist/server/server/daemon-config-store.js +194 -0
- package/dist/server/server/daemon-keypair.d.ts +8 -0
- package/dist/server/server/daemon-keypair.js +42 -0
- package/dist/server/server/daemon-version.d.ts +5 -0
- package/dist/server/server/daemon-version.js +22 -0
- package/dist/server/server/daemon-worker.d.ts +2 -0
- package/dist/server/server/daemon-worker.js +251 -0
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +87 -0
- package/dist/server/server/dictation/dictation-stream-manager.js +572 -0
- package/dist/server/server/editor-targets.d.ts +18 -0
- package/dist/server/server/editor-targets.js +109 -0
- package/dist/server/server/exports.d.ts +24 -0
- package/dist/server/server/exports.js +24 -0
- package/dist/server/server/file-download/token-store.d.ts +24 -0
- package/dist/server/server/file-download/token-store.js +40 -0
- package/dist/server/server/file-explorer/file-download-frames.d.ts +27 -0
- package/dist/server/server/file-explorer/file-download-frames.js +47 -0
- package/dist/server/server/file-explorer/service.d.ts +75 -0
- package/dist/server/server/file-explorer/service.js +308 -0
- package/dist/server/server/file-upload/index.d.ts +27 -0
- package/dist/server/server/file-upload/index.js +158 -0
- package/dist/server/server/hostnames.d.ts +13 -0
- package/dist/server/server/hostnames.js +94 -0
- package/dist/server/server/json-utils.d.ts +11 -0
- package/dist/server/server/json-utils.js +45 -0
- package/dist/server/server/lifecycle-reasons.d.ts +4 -0
- package/dist/server/server/lifecycle-reasons.js +6 -0
- package/dist/server/server/logger.d.ts +29 -0
- package/dist/server/server/logger.js +125 -0
- package/dist/server/server/loop-service.d.ts +234 -0
- package/dist/server/server/loop-service.js +769 -0
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/meetings/capture-http.d.ts +3 -0
- package/dist/server/server/meetings/capture-http.js +71 -0
- package/dist/server/server/meetings/capture-orchestrator.d.ts +123 -0
- package/dist/server/server/meetings/capture-orchestrator.js +531 -0
- package/dist/server/server/meetings/capture-stream-ws.d.ts +42 -0
- package/dist/server/server/meetings/capture-stream-ws.js +68 -0
- package/dist/server/server/meetings/live-transcription.d.ts +57 -0
- package/dist/server/server/meetings/live-transcription.js +280 -0
- package/dist/server/server/meetings/meetings-http.d.ts +42 -0
- package/dist/server/server/meetings/meetings-http.js +266 -0
- package/dist/server/server/messages.d.ts +9 -0
- package/dist/server/server/messages.js +29 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
- package/dist/server/server/monitoring/telemetry.d.ts +13 -0
- package/dist/server/server/monitoring/telemetry.js +21 -0
- package/dist/server/server/package-version.d.ts +19 -0
- package/dist/server/server/package-version.js +60 -0
- package/dist/server/server/pagination/cursor.d.ts +16 -0
- package/dist/server/server/pagination/cursor.js +62 -0
- package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
- package/dist/server/server/pagination/sortable-pager.js +68 -0
- package/dist/server/server/pairing-offer.d.ts +18 -0
- package/dist/server/server/pairing-offer.js +47 -0
- package/dist/server/server/pairing-qr.d.ts +7 -0
- package/dist/server/server/pairing-qr.js +45 -0
- package/dist/server/server/paseo-env.d.ts +13 -0
- package/dist/server/server/paseo-env.js +53 -0
- package/dist/server/server/paseo-home.d.ts +2 -0
- package/dist/server/server/paseo-home.js +19 -0
- package/dist/server/server/paseo-worktree-service.d.ts +52 -0
- package/dist/server/server/paseo-worktree-service.js +294 -0
- package/dist/server/server/path-utils.d.ts +5 -0
- package/dist/server/server/path-utils.js +34 -0
- package/dist/server/server/persisted-config.d.ts +333 -0
- package/dist/server/server/persisted-config.js +402 -0
- package/dist/server/server/persistence-hooks.d.ts +32 -0
- package/dist/server/server/persistence-hooks.js +97 -0
- package/dist/server/server/pid-lock.d.ts +32 -0
- package/dist/server/server/pid-lock.js +170 -0
- package/dist/server/server/private-files.d.ts +6 -0
- package/dist/server/server/private-files.js +37 -0
- package/dist/server/server/process-diagnostics.d.ts +17 -0
- package/dist/server/server/process-diagnostics.js +22 -0
- package/dist/server/server/push/categories.d.ts +35 -0
- package/dist/server/server/push/categories.js +40 -0
- package/dist/server/server/push/expo-worker.d.ts +51 -0
- package/dist/server/server/push/expo-worker.js +149 -0
- package/dist/server/server/push/index.d.ts +39 -0
- package/dist/server/server/push/index.js +45 -0
- package/dist/server/server/push/notifications.d.ts +9 -0
- package/dist/server/server/push/notifications.js +15 -0
- package/dist/server/server/push/push-service.d.ts +20 -0
- package/dist/server/server/push/push-service.js +66 -0
- package/dist/server/server/push/routes.d.ts +28 -0
- package/dist/server/server/push/routes.js +74 -0
- package/dist/server/server/push/subscriptions.d.ts +42 -0
- package/dist/server/server/push/subscriptions.js +112 -0
- package/dist/server/server/push/token-store.d.ts +29 -0
- package/dist/server/server/push/token-store.js +78 -0
- package/dist/server/server/push/triggers.d.ts +68 -0
- package/dist/server/server/push/triggers.js +103 -0
- package/dist/server/server/push/vapid-keys.d.ts +15 -0
- package/dist/server/server/push/vapid-keys.js +52 -0
- package/dist/server/server/push/web-worker.d.ts +44 -0
- package/dist/server/server/push/web-worker.js +99 -0
- package/dist/server/server/relay-transport.d.ts +31 -0
- package/dist/server/server/relay-transport.js +514 -0
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +32 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +121 -0
- package/dist/server/server/schedule/cron.d.ts +4 -0
- package/dist/server/server/schedule/cron.js +81 -0
- package/dist/server/server/schedule/service.d.ts +52 -0
- package/dist/server/server/schedule/service.js +628 -0
- package/dist/server/server/schedule/store.d.ts +13 -0
- package/dist/server/server/schedule/store.js +57 -0
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.js +159 -0
- package/dist/server/server/script-proxy.d.ts +3 -0
- package/dist/server/server/script-proxy.js +2 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.js +11 -0
- package/dist/server/server/script-status-projection.d.ts +36 -0
- package/dist/server/server/script-status-projection.js +200 -0
- package/dist/server/server/server-id.d.ts +17 -0
- package/dist/server/server/server-id.js +68 -0
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +714 -0
- package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
- package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +140 -0
- package/dist/server/server/session/checkout/checkout-session.js +927 -0
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
- package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +76 -0
- package/dist/server/server/session/daemon/daemon-session.js +148 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +431 -0
- package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
- package/dist/server/server/session/daemon/install-origin.js +64 -0
- package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
- package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
- package/dist/server/server/session/files/workspace-files-session.d.ts +43 -0
- package/dist/server/server/session/files/workspace-files-session.js +218 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
- package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
- package/dist/server/server/session/project-config/project-config-session.js +125 -0
- package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +343 -0
- package/dist/server/server/session/session-rename-route.d.ts +4 -0
- package/dist/server/server/session/session-rename-route.js +71 -0
- package/dist/server/server/session/session-rename-service.d.ts +25 -0
- package/dist/server/server/session/session-rename-service.js +76 -0
- package/dist/server/server/session/voice/voice-session.d.ts +166 -0
- package/dist/server/server/session/voice/voice-session.js +893 -0
- package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
- package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
- package/dist/server/server/session.d.ts +562 -0
- package/dist/server/server/session.js +5787 -0
- package/dist/server/server/speech/audio.d.ts +10 -0
- package/dist/server/server/speech/audio.js +101 -0
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/providers/local/config.d.ts +30 -0
- package/dist/server/server/speech/providers/local/config.js +134 -0
- package/dist/server/server/speech/providers/local/models.d.ts +11 -0
- package/dist/server/server/speech/providers/local/models.js +17 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
- package/dist/server/server/speech/providers/local/runtime.js +135 -0
- package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +56 -0
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +74 -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.js +142 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +21 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +101 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
- package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
- package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
- package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
- package/dist/server/server/speech/providers/local/worker-client.js +611 -0
- package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
- package/dist/server/server/speech/providers/local/worker-process.js +270 -0
- package/dist/server/server/speech/providers/local/worker-protocol.d.ts +95 -0
- package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
- package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/config.js +148 -0
- package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
- package/dist/server/server/speech/providers/openai/runtime.js +102 -0
- package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
- package/dist/server/server/speech/providers/openai/stt.js +209 -0
- package/dist/server/server/speech/providers/openai/tts.d.ts +19 -0
- package/dist/server/server/speech/providers/openai/tts.js +48 -0
- package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
- package/dist/server/server/speech/speech-config-resolver.js +132 -0
- package/dist/server/server/speech/speech-provider.d.ts +59 -0
- package/dist/server/server/speech/speech-provider.js +2 -0
- package/dist/server/server/speech/speech-runtime.d.ts +45 -0
- package/dist/server/server/speech/speech-runtime.js +562 -0
- package/dist/server/server/speech/speech-types.d.ts +22 -0
- package/dist/server/server/speech/speech-types.js +8 -0
- package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
- package/dist/server/server/speech/turn-detection-provider.js +2 -0
- package/dist/server/server/uploads/file-attachment-validation.d.ts +61 -0
- package/dist/server/server/uploads/file-attachment-validation.js +56 -0
- package/dist/server/server/uploads/staging-service.d.ts +91 -0
- package/dist/server/server/uploads/staging-service.js +144 -0
- package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
- package/dist/server/server/utils/diff-highlighter.js +279 -0
- package/dist/server/server/voice/voice-turn-controller.d.ts +38 -0
- package/dist/server/server/voice/voice-turn-controller.js +393 -0
- package/dist/server/server/voice-config.d.ts +15 -0
- package/dist/server/server/voice-config.js +54 -0
- package/dist/server/server/voice-permission-policy.d.ts +4 -0
- package/dist/server/server/voice-permission-policy.js +13 -0
- package/dist/server/server/voice-types.d.ts +12 -0
- package/dist/server/server/voice-types.js +2 -0
- package/dist/server/server/watch/composer-dictate.d.ts +42 -0
- package/dist/server/server/watch/composer-dictate.js +37 -0
- package/dist/server/server/watch/notification-action.d.ts +38 -0
- package/dist/server/server/watch/notification-action.js +54 -0
- package/dist/server/server/watch/notification-registry.d.ts +16 -0
- package/dist/server/server/watch/notification-registry.js +2 -0
- package/dist/server/server/web-ui.d.ts +10 -0
- package/dist/server/server/web-ui.js +205 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +94 -0
- package/dist/server/server/websocket/runtime-metrics.js +148 -0
- package/dist/server/server/websocket-server.d.ts +193 -0
- package/dist/server/server/websocket-server.js +1768 -0
- package/dist/server/server/workflow/workflow-feature.d.ts +2 -0
- package/dist/server/server/workflow/workflow-feature.js +8 -0
- package/dist/server/server/workflow/workflow-http-routes.d.ts +27 -0
- package/dist/server/server/workflow/workflow-http-routes.js +97 -0
- package/dist/server/server/workflow/workflow-manager.d.ts +228 -0
- package/dist/server/server/workflow/workflow-manager.js +444 -0
- package/dist/server/server/workflow/workflow-progress.d.ts +95 -0
- package/dist/server/server/workflow/workflow-progress.js +178 -0
- package/dist/server/server/workflow/workflow-storage.d.ts +36 -0
- package/dist/server/server/workflow/workflow-storage.js +143 -0
- package/dist/server/server/workspace/all-sessions-route.d.ts +32 -0
- package/dist/server/server/workspace/all-sessions-route.js +138 -0
- package/dist/server/server/workspace/session-openable.d.ts +16 -0
- package/dist/server/server/workspace/session-openable.js +48 -0
- package/dist/server/server/workspace/sessions-route.d.ts +15 -0
- package/dist/server/server/workspace/sessions-route.js +108 -0
- package/dist/server/server/workspace/sessions-search-route.d.ts +68 -0
- package/dist/server/server/workspace/sessions-search-route.js +203 -0
- package/dist/server/server/workspace-archive-service.d.ts +65 -0
- package/dist/server/server/workspace-archive-service.js +229 -0
- package/dist/server/server/workspace-auto-name.d.ts +55 -0
- package/dist/server/server/workspace-auto-name.js +128 -0
- package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
- package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
- package/dist/server/server/workspace-directory.d.ts +93 -0
- package/dist/server/server/workspace-directory.js +422 -0
- package/dist/server/server/workspace-git-metadata.d.ts +24 -0
- package/dist/server/server/workspace-git-metadata.js +55 -0
- package/dist/server/server/workspace-git-service.d.ts +257 -0
- package/dist/server/server/workspace-git-service.js +1386 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts +57 -0
- package/dist/server/server/workspace-reconciliation-service.js +278 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts +13 -0
- package/dist/server/server/workspace-registry-bootstrap.js +121 -0
- package/dist/server/server/workspace-registry-model.d.ts +55 -0
- package/dist/server/server/workspace-registry-model.js +190 -0
- package/dist/server/server/workspace-registry.d.ts +117 -0
- package/dist/server/server/workspace-registry.js +194 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-service-env.d.ts +18 -0
- package/dist/server/server/workspace-service-env.js +85 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
- package/dist/server/server/workspace-service-port-registry.js +59 -0
- package/dist/server/server/worktree/commands.d.ts +55 -0
- package/dist/server/server/worktree/commands.js +107 -0
- package/dist/server/server/worktree-bootstrap.d.ts +76 -0
- package/dist/server/server/worktree-bootstrap.js +720 -0
- package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
- package/dist/server/server/worktree-branch-name-generator.js +91 -0
- package/dist/server/server/worktree-core.d.ts +31 -0
- package/dist/server/server/worktree-core.js +105 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.js +28 -0
- package/dist/server/server/worktree-session.d.ts +152 -0
- package/dist/server/server/worktree-session.js +466 -0
- package/dist/server/services/github-service.d.ts +337 -0
- package/dist/server/services/github-service.js +2101 -0
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +52 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
- package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/server/terminal/terminal-capture.d.ts +12 -0
- package/dist/server/terminal/terminal-capture.js +43 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
- package/dist/server/terminal/terminal-manager-factory.js +5 -0
- package/dist/server/terminal/terminal-manager.d.ts +78 -0
- package/dist/server/terminal/terminal-manager.js +319 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
- package/dist/server/terminal/terminal-output-coalescer.js +73 -0
- package/dist/server/terminal/terminal-restore.d.ts +18 -0
- package/dist/server/terminal/terminal-restore.js +52 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +85 -0
- package/dist/server/terminal/terminal-session-controller.js +819 -0
- package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
- package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
- package/dist/server/terminal/terminal-worker-process.js +304 -0
- package/dist/server/terminal/terminal-worker-protocol.d.ts +119 -0
- package/dist/server/terminal/terminal-worker-protocol.js +2 -0
- package/dist/server/terminal/terminal.d.ts +151 -0
- package/dist/server/terminal/terminal.js +1161 -0
- package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
- package/dist/server/terminal/worker-terminal-manager.js +688 -0
- package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
- package/dist/server/utils/build-metadata-prompt.js +29 -0
- package/dist/server/utils/checkout-git.d.ts +253 -0
- package/dist/server/utils/checkout-git.js +2238 -0
- package/dist/server/utils/directory-suggestions.d.ts +26 -0
- package/dist/server/utils/directory-suggestions.js +830 -0
- package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
- package/dist/server/utils/git-rev-parse-path.js +21 -0
- package/dist/server/utils/github-remote.d.ts +9 -0
- package/dist/server/utils/github-remote.js +62 -0
- package/dist/server/utils/paseo-config-file.d.ts +30 -0
- package/dist/server/utils/paseo-config-file.js +90 -0
- package/dist/server/utils/path.d.ts +18 -0
- package/dist/server/utils/path.js +123 -0
- package/dist/server/utils/project-icon.d.ts +39 -0
- package/dist/server/utils/project-icon.js +364 -0
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/run-git-command.d.ts +37 -0
- package/dist/server/utils/run-git-command.js +232 -0
- package/dist/server/utils/spawn.d.ts +33 -0
- package/dist/server/utils/spawn.js +71 -0
- package/dist/server/utils/string-command-shell.d.ts +10 -0
- package/dist/server/utils/string-command-shell.js +21 -0
- package/dist/server/utils/tree-kill.d.ts +18 -0
- package/dist/server/utils/tree-kill.js +77 -0
- package/dist/server/utils/windows-command.d.ts +15 -0
- package/dist/server/utils/windows-command.js +41 -0
- package/dist/server/utils/worktree-metadata.d.ts +39 -0
- package/dist/server/utils/worktree-metadata.js +174 -0
- package/dist/server/utils/worktree.d.ts +205 -0
- package/dist/server/utils/worktree.js +1119 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js +5 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-127b8d280bb901a810b0c9fa094d4945.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-0e6ed782245f4dda6e0a73171a133d45.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js +16533 -0
- package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-b31c4d6fb6383454381e43410736cb89.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-0d03551c4ea3780f5b81db6a7aebb017.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js +5 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-feda55adc8031781db083743ca73cbc3.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js +11 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-593c9481537cb12f4b6c17ef48bcba1c.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js +60 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-533a4fe1acfc37460ac2912610062d3a.js.map.gz +0 -0
- package/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
- package/dist/server/web-ui/favicon.ico +0 -0
- package/dist/server/web-ui/index.html +90 -0
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/server/web-ui/manifest.json +27 -0
- package/dist/server/web-ui/manifest.json.br +0 -0
- package/dist/server/web-ui/manifest.json.gz +0 -0
- package/dist/server/web-ui/metadata.json +1 -0
- package/dist/server/web-ui/metadata.json.br +1 -0
- package/dist/server/web-ui/metadata.json.gz +0 -0
- package/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/dist/server/web-ui/robots.txt +2 -0
- package/dist/server/web-ui/service-worker.js +87 -0
- package/dist/server/web-ui/service-worker.js.br +0 -0
- package/dist/server/web-ui/service-worker.js.gz +0 -0
- package/dist/src/executable-resolution/executable-resolution.js +118 -0
- package/dist/src/executable-resolution/windows.js +65 -0
- package/dist/src/server/agent/provider-launch-config.js +183 -0
- package/dist/src/server/paseo-env.js +53 -0
- package/dist/src/server/paseo-home.js +19 -0
- package/dist/src/server/persisted-config.js +402 -0
- package/dist/src/server/pid-lock.js +170 -0
- package/dist/src/server/private-files.js +37 -0
- package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/src/utils/spawn.js +71 -0
- package/dist/src/utils/windows-command.js +41 -0
- package/package.json +143 -0
|
@@ -0,0 +1,4606 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { promises } from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { mapClaudeCanceledToolCall, mapClaudeCompletedToolCall, mapClaudeFailedToolCall, mapClaudeRunningToolCall, } from "./tool-call-mapper.js";
|
|
7
|
+
import { mapTaskNotificationSystemRecordToToolCall, mapTaskNotificationUserContentToToolCall, } from "./task-notification-tool-call.js";
|
|
8
|
+
import { findClaudeModel, getClaudeModelsWithSettings, normalizeClaudeRuntimeModelId, } from "./models.js";
|
|
9
|
+
import { CLAUDE_ULTRACODE_THINKING_OPTION_ID } from "./model-manifest.js";
|
|
10
|
+
import { parsePartialJsonObject } from "./partial-json.js";
|
|
11
|
+
import { resolveTranscriptPath, findTranscriptAcrossProjects } from "./transport/claude-paths.js";
|
|
12
|
+
import { ClaudeSidechainTracker } from "./sidechain-tracker.js";
|
|
13
|
+
import { ClaudeBackgroundTaskTracker } from "./background-task-tracker.js";
|
|
14
|
+
import { buildBackgroundKillPresses } from "./background-shell-kill-keys.js";
|
|
15
|
+
import { extractTaskNotificationLifecycleFromSystemRecord, extractTaskNotificationLifecycleFromUserContent, } from "./task-notification-tool-call.js";
|
|
16
|
+
import { buildClaudeFeatures, claudeModelSupportsFastMode } from "./feature-definitions.js";
|
|
17
|
+
import { buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, formatProviderDiagnostic, formatProviderDiagnosticError, } from "../diagnostic-utils.js";
|
|
18
|
+
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "../provider-runner.js";
|
|
19
|
+
import { renderPromptAttachmentAsText } from "../../prompt-attachments.js";
|
|
20
|
+
import { compressImageForApi } from "./image-compression.js";
|
|
21
|
+
import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from "./rewind.js";
|
|
22
|
+
import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
|
|
23
|
+
import { composeSystemPromptParts } from "../../system-prompt.js";
|
|
24
|
+
import { SdkTransport } from "./transport/sdk.js";
|
|
25
|
+
import { PtyTransport } from "./transport/pty.js";
|
|
26
|
+
import { createPtySession } from "./pty-session-launcher.js";
|
|
27
|
+
import { trackTransportSpawned } from "../../../monitoring/telemetry.js";
|
|
28
|
+
import { isProviderImageMarkdown, materializeProviderImage, renderProviderImageOutputAsAssistantMarkdown, } from "../provider-image-output.js";
|
|
29
|
+
import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
|
|
30
|
+
import { importSessionFromPersistence } from "../../provider-session-import.js";
|
|
31
|
+
import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
|
|
32
|
+
import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
|
|
33
|
+
import { isNodePtyAvailable, NODE_PTY_UNAVAILABLE_REASON, resolveClaudeTransport, } from "../../provider-registry.js";
|
|
34
|
+
import { isCommandAvailable } from "../../../../executable-resolution/executable-resolution.js";
|
|
35
|
+
import { withTimeout } from "../../../../utils/promise-timeout.js";
|
|
36
|
+
import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
|
|
37
|
+
import { execCommand } from "../../../../utils/spawn.js";
|
|
38
|
+
const fsPromises = promises;
|
|
39
|
+
const CLAUDE_SETTING_SOURCES = [
|
|
40
|
+
"user",
|
|
41
|
+
"project",
|
|
42
|
+
"local",
|
|
43
|
+
];
|
|
44
|
+
function readNonEmptyString(value) {
|
|
45
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
46
|
+
}
|
|
47
|
+
export function normalizeClaudeAskUserQuestionRequestInput(toolName, input) {
|
|
48
|
+
if (toolName !== "AskUserQuestion" || !Array.isArray(input.questions)) {
|
|
49
|
+
return input;
|
|
50
|
+
}
|
|
51
|
+
// Claude Code's AskUserQuestion schema says "Other" is host-provided, not a
|
|
52
|
+
// model-supplied option. Paseo's shared question UI uses allowOther for that
|
|
53
|
+
// freeform answer path.
|
|
54
|
+
return {
|
|
55
|
+
...input,
|
|
56
|
+
questions: input.questions.map((item) => {
|
|
57
|
+
if (!isMetadata(item)) {
|
|
58
|
+
return item;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...item,
|
|
62
|
+
allowOther: true,
|
|
63
|
+
};
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function stripClaudeAskUserQuestionUiMetadata(input) {
|
|
68
|
+
if (!Array.isArray(input.questions)) {
|
|
69
|
+
return input;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...input,
|
|
73
|
+
questions: input.questions.map((item) => {
|
|
74
|
+
if (!isMetadata(item) || !("allowOther" in item)) {
|
|
75
|
+
return item;
|
|
76
|
+
}
|
|
77
|
+
const itemForClaude = { ...item };
|
|
78
|
+
delete itemForClaude.allowOther;
|
|
79
|
+
return itemForClaude;
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function normalizeClaudeAskUserQuestionUpdatedInput(updatedInput, fallbackInput) {
|
|
84
|
+
const fallback = isMetadata(fallbackInput) ? fallbackInput : {};
|
|
85
|
+
const base = isMetadata(updatedInput) ? updatedInput : {};
|
|
86
|
+
// Paseo's shared question UI serializes answers by question header, but Claude's
|
|
87
|
+
// AskUserQuestion tool expects answer keys to match the full question text. Merge
|
|
88
|
+
// the original request payload back in so provider callbacks that only return
|
|
89
|
+
// `{ answers }` still satisfy Claude's full tool input schema.
|
|
90
|
+
const merged = stripClaudeAskUserQuestionUiMetadata({ ...fallback, ...base });
|
|
91
|
+
const questions = (Array.isArray(base.questions) ? base.questions : null) ??
|
|
92
|
+
(Array.isArray(fallback.questions) ? fallback.questions : null);
|
|
93
|
+
const answers = isMetadata(base.answers) ? base.answers : null;
|
|
94
|
+
if (!questions || !answers) {
|
|
95
|
+
return merged;
|
|
96
|
+
}
|
|
97
|
+
const normalizedAnswers = {};
|
|
98
|
+
for (const item of questions) {
|
|
99
|
+
const question = isMetadata(item) ? item : null;
|
|
100
|
+
if (!question) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const questionText = readNonEmptyString(question.question);
|
|
104
|
+
if (!questionText) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const header = readNonEmptyString(question.header);
|
|
108
|
+
const answer = readNonEmptyString(answers[questionText]) ??
|
|
109
|
+
(header ? readNonEmptyString(answers[header]) : null);
|
|
110
|
+
if (answer) {
|
|
111
|
+
normalizedAnswers[questionText] = answer;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (Object.keys(normalizedAnswers).length === 0) {
|
|
115
|
+
return merged;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
...merged,
|
|
119
|
+
answers: normalizedAnswers,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function isObjectRecord(value) {
|
|
123
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
124
|
+
}
|
|
125
|
+
function toObjectRecord(value) {
|
|
126
|
+
return isObjectRecord(value) ? value : undefined;
|
|
127
|
+
}
|
|
128
|
+
function isUnknownArray(value) {
|
|
129
|
+
return Array.isArray(value);
|
|
130
|
+
}
|
|
131
|
+
function isImageMimeType(value) {
|
|
132
|
+
return (value === "image/jpeg" ||
|
|
133
|
+
value === "image/png" ||
|
|
134
|
+
value === "image/gif" ||
|
|
135
|
+
value === "image/webp");
|
|
136
|
+
}
|
|
137
|
+
// Anthropic enforces a 32MB cap on a single API request body. A long session that reads
|
|
138
|
+
// many images (e.g. iterating video frames) accumulates base64 image blocks in the SDK's
|
|
139
|
+
// conversation history and eventually breaches the cap mid-turn with a cryptic upstream
|
|
140
|
+
// error. We compress on input and track cumulative bytes so we either stay well under
|
|
141
|
+
// the cap or fail fast with a clean message before the SDK does.
|
|
142
|
+
const CUMULATIVE_IMAGE_WARN_BYTES = 25 * 1024 * 1024;
|
|
143
|
+
const CUMULATIVE_IMAGE_LIMIT_BYTES = 30 * 1024 * 1024;
|
|
144
|
+
// Stub: rewind plumbing is dormant after the PTY migration. The SDK transport
|
|
145
|
+
// used to expose a `--version` resolver here; the PTY transport reads version
|
|
146
|
+
// via `claude --version` at spawn time and caches it on the session. Returning
|
|
147
|
+
// undefined makes the version telemetry log "unknown" until the PTY-flavored
|
|
148
|
+
// rewind/version resolver is wired up. Tracked in docs/tech-debt.md.
|
|
149
|
+
async function resolveClaudeVersion(_runtimeSettings) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const CLAUDE_CAPABILITIES = {
|
|
153
|
+
supportsStreaming: true,
|
|
154
|
+
supportsSessionPersistence: true,
|
|
155
|
+
supportsSessionListing: true,
|
|
156
|
+
supportsDynamicModes: true,
|
|
157
|
+
supportsMcpServers: true,
|
|
158
|
+
supportsReasoningStream: true,
|
|
159
|
+
supportsToolInvocations: true,
|
|
160
|
+
supportsRewindConversation: true,
|
|
161
|
+
supportsRewindFiles: true,
|
|
162
|
+
supportsRewindBoth: true,
|
|
163
|
+
};
|
|
164
|
+
const DEFAULT_MODES = [
|
|
165
|
+
{
|
|
166
|
+
id: "default",
|
|
167
|
+
label: "Always Ask",
|
|
168
|
+
description: "Prompts for permission the first time a tool is used",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "auto",
|
|
172
|
+
label: "Auto mode",
|
|
173
|
+
description: "Uses a model classifier to review permission prompts automatically",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "acceptEdits",
|
|
177
|
+
label: "Accept File Edits",
|
|
178
|
+
description: "Automatically approves edit-focused tools without prompting",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "plan",
|
|
182
|
+
label: "Plan Mode",
|
|
183
|
+
description: "Analyze the codebase without executing tools or edits",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "bypassPermissions",
|
|
187
|
+
label: "Bypass",
|
|
188
|
+
description: "Skip all permission prompts (use with caution)",
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
const VALID_CLAUDE_MODES = new Set(DEFAULT_MODES.map((mode) => mode.id));
|
|
192
|
+
const REWIND_COMMAND_NAME = "rewind";
|
|
193
|
+
const REWIND_COMMAND = {
|
|
194
|
+
name: REWIND_COMMAND_NAME,
|
|
195
|
+
description: "Rewind tracked files to a previous user message",
|
|
196
|
+
argumentHint: "[user_message_uuid]",
|
|
197
|
+
};
|
|
198
|
+
const CLAUDE_ROOT_ONLY_COMMANDS = new Set([
|
|
199
|
+
"clear",
|
|
200
|
+
"compact",
|
|
201
|
+
"context",
|
|
202
|
+
"debug",
|
|
203
|
+
"extra-usage",
|
|
204
|
+
"heapdump",
|
|
205
|
+
"init",
|
|
206
|
+
"loop",
|
|
207
|
+
"schedule",
|
|
208
|
+
"usage",
|
|
209
|
+
]);
|
|
210
|
+
const INTERRUPT_TOOL_USE_PLACEHOLDER = "[Request interrupted by user for tool use]";
|
|
211
|
+
const INTERRUPT_PLACEHOLDER_PATTERN = /^\[Request interrupted by user(?:[^\]]*)\]$/;
|
|
212
|
+
const NO_RESPONSE_REQUESTED_PLACEHOLDER = "No response requested.";
|
|
213
|
+
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;
|
|
214
|
+
function classifyClaudeSlashCommand(commandName) {
|
|
215
|
+
// Claude exposes commands and skills as one flat SDK list, without structured source
|
|
216
|
+
// metadata. Keep obvious root-only/session controls out of inline autocomplete and
|
|
217
|
+
// treat the rest as skills; the worst failure mode is an inert inline suggestion.
|
|
218
|
+
return CLAUDE_ROOT_ONLY_COMMANDS.has(commandName) ? "command" : "skill";
|
|
219
|
+
}
|
|
220
|
+
function resolvePathEnvKey() {
|
|
221
|
+
if (process.env["Path"] !== undefined)
|
|
222
|
+
return "Path";
|
|
223
|
+
if (process.env["PATH"] !== undefined)
|
|
224
|
+
return "PATH";
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
function errorToMessageString(error) {
|
|
228
|
+
if (typeof error === "string")
|
|
229
|
+
return error;
|
|
230
|
+
if (error instanceof Error)
|
|
231
|
+
return error.message;
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
function firstStringField(input, primaryKey, secondaryKey) {
|
|
235
|
+
const primary = input[primaryKey];
|
|
236
|
+
if (typeof primary === "string")
|
|
237
|
+
return primary;
|
|
238
|
+
const secondary = input[secondaryKey];
|
|
239
|
+
if (typeof secondary === "string")
|
|
240
|
+
return secondary;
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
function extractSessionIdRaw(msg) {
|
|
244
|
+
if (typeof msg.session_id === "string")
|
|
245
|
+
return msg.session_id;
|
|
246
|
+
if (typeof msg.sessionId === "string")
|
|
247
|
+
return msg.sessionId;
|
|
248
|
+
if (typeof msg.session?.id === "string")
|
|
249
|
+
return msg.session.id;
|
|
250
|
+
return "";
|
|
251
|
+
}
|
|
252
|
+
function isClaudeThinkingEffort(value) {
|
|
253
|
+
return (value === "low" ||
|
|
254
|
+
value === "medium" ||
|
|
255
|
+
value === "high" ||
|
|
256
|
+
value === "xhigh" ||
|
|
257
|
+
value === "max");
|
|
258
|
+
}
|
|
259
|
+
function isClaudeThinkingOption(value) {
|
|
260
|
+
return value === CLAUDE_ULTRACODE_THINKING_OPTION_ID || isClaudeThinkingEffort(value);
|
|
261
|
+
}
|
|
262
|
+
const MAX_RECENT_STDERR_CHARS = 4000;
|
|
263
|
+
const STDERR_FLUSH_WAIT_MS = 150;
|
|
264
|
+
const STDERR_FLUSH_POLL_INTERVAL_MS = 10;
|
|
265
|
+
function summarizeClaudeOptionsForLog(options) {
|
|
266
|
+
const systemPromptRaw = options.systemPrompt;
|
|
267
|
+
const systemPromptSummary = (() => {
|
|
268
|
+
if (!systemPromptRaw) {
|
|
269
|
+
return { mode: "none", preset: null };
|
|
270
|
+
}
|
|
271
|
+
if (typeof systemPromptRaw === "string") {
|
|
272
|
+
return { mode: "string", preset: null };
|
|
273
|
+
}
|
|
274
|
+
const prompt = toObjectRecord(systemPromptRaw);
|
|
275
|
+
const promptType = typeof prompt?.type === "string" ? prompt.type : "custom";
|
|
276
|
+
return {
|
|
277
|
+
mode: promptType === "preset" ? "preset" : "custom",
|
|
278
|
+
preset: typeof prompt?.preset === "string" && prompt.preset.length > 0 ? prompt.preset : null,
|
|
279
|
+
};
|
|
280
|
+
})();
|
|
281
|
+
const mcpServerNames = options.mcpServers ? Object.keys(options.mcpServers).sort() : [];
|
|
282
|
+
return {
|
|
283
|
+
cwd: typeof options.cwd === "string" ? options.cwd : null,
|
|
284
|
+
permissionMode: typeof options.permissionMode === "string" ? options.permissionMode : null,
|
|
285
|
+
model: typeof options.model === "string" ? options.model : null,
|
|
286
|
+
includePartialMessages: options.includePartialMessages === true,
|
|
287
|
+
settingSources: Array.isArray(options.settingSources) ? options.settingSources : [],
|
|
288
|
+
enableFileCheckpointing: options.enableFileCheckpointing === true,
|
|
289
|
+
hasResume: typeof options.resume === "string" && options.resume.length > 0,
|
|
290
|
+
maxThinkingTokens: typeof options.maxThinkingTokens === "number" ? options.maxThinkingTokens : null,
|
|
291
|
+
hasEnv: !!options.env,
|
|
292
|
+
envKeyCount: Object.keys(options.env ?? {}).length,
|
|
293
|
+
hasMcpServers: mcpServerNames.length > 0,
|
|
294
|
+
mcpServerNames,
|
|
295
|
+
systemPromptMode: systemPromptSummary.mode,
|
|
296
|
+
systemPromptPreset: systemPromptSummary.preset,
|
|
297
|
+
hasCanUseTool: typeof options.canUseTool === "function",
|
|
298
|
+
hasSpawnOverride: typeof options.spawnClaudeCodeProcess === "function",
|
|
299
|
+
hasStderrHandler: typeof options.stderr === "function",
|
|
300
|
+
pathToClaudeCodeExecutable: typeof options.pathToClaudeCodeExecutable === "string"
|
|
301
|
+
? options.pathToClaudeCodeExecutable
|
|
302
|
+
: null,
|
|
303
|
+
persistSession: typeof options.persistSession === "boolean" ? options.persistSession : null,
|
|
304
|
+
fastMode: readClaudeFastModeSetting(options.settings),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function readClaudeFastModeSetting(settings) {
|
|
308
|
+
if (!settings || typeof settings === "string") {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
return typeof settings.fastMode === "boolean" ? settings.fastMode : null;
|
|
312
|
+
}
|
|
313
|
+
function mergeClaudeSettings(settings, updates) {
|
|
314
|
+
if (!settings || typeof settings === "string") {
|
|
315
|
+
return settings ?? updates;
|
|
316
|
+
}
|
|
317
|
+
return { ...settings, ...updates };
|
|
318
|
+
}
|
|
319
|
+
function isToolResultTextBlock(value) {
|
|
320
|
+
return (!!value &&
|
|
321
|
+
typeof value === "object" &&
|
|
322
|
+
value.type === "text" &&
|
|
323
|
+
typeof value.text === "string");
|
|
324
|
+
}
|
|
325
|
+
function normalizeForDeterministicString(value, seen) {
|
|
326
|
+
if (value === null ||
|
|
327
|
+
typeof value === "string" ||
|
|
328
|
+
typeof value === "number" ||
|
|
329
|
+
typeof value === "boolean") {
|
|
330
|
+
return value;
|
|
331
|
+
}
|
|
332
|
+
if (typeof value === "bigint") {
|
|
333
|
+
return value.toString();
|
|
334
|
+
}
|
|
335
|
+
if (typeof value === "function") {
|
|
336
|
+
return "[function]";
|
|
337
|
+
}
|
|
338
|
+
if (typeof value === "symbol") {
|
|
339
|
+
return value.toString();
|
|
340
|
+
}
|
|
341
|
+
if (typeof value === "undefined") {
|
|
342
|
+
return "[undefined]";
|
|
343
|
+
}
|
|
344
|
+
if (Array.isArray(value)) {
|
|
345
|
+
return value.map((entry) => normalizeForDeterministicString(entry, seen));
|
|
346
|
+
}
|
|
347
|
+
if (typeof value === "object") {
|
|
348
|
+
const objectValue = value;
|
|
349
|
+
if (seen.has(objectValue)) {
|
|
350
|
+
return "[circular]";
|
|
351
|
+
}
|
|
352
|
+
seen.add(objectValue);
|
|
353
|
+
const record = toObjectRecord(value);
|
|
354
|
+
if (!record) {
|
|
355
|
+
seen.delete(objectValue);
|
|
356
|
+
return "[invalid]";
|
|
357
|
+
}
|
|
358
|
+
const normalized = {};
|
|
359
|
+
for (const key of Object.keys(record).sort()) {
|
|
360
|
+
normalized[key] = normalizeForDeterministicString(record[key], seen);
|
|
361
|
+
}
|
|
362
|
+
seen.delete(objectValue);
|
|
363
|
+
return normalized;
|
|
364
|
+
}
|
|
365
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
366
|
+
return String(value);
|
|
367
|
+
}
|
|
368
|
+
return "[unsupported]";
|
|
369
|
+
}
|
|
370
|
+
function deterministicStringify(value) {
|
|
371
|
+
if (typeof value === "undefined") {
|
|
372
|
+
return "";
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const normalized = normalizeForDeterministicString(value, new WeakSet());
|
|
376
|
+
if (typeof normalized === "string") {
|
|
377
|
+
return normalized;
|
|
378
|
+
}
|
|
379
|
+
return JSON.stringify(normalized);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
if (typeof value === "string") {
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
386
|
+
return String(value);
|
|
387
|
+
}
|
|
388
|
+
return "[unserializable]";
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function coerceToolResultContentToString(content) {
|
|
392
|
+
if (typeof content === "string") {
|
|
393
|
+
return content;
|
|
394
|
+
}
|
|
395
|
+
if (Array.isArray(content) && content.every((block) => isToolResultTextBlock(block))) {
|
|
396
|
+
return content.map((block) => block.text).join("");
|
|
397
|
+
}
|
|
398
|
+
return deterministicStringify(content);
|
|
399
|
+
}
|
|
400
|
+
function toBase64ImageOutput(block) {
|
|
401
|
+
const record = toObjectRecord(block);
|
|
402
|
+
if (!record || record.type !== "image") {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const source = toObjectRecord(record.source);
|
|
406
|
+
if (!source || source.type !== "base64" || typeof source.data !== "string") {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
data: source.data,
|
|
411
|
+
mimeType: typeof source.media_type === "string" ? source.media_type : null,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// Claude returns images inside tool_result content as base64 Anthropic blocks. Left in place they
|
|
415
|
+
// reach coerceToolResultContentToString, which JSON.stringifies the whole array — dumping base64
|
|
416
|
+
// into the tool output. We pull those blocks out to render them as image markdown and leave a
|
|
417
|
+
// "[image]" placeholder so image-only results still produce non-empty output.
|
|
418
|
+
function splitClaudeToolResultImages(content) {
|
|
419
|
+
if (!Array.isArray(content)) {
|
|
420
|
+
return { images: [], text: content };
|
|
421
|
+
}
|
|
422
|
+
const images = [];
|
|
423
|
+
const text = content.map((block) => {
|
|
424
|
+
const image = toBase64ImageOutput(block);
|
|
425
|
+
if (image) {
|
|
426
|
+
images.push(image);
|
|
427
|
+
return { type: "text", text: "[image]" };
|
|
428
|
+
}
|
|
429
|
+
return block;
|
|
430
|
+
});
|
|
431
|
+
return { images, text };
|
|
432
|
+
}
|
|
433
|
+
function normalizeClaudeTranscriptText(value) {
|
|
434
|
+
if (typeof value !== "string") {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
const normalized = value.trim();
|
|
438
|
+
return normalized.length > 0 ? normalized : null;
|
|
439
|
+
}
|
|
440
|
+
function isClaudeInterruptPlaceholderText(value) {
|
|
441
|
+
const normalized = normalizeClaudeTranscriptText(value);
|
|
442
|
+
return normalized !== null && INTERRUPT_PLACEHOLDER_PATTERN.test(normalized);
|
|
443
|
+
}
|
|
444
|
+
function isClaudeNoResponsePlaceholderText(value) {
|
|
445
|
+
return normalizeClaudeTranscriptText(value) === NO_RESPONSE_REQUESTED_PLACEHOLDER;
|
|
446
|
+
}
|
|
447
|
+
const LOCAL_COMMAND_STDOUT_PATTERN = /^\s*<local-command-stdout>[\s\S]*<\/local-command-stdout>\s*$/;
|
|
448
|
+
const CLAUDE_COMMAND_MESSAGE_PATTERN = /<command-message>([\s\S]*?)<\/command-message>/;
|
|
449
|
+
const CLAUDE_COMMAND_ARGS_PATTERN = /<command-args>([\s\S]*?)<\/command-args>/;
|
|
450
|
+
const CLAUDE_COMMAND_NAME_PATTERN = /<command-name>([\s\S]*?)<\/command-name>/;
|
|
451
|
+
function isClaudeLocalCommandStdout(value) {
|
|
452
|
+
const normalized = normalizeClaudeTranscriptText(value);
|
|
453
|
+
return normalized !== null && LOCAL_COMMAND_STDOUT_PATTERN.test(normalized);
|
|
454
|
+
}
|
|
455
|
+
function isClaudeTranscriptNoiseText(value) {
|
|
456
|
+
return (isClaudeInterruptPlaceholderText(value) ||
|
|
457
|
+
isClaudeNoResponsePlaceholderText(value) ||
|
|
458
|
+
isClaudeLocalCommandStdout(value));
|
|
459
|
+
}
|
|
460
|
+
function collectClaudeTextContentParts(content) {
|
|
461
|
+
if (typeof content === "string") {
|
|
462
|
+
const normalized = normalizeClaudeTranscriptText(content);
|
|
463
|
+
return normalized ? [normalized] : [];
|
|
464
|
+
}
|
|
465
|
+
if (!isUnknownArray(content)) {
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
const parts = [];
|
|
469
|
+
for (const block of content) {
|
|
470
|
+
const blockRecord = toObjectRecord(block);
|
|
471
|
+
if (!blockRecord) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const text = normalizeClaudeTranscriptText(blockRecord.text);
|
|
475
|
+
if (text) {
|
|
476
|
+
parts.push(text);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const input = normalizeClaudeTranscriptText(blockRecord.input);
|
|
480
|
+
if (input) {
|
|
481
|
+
parts.push(input);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return parts;
|
|
485
|
+
}
|
|
486
|
+
function isClaudeTranscriptNoiseContent(content) {
|
|
487
|
+
const parts = collectClaudeTextContentParts(content);
|
|
488
|
+
return parts.length > 0 && parts.every((part) => isClaudeTranscriptNoiseText(part));
|
|
489
|
+
}
|
|
490
|
+
export function extractUserMessageText(content) {
|
|
491
|
+
if (typeof content === "string") {
|
|
492
|
+
const normalized = content.trim();
|
|
493
|
+
if (!normalized || isClaudeTranscriptNoiseText(normalized)) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
return normalizeClaudeUserPromptText(normalized);
|
|
497
|
+
}
|
|
498
|
+
if (!Array.isArray(content)) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
const parts = [];
|
|
502
|
+
for (const block of content) {
|
|
503
|
+
if (!block || typeof block !== "object") {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const text = typeof block.text === "string" ? block.text : undefined;
|
|
507
|
+
if (text && text.trim()) {
|
|
508
|
+
const trimmed = text.trim();
|
|
509
|
+
if (!isClaudeTranscriptNoiseText(trimmed)) {
|
|
510
|
+
const normalized = normalizeClaudeUserPromptText(trimmed);
|
|
511
|
+
if (normalized) {
|
|
512
|
+
parts.push(normalized);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const input = typeof block.input === "string" ? block.input : undefined;
|
|
518
|
+
if (input && input.trim()) {
|
|
519
|
+
const trimmed = input.trim();
|
|
520
|
+
if (!isClaudeTranscriptNoiseText(trimmed)) {
|
|
521
|
+
const normalized = normalizeClaudeUserPromptText(trimmed);
|
|
522
|
+
if (normalized) {
|
|
523
|
+
parts.push(normalized);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (parts.length === 0) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const combined = parts.join("\n\n").trim();
|
|
532
|
+
return combined.length > 0 ? combined : null;
|
|
533
|
+
}
|
|
534
|
+
function isMetadata(value) {
|
|
535
|
+
return typeof value === "object" && value !== null;
|
|
536
|
+
}
|
|
537
|
+
function createDefaultToolUseCacheEntry(id, block) {
|
|
538
|
+
const nameFromBlock = typeof block.name === "string" && block.name.length > 0 ? block.name : "tool";
|
|
539
|
+
let server;
|
|
540
|
+
if (typeof block.server === "string" && block.server.length > 0) {
|
|
541
|
+
server = block.server;
|
|
542
|
+
}
|
|
543
|
+
else if (typeof block.name === "string" && block.name.length > 0) {
|
|
544
|
+
server = block.name;
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
server = "tool";
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
id,
|
|
551
|
+
name: nameFromBlock,
|
|
552
|
+
server,
|
|
553
|
+
classification: "generic",
|
|
554
|
+
started: false,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function readTrimmedString(value) {
|
|
558
|
+
if (typeof value !== "string") {
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
const trimmed = value.trim();
|
|
562
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
563
|
+
}
|
|
564
|
+
function isMcpServerConfig(value) {
|
|
565
|
+
if (!isMetadata(value)) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
const type = value.type;
|
|
569
|
+
if (type === "stdio") {
|
|
570
|
+
return typeof value.command === "string";
|
|
571
|
+
}
|
|
572
|
+
if (type === "http" || type === "sse") {
|
|
573
|
+
return typeof value.url === "string";
|
|
574
|
+
}
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
function isMcpServersRecord(value) {
|
|
578
|
+
if (!isMetadata(value)) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
for (const config of Object.values(value)) {
|
|
582
|
+
if (!isMcpServerConfig(config)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
function isPermissionMode(value) {
|
|
589
|
+
return typeof value === "string" && VALID_CLAUDE_MODES.has(value);
|
|
590
|
+
}
|
|
591
|
+
function isTruthyEnvValue(value) {
|
|
592
|
+
const normalized = value?.trim().toLowerCase();
|
|
593
|
+
return (normalized !== undefined &&
|
|
594
|
+
normalized.length > 0 &&
|
|
595
|
+
normalized !== "0" &&
|
|
596
|
+
normalized !== "false" &&
|
|
597
|
+
normalized !== "no" &&
|
|
598
|
+
normalized !== "off");
|
|
599
|
+
}
|
|
600
|
+
function detectIneligibleAutoModeTransport(env) {
|
|
601
|
+
if (isTruthyEnvValue(env.CLAUDE_CODE_USE_BEDROCK)) {
|
|
602
|
+
return "Bedrock";
|
|
603
|
+
}
|
|
604
|
+
if (isTruthyEnvValue(env.CLAUDE_CODE_USE_VERTEX)) {
|
|
605
|
+
return "Vertex";
|
|
606
|
+
}
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
function assertClaudeAutoModeEligible(mode, env) {
|
|
610
|
+
if (mode !== "auto") {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const transport = detectIneligibleAutoModeTransport(env);
|
|
614
|
+
if (transport === null) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
throw new Error(`Claude Auto mode requires the Anthropic API and is not supported when Claude Code uses ${transport}. Select another permission mode or unset the ${transport === "Bedrock" ? "CLAUDE_CODE_USE_BEDROCK" : "CLAUDE_CODE_USE_VERTEX"} environment variable.`);
|
|
618
|
+
}
|
|
619
|
+
function coerceSessionMetadata(metadata) {
|
|
620
|
+
if (!isMetadata(metadata)) {
|
|
621
|
+
return {};
|
|
622
|
+
}
|
|
623
|
+
const result = {};
|
|
624
|
+
if (metadata.provider === "claude" || metadata.provider === "codex") {
|
|
625
|
+
result.provider = metadata.provider;
|
|
626
|
+
}
|
|
627
|
+
if (typeof metadata.cwd === "string") {
|
|
628
|
+
result.cwd = metadata.cwd;
|
|
629
|
+
}
|
|
630
|
+
if (typeof metadata.modeId === "string") {
|
|
631
|
+
result.modeId = metadata.modeId;
|
|
632
|
+
}
|
|
633
|
+
if (typeof metadata.model === "string") {
|
|
634
|
+
result.model = metadata.model;
|
|
635
|
+
}
|
|
636
|
+
if (typeof metadata.title === "string" || metadata.title === null) {
|
|
637
|
+
result.title = metadata.title;
|
|
638
|
+
}
|
|
639
|
+
if (typeof metadata.approvalPolicy === "string") {
|
|
640
|
+
result.approvalPolicy = metadata.approvalPolicy;
|
|
641
|
+
}
|
|
642
|
+
if (typeof metadata.sandboxMode === "string") {
|
|
643
|
+
result.sandboxMode = metadata.sandboxMode;
|
|
644
|
+
}
|
|
645
|
+
if (typeof metadata.networkAccess === "boolean") {
|
|
646
|
+
result.networkAccess = metadata.networkAccess;
|
|
647
|
+
}
|
|
648
|
+
if (typeof metadata.webSearch === "boolean") {
|
|
649
|
+
result.webSearch = metadata.webSearch;
|
|
650
|
+
}
|
|
651
|
+
if (isMetadata(metadata.extra)) {
|
|
652
|
+
const extra = {};
|
|
653
|
+
if (isMetadata(metadata.extra.codex)) {
|
|
654
|
+
extra.codex = metadata.extra.codex;
|
|
655
|
+
}
|
|
656
|
+
if (isClaudeExtra(metadata.extra.claude)) {
|
|
657
|
+
extra.claude = metadata.extra.claude;
|
|
658
|
+
}
|
|
659
|
+
if (extra.codex || extra.claude) {
|
|
660
|
+
result.extra = extra;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (typeof metadata.systemPrompt === "string") {
|
|
664
|
+
result.systemPrompt = metadata.systemPrompt;
|
|
665
|
+
}
|
|
666
|
+
if (isMcpServersRecord(metadata.mcpServers)) {
|
|
667
|
+
result.mcpServers = metadata.mcpServers;
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
}
|
|
671
|
+
export function toClaudeSdkMcpConfig(config) {
|
|
672
|
+
switch (config.type) {
|
|
673
|
+
case "stdio":
|
|
674
|
+
return {
|
|
675
|
+
type: "stdio",
|
|
676
|
+
command: config.command,
|
|
677
|
+
args: config.args,
|
|
678
|
+
env: config.env,
|
|
679
|
+
alwaysLoad: config.alwaysLoad,
|
|
680
|
+
};
|
|
681
|
+
case "http":
|
|
682
|
+
return {
|
|
683
|
+
type: "http",
|
|
684
|
+
url: config.url,
|
|
685
|
+
headers: config.headers,
|
|
686
|
+
alwaysLoad: config.alwaysLoad,
|
|
687
|
+
};
|
|
688
|
+
case "sse":
|
|
689
|
+
return {
|
|
690
|
+
type: "sse",
|
|
691
|
+
url: config.url,
|
|
692
|
+
headers: config.headers,
|
|
693
|
+
alwaysLoad: config.alwaysLoad,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
throw new Error("Unhandled MCP server config type");
|
|
697
|
+
}
|
|
698
|
+
function isClaudeContentChunk(value) {
|
|
699
|
+
return isMetadata(value) && typeof value.type === "string";
|
|
700
|
+
}
|
|
701
|
+
function isClaudeExtra(value) {
|
|
702
|
+
return isMetadata(value);
|
|
703
|
+
}
|
|
704
|
+
function isPermissionUpdate(value) {
|
|
705
|
+
if (!isMetadata(value)) {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
const type = value.type;
|
|
709
|
+
if (type !== "addRules" && type !== "replaceRules" && type !== "removeRules") {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
const rules = value.rules;
|
|
713
|
+
const behavior = value.behavior;
|
|
714
|
+
const destination = value.destination;
|
|
715
|
+
return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
|
|
716
|
+
}
|
|
717
|
+
function resolvePermissionKind(toolName, input) {
|
|
718
|
+
if (toolName === "ExitPlanMode")
|
|
719
|
+
return "plan";
|
|
720
|
+
if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
|
|
721
|
+
return "question";
|
|
722
|
+
}
|
|
723
|
+
return "tool";
|
|
724
|
+
}
|
|
725
|
+
function getClaudeModeLabel(modeId) {
|
|
726
|
+
return DEFAULT_MODES.find((mode) => mode.id === modeId)?.label ?? modeId;
|
|
727
|
+
}
|
|
728
|
+
function buildClaudePlanPermissionActions(resumeMode) {
|
|
729
|
+
const actions = [
|
|
730
|
+
{
|
|
731
|
+
id: "reject",
|
|
732
|
+
label: "Reject",
|
|
733
|
+
behavior: "deny",
|
|
734
|
+
variant: "danger",
|
|
735
|
+
intent: "dismiss",
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
id: "implement",
|
|
739
|
+
label: "Implement",
|
|
740
|
+
behavior: "allow",
|
|
741
|
+
variant: "primary",
|
|
742
|
+
intent: "implement",
|
|
743
|
+
},
|
|
744
|
+
];
|
|
745
|
+
if (resumeMode === "bypassPermissions") {
|
|
746
|
+
actions.push({
|
|
747
|
+
id: "implement_resume",
|
|
748
|
+
label: `Implement with ${getClaudeModeLabel(resumeMode)}`,
|
|
749
|
+
behavior: "allow",
|
|
750
|
+
variant: "secondary",
|
|
751
|
+
intent: "implement_resume",
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return actions;
|
|
755
|
+
}
|
|
756
|
+
class TimelineAssembler {
|
|
757
|
+
constructor() {
|
|
758
|
+
this.messages = new Map();
|
|
759
|
+
this.finalizedMessageIds = new Set();
|
|
760
|
+
this.activeMessageByRun = new Map();
|
|
761
|
+
this.syntheticMessageCounter = 0;
|
|
762
|
+
}
|
|
763
|
+
consume(input) {
|
|
764
|
+
if (input.message.type === "assistant") {
|
|
765
|
+
return this.consumeAssistantMessage(input.message, input.runId, input.messageIdHint ?? null);
|
|
766
|
+
}
|
|
767
|
+
if (input.message.type === "stream_event") {
|
|
768
|
+
return this.consumeStreamEvent(input.message, input.runId, input.messageIdHint ?? null);
|
|
769
|
+
}
|
|
770
|
+
return [];
|
|
771
|
+
}
|
|
772
|
+
consumeAssistantMessage(message, runId, messageIdHint) {
|
|
773
|
+
const messageId = this.readMessageIdFromAssistantMessage(message) ??
|
|
774
|
+
messageIdHint ??
|
|
775
|
+
this.resolveMessageId({ runId, createIfMissing: true, messageId: null });
|
|
776
|
+
if (!messageId) {
|
|
777
|
+
return [];
|
|
778
|
+
}
|
|
779
|
+
if (this.finalizedMessageIds.has(messageId)) {
|
|
780
|
+
return [];
|
|
781
|
+
}
|
|
782
|
+
const state = this.ensureMessageState(messageId, runId);
|
|
783
|
+
const fragments = this.extractFragments(message.message?.content);
|
|
784
|
+
return this.applyAbsoluteFragments(state, fragments);
|
|
785
|
+
}
|
|
786
|
+
consumeStreamEvent(message, runId, messageIdHint) {
|
|
787
|
+
const event = toObjectRecord(message.event) ?? {};
|
|
788
|
+
const eventType = readTrimmedString(event.type);
|
|
789
|
+
const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
|
|
790
|
+
if (eventType === "message_start") {
|
|
791
|
+
const messageId = this.resolveMessageId({
|
|
792
|
+
runId,
|
|
793
|
+
createIfMissing: true,
|
|
794
|
+
messageId: streamEventMessageId,
|
|
795
|
+
});
|
|
796
|
+
if (!messageId) {
|
|
797
|
+
return [];
|
|
798
|
+
}
|
|
799
|
+
this.ensureMessageState(messageId, runId);
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
if (eventType === "message_stop") {
|
|
803
|
+
const messageId = this.resolveMessageId({
|
|
804
|
+
runId,
|
|
805
|
+
createIfMissing: false,
|
|
806
|
+
messageId: streamEventMessageId,
|
|
807
|
+
});
|
|
808
|
+
if (!messageId) {
|
|
809
|
+
return [];
|
|
810
|
+
}
|
|
811
|
+
return this.finalizeMessage(messageId, runId);
|
|
812
|
+
}
|
|
813
|
+
if (eventType === "content_block_start") {
|
|
814
|
+
return this.consumeDeltaContent(event.content_block, runId, streamEventMessageId);
|
|
815
|
+
}
|
|
816
|
+
if (eventType === "content_block_delta") {
|
|
817
|
+
return this.consumeDeltaContent(event.delta, runId, streamEventMessageId);
|
|
818
|
+
}
|
|
819
|
+
return [];
|
|
820
|
+
}
|
|
821
|
+
consumeDeltaContent(content, runId, messageIdHint) {
|
|
822
|
+
const fragments = this.extractFragments(content);
|
|
823
|
+
if (fragments.length === 0) {
|
|
824
|
+
return [];
|
|
825
|
+
}
|
|
826
|
+
const messageId = this.resolveMessageId({
|
|
827
|
+
runId,
|
|
828
|
+
createIfMissing: true,
|
|
829
|
+
messageId: messageIdHint,
|
|
830
|
+
});
|
|
831
|
+
if (!messageId) {
|
|
832
|
+
return [];
|
|
833
|
+
}
|
|
834
|
+
const state = this.ensureMessageState(messageId, runId);
|
|
835
|
+
return this.appendFragments(state, fragments);
|
|
836
|
+
}
|
|
837
|
+
appendFragments(state, fragments) {
|
|
838
|
+
for (const fragment of fragments) {
|
|
839
|
+
if (fragment.kind === "assistant") {
|
|
840
|
+
state.assistantText += fragment.text;
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
state.reasoningText += fragment.text;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return this.emitNewContent(state);
|
|
847
|
+
}
|
|
848
|
+
applyAbsoluteFragments(state, fragments) {
|
|
849
|
+
const assistantText = fragments
|
|
850
|
+
.filter((fragment) => fragment.kind === "assistant")
|
|
851
|
+
.map((fragment) => fragment.text)
|
|
852
|
+
.join("");
|
|
853
|
+
const reasoningText = fragments
|
|
854
|
+
.filter((fragment) => fragment.kind === "reasoning")
|
|
855
|
+
.map((fragment) => fragment.text)
|
|
856
|
+
.join("");
|
|
857
|
+
if (assistantText.length > 0) {
|
|
858
|
+
if (!assistantText.startsWith(state.assistantText)) {
|
|
859
|
+
state.emittedAssistantLength = 0;
|
|
860
|
+
}
|
|
861
|
+
state.assistantText = assistantText;
|
|
862
|
+
}
|
|
863
|
+
if (reasoningText.length > 0) {
|
|
864
|
+
if (!reasoningText.startsWith(state.reasoningText)) {
|
|
865
|
+
state.emittedReasoningLength = 0;
|
|
866
|
+
}
|
|
867
|
+
state.reasoningText = reasoningText;
|
|
868
|
+
}
|
|
869
|
+
return this.emitNewContent(state);
|
|
870
|
+
}
|
|
871
|
+
finalizeMessage(messageId, runId) {
|
|
872
|
+
const state = this.messages.get(messageId);
|
|
873
|
+
if (!state) {
|
|
874
|
+
return [];
|
|
875
|
+
}
|
|
876
|
+
state.stopped = true;
|
|
877
|
+
const items = this.emitNewContent(state);
|
|
878
|
+
if (runId && this.activeMessageByRun.get(runId) === messageId) {
|
|
879
|
+
this.activeMessageByRun.delete(runId);
|
|
880
|
+
}
|
|
881
|
+
this.finalizedMessageIds.add(messageId);
|
|
882
|
+
this.messages.delete(messageId);
|
|
883
|
+
return items;
|
|
884
|
+
}
|
|
885
|
+
emitNewContent(state) {
|
|
886
|
+
const items = [];
|
|
887
|
+
const nextAssistantText = state.assistantText.slice(state.emittedAssistantLength);
|
|
888
|
+
if (nextAssistantText.length > 0 &&
|
|
889
|
+
nextAssistantText !== INTERRUPT_TOOL_USE_PLACEHOLDER &&
|
|
890
|
+
!isClaudeTranscriptNoiseText(nextAssistantText)) {
|
|
891
|
+
state.emittedAssistantLength = state.assistantText.length;
|
|
892
|
+
items.push({ type: "assistant_message", text: nextAssistantText, messageId: state.id });
|
|
893
|
+
}
|
|
894
|
+
const nextReasoningText = state.reasoningText.slice(state.emittedReasoningLength);
|
|
895
|
+
if (nextReasoningText.length > 0) {
|
|
896
|
+
state.emittedReasoningLength = state.reasoningText.length;
|
|
897
|
+
items.push({ type: "reasoning", text: nextReasoningText });
|
|
898
|
+
}
|
|
899
|
+
return items;
|
|
900
|
+
}
|
|
901
|
+
ensureMessageState(messageId, runId) {
|
|
902
|
+
const existing = this.messages.get(messageId);
|
|
903
|
+
if (existing) {
|
|
904
|
+
existing.stopped = false;
|
|
905
|
+
if (runId) {
|
|
906
|
+
this.activeMessageByRun.set(runId, messageId);
|
|
907
|
+
}
|
|
908
|
+
return existing;
|
|
909
|
+
}
|
|
910
|
+
const created = {
|
|
911
|
+
id: messageId,
|
|
912
|
+
assistantText: "",
|
|
913
|
+
reasoningText: "",
|
|
914
|
+
emittedAssistantLength: 0,
|
|
915
|
+
emittedReasoningLength: 0,
|
|
916
|
+
stopped: false,
|
|
917
|
+
};
|
|
918
|
+
this.messages.set(messageId, created);
|
|
919
|
+
if (runId) {
|
|
920
|
+
this.activeMessageByRun.set(runId, messageId);
|
|
921
|
+
}
|
|
922
|
+
return created;
|
|
923
|
+
}
|
|
924
|
+
resolveMessageId(input) {
|
|
925
|
+
if (input.messageId) {
|
|
926
|
+
return input.messageId;
|
|
927
|
+
}
|
|
928
|
+
if (input.runId) {
|
|
929
|
+
const active = this.activeMessageByRun.get(input.runId);
|
|
930
|
+
if (active) {
|
|
931
|
+
return active;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (!input.createIfMissing) {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
const synthetic = `synthetic-message-${++this.syntheticMessageCounter}`;
|
|
938
|
+
if (input.runId) {
|
|
939
|
+
this.activeMessageByRun.set(input.runId, synthetic);
|
|
940
|
+
}
|
|
941
|
+
return synthetic;
|
|
942
|
+
}
|
|
943
|
+
extractFragments(content) {
|
|
944
|
+
if (typeof content === "string") {
|
|
945
|
+
if (content.length === 0) {
|
|
946
|
+
return [];
|
|
947
|
+
}
|
|
948
|
+
return [{ kind: "assistant", text: content }];
|
|
949
|
+
}
|
|
950
|
+
const blocks = Array.isArray(content) ? content : [content];
|
|
951
|
+
const fragments = [];
|
|
952
|
+
for (const rawBlock of blocks) {
|
|
953
|
+
if (!isClaudeContentChunk(rawBlock)) {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if ((rawBlock.type === "text" || rawBlock.type === "text_delta") &&
|
|
957
|
+
typeof rawBlock.text === "string" &&
|
|
958
|
+
rawBlock.text.length > 0) {
|
|
959
|
+
fragments.push({ kind: "assistant", text: rawBlock.text });
|
|
960
|
+
}
|
|
961
|
+
if ((rawBlock.type === "thinking" || rawBlock.type === "thinking_delta") &&
|
|
962
|
+
typeof rawBlock.thinking === "string" &&
|
|
963
|
+
rawBlock.thinking.length > 0) {
|
|
964
|
+
fragments.push({ kind: "reasoning", text: rawBlock.thinking });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return fragments;
|
|
968
|
+
}
|
|
969
|
+
readMessageIdFromAssistantMessage(message) {
|
|
970
|
+
const candidate = toObjectRecord(message);
|
|
971
|
+
const messageContainer = toObjectRecord(candidate?.message);
|
|
972
|
+
return (readTrimmedString(candidate?.message_id) ?? readTrimmedString(messageContainer?.id) ?? null);
|
|
973
|
+
}
|
|
974
|
+
readMessageIdFromStreamEvent(event) {
|
|
975
|
+
const messageContainer = toObjectRecord(event.message);
|
|
976
|
+
return readTrimmedString(event.message_id) ?? readTrimmedString(messageContainer?.id) ?? null;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function isSyntheticUserEntry(entry) {
|
|
980
|
+
const candidate = toObjectRecord(entry);
|
|
981
|
+
if (!candidate) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
return (candidate.isSynthetic === true || candidate.isMeta === true || Boolean(candidate.toolUseResult));
|
|
985
|
+
}
|
|
986
|
+
function isToolResultUserEntry(entry) {
|
|
987
|
+
const candidate = toObjectRecord(entry);
|
|
988
|
+
if (!candidate) {
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
const message = toObjectRecord(candidate.message);
|
|
992
|
+
const content = message?.content;
|
|
993
|
+
return (Array.isArray(content) && content.some((block) => toObjectRecord(block)?.type === "tool_result"));
|
|
994
|
+
}
|
|
995
|
+
function isSyntheticHistoryUserEntry(entry) {
|
|
996
|
+
return isSyntheticUserEntry(entry) && !isToolResultUserEntry(entry);
|
|
997
|
+
}
|
|
998
|
+
function firstTrimmedString(sources) {
|
|
999
|
+
for (const source of sources) {
|
|
1000
|
+
const value = readTrimmedString(source);
|
|
1001
|
+
if (value) {
|
|
1002
|
+
return value;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function readTranscriptUuid(message) {
|
|
1008
|
+
const root = toObjectRecord(message) ?? {};
|
|
1009
|
+
const messageType = readTrimmedString(root.type);
|
|
1010
|
+
if (messageType !== "user" && messageType !== "assistant") {
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
return firstTrimmedString([root.uuid]);
|
|
1014
|
+
}
|
|
1015
|
+
export function readEventIdentifiers(message) {
|
|
1016
|
+
const root = toObjectRecord(message) ?? {};
|
|
1017
|
+
const messageType = readTrimmedString(root.type);
|
|
1018
|
+
const streamEvent = toObjectRecord(root.event);
|
|
1019
|
+
const streamEventMessage = toObjectRecord(streamEvent?.message);
|
|
1020
|
+
const messageContainer = toObjectRecord(root.message);
|
|
1021
|
+
const messageIdFromUuid = messageType === "user" || messageType === "assistant" || messageType === "system"
|
|
1022
|
+
? root.uuid
|
|
1023
|
+
: undefined;
|
|
1024
|
+
return {
|
|
1025
|
+
taskId: firstTrimmedString([
|
|
1026
|
+
root.task_id,
|
|
1027
|
+
streamEvent?.task_id,
|
|
1028
|
+
streamEventMessage?.task_id,
|
|
1029
|
+
messageContainer?.task_id,
|
|
1030
|
+
]),
|
|
1031
|
+
parentMessageId: firstTrimmedString([
|
|
1032
|
+
root.parent_message_id,
|
|
1033
|
+
streamEvent?.parent_message_id,
|
|
1034
|
+
streamEventMessage?.parent_message_id,
|
|
1035
|
+
messageContainer?.parent_message_id,
|
|
1036
|
+
]),
|
|
1037
|
+
messageId: firstTrimmedString([
|
|
1038
|
+
root.message_id,
|
|
1039
|
+
streamEvent?.message_id,
|
|
1040
|
+
streamEventMessage?.id,
|
|
1041
|
+
streamEventMessage?.message_id,
|
|
1042
|
+
messageContainer?.id,
|
|
1043
|
+
messageContainer?.message_id,
|
|
1044
|
+
messageIdFromUuid,
|
|
1045
|
+
]),
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
export class ClaudeAgentClient {
|
|
1049
|
+
constructor(options) {
|
|
1050
|
+
this.provider = "claude";
|
|
1051
|
+
this.capabilities = CLAUDE_CAPABILITIES;
|
|
1052
|
+
this.defaults = options.defaults;
|
|
1053
|
+
this.logger = options.logger.child({ module: "agent", provider: "claude" });
|
|
1054
|
+
this.runtimeSettings = options.runtimeSettings;
|
|
1055
|
+
this.queryFactory = options.queryFactory;
|
|
1056
|
+
this.resolveBinary = options.resolveBinary ?? (() => resolveClaudeBinary(this.runtimeSettings));
|
|
1057
|
+
this.configDir = options.configDir;
|
|
1058
|
+
}
|
|
1059
|
+
async createSession(config, launchContext, options) {
|
|
1060
|
+
const claudeConfig = this.assertConfig(config);
|
|
1061
|
+
return new ClaudeAgentSession(claudeConfig, {
|
|
1062
|
+
defaults: this.defaults,
|
|
1063
|
+
runtimeSettings: this.runtimeSettings,
|
|
1064
|
+
agentId: launchContext?.agentId,
|
|
1065
|
+
launchEnv: launchContext?.env,
|
|
1066
|
+
persistSession: options?.persistSession,
|
|
1067
|
+
logger: this.logger,
|
|
1068
|
+
queryFactory: this.queryFactory,
|
|
1069
|
+
resolveBinary: this.resolveBinary,
|
|
1070
|
+
// Story 3.6 — Fork-resume: seed claudeSessionId without a persistence
|
|
1071
|
+
// handle so the SDK/PTY paths emit `--resume <sid>`, but no read/write
|
|
1072
|
+
// is performed against the source JSONL by paseo itself. Claude CLI
|
|
1073
|
+
// forks the conversation into a new SID; source remains byte-identical.
|
|
1074
|
+
...(options?.resumeSessionId ? { resumeSessionId: options.resumeSessionId } : {}),
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
async resumeSession(handle, overrides, launchContext) {
|
|
1078
|
+
const metadata = coerceSessionMetadata(handle.metadata);
|
|
1079
|
+
const merged = { ...metadata, ...overrides };
|
|
1080
|
+
if (!merged.cwd) {
|
|
1081
|
+
throw new Error("Claude resume requires the original working directory in metadata");
|
|
1082
|
+
}
|
|
1083
|
+
const mergedConfig = {
|
|
1084
|
+
...merged,
|
|
1085
|
+
provider: "claude",
|
|
1086
|
+
cwd: merged.cwd,
|
|
1087
|
+
};
|
|
1088
|
+
const claudeConfig = this.assertConfig(mergedConfig);
|
|
1089
|
+
return new ClaudeAgentSession(claudeConfig, {
|
|
1090
|
+
defaults: this.defaults,
|
|
1091
|
+
runtimeSettings: this.runtimeSettings,
|
|
1092
|
+
handle,
|
|
1093
|
+
agentId: launchContext?.agentId,
|
|
1094
|
+
launchEnv: launchContext?.env,
|
|
1095
|
+
logger: this.logger,
|
|
1096
|
+
queryFactory: this.queryFactory,
|
|
1097
|
+
resolveBinary: this.resolveBinary,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
async fetchCatalog(_options) {
|
|
1101
|
+
// Claude exposes a global catalog here; cwd/force are intentionally irrelevant.
|
|
1102
|
+
const models = await getClaudeModelsWithSettings(this.logger, this.configDir);
|
|
1103
|
+
return { models, modes: DEFAULT_MODES };
|
|
1104
|
+
}
|
|
1105
|
+
async listFeatures(config) {
|
|
1106
|
+
const claudeConfig = this.assertConfig(config);
|
|
1107
|
+
return buildClaudeFeatures({
|
|
1108
|
+
modelId: claudeConfig.model,
|
|
1109
|
+
fastModeEnabled: claudeConfig.featureValues?.fast_mode === true,
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
async listImportableSessions(options) {
|
|
1113
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
1114
|
+
const projectsRoot = path.join(configDir, "projects");
|
|
1115
|
+
if (!(await pathExists(projectsRoot))) {
|
|
1116
|
+
return [];
|
|
1117
|
+
}
|
|
1118
|
+
const limit = options?.limit ?? 20;
|
|
1119
|
+
const candidates = await collectRecentClaudeSessions(projectsRoot, limit * 3);
|
|
1120
|
+
const parsed = await Promise.all(candidates.map((candidate) => parseClaudeSessionDescriptor(candidate.path, candidate.mtime)));
|
|
1121
|
+
return parsed
|
|
1122
|
+
.filter((session) => session !== null)
|
|
1123
|
+
.slice(0, limit);
|
|
1124
|
+
}
|
|
1125
|
+
async importSession(input, context) {
|
|
1126
|
+
return importSessionFromPersistence({
|
|
1127
|
+
provider: "claude",
|
|
1128
|
+
request: input,
|
|
1129
|
+
context,
|
|
1130
|
+
resumeSession: this.resumeSession.bind(this),
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
async isAvailable() {
|
|
1134
|
+
// Story 4.1 — When transport is explicitly "pty", the daemon needs node-pty
|
|
1135
|
+
// loadable. Short-circuit unavailable with a reason exposed via diagnostic.
|
|
1136
|
+
if (this.runtimeSettings?.transport === "pty" && !isNodePtyAvailable()) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
const command = this.runtimeSettings?.command;
|
|
1140
|
+
if (command?.mode === "replace") {
|
|
1141
|
+
return await isCommandAvailable(command.argv[0]);
|
|
1142
|
+
}
|
|
1143
|
+
return await isCommandAvailable("claude");
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Story 4.1 — Detailed availability with rebuild diagnostic. The public
|
|
1147
|
+
* `isAvailable()` keeps its Promise<boolean> contract (AgentClient interface);
|
|
1148
|
+
* tests and `getDiagnostic()` consume this for the rebuild hint.
|
|
1149
|
+
*/
|
|
1150
|
+
async checkAvailability() {
|
|
1151
|
+
if (this.runtimeSettings?.transport === "pty" && !isNodePtyAvailable()) {
|
|
1152
|
+
return { available: false, reason: NODE_PTY_UNAVAILABLE_REASON };
|
|
1153
|
+
}
|
|
1154
|
+
return { available: await this.isAvailable() };
|
|
1155
|
+
}
|
|
1156
|
+
async getDiagnostic() {
|
|
1157
|
+
try {
|
|
1158
|
+
const launch = await resolveProviderLaunch({
|
|
1159
|
+
commandConfig: this.runtimeSettings?.command,
|
|
1160
|
+
defaultBinary: "claude",
|
|
1161
|
+
});
|
|
1162
|
+
const availability = await checkProviderLaunchAvailable(launch);
|
|
1163
|
+
const auth = availability.available
|
|
1164
|
+
? await resolveClaudeAuth(launch, availability, this.runtimeSettings)
|
|
1165
|
+
: null;
|
|
1166
|
+
return {
|
|
1167
|
+
diagnostic: formatProviderDiagnostic("Claude Code", [
|
|
1168
|
+
...(await buildCommandResolutionDiagnosticRows(launch, {
|
|
1169
|
+
knownBinaryNames: ["claude"],
|
|
1170
|
+
})),
|
|
1171
|
+
...(await buildBinaryDiagnosticRows(launch, availability)),
|
|
1172
|
+
...(auth ? [{ label: "Auth", value: auth }] : []),
|
|
1173
|
+
]),
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
catch (error) {
|
|
1177
|
+
return {
|
|
1178
|
+
diagnostic: formatProviderDiagnosticError("Claude Code", error),
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
assertConfig(config) {
|
|
1183
|
+
if (config.provider !== "claude") {
|
|
1184
|
+
throw new Error(`ClaudeAgentClient received config for provider '${config.provider}'`);
|
|
1185
|
+
}
|
|
1186
|
+
return { ...config, provider: "claude" };
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
async function resolveClaudeBinary(runtimeSettings) {
|
|
1190
|
+
const launch = await resolveProviderLaunch({
|
|
1191
|
+
commandConfig: runtimeSettings?.command,
|
|
1192
|
+
defaultBinary: "claude",
|
|
1193
|
+
});
|
|
1194
|
+
const availability = await checkProviderLaunchAvailable(launch);
|
|
1195
|
+
if (availability.available) {
|
|
1196
|
+
return availability.resolvedPath ?? launch.command;
|
|
1197
|
+
}
|
|
1198
|
+
throw new Error("Claude binary not found. Install Claude Code (https://github.com/anthropics/claude-code) and ensure it is available in your shell PATH.");
|
|
1199
|
+
}
|
|
1200
|
+
async function resolveClaudeAuth(launch, availability, runtimeSettings) {
|
|
1201
|
+
const run = async (executable, args) => {
|
|
1202
|
+
try {
|
|
1203
|
+
return await execCommand(executable, args, {
|
|
1204
|
+
...createProviderEnvSpec({ runtimeSettings }),
|
|
1205
|
+
timeout: 5000,
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
catch (error) {
|
|
1209
|
+
const err = toObjectRecord(error);
|
|
1210
|
+
const stdout = typeof err?.stdout === "string" ? err.stdout : "";
|
|
1211
|
+
const stderr = typeof err?.stderr === "string" ? err.stderr : "";
|
|
1212
|
+
const fallbackMessage = typeof err?.message === "string" ? err.message : "";
|
|
1213
|
+
return { stdout, stderr: stderr || fallbackMessage };
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
try {
|
|
1217
|
+
const executable = availability.resolvedPath ?? launch.command;
|
|
1218
|
+
const result = await run(executable, [...launch.args, "auth", "status"]);
|
|
1219
|
+
const combined = [result.stdout, result.stderr]
|
|
1220
|
+
.map((s) => s.trim())
|
|
1221
|
+
.filter((s) => s.length > 0)
|
|
1222
|
+
.join("\n");
|
|
1223
|
+
return combined || null;
|
|
1224
|
+
}
|
|
1225
|
+
catch {
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
function extractContextWindowSize(modelUsage) {
|
|
1230
|
+
const usageRecord = toObjectRecord(modelUsage);
|
|
1231
|
+
if (!usageRecord) {
|
|
1232
|
+
return undefined;
|
|
1233
|
+
}
|
|
1234
|
+
let maxContextWindow;
|
|
1235
|
+
for (const value of Object.values(usageRecord)) {
|
|
1236
|
+
const valueRecord = toObjectRecord(value);
|
|
1237
|
+
if (!valueRecord) {
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
const contextWindow = valueRecord.contextWindow;
|
|
1241
|
+
if (typeof contextWindow !== "number" ||
|
|
1242
|
+
!Number.isFinite(contextWindow) ||
|
|
1243
|
+
contextWindow <= 0) {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow);
|
|
1247
|
+
}
|
|
1248
|
+
return maxContextWindow;
|
|
1249
|
+
}
|
|
1250
|
+
function readStreamRequestInputTokens(event) {
|
|
1251
|
+
const messageUsage = toObjectRecord(toObjectRecord(event.message)?.usage);
|
|
1252
|
+
if (!messageUsage) {
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
const usage = messageUsage;
|
|
1256
|
+
const inputTokens = typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
|
|
1257
|
+
? usage.input_tokens
|
|
1258
|
+
: undefined;
|
|
1259
|
+
const cacheCreationInputTokens = typeof usage.cache_creation_input_tokens === "number" &&
|
|
1260
|
+
Number.isFinite(usage.cache_creation_input_tokens)
|
|
1261
|
+
? usage.cache_creation_input_tokens
|
|
1262
|
+
: 0;
|
|
1263
|
+
const cacheReadInputTokens = typeof usage.cache_read_input_tokens === "number" &&
|
|
1264
|
+
Number.isFinite(usage.cache_read_input_tokens)
|
|
1265
|
+
? usage.cache_read_input_tokens
|
|
1266
|
+
: 0;
|
|
1267
|
+
if (typeof inputTokens !== "number" || inputTokens < 0) {
|
|
1268
|
+
return undefined;
|
|
1269
|
+
}
|
|
1270
|
+
return inputTokens + cacheCreationInputTokens + cacheReadInputTokens;
|
|
1271
|
+
}
|
|
1272
|
+
function readStreamRequestOutputTokens(event) {
|
|
1273
|
+
const outputTokens = toObjectRecord(event.usage)?.output_tokens;
|
|
1274
|
+
if (typeof outputTokens !== "number" || !Number.isFinite(outputTokens) || outputTokens < 0) {
|
|
1275
|
+
return undefined;
|
|
1276
|
+
}
|
|
1277
|
+
return outputTokens;
|
|
1278
|
+
}
|
|
1279
|
+
function readLastUsageIteration(usage) {
|
|
1280
|
+
const iterations = toObjectRecord(usage)?.iterations;
|
|
1281
|
+
if (!Array.isArray(iterations)) {
|
|
1282
|
+
return undefined;
|
|
1283
|
+
}
|
|
1284
|
+
for (let index = iterations.length - 1; index >= 0; index -= 1) {
|
|
1285
|
+
const candidate = toObjectRecord(iterations[index]);
|
|
1286
|
+
if (candidate) {
|
|
1287
|
+
return candidate;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return undefined;
|
|
1291
|
+
}
|
|
1292
|
+
function readUsageTokenTotal(usage) {
|
|
1293
|
+
const usageWithCacheCreation = usage;
|
|
1294
|
+
const inputTokens = typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens)
|
|
1295
|
+
? usage.input_tokens
|
|
1296
|
+
: 0;
|
|
1297
|
+
const cacheCreationInputTokens = typeof usageWithCacheCreation.cache_creation_input_tokens === "number" &&
|
|
1298
|
+
Number.isFinite(usageWithCacheCreation.cache_creation_input_tokens)
|
|
1299
|
+
? usageWithCacheCreation.cache_creation_input_tokens
|
|
1300
|
+
: 0;
|
|
1301
|
+
const cacheReadInputTokens = typeof usage.cache_read_input_tokens === "number" &&
|
|
1302
|
+
Number.isFinite(usage.cache_read_input_tokens)
|
|
1303
|
+
? usage.cache_read_input_tokens
|
|
1304
|
+
: 0;
|
|
1305
|
+
const outputTokens = typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens)
|
|
1306
|
+
? usage.output_tokens
|
|
1307
|
+
: 0;
|
|
1308
|
+
const total = inputTokens + cacheCreationInputTokens + cacheReadInputTokens + outputTokens;
|
|
1309
|
+
return total > 0 ? total : undefined;
|
|
1310
|
+
}
|
|
1311
|
+
function readActiveUsageTokens(usage) {
|
|
1312
|
+
const activeUsage = readLastUsageIteration(usage);
|
|
1313
|
+
return activeUsage ? readUsageTokenTotal(activeUsage) : undefined;
|
|
1314
|
+
}
|
|
1315
|
+
function readLegacyResultUsageTokens(usage) {
|
|
1316
|
+
const usageRecord = toObjectRecord(usage);
|
|
1317
|
+
return usageRecord ? readUsageTokenTotal(usageRecord) : undefined;
|
|
1318
|
+
}
|
|
1319
|
+
function isClaudeSubagentToolName(name) {
|
|
1320
|
+
return name === "Task" || name === "Agent";
|
|
1321
|
+
}
|
|
1322
|
+
function readClaudeParentToolUseId(message) {
|
|
1323
|
+
if (!("parent_tool_use_id" in message)) {
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
const parentToolUseId = message.parent_tool_use_id;
|
|
1327
|
+
return typeof parentToolUseId === "string" && parentToolUseId.length > 0 ? parentToolUseId : null;
|
|
1328
|
+
}
|
|
1329
|
+
class ClaudeContextUsageState {
|
|
1330
|
+
constructor(initialContextWindowMaxTokens) {
|
|
1331
|
+
this.completedResultTurns = 0;
|
|
1332
|
+
this.contextWindowMaxTokens = initialContextWindowMaxTokens;
|
|
1333
|
+
}
|
|
1334
|
+
beginTurn() {
|
|
1335
|
+
this.streamRequestInputTokens = undefined;
|
|
1336
|
+
this.streamRequestOutputTokens = undefined;
|
|
1337
|
+
this.compactedContextWindowUsedTokens = undefined;
|
|
1338
|
+
}
|
|
1339
|
+
setInitialContextWindowMaxTokens(contextWindowMaxTokens) {
|
|
1340
|
+
this.contextWindowMaxTokens = contextWindowMaxTokens;
|
|
1341
|
+
}
|
|
1342
|
+
recordModelUsage(modelUsage) {
|
|
1343
|
+
const contextWindowMaxTokens = extractContextWindowSize(modelUsage);
|
|
1344
|
+
if (contextWindowMaxTokens !== undefined) {
|
|
1345
|
+
this.contextWindowMaxTokens = contextWindowMaxTokens;
|
|
1346
|
+
}
|
|
1347
|
+
return this.contextWindowMaxTokens;
|
|
1348
|
+
}
|
|
1349
|
+
buildStreamUsageEvent(event) {
|
|
1350
|
+
const streamEvent = toObjectRecord(event);
|
|
1351
|
+
if (!streamEvent) {
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
const eventType = readTrimmedString(streamEvent.type);
|
|
1355
|
+
if (eventType === "message_start") {
|
|
1356
|
+
const inputTokens = readStreamRequestInputTokens(streamEvent);
|
|
1357
|
+
if (typeof inputTokens !== "number") {
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
this.streamRequestInputTokens = inputTokens;
|
|
1361
|
+
this.streamRequestOutputTokens = 0;
|
|
1362
|
+
}
|
|
1363
|
+
else if (eventType === "message_delta") {
|
|
1364
|
+
const outputTokens = readStreamRequestOutputTokens(streamEvent);
|
|
1365
|
+
if (typeof outputTokens !== "number") {
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
this.streamRequestOutputTokens = outputTokens;
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
const usedTokens = this.streamUsedTokens();
|
|
1374
|
+
if (usedTokens === undefined) {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
return this.createUsageUpdatedEvent(usedTokens);
|
|
1378
|
+
}
|
|
1379
|
+
buildResultUsage(message, modelUsage) {
|
|
1380
|
+
try {
|
|
1381
|
+
if (!message.usage) {
|
|
1382
|
+
return undefined;
|
|
1383
|
+
}
|
|
1384
|
+
const usage = {
|
|
1385
|
+
inputTokens: message.usage.input_tokens,
|
|
1386
|
+
cachedInputTokens: message.usage.cache_read_input_tokens,
|
|
1387
|
+
outputTokens: message.usage.output_tokens,
|
|
1388
|
+
totalCostUsd: message.total_cost_usd,
|
|
1389
|
+
};
|
|
1390
|
+
const modelContextWindowMaxTokens = this.recordModelUsage(modelUsage ?? message.modelUsage);
|
|
1391
|
+
if (this.contextWindowMaxTokens !== undefined) {
|
|
1392
|
+
usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
|
|
1393
|
+
}
|
|
1394
|
+
else if (modelContextWindowMaxTokens !== undefined) {
|
|
1395
|
+
usage.contextWindowMaxTokens = modelContextWindowMaxTokens;
|
|
1396
|
+
}
|
|
1397
|
+
const activeResultUsageTokens = readActiveUsageTokens(message.usage) ??
|
|
1398
|
+
(this.completedResultTurns === 0 ? readLegacyResultUsageTokens(message.usage) : undefined);
|
|
1399
|
+
const usedTokens = this.streamUsedTokens() ?? activeResultUsageTokens ?? this.compactedContextWindowUsedTokens;
|
|
1400
|
+
if (usedTokens !== undefined) {
|
|
1401
|
+
usage.contextWindowUsedTokens = usedTokens;
|
|
1402
|
+
}
|
|
1403
|
+
return usage;
|
|
1404
|
+
}
|
|
1405
|
+
finally {
|
|
1406
|
+
this.compactedContextWindowUsedTokens = undefined;
|
|
1407
|
+
this.completedResultTurns += 1;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
streamUsedTokens() {
|
|
1411
|
+
if (typeof this.streamRequestInputTokens !== "number" ||
|
|
1412
|
+
typeof this.streamRequestOutputTokens !== "number") {
|
|
1413
|
+
return undefined;
|
|
1414
|
+
}
|
|
1415
|
+
const usedTokens = this.streamRequestInputTokens + this.streamRequestOutputTokens;
|
|
1416
|
+
return usedTokens > 0 ? usedTokens : undefined;
|
|
1417
|
+
}
|
|
1418
|
+
createUsageUpdatedEvent(contextWindowUsedTokens) {
|
|
1419
|
+
const usage = {
|
|
1420
|
+
contextWindowUsedTokens,
|
|
1421
|
+
};
|
|
1422
|
+
if (this.contextWindowMaxTokens !== undefined) {
|
|
1423
|
+
usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
|
|
1424
|
+
}
|
|
1425
|
+
return {
|
|
1426
|
+
type: "usage_updated",
|
|
1427
|
+
provider: "claude",
|
|
1428
|
+
usage,
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
buildCompactionUsageEvent(postTokens) {
|
|
1432
|
+
this.streamRequestInputTokens = undefined;
|
|
1433
|
+
this.streamRequestOutputTokens = undefined;
|
|
1434
|
+
this.compactedContextWindowUsedTokens = postTokens;
|
|
1435
|
+
const usage = {};
|
|
1436
|
+
if (this.contextWindowMaxTokens !== undefined) {
|
|
1437
|
+
usage.contextWindowMaxTokens = this.contextWindowMaxTokens;
|
|
1438
|
+
}
|
|
1439
|
+
if (postTokens !== undefined) {
|
|
1440
|
+
usage.contextWindowUsedTokens = postTokens;
|
|
1441
|
+
}
|
|
1442
|
+
return {
|
|
1443
|
+
type: "usage_updated",
|
|
1444
|
+
provider: "claude",
|
|
1445
|
+
usage,
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
export class ClaudeAgentSession {
|
|
1450
|
+
/**
|
|
1451
|
+
* Register a hook event handler for an agent. Stub in Epic 1 — Epic 3 wires
|
|
1452
|
+
* incoming hook events from the PTY transport into this handler.
|
|
1453
|
+
*/
|
|
1454
|
+
registerHookHandler(agentId, handler) {
|
|
1455
|
+
this.hookEventHandlers.set(agentId, handler);
|
|
1456
|
+
}
|
|
1457
|
+
unregisterHookHandler(agentId) {
|
|
1458
|
+
this.hookEventHandlers.delete(agentId);
|
|
1459
|
+
}
|
|
1460
|
+
constructor(config, options) {
|
|
1461
|
+
this.provider = "claude";
|
|
1462
|
+
this.capabilities = CLAUDE_CAPABILITIES;
|
|
1463
|
+
this.query = null;
|
|
1464
|
+
this.childProcess = null;
|
|
1465
|
+
this.input = null;
|
|
1466
|
+
this.planResumeMode = null;
|
|
1467
|
+
this.availableModes = DEFAULT_MODES;
|
|
1468
|
+
this.toolUseCache = new Map();
|
|
1469
|
+
this.toolUseIndexToId = new Map();
|
|
1470
|
+
this.toolUseInputBuffers = new Map();
|
|
1471
|
+
this.pendingPermissions = new Map();
|
|
1472
|
+
this.activeForegroundTurnId = null;
|
|
1473
|
+
this.autonomousTurn = null;
|
|
1474
|
+
this.subscribers = new Set();
|
|
1475
|
+
this.timelineAssembler = new TimelineAssembler();
|
|
1476
|
+
this.sidechainTracker = new ClaudeSidechainTracker({
|
|
1477
|
+
getToolInput: (toolUseId) => this.toolUseCache.get(toolUseId)?.input ?? null,
|
|
1478
|
+
});
|
|
1479
|
+
// Tracks Claude Code background shells (`Bash` run_in_background) that outlive
|
|
1480
|
+
// the turn, so the agent keeps surfacing as "Running" until they finish or are
|
|
1481
|
+
// dismissed. Fed from the transport-agnostic message stream (works for PTY).
|
|
1482
|
+
this.backgroundTaskTracker = new ClaudeBackgroundTaskTracker();
|
|
1483
|
+
this.persistedHistory = [];
|
|
1484
|
+
this.historyPending = false;
|
|
1485
|
+
this.turnState = "idle";
|
|
1486
|
+
this.nextTurnOrdinal = 1;
|
|
1487
|
+
this.cancelCurrentTurn = null;
|
|
1488
|
+
this.cachedRuntimeInfo = null;
|
|
1489
|
+
this.lastOptionsModel = null;
|
|
1490
|
+
this.lastRuntimeModel = null;
|
|
1491
|
+
this.compacting = false;
|
|
1492
|
+
this.queryPumpPromise = null;
|
|
1493
|
+
this.queryRestartNeeded = false;
|
|
1494
|
+
// Story 3.3 — transport handle used by setMode/setModel/setThinkingOption to
|
|
1495
|
+
// discriminate between PTY (slash-command) and SDK (query-object) paths.
|
|
1496
|
+
// Populated by ensureQuery() with the live SdkTransport; tests inject a
|
|
1497
|
+
// PtyTransport stub directly.
|
|
1498
|
+
this.activeTransport = null;
|
|
1499
|
+
// Story 4.1 — Resolved transport ("sdk" | "pty"), cached for session lifetime.
|
|
1500
|
+
// Populated lazily by ensureTransport() from runtimeSettings.transport.
|
|
1501
|
+
this.resolvedTransport = null;
|
|
1502
|
+
// Story 4.4 — Cached `claude --version` string, populated lazily in the
|
|
1503
|
+
// background after the first ensureTransport() call. "unknown" if probing
|
|
1504
|
+
// fails or has not yet resolved when telemetry fires.
|
|
1505
|
+
this.claudeVersion = null;
|
|
1506
|
+
this.transportSpawnedTelemetryFired = false;
|
|
1507
|
+
this.pendingInterruptAbort = false;
|
|
1508
|
+
this.foregroundHasVisibleActivity = false;
|
|
1509
|
+
this.activeTurnHasAssistantText = false;
|
|
1510
|
+
this.userMessageIds = [];
|
|
1511
|
+
this.emittedUserMessageIds = new Set();
|
|
1512
|
+
this.rewindTurnAnchors = [];
|
|
1513
|
+
this.pendingFreshSessionId = null;
|
|
1514
|
+
this.cumulativeImageBase64Bytes = 0;
|
|
1515
|
+
this.cumulativeImageWarnEmitted = false;
|
|
1516
|
+
this.recentStderr = "";
|
|
1517
|
+
this.closed = false;
|
|
1518
|
+
this.hookEventHandlers = new Map();
|
|
1519
|
+
this.handlePermissionRequest = async (toolName, input, options) => {
|
|
1520
|
+
const requestId = `permission-${randomUUID()}`;
|
|
1521
|
+
const kind = resolvePermissionKind(toolName, input);
|
|
1522
|
+
const requestInput = normalizeClaudeAskUserQuestionRequestInput(toolName, input);
|
|
1523
|
+
const metadata = {};
|
|
1524
|
+
if (options.toolUseID) {
|
|
1525
|
+
metadata.toolUseId = options.toolUseID;
|
|
1526
|
+
}
|
|
1527
|
+
if (toolName === "ExitPlanMode" && typeof input.plan === "string") {
|
|
1528
|
+
metadata.planText = input.plan;
|
|
1529
|
+
}
|
|
1530
|
+
const toolDetail = kind === "tool"
|
|
1531
|
+
? mapClaudeRunningToolCall({
|
|
1532
|
+
name: toolName,
|
|
1533
|
+
callId: options.toolUseID ?? requestId,
|
|
1534
|
+
input,
|
|
1535
|
+
output: null,
|
|
1536
|
+
})?.detail
|
|
1537
|
+
: undefined;
|
|
1538
|
+
const request = {
|
|
1539
|
+
id: requestId,
|
|
1540
|
+
provider: "claude",
|
|
1541
|
+
name: toolName,
|
|
1542
|
+
kind,
|
|
1543
|
+
input: requestInput,
|
|
1544
|
+
detail: toolDetail,
|
|
1545
|
+
suggestions: options.suggestions?.map((suggestion) => ({
|
|
1546
|
+
...suggestion,
|
|
1547
|
+
})),
|
|
1548
|
+
actions: kind === "plan" ? buildClaudePlanPermissionActions(this.planResumeMode) : undefined,
|
|
1549
|
+
metadata: Object.keys(metadata).length ? metadata : undefined,
|
|
1550
|
+
};
|
|
1551
|
+
this.pushEvent({
|
|
1552
|
+
type: "permission_requested",
|
|
1553
|
+
provider: "claude",
|
|
1554
|
+
request,
|
|
1555
|
+
});
|
|
1556
|
+
return await new Promise((resolve, reject) => {
|
|
1557
|
+
const cleanupFns = [];
|
|
1558
|
+
const cleanup = () => {
|
|
1559
|
+
while (cleanupFns.length) {
|
|
1560
|
+
const fn = cleanupFns.pop();
|
|
1561
|
+
try {
|
|
1562
|
+
fn?.();
|
|
1563
|
+
}
|
|
1564
|
+
catch {
|
|
1565
|
+
// ignore cleanup errors
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
const abortHandler = () => {
|
|
1570
|
+
this.pendingPermissions.delete(requestId);
|
|
1571
|
+
cleanup();
|
|
1572
|
+
reject(new Error("Permission request aborted"));
|
|
1573
|
+
};
|
|
1574
|
+
if (options?.signal) {
|
|
1575
|
+
if (options.signal.aborted) {
|
|
1576
|
+
abortHandler();
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
1580
|
+
cleanupFns.push(() => options.signal?.removeEventListener("abort", abortHandler));
|
|
1581
|
+
}
|
|
1582
|
+
this.pendingPermissions.set(requestId, {
|
|
1583
|
+
request,
|
|
1584
|
+
resolve,
|
|
1585
|
+
reject,
|
|
1586
|
+
cleanup,
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1589
|
+
};
|
|
1590
|
+
this.config = config;
|
|
1591
|
+
this.launchEnv = options.launchEnv;
|
|
1592
|
+
this.agentId = options.agentId;
|
|
1593
|
+
this.defaults = options.defaults;
|
|
1594
|
+
this.runtimeSettings = options.runtimeSettings;
|
|
1595
|
+
this.persistSession = options.persistSession;
|
|
1596
|
+
this.logger = options.logger.child({ agentId: this.agentId });
|
|
1597
|
+
this.queryFactory = options.queryFactory;
|
|
1598
|
+
this.resolveBinary = options.resolveBinary;
|
|
1599
|
+
this.contextUsage = new ClaudeContextUsageState(findClaudeModel(this.config.model)?.contextWindowMaxTokens);
|
|
1600
|
+
const handle = options.handle;
|
|
1601
|
+
if (handle) {
|
|
1602
|
+
if (!handle.sessionId) {
|
|
1603
|
+
throw new Error("Cannot resume: persistence handle has no sessionId");
|
|
1604
|
+
}
|
|
1605
|
+
this.claudeSessionId = handle.sessionId;
|
|
1606
|
+
this.persistence = handle;
|
|
1607
|
+
this.loadPersistedHistory(handle.sessionId);
|
|
1608
|
+
}
|
|
1609
|
+
else if (options.resumeSessionId) {
|
|
1610
|
+
// Story 3.6 — Fork-resume: seed the SID so buildOptions() / PTY emits
|
|
1611
|
+
// `--resume <sid>`. Do NOT set a persistence handle — Claude CLI forks to
|
|
1612
|
+
// a NEW SID at spawn time and the SOURCE JSONL stays byte-identical (AC4:
|
|
1613
|
+
// only WRITES fork; reads are fine).
|
|
1614
|
+
this.claudeSessionId = options.resumeSessionId;
|
|
1615
|
+
this.persistence = null;
|
|
1616
|
+
// Load the source transcript into persistedHistory for DISPLAY only.
|
|
1617
|
+
// loadPersistedHistory is a read-only fs.readFileSync, so the source
|
|
1618
|
+
// stays byte-identical — but without it the resumed chat renders empty
|
|
1619
|
+
// ("Start chatting…") until the next turn, even though Claude has the
|
|
1620
|
+
// full context internally. The create handler calls
|
|
1621
|
+
// hydrateTimelineFromProvider to stream this into the timeline.
|
|
1622
|
+
this.loadPersistedHistory(options.resumeSessionId);
|
|
1623
|
+
}
|
|
1624
|
+
else {
|
|
1625
|
+
this.claudeSessionId = null;
|
|
1626
|
+
this.persistence = null;
|
|
1627
|
+
}
|
|
1628
|
+
// Validate mode if provided
|
|
1629
|
+
if (config.modeId && !VALID_CLAUDE_MODES.has(config.modeId)) {
|
|
1630
|
+
const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
|
|
1631
|
+
throw new Error(`Invalid mode '${config.modeId}' for Claude provider. Valid modes: ${validModesList}`);
|
|
1632
|
+
}
|
|
1633
|
+
this.currentMode = isPermissionMode(config.modeId) ? config.modeId : "default";
|
|
1634
|
+
if (this.currentMode !== "plan") {
|
|
1635
|
+
this.planResumeMode = this.currentMode;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
get id() {
|
|
1639
|
+
return this.claudeSessionId;
|
|
1640
|
+
}
|
|
1641
|
+
get features() {
|
|
1642
|
+
return buildClaudeFeatures({
|
|
1643
|
+
modelId: this.config.model,
|
|
1644
|
+
fastModeEnabled: this.config.featureValues?.fast_mode === true,
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
async getRuntimeInfo() {
|
|
1648
|
+
if (this.cachedRuntimeInfo) {
|
|
1649
|
+
return { ...this.cachedRuntimeInfo };
|
|
1650
|
+
}
|
|
1651
|
+
const info = {
|
|
1652
|
+
provider: "claude",
|
|
1653
|
+
sessionId: this.claudeSessionId,
|
|
1654
|
+
model: this.lastOptionsModel,
|
|
1655
|
+
modeId: this.currentMode ?? null,
|
|
1656
|
+
...(this.lastRuntimeModel
|
|
1657
|
+
? {
|
|
1658
|
+
extra: {
|
|
1659
|
+
runtimeModel: this.lastRuntimeModel,
|
|
1660
|
+
},
|
|
1661
|
+
}
|
|
1662
|
+
: {}),
|
|
1663
|
+
};
|
|
1664
|
+
this.cachedRuntimeInfo = info;
|
|
1665
|
+
return { ...info };
|
|
1666
|
+
}
|
|
1667
|
+
async run(prompt, options) {
|
|
1668
|
+
const result = await runProviderTurn({
|
|
1669
|
+
prompt,
|
|
1670
|
+
runOptions: options,
|
|
1671
|
+
startTurn: (p, o) => this.startTurn(p, o),
|
|
1672
|
+
subscribe: (callback) => this.subscribe(callback),
|
|
1673
|
+
getSessionId: () => this.claudeSessionId ?? "",
|
|
1674
|
+
reduceFinalText: appendOrReplaceGrowingAssistantMessage,
|
|
1675
|
+
});
|
|
1676
|
+
this.cachedRuntimeInfo = {
|
|
1677
|
+
provider: "claude",
|
|
1678
|
+
sessionId: this.claudeSessionId,
|
|
1679
|
+
model: this.lastOptionsModel,
|
|
1680
|
+
modeId: this.currentMode ?? null,
|
|
1681
|
+
};
|
|
1682
|
+
if (!this.claudeSessionId) {
|
|
1683
|
+
throw new Error("Session ID not set after run completed");
|
|
1684
|
+
}
|
|
1685
|
+
return result;
|
|
1686
|
+
}
|
|
1687
|
+
async startTurn(prompt, _options) {
|
|
1688
|
+
if (this.closed) {
|
|
1689
|
+
throw new Error("Claude session is closed");
|
|
1690
|
+
}
|
|
1691
|
+
if (this.activeForegroundTurnId) {
|
|
1692
|
+
throw new Error("A foreground turn is already active");
|
|
1693
|
+
}
|
|
1694
|
+
const slashCommand = this.resolveSlashCommandInvocation(prompt);
|
|
1695
|
+
if (slashCommand?.commandName === REWIND_COMMAND_NAME) {
|
|
1696
|
+
const turnId = this.createTurnId("foreground");
|
|
1697
|
+
this.activeForegroundTurnId = turnId;
|
|
1698
|
+
this.transitionTurnState("foreground", "rewind command");
|
|
1699
|
+
void this.executeRewindTurn(turnId, slashCommand);
|
|
1700
|
+
return { turnId };
|
|
1701
|
+
}
|
|
1702
|
+
if (this.autonomousTurn) {
|
|
1703
|
+
this.completeAutonomousTurn();
|
|
1704
|
+
}
|
|
1705
|
+
const turnId = this.createTurnId("foreground");
|
|
1706
|
+
this.activeForegroundTurnId = turnId;
|
|
1707
|
+
this.foregroundHasVisibleActivity = false;
|
|
1708
|
+
this.activeTurnHasAssistantText = false;
|
|
1709
|
+
this.contextUsage.beginTurn();
|
|
1710
|
+
this.transitionTurnState("foreground", "foreground turn started");
|
|
1711
|
+
this.clearRecentStderr();
|
|
1712
|
+
let sdkMessage;
|
|
1713
|
+
try {
|
|
1714
|
+
sdkMessage = await this.toSdkUserMessage(prompt);
|
|
1715
|
+
}
|
|
1716
|
+
catch (error) {
|
|
1717
|
+
this.finishForegroundTurn(this.buildTurnFailedEvent(error instanceof Error ? error.message : "Failed to prepare user message"));
|
|
1718
|
+
return { turnId };
|
|
1719
|
+
}
|
|
1720
|
+
let cancelIssued = false;
|
|
1721
|
+
const requestCancel = () => {
|
|
1722
|
+
if (cancelIssued) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
cancelIssued = true;
|
|
1726
|
+
if (this.cancelCurrentTurn === requestCancel) {
|
|
1727
|
+
this.cancelCurrentTurn = null;
|
|
1728
|
+
}
|
|
1729
|
+
this.rejectAllPendingPermissions(new Error("Permission request aborted"));
|
|
1730
|
+
this.finishForegroundTurn({
|
|
1731
|
+
type: "turn_canceled",
|
|
1732
|
+
provider: "claude",
|
|
1733
|
+
reason: "Interrupted",
|
|
1734
|
+
});
|
|
1735
|
+
void this.interruptActiveTurn().catch((error) => {
|
|
1736
|
+
this.logger.warn({ err: error }, "Failed to interrupt during cancel");
|
|
1737
|
+
});
|
|
1738
|
+
};
|
|
1739
|
+
this.cancelCurrentTurn = requestCancel;
|
|
1740
|
+
this.notifySubscribers({ type: "turn_started", provider: "claude" });
|
|
1741
|
+
try {
|
|
1742
|
+
await this.ensureQuery();
|
|
1743
|
+
if (!this.input) {
|
|
1744
|
+
throw new Error("Claude session input stream not initialized");
|
|
1745
|
+
}
|
|
1746
|
+
this.startQueryPump();
|
|
1747
|
+
this.input.push(sdkMessage);
|
|
1748
|
+
setTimeout(() => {
|
|
1749
|
+
if (this.activeForegroundTurnId === turnId) {
|
|
1750
|
+
this.emitSubmittedUserMessage(sdkMessage, turnId);
|
|
1751
|
+
}
|
|
1752
|
+
}, 0);
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
this.finishForegroundTurn(this.buildTurnFailedEvent(error instanceof Error ? error.message : "Claude stream failed"));
|
|
1756
|
+
}
|
|
1757
|
+
return { turnId };
|
|
1758
|
+
}
|
|
1759
|
+
subscribe(callback) {
|
|
1760
|
+
this.subscribers.add(callback);
|
|
1761
|
+
return () => {
|
|
1762
|
+
this.subscribers.delete(callback);
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
async interrupt() {
|
|
1766
|
+
if (this.cancelCurrentTurn) {
|
|
1767
|
+
this.cancelCurrentTurn();
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
if (this.autonomousTurn) {
|
|
1771
|
+
this.flushPendingToolCalls();
|
|
1772
|
+
this.completeAutonomousTurn();
|
|
1773
|
+
}
|
|
1774
|
+
await this.interruptActiveTurn();
|
|
1775
|
+
}
|
|
1776
|
+
async *streamHistory() {
|
|
1777
|
+
if (!this.historyPending || this.persistedHistory.length === 0) {
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
const history = this.persistedHistory;
|
|
1781
|
+
this.persistedHistory = [];
|
|
1782
|
+
this.historyPending = false;
|
|
1783
|
+
for (const entry of history) {
|
|
1784
|
+
yield {
|
|
1785
|
+
type: "timeline",
|
|
1786
|
+
item: entry.item,
|
|
1787
|
+
provider: "claude",
|
|
1788
|
+
timestamp: entry.timestamp,
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
async getAvailableModes() {
|
|
1793
|
+
return this.availableModes;
|
|
1794
|
+
}
|
|
1795
|
+
async getCurrentMode() {
|
|
1796
|
+
return this.currentMode ?? null;
|
|
1797
|
+
}
|
|
1798
|
+
async setMode(modeId) {
|
|
1799
|
+
// Validate mode
|
|
1800
|
+
if (!VALID_CLAUDE_MODES.has(modeId)) {
|
|
1801
|
+
const validModesList = Array.from(VALID_CLAUDE_MODES).join(", ");
|
|
1802
|
+
throw new Error(`Invalid mode '${modeId}' for Claude provider. Valid modes: ${validModesList}`);
|
|
1803
|
+
}
|
|
1804
|
+
const normalized = isPermissionMode(modeId) ? modeId : "default";
|
|
1805
|
+
assertClaudeAutoModeEligible(normalized, this.buildSdkEnv(this.config.extra?.claude));
|
|
1806
|
+
const previousMode = this.currentMode;
|
|
1807
|
+
const activeQuery = await this.ensureQuery();
|
|
1808
|
+
if (this.activeTransport instanceof PtyTransport) {
|
|
1809
|
+
// PTY path: claude 2.1.119 has no /mode or /permission-mode slash command.
|
|
1810
|
+
// Force a session reload so the new spawn picks up --permission-mode <mode>.
|
|
1811
|
+
await this.reloadAgentSession({ overrides: { permissionMode: normalized } });
|
|
1812
|
+
}
|
|
1813
|
+
else {
|
|
1814
|
+
await activeQuery.setPermissionMode(normalized);
|
|
1815
|
+
}
|
|
1816
|
+
if (normalized === "plan") {
|
|
1817
|
+
if (previousMode !== "plan") {
|
|
1818
|
+
this.planResumeMode = previousMode;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
else {
|
|
1822
|
+
this.planResumeMode = normalized;
|
|
1823
|
+
}
|
|
1824
|
+
this.currentMode = normalized;
|
|
1825
|
+
}
|
|
1826
|
+
async setModel(modelId) {
|
|
1827
|
+
const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
|
|
1828
|
+
const activeQuery = await this.ensureQuery();
|
|
1829
|
+
if (this.activeTransport instanceof PtyTransport) {
|
|
1830
|
+
// PTY path: /model <name> slash command. Null/empty → omit write, state-only.
|
|
1831
|
+
if (normalizedModelId !== null) {
|
|
1832
|
+
await this.activeTransport.write(`/model ${normalizedModelId}\r`);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
await activeQuery.setModel(normalizedModelId ?? undefined);
|
|
1837
|
+
}
|
|
1838
|
+
// Shared self-state update — both transport paths.
|
|
1839
|
+
this.config.model = normalizedModelId ?? undefined;
|
|
1840
|
+
if (!claudeModelSupportsFastMode(this.config.model) && this.config.featureValues?.fast_mode) {
|
|
1841
|
+
await this.applyFastModeFeature(false, activeQuery);
|
|
1842
|
+
}
|
|
1843
|
+
this.contextUsage.setInitialContextWindowMaxTokens(findClaudeModel(this.config.model)?.contextWindowMaxTokens);
|
|
1844
|
+
this.lastOptionsModel = normalizedModelId ?? this.lastOptionsModel;
|
|
1845
|
+
this.lastRuntimeModel = null;
|
|
1846
|
+
this.cachedRuntimeInfo = null;
|
|
1847
|
+
// Model change affects persistence metadata, so invalidate cached handle.
|
|
1848
|
+
this.persistence = null;
|
|
1849
|
+
}
|
|
1850
|
+
async setThinkingOption(thinkingOptionId) {
|
|
1851
|
+
const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
|
|
1852
|
+
? thinkingOptionId
|
|
1853
|
+
: null;
|
|
1854
|
+
if (!normalizedThinkingOptionId || normalizedThinkingOptionId === "default") {
|
|
1855
|
+
this.config.thinkingOptionId = undefined;
|
|
1856
|
+
}
|
|
1857
|
+
else if (isClaudeThinkingOption(normalizedThinkingOptionId)) {
|
|
1858
|
+
this.config.thinkingOptionId = normalizedThinkingOptionId;
|
|
1859
|
+
}
|
|
1860
|
+
else {
|
|
1861
|
+
throw new Error(`Unknown thinking option: ${normalizedThinkingOptionId}`);
|
|
1862
|
+
}
|
|
1863
|
+
if (this.activeTransport instanceof PtyTransport) {
|
|
1864
|
+
// PTY path: /effort <level> slash command, takes effect immediately.
|
|
1865
|
+
// Do NOT set queryRestartNeeded — that's an SDK-only restart signal.
|
|
1866
|
+
const effort = normalizedThinkingOptionId && normalizedThinkingOptionId !== "default"
|
|
1867
|
+
? normalizedThinkingOptionId
|
|
1868
|
+
: "low";
|
|
1869
|
+
await this.activeTransport.write(`/effort ${effort}\r`);
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
this.queryRestartNeeded = true;
|
|
1873
|
+
if (this.activeForegroundTurnId || this.autonomousTurn) {
|
|
1874
|
+
return SETTING_APPLIES_NEXT_TURN_NOTICE;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Story 3.3 — PTY-path session reload hook.
|
|
1879
|
+
*
|
|
1880
|
+
* On the PTY transport, runtime config changes that require a respawn (e.g.
|
|
1881
|
+
* permission mode) call this method. Default impl is a no-op stub so the
|
|
1882
|
+
* unit tests can `vi.spyOn(session, "reloadAgentSession")` to assert the
|
|
1883
|
+
* branch fires. The full integration is wired by agent-manager in a later
|
|
1884
|
+
* story; until then this method updates the in-session `currentMode` so the
|
|
1885
|
+
* next ensureQuery() rebuild picks up the new mode.
|
|
1886
|
+
*/
|
|
1887
|
+
async reloadAgentSession(opts) {
|
|
1888
|
+
if (opts?.overrides?.permissionMode) {
|
|
1889
|
+
this.currentMode = opts.overrides.permissionMode;
|
|
1890
|
+
}
|
|
1891
|
+
// Force the next ensureQuery() to rebuild so the new spawn picks up the
|
|
1892
|
+
// updated --permission-mode value.
|
|
1893
|
+
this.queryRestartNeeded = true;
|
|
1894
|
+
}
|
|
1895
|
+
async setFeature(featureId, value) {
|
|
1896
|
+
if (featureId !== "fast_mode") {
|
|
1897
|
+
throw new Error(`Unknown Claude feature: ${featureId}`);
|
|
1898
|
+
}
|
|
1899
|
+
const enabled = Boolean(value);
|
|
1900
|
+
if (enabled && !claudeModelSupportsFastMode(this.config.model)) {
|
|
1901
|
+
throw new Error(`Claude fast mode is not available for model '${this.config.model ?? "default"}'`);
|
|
1902
|
+
}
|
|
1903
|
+
await this.applyFastModeFeature(enabled);
|
|
1904
|
+
}
|
|
1905
|
+
async applyFastModeFeature(enabled, query) {
|
|
1906
|
+
this.config.featureValues = {
|
|
1907
|
+
...this.config.featureValues,
|
|
1908
|
+
fast_mode: enabled,
|
|
1909
|
+
};
|
|
1910
|
+
const activeQuery = query ?? this.query;
|
|
1911
|
+
if (activeQuery) {
|
|
1912
|
+
await activeQuery.applyFlagSettings({ fastMode: enabled });
|
|
1913
|
+
}
|
|
1914
|
+
this.cachedRuntimeInfo = null;
|
|
1915
|
+
}
|
|
1916
|
+
getPendingPermissions() {
|
|
1917
|
+
return Array.from(this.pendingPermissions.values()).map((entry) => entry.request);
|
|
1918
|
+
}
|
|
1919
|
+
getActiveBackgroundTaskCount() {
|
|
1920
|
+
return this.backgroundTaskTracker.count;
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Dismiss live background shell(s) the way a human would in the Claude Code
|
|
1924
|
+
* CLI: drive Claude Code's own TUI over the PTY with its `ctrl+x ctrl+k` kill
|
|
1925
|
+
* chord (sent twice — arm + confirm), killing the shell INSIDE Claude Code
|
|
1926
|
+
* rather than via an external OS kill. Then clear our tracked set.
|
|
1927
|
+
*
|
|
1928
|
+
* Caveat: `ctrl+x ctrl+k` is Claude Code's *global* stop-background-work
|
|
1929
|
+
* chord (no per-shell-by-id interactive binding exists), so we clear the
|
|
1930
|
+
* whole set afterward. `taskId` is accepted for API symmetry/logging.
|
|
1931
|
+
*
|
|
1932
|
+
* Returns true if keystrokes were delivered to a live PTY-driven session. On
|
|
1933
|
+
* the SDK transport (no TUI to drive) it falls back to untracking the id.
|
|
1934
|
+
*/
|
|
1935
|
+
async dismissBackgroundTask(taskId) {
|
|
1936
|
+
const transport = this.activeTransport;
|
|
1937
|
+
if (transport instanceof PtyTransport && typeof transport.writeRaw === "function") {
|
|
1938
|
+
for (const press of buildBackgroundKillPresses()) {
|
|
1939
|
+
await transport.writeRaw(press);
|
|
1940
|
+
}
|
|
1941
|
+
this.backgroundTaskTracker.clear();
|
|
1942
|
+
this.logger.debug({ taskId }, "Dismissed background shells via Claude Code ctrl+x ctrl+k");
|
|
1943
|
+
return true;
|
|
1944
|
+
}
|
|
1945
|
+
// No interactive TUI to drive (e.g. SDK transport) — best-effort untrack.
|
|
1946
|
+
return this.backgroundTaskTracker.remove(taskId);
|
|
1947
|
+
}
|
|
1948
|
+
async respondToPermission(requestId, response) {
|
|
1949
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
1950
|
+
if (!pending) {
|
|
1951
|
+
throw new Error(`No pending permission request with id '${requestId}'`);
|
|
1952
|
+
}
|
|
1953
|
+
this.pendingPermissions.delete(requestId);
|
|
1954
|
+
pending.cleanup?.();
|
|
1955
|
+
if (response.behavior === "allow") {
|
|
1956
|
+
if (pending.request.kind === "plan") {
|
|
1957
|
+
const selectedActionId = response.selectedActionId;
|
|
1958
|
+
const shouldResumePriorMode = selectedActionId === "implement_resume" && this.planResumeMode === "bypassPermissions";
|
|
1959
|
+
const targetMode = shouldResumePriorMode
|
|
1960
|
+
? "bypassPermissions"
|
|
1961
|
+
: "acceptEdits";
|
|
1962
|
+
await this.setMode(targetMode);
|
|
1963
|
+
this.pushToolCall(mapClaudeCompletedToolCall({
|
|
1964
|
+
name: "plan_approval",
|
|
1965
|
+
callId: pending.request.id,
|
|
1966
|
+
input: pending.request.input ?? null,
|
|
1967
|
+
output: {
|
|
1968
|
+
approved: true,
|
|
1969
|
+
actionId: selectedActionId ?? "implement",
|
|
1970
|
+
},
|
|
1971
|
+
}));
|
|
1972
|
+
}
|
|
1973
|
+
const updatedInput = pending.request.kind === "question"
|
|
1974
|
+
? normalizeClaudeAskUserQuestionUpdatedInput(response.updatedInput, pending.request.input ?? undefined)
|
|
1975
|
+
: (response.updatedInput ?? pending.request.input ?? {});
|
|
1976
|
+
const result = {
|
|
1977
|
+
behavior: "allow",
|
|
1978
|
+
updatedInput,
|
|
1979
|
+
updatedPermissions: this.normalizePermissionUpdates(response.updatedPermissions),
|
|
1980
|
+
};
|
|
1981
|
+
pending.resolve(result);
|
|
1982
|
+
}
|
|
1983
|
+
else {
|
|
1984
|
+
if (pending.request.kind === "tool") {
|
|
1985
|
+
this.pushToolCall(mapClaudeFailedToolCall({
|
|
1986
|
+
name: pending.request.name,
|
|
1987
|
+
callId: (typeof pending.request.metadata?.toolUseId === "string"
|
|
1988
|
+
? pending.request.metadata.toolUseId
|
|
1989
|
+
: null) ?? pending.request.id,
|
|
1990
|
+
input: pending.request.input ?? null,
|
|
1991
|
+
output: null,
|
|
1992
|
+
error: { message: response.message ?? "Permission denied" },
|
|
1993
|
+
}));
|
|
1994
|
+
}
|
|
1995
|
+
const result = {
|
|
1996
|
+
behavior: "deny",
|
|
1997
|
+
message: response.message ?? "Permission request denied",
|
|
1998
|
+
interrupt: response.interrupt,
|
|
1999
|
+
};
|
|
2000
|
+
pending.resolve(result);
|
|
2001
|
+
}
|
|
2002
|
+
this.pushEvent({
|
|
2003
|
+
type: "permission_resolved",
|
|
2004
|
+
provider: "claude",
|
|
2005
|
+
requestId,
|
|
2006
|
+
resolution: response,
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
describePersistence() {
|
|
2010
|
+
if (this.persistence) {
|
|
2011
|
+
return this.persistence;
|
|
2012
|
+
}
|
|
2013
|
+
if (!this.claudeSessionId) {
|
|
2014
|
+
return null;
|
|
2015
|
+
}
|
|
2016
|
+
this.persistence = {
|
|
2017
|
+
provider: "claude",
|
|
2018
|
+
sessionId: this.claudeSessionId,
|
|
2019
|
+
nativeHandle: this.claudeSessionId,
|
|
2020
|
+
metadata: { ...this.config },
|
|
2021
|
+
};
|
|
2022
|
+
return this.persistence;
|
|
2023
|
+
}
|
|
2024
|
+
async close() {
|
|
2025
|
+
this.logger.trace({
|
|
2026
|
+
agentId: this.agentId,
|
|
2027
|
+
provider: "claude",
|
|
2028
|
+
sessionId: this.claudeSessionId,
|
|
2029
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2030
|
+
turnState: this.turnState,
|
|
2031
|
+
hasQuery: Boolean(this.query),
|
|
2032
|
+
hasInput: Boolean(this.input),
|
|
2033
|
+
hasActiveForegroundTurnId: Boolean(this.activeForegroundTurnId),
|
|
2034
|
+
}, "provider.claude.session_close.start");
|
|
2035
|
+
this.closed = true;
|
|
2036
|
+
this.rejectAllPendingPermissions(new Error("Claude session closed"));
|
|
2037
|
+
this.cancelCurrentTurn?.();
|
|
2038
|
+
this.subscribers.clear();
|
|
2039
|
+
this.activeForegroundTurnId = null;
|
|
2040
|
+
this.autonomousTurn = null;
|
|
2041
|
+
this.cancelCurrentTurn = null;
|
|
2042
|
+
this.turnState = "idle";
|
|
2043
|
+
this.sidechainTracker.clear();
|
|
2044
|
+
this.backgroundTaskTracker.clear();
|
|
2045
|
+
this.input?.end();
|
|
2046
|
+
this.query?.close?.();
|
|
2047
|
+
await this.awaitWithTimeout(this.query?.interrupt?.(), "close query interrupt");
|
|
2048
|
+
await this.awaitWithTimeout(this.query?.return?.(), "close query return");
|
|
2049
|
+
this.query = null;
|
|
2050
|
+
this.input = null;
|
|
2051
|
+
// Terminate the entire process tree (claude + MCP children) to prevent
|
|
2052
|
+
// orphan accumulation. The SDK's internal cleanup may only kill the
|
|
2053
|
+
// direct child process.
|
|
2054
|
+
if (this.childProcess) {
|
|
2055
|
+
const result = await terminateWithTreeKill(this.childProcess, {
|
|
2056
|
+
gracefulTimeoutMs: 2000,
|
|
2057
|
+
forceTimeoutMs: 2000,
|
|
2058
|
+
});
|
|
2059
|
+
if (result === "kill-timeout") {
|
|
2060
|
+
this.logger.warn({ pid: this.childProcess.pid, agentId: this.agentId }, "Claude process tree did not report exit after SIGKILL");
|
|
2061
|
+
}
|
|
2062
|
+
this.childProcess = null;
|
|
2063
|
+
}
|
|
2064
|
+
if (this.persistSession === false && this.claudeSessionId) {
|
|
2065
|
+
// Claude Code currently ignores --no-session-persistence outside --print mode
|
|
2066
|
+
// (see `claude --help`), so the SDK's persistSession=false is silently dropped
|
|
2067
|
+
// in stream-json mode. Sweep the transcript ourselves so ephemeral runs
|
|
2068
|
+
// (metadata generator, branch-name generator) don't show up as resumable.
|
|
2069
|
+
const historyPath = this.resolveHistoryPath(this.claudeSessionId);
|
|
2070
|
+
if (historyPath) {
|
|
2071
|
+
try {
|
|
2072
|
+
await promises.rm(historyPath, { force: true });
|
|
2073
|
+
}
|
|
2074
|
+
catch (error) {
|
|
2075
|
+
this.logger.warn({ err: error, historyPath, claudeSessionId: this.claudeSessionId }, "Failed to delete ephemeral Claude session transcript");
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
this.logger.trace({
|
|
2080
|
+
agentId: this.agentId,
|
|
2081
|
+
provider: "claude",
|
|
2082
|
+
sessionId: this.claudeSessionId,
|
|
2083
|
+
turnState: this.turnState,
|
|
2084
|
+
}, "provider.claude.session_close.complete");
|
|
2085
|
+
}
|
|
2086
|
+
async listCommands() {
|
|
2087
|
+
const q = await this.ensureQuery();
|
|
2088
|
+
const commands = await q.supportedCommands();
|
|
2089
|
+
const commandMap = new Map();
|
|
2090
|
+
for (const cmd of commands) {
|
|
2091
|
+
if (!commandMap.has(cmd.name)) {
|
|
2092
|
+
commandMap.set(cmd.name, {
|
|
2093
|
+
name: cmd.name,
|
|
2094
|
+
description: cmd.description,
|
|
2095
|
+
argumentHint: cmd.argumentHint,
|
|
2096
|
+
kind: classifyClaudeSlashCommand(cmd.name),
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (!commandMap.has(REWIND_COMMAND_NAME)) {
|
|
2101
|
+
commandMap.set(REWIND_COMMAND_NAME, REWIND_COMMAND);
|
|
2102
|
+
}
|
|
2103
|
+
return Array.from(commandMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
2104
|
+
}
|
|
2105
|
+
async revertConversation(input) {
|
|
2106
|
+
const target = this.resolveConversationRewindTarget(input.messageId);
|
|
2107
|
+
if (target.kind === "fresh-session") {
|
|
2108
|
+
this.startFreshConversationSession();
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
await revertClaudeConversation({
|
|
2112
|
+
sdk: realClaudeRewindSdk,
|
|
2113
|
+
sessionId: this.claudeSessionId,
|
|
2114
|
+
messageId: target.messageId,
|
|
2115
|
+
resolveMessageId: (messageId) => this.resolveClaudeMessageId(messageId),
|
|
2116
|
+
setSessionId: (sessionId) => {
|
|
2117
|
+
this.rebindConversationSession(sessionId);
|
|
2118
|
+
},
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
async revertFiles(input) {
|
|
2122
|
+
const messageId = await this.resolveClaudeMessageId(input.messageId);
|
|
2123
|
+
await revertClaudeFiles({
|
|
2124
|
+
query: await this.ensureQuery(),
|
|
2125
|
+
messageId,
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
async revertBoth(input) {
|
|
2129
|
+
await this.revertFiles(input);
|
|
2130
|
+
await this.revertConversation(input);
|
|
2131
|
+
}
|
|
2132
|
+
resolveSlashCommandInvocation(prompt) {
|
|
2133
|
+
if (typeof prompt !== "string") {
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
const parsed = this.parseSlashCommandInput(prompt);
|
|
2137
|
+
if (!parsed) {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
return parsed.commandName === REWIND_COMMAND_NAME ? parsed : null;
|
|
2141
|
+
}
|
|
2142
|
+
parseSlashCommandInput(text) {
|
|
2143
|
+
const trimmed = text.trim();
|
|
2144
|
+
if (!trimmed.startsWith("/") || trimmed.length <= 1) {
|
|
2145
|
+
return null;
|
|
2146
|
+
}
|
|
2147
|
+
const withoutPrefix = trimmed.slice(1);
|
|
2148
|
+
const firstWhitespaceIdx = withoutPrefix.search(/\s/);
|
|
2149
|
+
const commandName = firstWhitespaceIdx === -1 ? withoutPrefix : withoutPrefix.slice(0, firstWhitespaceIdx);
|
|
2150
|
+
if (!commandName || commandName.includes("/")) {
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
const rawArgs = firstWhitespaceIdx === -1 ? "" : withoutPrefix.slice(firstWhitespaceIdx + 1).trim();
|
|
2154
|
+
return rawArgs.length > 0
|
|
2155
|
+
? { commandName, args: rawArgs, rawInput: trimmed }
|
|
2156
|
+
: { commandName, rawInput: trimmed };
|
|
2157
|
+
}
|
|
2158
|
+
buildRewindSuccessMessage(targetUserMessageId, rewindResult) {
|
|
2159
|
+
const fileCount = Array.isArray(rewindResult.filesChanged)
|
|
2160
|
+
? rewindResult.filesChanged.length
|
|
2161
|
+
: undefined;
|
|
2162
|
+
const stats = [];
|
|
2163
|
+
if (typeof fileCount === "number") {
|
|
2164
|
+
stats.push(`${fileCount} file${fileCount === 1 ? "" : "s"}`);
|
|
2165
|
+
}
|
|
2166
|
+
if (typeof rewindResult.insertions === "number") {
|
|
2167
|
+
stats.push(`${rewindResult.insertions} insertions`);
|
|
2168
|
+
}
|
|
2169
|
+
if (typeof rewindResult.deletions === "number") {
|
|
2170
|
+
stats.push(`${rewindResult.deletions} deletions`);
|
|
2171
|
+
}
|
|
2172
|
+
if (stats.length > 0) {
|
|
2173
|
+
return `Rewound tracked files to message ${targetUserMessageId} (${stats.join(", ")}).`;
|
|
2174
|
+
}
|
|
2175
|
+
return `Rewound tracked files to message ${targetUserMessageId}.`;
|
|
2176
|
+
}
|
|
2177
|
+
async attemptRewind(args) {
|
|
2178
|
+
if (typeof args === "string" && args.trim().length > 0) {
|
|
2179
|
+
const candidate = args.trim().split(/\s+/)[0] ?? "";
|
|
2180
|
+
if (!UUID_PATTERN.test(candidate)) {
|
|
2181
|
+
return {
|
|
2182
|
+
messageId: null,
|
|
2183
|
+
error: "Invalid message UUID. Usage: /rewind <user_message_uuid> or /rewind",
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
const rewindResult = await this.rewindFilesOnce(candidate);
|
|
2187
|
+
if (rewindResult.canRewind) {
|
|
2188
|
+
return { messageId: candidate, result: rewindResult };
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
messageId: null,
|
|
2192
|
+
error: rewindResult.error ?? `No file checkpoint found for message ${candidate}.`,
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
const candidates = this.getRewindCandidateUserMessageIds();
|
|
2196
|
+
if (candidates.length === 0) {
|
|
2197
|
+
return {
|
|
2198
|
+
messageId: null,
|
|
2199
|
+
error: "No prior user message available to rewind. Use /rewind <user_message_uuid>.",
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
let lastError;
|
|
2203
|
+
for (const candidate of candidates) {
|
|
2204
|
+
try {
|
|
2205
|
+
const rewindResult = await this.rewindFilesOnce(candidate);
|
|
2206
|
+
if (rewindResult.canRewind) {
|
|
2207
|
+
return { messageId: candidate, result: rewindResult };
|
|
2208
|
+
}
|
|
2209
|
+
if (rewindResult.error) {
|
|
2210
|
+
lastError = rewindResult.error;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
catch (error) {
|
|
2214
|
+
lastError = error instanceof Error ? error.message : "Failed to rewind tracked files.";
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return {
|
|
2218
|
+
messageId: null,
|
|
2219
|
+
error: lastError ?? "No rewind checkpoints are currently available for this session.",
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
async rewindFilesOnce(messageId) {
|
|
2223
|
+
try {
|
|
2224
|
+
const activeQuery = await this.ensureFreshQuery();
|
|
2225
|
+
return await activeQuery.rewindFiles(messageId, { dryRun: false });
|
|
2226
|
+
}
|
|
2227
|
+
catch (error) {
|
|
2228
|
+
// The Claude SDK transport can close after a rewind call.
|
|
2229
|
+
// If that happens, mark the query stale so a follow-up attempt uses a fresh query.
|
|
2230
|
+
this.queryRestartNeeded = true;
|
|
2231
|
+
throw error;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
async ensureFreshQuery() {
|
|
2235
|
+
if (this.query) {
|
|
2236
|
+
this.queryRestartNeeded = true;
|
|
2237
|
+
}
|
|
2238
|
+
return this.ensureQuery();
|
|
2239
|
+
}
|
|
2240
|
+
getRewindCandidateUserMessageIds() {
|
|
2241
|
+
const candidates = [];
|
|
2242
|
+
const pushUnique = (value) => {
|
|
2243
|
+
if (typeof value === "string" && value.length > 0 && !candidates.includes(value)) {
|
|
2244
|
+
candidates.push(value);
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
for (let idx = this.persistedHistory.length - 1; idx >= 0; idx -= 1) {
|
|
2248
|
+
const entry = this.persistedHistory[idx];
|
|
2249
|
+
if (entry?.item.type === "user_message") {
|
|
2250
|
+
pushUnique(entry.item.messageId);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
for (let idx = this.userMessageIds.length - 1; idx >= 0; idx -= 1) {
|
|
2254
|
+
pushUnique(this.userMessageIds[idx]);
|
|
2255
|
+
}
|
|
2256
|
+
return candidates;
|
|
2257
|
+
}
|
|
2258
|
+
rebindConversationSession(sessionId) {
|
|
2259
|
+
const oldSessionId = this.claudeSessionId;
|
|
2260
|
+
this.claudeSessionId = sessionId;
|
|
2261
|
+
this.pendingFreshSessionId = null;
|
|
2262
|
+
this.persistence = null;
|
|
2263
|
+
this.cachedRuntimeInfo = null;
|
|
2264
|
+
this.queryRestartNeeded = true;
|
|
2265
|
+
this.persistedHistory = [];
|
|
2266
|
+
this.historyPending = false;
|
|
2267
|
+
this.userMessageIds = [];
|
|
2268
|
+
this.emittedUserMessageIds.clear();
|
|
2269
|
+
this.rewindTurnAnchors.length = 0;
|
|
2270
|
+
this.loadPersistedHistory(sessionId);
|
|
2271
|
+
if (oldSessionId && oldSessionId !== sessionId) {
|
|
2272
|
+
this.dispatchEvents([
|
|
2273
|
+
{
|
|
2274
|
+
type: "timeline",
|
|
2275
|
+
provider: "claude",
|
|
2276
|
+
item: this.createClaudeSessionChangedNotice(oldSessionId, sessionId),
|
|
2277
|
+
},
|
|
2278
|
+
{
|
|
2279
|
+
type: "thread_started",
|
|
2280
|
+
provider: "claude",
|
|
2281
|
+
sessionId,
|
|
2282
|
+
},
|
|
2283
|
+
]);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
startFreshConversationSession() {
|
|
2287
|
+
const sessionId = randomUUID();
|
|
2288
|
+
this.claudeSessionId = sessionId;
|
|
2289
|
+
this.pendingFreshSessionId = sessionId;
|
|
2290
|
+
this.persistence = null;
|
|
2291
|
+
this.cachedRuntimeInfo = null;
|
|
2292
|
+
this.queryRestartNeeded = true;
|
|
2293
|
+
this.persistedHistory = [];
|
|
2294
|
+
this.historyPending = false;
|
|
2295
|
+
this.userMessageIds = [];
|
|
2296
|
+
this.emittedUserMessageIds.clear();
|
|
2297
|
+
this.rewindTurnAnchors.length = 0;
|
|
2298
|
+
}
|
|
2299
|
+
rememberUserMessageId(messageId) {
|
|
2300
|
+
if (typeof messageId !== "string" || messageId.length === 0) {
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
const last = this.userMessageIds[this.userMessageIds.length - 1];
|
|
2304
|
+
if (last === messageId) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
this.userMessageIds.push(messageId);
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Story 4.1 — Resolve the effective transport for this session and cache it.
|
|
2311
|
+
* Called once per session lifetime; further calls return the cached value.
|
|
2312
|
+
* Epic 3 wiring will use this to choose between SdkTransport and PtyTransport.
|
|
2313
|
+
*/
|
|
2314
|
+
ensureTransport() {
|
|
2315
|
+
if (this.resolvedTransport)
|
|
2316
|
+
return this.resolvedTransport;
|
|
2317
|
+
this.resolvedTransport = resolveClaudeTransport(this.runtimeSettings?.transport);
|
|
2318
|
+
this.fireTransportSpawnedTelemetry(this.resolvedTransport);
|
|
2319
|
+
return this.resolvedTransport;
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Story 4.4 — Fire `agent.transport.spawned` once per session. Probes
|
|
2323
|
+
* `claude --version` in the background; emits with `version: "unknown"` if
|
|
2324
|
+
* the probe is not yet resolved or fails. Never throws.
|
|
2325
|
+
*/
|
|
2326
|
+
fireTransportSpawnedTelemetry(transport) {
|
|
2327
|
+
if (this.transportSpawnedTelemetryFired)
|
|
2328
|
+
return;
|
|
2329
|
+
this.transportSpawnedTelemetryFired = true;
|
|
2330
|
+
const emit = (version) => {
|
|
2331
|
+
try {
|
|
2332
|
+
trackTransportSpawned({ transport, provider: "claude", version }, this.logger);
|
|
2333
|
+
}
|
|
2334
|
+
catch {
|
|
2335
|
+
// belt-and-suspenders: telemetry must never crash the session
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
if (this.claudeVersion) {
|
|
2339
|
+
emit(this.claudeVersion);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
resolveClaudeVersion(this.runtimeSettings)
|
|
2343
|
+
.then((version) => {
|
|
2344
|
+
const resolved = version ?? "unknown";
|
|
2345
|
+
this.claudeVersion = resolved;
|
|
2346
|
+
emit(resolved);
|
|
2347
|
+
return resolved;
|
|
2348
|
+
})
|
|
2349
|
+
.catch(() => {
|
|
2350
|
+
emit("unknown");
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
2354
|
+
// Rewind anchor bookkeeping. Ported from the SDK-transport implementation;
|
|
2355
|
+
// currently inert under the PTY transport (the PTY path doesn't call the
|
|
2356
|
+
// remember*/resolveConversationRewindTarget paths yet). Restored so the
|
|
2357
|
+
// file compiles end-to-end; the PTY-aware rewind rewrite is tracked in
|
|
2358
|
+
// docs/tech-debt.md. Do not delete without coordinating the rewrite.
|
|
2359
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
2360
|
+
rememberEmittedUserMessageId(messageId) {
|
|
2361
|
+
if (typeof messageId !== "string" || messageId.length === 0) {
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
this.emittedUserMessageIds.add(messageId);
|
|
2365
|
+
}
|
|
2366
|
+
rememberRewindUserAnchor(userMessageId) {
|
|
2367
|
+
if (typeof userMessageId !== "string" || userMessageId.length === 0) {
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
if (this.rewindTurnAnchors.some((anchor) => anchor.userMessageId === userMessageId)) {
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
this.rewindTurnAnchors.push({
|
|
2374
|
+
userMessageId,
|
|
2375
|
+
assistantMessageId: null,
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
rememberRewindAssistantAnchor(assistantMessageId) {
|
|
2379
|
+
if (typeof assistantMessageId !== "string" || assistantMessageId.length === 0) {
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
for (let index = this.rewindTurnAnchors.length - 1; index >= 0; index -= 1) {
|
|
2383
|
+
const anchor = this.rewindTurnAnchors[index];
|
|
2384
|
+
if (!anchor) {
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
anchor.assistantMessageId = assistantMessageId;
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
rememberTranscriptProgress(message, messageId) {
|
|
2392
|
+
if (!messageId) {
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
if (message.type === "user" &&
|
|
2396
|
+
!isSyntheticUserEntry(message) &&
|
|
2397
|
+
!isToolResultUserEntry(message)) {
|
|
2398
|
+
this.rememberRewindUserAnchor(messageId);
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
if (message.type === "assistant") {
|
|
2402
|
+
this.rememberRewindAssistantAnchor(messageId);
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
if (message.type === "stream_event") {
|
|
2406
|
+
const event = toObjectRecord(message.event) ?? {};
|
|
2407
|
+
const eventType = readTrimmedString(event.type);
|
|
2408
|
+
if (eventType === "message_start") {
|
|
2409
|
+
this.rememberRewindAssistantAnchor(messageId);
|
|
2410
|
+
}
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
resolveClaudeMessageId(messageId) {
|
|
2415
|
+
return messageId;
|
|
2416
|
+
}
|
|
2417
|
+
resolveConversationRewindTarget(messageId) {
|
|
2418
|
+
const targetUserMessageId = this.resolveClaudeMessageId(messageId);
|
|
2419
|
+
const index = this.rewindTurnAnchors.findIndex((anchor) => anchor.userMessageId === targetUserMessageId);
|
|
2420
|
+
if (index < 0) {
|
|
2421
|
+
throw new Error(`Claude rewind target ${messageId} is not in the tracked conversation`);
|
|
2422
|
+
}
|
|
2423
|
+
if (index === 0) {
|
|
2424
|
+
return { kind: "fresh-session" };
|
|
2425
|
+
}
|
|
2426
|
+
const previousTurn = this.rewindTurnAnchors[index - 1];
|
|
2427
|
+
if (!previousTurn?.assistantMessageId) {
|
|
2428
|
+
throw new Error(`Claude rewind cannot preserve turn ${index} because its assistant response id was not observed`);
|
|
2429
|
+
}
|
|
2430
|
+
return { kind: "fork", messageId: previousTurn.assistantMessageId };
|
|
2431
|
+
}
|
|
2432
|
+
async ensureQuery() {
|
|
2433
|
+
// Story 4.1 — Cache the resolved transport on first use. Even though the
|
|
2434
|
+
// current path always instantiates SdkTransport, recording the choice means
|
|
2435
|
+
// setMode/setModel/swapAgentProvider can read it without re-probing.
|
|
2436
|
+
this.ensureTransport();
|
|
2437
|
+
if (this.query && !this.queryRestartNeeded) {
|
|
2438
|
+
return this.query;
|
|
2439
|
+
}
|
|
2440
|
+
if (this.queryRestartNeeded && this.query) {
|
|
2441
|
+
const oldQuery = this.query;
|
|
2442
|
+
const oldInput = this.input;
|
|
2443
|
+
// Null out query/input BEFORE awaiting the old iterator's return so the
|
|
2444
|
+
// old pump sees this.query !== activeQuery and skips failActiveTurns.
|
|
2445
|
+
this.query = null;
|
|
2446
|
+
this.input = null;
|
|
2447
|
+
this.queryPumpPromise = null;
|
|
2448
|
+
this.queryRestartNeeded = false;
|
|
2449
|
+
oldInput?.end();
|
|
2450
|
+
oldQuery.close?.();
|
|
2451
|
+
try {
|
|
2452
|
+
await oldQuery.return?.();
|
|
2453
|
+
}
|
|
2454
|
+
catch {
|
|
2455
|
+
/* ignore */
|
|
2456
|
+
}
|
|
2457
|
+
// Tree-kill the old process tree now that the SDK has cleaned up.
|
|
2458
|
+
// If we skip this, MCP children of the previous claude process can
|
|
2459
|
+
// survive as orphans when the session spawns a replacement query.
|
|
2460
|
+
if (this.childProcess) {
|
|
2461
|
+
await terminateWithTreeKill(this.childProcess, {
|
|
2462
|
+
gracefulTimeoutMs: 2000,
|
|
2463
|
+
forceTimeoutMs: 2000,
|
|
2464
|
+
}).catch(() => {
|
|
2465
|
+
/* process may already be dead */
|
|
2466
|
+
});
|
|
2467
|
+
this.childProcess = null;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
// Preserve claudeSessionId across query recreation so buildOptions() passes
|
|
2471
|
+
// resume: sessionId and the new query continues the existing conversation.
|
|
2472
|
+
this.persistence = null;
|
|
2473
|
+
const input = createAsyncMessageInput();
|
|
2474
|
+
const options = await this.buildOptions();
|
|
2475
|
+
this.logger.debug({ options: summarizeClaudeOptionsForLog(options) }, "claude query");
|
|
2476
|
+
this.input = input;
|
|
2477
|
+
if (this.runtimeSettings?.transport === "pty") {
|
|
2478
|
+
// Interactive-PTY path (Max-plan billing). Reconstructs the SDKMessage stream from
|
|
2479
|
+
// the session transcript instead of --output-format stream-json. See
|
|
2480
|
+
// pty-session-launcher / PtyQuery for the details.
|
|
2481
|
+
//
|
|
2482
|
+
// OPT-IN ONLY: gated on an EXPLICIT `transport: "pty"` — deliberately NOT on
|
|
2483
|
+
// `resolvedTransport`/"auto", because resolveClaudeTransport(undefined) resolves to
|
|
2484
|
+
// "pty" whenever node-pty is present, which would silently move every agent onto the
|
|
2485
|
+
// (ToS-gray) Max-plan path. Rollout to auto is a separate, later decision.
|
|
2486
|
+
const freshId = this.pendingFreshSessionId;
|
|
2487
|
+
const resumeId = !freshId && this.claudeSessionId ? this.claudeSessionId : undefined;
|
|
2488
|
+
const ptySessionId = freshId ?? resumeId ?? randomUUID();
|
|
2489
|
+
const ptySession = await createPtySession({
|
|
2490
|
+
binary: await this.resolveBinary(),
|
|
2491
|
+
cwd: this.config.cwd,
|
|
2492
|
+
sessionId: ptySessionId,
|
|
2493
|
+
resume: Boolean(resumeId),
|
|
2494
|
+
model: this.config.model,
|
|
2495
|
+
permissionMode: this.currentMode,
|
|
2496
|
+
appendSystemPrompt: this.buildAppendedSystemPrompt(),
|
|
2497
|
+
input: input.iterable,
|
|
2498
|
+
runtimeSettings: this.runtimeSettings,
|
|
2499
|
+
launchEnv: this.launchEnv,
|
|
2500
|
+
mcpServers: this.config.mcpServers
|
|
2501
|
+
? this.normalizeMcpServers(this.config.mcpServers)
|
|
2502
|
+
: undefined,
|
|
2503
|
+
logger: this.logger,
|
|
2504
|
+
});
|
|
2505
|
+
this.activeTransport = ptySession.transport;
|
|
2506
|
+
this.query = ptySession.query;
|
|
2507
|
+
// Pin the resolved session id so a later query restart resumes this conversation.
|
|
2508
|
+
this.claudeSessionId = ptySessionId;
|
|
2509
|
+
}
|
|
2510
|
+
else {
|
|
2511
|
+
const sdkTransport = new SdkTransport({
|
|
2512
|
+
runtimeSettings: this.runtimeSettings,
|
|
2513
|
+
launchEnv: this.launchEnv,
|
|
2514
|
+
queryFactory: this.queryFactory,
|
|
2515
|
+
logger: this.logger,
|
|
2516
|
+
});
|
|
2517
|
+
this.activeTransport = sdkTransport;
|
|
2518
|
+
this.query = sdkTransport.createQuery({ prompt: input.iterable, options });
|
|
2519
|
+
}
|
|
2520
|
+
// Apply initial flag-settings to the freshly-created query. The options
|
|
2521
|
+
// we passed to createQuery seeded fastMode in settings, but the SDK's
|
|
2522
|
+
// applyFlagSettings is what actually flips the per-turn flag on the live
|
|
2523
|
+
// query; ensure both paths are in sync from turn 1.
|
|
2524
|
+
if (this.config.featureValues?.fast_mode === true) {
|
|
2525
|
+
try {
|
|
2526
|
+
await this.query.applyFlagSettings({ fastMode: true });
|
|
2527
|
+
}
|
|
2528
|
+
catch (err) {
|
|
2529
|
+
this.logger.warn({ err }, "applyFlagSettings({fastMode:true}) failed at ensureQuery");
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
// Do not kick off background control-plane queries here. Methods like
|
|
2533
|
+
// supportedCommands()/setPermissionMode() may execute immediately after
|
|
2534
|
+
// ensureQuery() (for listCommands()/setMode()), and sharing the same query
|
|
2535
|
+
// control plane can cause those calls to wait behind supportedModels().
|
|
2536
|
+
return this.query;
|
|
2537
|
+
}
|
|
2538
|
+
async awaitWithTimeout(promise, label) {
|
|
2539
|
+
if (!promise) {
|
|
2540
|
+
this.logger.trace({
|
|
2541
|
+
agentId: this.agentId,
|
|
2542
|
+
provider: "claude",
|
|
2543
|
+
sessionId: this.claudeSessionId,
|
|
2544
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2545
|
+
label,
|
|
2546
|
+
}, "provider.claude.query_operation.skip");
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
const startedAt = Date.now();
|
|
2550
|
+
this.logger.trace({
|
|
2551
|
+
agentId: this.agentId,
|
|
2552
|
+
provider: "claude",
|
|
2553
|
+
sessionId: this.claudeSessionId,
|
|
2554
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2555
|
+
label,
|
|
2556
|
+
}, "provider.claude.query_operation.start");
|
|
2557
|
+
try {
|
|
2558
|
+
await withTimeout(promise, 3000, "timeout");
|
|
2559
|
+
this.logger.trace({
|
|
2560
|
+
agentId: this.agentId,
|
|
2561
|
+
provider: "claude",
|
|
2562
|
+
sessionId: this.claudeSessionId,
|
|
2563
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2564
|
+
label,
|
|
2565
|
+
durationMs: Date.now() - startedAt,
|
|
2566
|
+
}, "provider.claude.query_operation.settled");
|
|
2567
|
+
}
|
|
2568
|
+
catch (error) {
|
|
2569
|
+
this.logger.warn({ err: error, label }, "Claude query operation did not settle cleanly");
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
resolveThinkingConfig() {
|
|
2573
|
+
const thinkingOptionId = this.config.thinkingOptionId && this.config.thinkingOptionId !== "default"
|
|
2574
|
+
? this.config.thinkingOptionId
|
|
2575
|
+
: undefined;
|
|
2576
|
+
if (thinkingOptionId === CLAUDE_ULTRACODE_THINKING_OPTION_ID) {
|
|
2577
|
+
return { thinking: { type: "adaptive" }, effort: "xhigh", ultracode: true };
|
|
2578
|
+
}
|
|
2579
|
+
if (thinkingOptionId && isClaudeThinkingEffort(thinkingOptionId)) {
|
|
2580
|
+
return { thinking: { type: "adaptive" }, effort: thinkingOptionId, ultracode: false };
|
|
2581
|
+
}
|
|
2582
|
+
return { thinking: undefined, effort: undefined, ultracode: false };
|
|
2583
|
+
}
|
|
2584
|
+
buildAppendedSystemPrompt() {
|
|
2585
|
+
return (composeSystemPromptParts(this.config.systemPrompt, this.config.daemonAppendSystemPrompt) ?? "");
|
|
2586
|
+
}
|
|
2587
|
+
buildSdkEnv(extraClaudeOptions) {
|
|
2588
|
+
return createProviderEnv({
|
|
2589
|
+
baseEnv: process.env,
|
|
2590
|
+
runtimeSettings: this.runtimeSettings,
|
|
2591
|
+
overlays: [
|
|
2592
|
+
extraClaudeOptions?.env,
|
|
2593
|
+
{
|
|
2594
|
+
// Increase MCP timeouts for long-running tool calls (10 minutes)
|
|
2595
|
+
MCP_TIMEOUT: "600000",
|
|
2596
|
+
MCP_TOOL_TIMEOUT: "600000",
|
|
2597
|
+
},
|
|
2598
|
+
this.launchEnv,
|
|
2599
|
+
],
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
async buildOptions() {
|
|
2603
|
+
const { thinking, effort, ultracode } = this.resolveThinkingConfig();
|
|
2604
|
+
const appendedSystemPrompt = this.buildAppendedSystemPrompt();
|
|
2605
|
+
const extraClaudeOptions = this.config.extra?.claude;
|
|
2606
|
+
const settingsOptions = this.buildSettingsOptions(extraClaudeOptions, { ultracode });
|
|
2607
|
+
const sdkEnv = this.buildSdkEnv(extraClaudeOptions);
|
|
2608
|
+
assertClaudeAutoModeEligible(this.currentMode, sdkEnv);
|
|
2609
|
+
const claudeBinary = await this.resolveBinary();
|
|
2610
|
+
this.logger.debug({
|
|
2611
|
+
claudeBinary,
|
|
2612
|
+
pathEnvKey: resolvePathEnvKey(),
|
|
2613
|
+
pathIncludesClaudeLocalBin: (process.env["Path"] ?? process.env["PATH"] ?? "")
|
|
2614
|
+
.toLowerCase()
|
|
2615
|
+
.includes("\\.local\\bin"),
|
|
2616
|
+
}, "Resolved Claude executable");
|
|
2617
|
+
const sessionBinding = {};
|
|
2618
|
+
if (this.pendingFreshSessionId) {
|
|
2619
|
+
sessionBinding.sessionId = this.pendingFreshSessionId;
|
|
2620
|
+
}
|
|
2621
|
+
else if (this.claudeSessionId) {
|
|
2622
|
+
sessionBinding.resume = this.claudeSessionId;
|
|
2623
|
+
}
|
|
2624
|
+
const base = {
|
|
2625
|
+
cwd: this.config.cwd,
|
|
2626
|
+
includePartialMessages: true,
|
|
2627
|
+
permissionMode: this.currentMode,
|
|
2628
|
+
// Dynamic mode switching can recreate the underlying Claude query. Keep the
|
|
2629
|
+
// bypass launch capability available so later setPermissionMode("bypassPermissions")
|
|
2630
|
+
// calls do not fail after a model/thinking/rewind-driven restart.
|
|
2631
|
+
allowDangerouslySkipPermissions: true,
|
|
2632
|
+
agents: this.defaults?.agents,
|
|
2633
|
+
canUseTool: this.handlePermissionRequest,
|
|
2634
|
+
pathToClaudeCodeExecutable: claudeBinary,
|
|
2635
|
+
// Use Claude Code preset system prompt and load CLAUDE.md files
|
|
2636
|
+
// Append provider-agnostic system prompts for agents.
|
|
2637
|
+
systemPrompt: {
|
|
2638
|
+
type: "preset",
|
|
2639
|
+
preset: "claude_code",
|
|
2640
|
+
append: appendedSystemPrompt,
|
|
2641
|
+
},
|
|
2642
|
+
settingSources: CLAUDE_SETTING_SOURCES,
|
|
2643
|
+
stderr: (data) => {
|
|
2644
|
+
this.captureStderr(data);
|
|
2645
|
+
this.logger.error({ stderr: data.trim() }, "Claude Agent SDK stderr");
|
|
2646
|
+
},
|
|
2647
|
+
// Required for provider-level /rewind support.
|
|
2648
|
+
enableFileCheckpointing: true,
|
|
2649
|
+
// If we have a session ID from a previous query (e.g., after interrupt),
|
|
2650
|
+
// resume that session to continue the conversation history.
|
|
2651
|
+
...sessionBinding,
|
|
2652
|
+
...(thinking ? { thinking } : {}),
|
|
2653
|
+
...(effort ? { effort } : {}),
|
|
2654
|
+
...extraClaudeOptions,
|
|
2655
|
+
...settingsOptions,
|
|
2656
|
+
...(this.persistSession === undefined ? {} : { persistSession: this.persistSession }),
|
|
2657
|
+
env: sdkEnv,
|
|
2658
|
+
};
|
|
2659
|
+
if (this.config.mcpServers) {
|
|
2660
|
+
base.mcpServers = this.normalizeMcpServers(this.config.mcpServers);
|
|
2661
|
+
}
|
|
2662
|
+
if (this.config.model) {
|
|
2663
|
+
base.model = this.config.model;
|
|
2664
|
+
}
|
|
2665
|
+
this.lastOptionsModel = base.model ?? null;
|
|
2666
|
+
if (this.claudeSessionId && !this.pendingFreshSessionId) {
|
|
2667
|
+
base.resume = this.claudeSessionId;
|
|
2668
|
+
}
|
|
2669
|
+
if (this.runtimeSettings?.disallowedTools?.length) {
|
|
2670
|
+
base.disallowedTools = [
|
|
2671
|
+
...(base.disallowedTools ?? []),
|
|
2672
|
+
...this.runtimeSettings.disallowedTools,
|
|
2673
|
+
];
|
|
2674
|
+
}
|
|
2675
|
+
return base;
|
|
2676
|
+
}
|
|
2677
|
+
buildSettingsOptions(extraClaudeOptions, input) {
|
|
2678
|
+
const fastMode = this.resolveFastModeSetting();
|
|
2679
|
+
if (fastMode === null && !input.ultracode) {
|
|
2680
|
+
return {};
|
|
2681
|
+
}
|
|
2682
|
+
return {
|
|
2683
|
+
settings: mergeClaudeSettings(extraClaudeOptions?.settings, {
|
|
2684
|
+
...(fastMode === null ? {} : { fastMode }),
|
|
2685
|
+
...(input.ultracode ? { ultracode: true } : {}),
|
|
2686
|
+
}),
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
resolveFastModeSetting() {
|
|
2690
|
+
if (!claudeModelSupportsFastMode(this.config.model)) {
|
|
2691
|
+
return null;
|
|
2692
|
+
}
|
|
2693
|
+
return this.config.featureValues?.fast_mode === true;
|
|
2694
|
+
}
|
|
2695
|
+
normalizeMcpServers(servers) {
|
|
2696
|
+
const result = {};
|
|
2697
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
2698
|
+
result[name] = toClaudeSdkMcpConfig(config);
|
|
2699
|
+
}
|
|
2700
|
+
return result;
|
|
2701
|
+
}
|
|
2702
|
+
async toSdkUserMessage(prompt) {
|
|
2703
|
+
const content = [];
|
|
2704
|
+
if (Array.isArray(prompt)) {
|
|
2705
|
+
for (const chunk of prompt) {
|
|
2706
|
+
if (chunk.type === "text") {
|
|
2707
|
+
content.push({ type: "text", text: chunk.text });
|
|
2708
|
+
}
|
|
2709
|
+
else if (chunk.type === "image") {
|
|
2710
|
+
if (isImageMimeType(chunk.mimeType)) {
|
|
2711
|
+
const compressed = await compressImageForApi(chunk.data, chunk.mimeType, this.logger);
|
|
2712
|
+
this.enforceCumulativeImageBudget(compressed.compressedBytes);
|
|
2713
|
+
this.logger.debug({
|
|
2714
|
+
sourceMimeType: chunk.mimeType,
|
|
2715
|
+
originalBytes: compressed.originalBytes,
|
|
2716
|
+
compressedBytes: compressed.compressedBytes,
|
|
2717
|
+
cumulativeBytes: this.cumulativeImageBase64Bytes,
|
|
2718
|
+
}, "Compressed inline image before forwarding to Claude SDK");
|
|
2719
|
+
content.push({
|
|
2720
|
+
type: "image",
|
|
2721
|
+
source: {
|
|
2722
|
+
type: "base64",
|
|
2723
|
+
media_type: compressed.mediaType,
|
|
2724
|
+
data: compressed.data,
|
|
2725
|
+
},
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
else {
|
|
2730
|
+
content.push({ type: "text", text: renderPromptAttachmentAsText(chunk) });
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
else {
|
|
2735
|
+
content.push({ type: "text", text: prompt });
|
|
2736
|
+
}
|
|
2737
|
+
const messageId = randomUUID();
|
|
2738
|
+
this.rememberUserMessageId(messageId);
|
|
2739
|
+
return {
|
|
2740
|
+
type: "user",
|
|
2741
|
+
message: {
|
|
2742
|
+
role: "user",
|
|
2743
|
+
content,
|
|
2744
|
+
},
|
|
2745
|
+
parent_tool_use_id: null,
|
|
2746
|
+
uuid: messageId,
|
|
2747
|
+
session_id: this.claudeSessionId ?? "",
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
enforceCumulativeImageBudget(addedBytes) {
|
|
2751
|
+
const projected = this.cumulativeImageBase64Bytes + addedBytes;
|
|
2752
|
+
if (projected > CUMULATIVE_IMAGE_LIMIT_BYTES) {
|
|
2753
|
+
const limitMb = (CUMULATIVE_IMAGE_LIMIT_BYTES / (1024 * 1024)).toFixed(0);
|
|
2754
|
+
const projectedMb = (projected / (1024 * 1024)).toFixed(1);
|
|
2755
|
+
throw new Error(`Aborting image attach: cumulative inline images in this session would reach ${projectedMb}MB, ` +
|
|
2756
|
+
`over the ${limitMb}MB safety limit set to stay under Anthropic's 32MB single-request cap. ` +
|
|
2757
|
+
"Start a new agent session to clear the image history, or send fewer/smaller images in this one.");
|
|
2758
|
+
}
|
|
2759
|
+
this.cumulativeImageBase64Bytes = projected;
|
|
2760
|
+
if (!this.cumulativeImageWarnEmitted &&
|
|
2761
|
+
this.cumulativeImageBase64Bytes > CUMULATIVE_IMAGE_WARN_BYTES) {
|
|
2762
|
+
this.cumulativeImageWarnEmitted = true;
|
|
2763
|
+
this.logger.warn({
|
|
2764
|
+
cumulativeBytes: this.cumulativeImageBase64Bytes,
|
|
2765
|
+
warnThresholdBytes: CUMULATIVE_IMAGE_WARN_BYTES,
|
|
2766
|
+
hardLimitBytes: CUMULATIVE_IMAGE_LIMIT_BYTES,
|
|
2767
|
+
}, "Cumulative inline image bytes nearing per-session safety limit; further images may be rejected");
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
transitionTurnState(next, reason) {
|
|
2771
|
+
if (this.turnState === next) {
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
this.logger.debug({ from: this.turnState, to: next, reason }, "Claude turn state transition");
|
|
2775
|
+
this.turnState = next;
|
|
2776
|
+
}
|
|
2777
|
+
syncTurnState(reason) {
|
|
2778
|
+
if (this.activeForegroundTurnId) {
|
|
2779
|
+
this.transitionTurnState("foreground", reason);
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
if (this.autonomousTurn) {
|
|
2783
|
+
this.transitionTurnState("autonomous", reason);
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
this.transitionTurnState("idle", reason);
|
|
2787
|
+
}
|
|
2788
|
+
isAbortError(message) {
|
|
2789
|
+
const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
|
|
2790
|
+
return errors.some((e) => /\baborted\b/i.test(e));
|
|
2791
|
+
}
|
|
2792
|
+
buildTurnFailedEvent(errorMessage) {
|
|
2793
|
+
const normalized = errorMessage.trim() || "Claude run failed";
|
|
2794
|
+
const exitCodeMatch = normalized.match(/\bcode\s+(\d+)\b/i);
|
|
2795
|
+
const code = exitCodeMatch ? exitCodeMatch[1] : undefined;
|
|
2796
|
+
const diagnostic = this.getRecentStderrDiagnostic();
|
|
2797
|
+
return {
|
|
2798
|
+
type: "turn_failed",
|
|
2799
|
+
provider: "claude",
|
|
2800
|
+
error: normalized,
|
|
2801
|
+
...(code ? { code } : {}),
|
|
2802
|
+
...(diagnostic ? { diagnostic } : {}),
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
captureStderr(data) {
|
|
2806
|
+
const text = data.trim();
|
|
2807
|
+
if (!text) {
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
const combined = this.recentStderr ? `${this.recentStderr}\n${text}` : text;
|
|
2811
|
+
this.recentStderr = combined.slice(-MAX_RECENT_STDERR_CHARS);
|
|
2812
|
+
}
|
|
2813
|
+
clearRecentStderr() {
|
|
2814
|
+
this.recentStderr = "";
|
|
2815
|
+
}
|
|
2816
|
+
getRecentStderrDiagnostic() {
|
|
2817
|
+
return this.recentStderr.trim() || undefined;
|
|
2818
|
+
}
|
|
2819
|
+
async awaitRecentStderrAfterProcessExit(error) {
|
|
2820
|
+
if (this.getRecentStderrDiagnostic()) {
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
const message = errorToMessageString(error);
|
|
2824
|
+
if (!/\bprocess exited with code\b/i.test(message) &&
|
|
2825
|
+
!/\bterminated by signal\b/i.test(message)) {
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
const startedAt = Date.now();
|
|
2829
|
+
while (!this.closed && !this.getRecentStderrDiagnostic()) {
|
|
2830
|
+
if (Date.now() - startedAt >= STDERR_FLUSH_WAIT_MS) {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
await new Promise((resolve) => setTimeout(resolve, STDERR_FLUSH_POLL_INTERVAL_MS));
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
createTurnId(owner) {
|
|
2837
|
+
return `${owner}-turn-${this.nextTurnOrdinal++}`;
|
|
2838
|
+
}
|
|
2839
|
+
isTerminalTurnEvent(event) {
|
|
2840
|
+
return (event.type === "turn_completed" ||
|
|
2841
|
+
event.type === "turn_failed" ||
|
|
2842
|
+
event.type === "turn_canceled");
|
|
2843
|
+
}
|
|
2844
|
+
async executeRewindTurn(_turnId, invocation) {
|
|
2845
|
+
this.notifySubscribers({ type: "turn_started", provider: "claude" });
|
|
2846
|
+
try {
|
|
2847
|
+
const rewindAttempt = await this.attemptRewind(invocation.args);
|
|
2848
|
+
if (!rewindAttempt.messageId || !rewindAttempt.result) {
|
|
2849
|
+
this.finishForegroundTurn({
|
|
2850
|
+
type: "turn_failed",
|
|
2851
|
+
provider: "claude",
|
|
2852
|
+
error: rewindAttempt.error ??
|
|
2853
|
+
"No prior user message available to rewind. Use /rewind <user_message_uuid>.",
|
|
2854
|
+
});
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
this.notifySubscribers({
|
|
2858
|
+
type: "timeline",
|
|
2859
|
+
provider: "claude",
|
|
2860
|
+
item: {
|
|
2861
|
+
type: "assistant_message",
|
|
2862
|
+
text: this.buildRewindSuccessMessage(rewindAttempt.messageId, rewindAttempt.result),
|
|
2863
|
+
},
|
|
2864
|
+
});
|
|
2865
|
+
this.finishForegroundTurn({ type: "turn_completed", provider: "claude" });
|
|
2866
|
+
}
|
|
2867
|
+
catch (error) {
|
|
2868
|
+
this.finishForegroundTurn({
|
|
2869
|
+
type: "turn_failed",
|
|
2870
|
+
provider: "claude",
|
|
2871
|
+
error: error instanceof Error ? error.message : "Failed to rewind tracked files",
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
shouldRecoverInterruptedQueryAbort(error, consecutiveRecoveries) {
|
|
2876
|
+
if (consecutiveRecoveries >= 3) {
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
let message;
|
|
2880
|
+
if (typeof error === "string") {
|
|
2881
|
+
message = error;
|
|
2882
|
+
}
|
|
2883
|
+
else if (error instanceof Error) {
|
|
2884
|
+
message = `${error.message}\n${error.stack ?? ""}`;
|
|
2885
|
+
}
|
|
2886
|
+
else {
|
|
2887
|
+
message = JSON.stringify(error);
|
|
2888
|
+
}
|
|
2889
|
+
return message.toLowerCase().includes("request was aborted");
|
|
2890
|
+
}
|
|
2891
|
+
finishForegroundTurn(event) {
|
|
2892
|
+
if (event.type === "turn_failed" || event.type === "turn_canceled") {
|
|
2893
|
+
this.flushPendingToolCalls();
|
|
2894
|
+
}
|
|
2895
|
+
this.notifySubscribers(event);
|
|
2896
|
+
this.activeForegroundTurnId = null;
|
|
2897
|
+
this.cancelCurrentTurn = null;
|
|
2898
|
+
this.activeTurnHasAssistantText = false;
|
|
2899
|
+
this.syncTurnState("foreground turn terminal");
|
|
2900
|
+
}
|
|
2901
|
+
dispatchEvents(events) {
|
|
2902
|
+
let terminalSeen = false;
|
|
2903
|
+
for (const event of events) {
|
|
2904
|
+
this.notifySubscribers(event);
|
|
2905
|
+
terminalSeen || (terminalSeen = this.isTerminalTurnEvent(event));
|
|
2906
|
+
}
|
|
2907
|
+
if (terminalSeen) {
|
|
2908
|
+
if (this.activeForegroundTurnId) {
|
|
2909
|
+
this.activeForegroundTurnId = null;
|
|
2910
|
+
this.cancelCurrentTurn = null;
|
|
2911
|
+
this.activeTurnHasAssistantText = false;
|
|
2912
|
+
this.syncTurnState("foreground turn terminal");
|
|
2913
|
+
}
|
|
2914
|
+
else if (this.autonomousTurn) {
|
|
2915
|
+
this.autonomousTurn = null;
|
|
2916
|
+
this.activeTurnHasAssistantText = false;
|
|
2917
|
+
this.syncTurnState("autonomous turn terminal");
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
startAutonomousTurn() {
|
|
2922
|
+
if (this.autonomousTurn) {
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
this.autonomousTurn = {
|
|
2926
|
+
id: this.createTurnId("autonomous"),
|
|
2927
|
+
};
|
|
2928
|
+
this.activeTurnHasAssistantText = false;
|
|
2929
|
+
this.contextUsage.beginTurn();
|
|
2930
|
+
this.notifySubscribers({ type: "turn_started", provider: "claude" });
|
|
2931
|
+
this.syncTurnState("autonomous turn started");
|
|
2932
|
+
}
|
|
2933
|
+
completeAutonomousTurn() {
|
|
2934
|
+
if (!this.autonomousTurn) {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
this.notifySubscribers({ type: "turn_completed", provider: "claude" });
|
|
2938
|
+
this.autonomousTurn = null;
|
|
2939
|
+
this.activeTurnHasAssistantText = false;
|
|
2940
|
+
this.syncTurnState("autonomous turn completed");
|
|
2941
|
+
}
|
|
2942
|
+
failActiveTurns(errorMessage) {
|
|
2943
|
+
const failure = this.buildTurnFailedEvent(errorMessage);
|
|
2944
|
+
this.flushPendingToolCalls();
|
|
2945
|
+
if (this.activeForegroundTurnId) {
|
|
2946
|
+
this.finishForegroundTurn(failure);
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
if (this.autonomousTurn) {
|
|
2950
|
+
this.dispatchEvents([failure]);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
startQueryPump() {
|
|
2954
|
+
if (this.closed || this.queryPumpPromise) {
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
const pump = this.runQueryPump().catch((error) => {
|
|
2958
|
+
this.logger.trace({
|
|
2959
|
+
agentId: this.agentId,
|
|
2960
|
+
provider: "claude",
|
|
2961
|
+
sessionId: this.claudeSessionId,
|
|
2962
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2963
|
+
err: error,
|
|
2964
|
+
}, "provider.claude.query_pump.exit_unexpected");
|
|
2965
|
+
});
|
|
2966
|
+
this.queryPumpPromise = pump;
|
|
2967
|
+
void pump.finally(() => {
|
|
2968
|
+
if (this.queryPumpPromise === pump) {
|
|
2969
|
+
this.queryPumpPromise = null;
|
|
2970
|
+
}
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
async runQueryPump() {
|
|
2974
|
+
let activeQuery;
|
|
2975
|
+
try {
|
|
2976
|
+
activeQuery = await this.ensureQuery();
|
|
2977
|
+
}
|
|
2978
|
+
catch (error) {
|
|
2979
|
+
this.logger.trace({
|
|
2980
|
+
agentId: this.agentId,
|
|
2981
|
+
provider: "claude",
|
|
2982
|
+
sessionId: this.claudeSessionId,
|
|
2983
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2984
|
+
err: error,
|
|
2985
|
+
}, "provider.claude.query_pump.init_failed");
|
|
2986
|
+
this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
let consecutiveInterruptAbortRecoveries = 0;
|
|
2990
|
+
const logRawMessage = (message) => {
|
|
2991
|
+
this.logger.trace({
|
|
2992
|
+
agentId: this.agentId,
|
|
2993
|
+
provider: "claude",
|
|
2994
|
+
sessionId: this.claudeSessionId,
|
|
2995
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
2996
|
+
messageType: message.type,
|
|
2997
|
+
messageSubtype: "subtype" in message ? message.subtype : undefined,
|
|
2998
|
+
messageUuid: "uuid" in message ? message.uuid : undefined,
|
|
2999
|
+
rawEvent: message,
|
|
3000
|
+
}, "provider.claude.raw_event");
|
|
3001
|
+
};
|
|
3002
|
+
const handlePumpedMessage = async (message) => {
|
|
3003
|
+
logRawMessage(message);
|
|
3004
|
+
consecutiveInterruptAbortRecoveries = 0;
|
|
3005
|
+
if (await this.handleMissingResumedConversation(message, activeQuery)) {
|
|
3006
|
+
return true;
|
|
3007
|
+
}
|
|
3008
|
+
await this.routeSdkMessageFromPump(message);
|
|
3009
|
+
return false;
|
|
3010
|
+
};
|
|
3011
|
+
const drainActiveQuery = async () => {
|
|
3012
|
+
for await (const message of activeQuery) {
|
|
3013
|
+
if (await handlePumpedMessage(message)) {
|
|
3014
|
+
return true;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
return false;
|
|
3018
|
+
};
|
|
3019
|
+
try {
|
|
3020
|
+
while (!this.closed && this.query === activeQuery) {
|
|
3021
|
+
try {
|
|
3022
|
+
if (await drainActiveQuery()) {
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
if (!this.closed && this.query === activeQuery) {
|
|
3026
|
+
this.failActiveTurns("Claude stream ended before terminal result");
|
|
3027
|
+
}
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
catch (error) {
|
|
3031
|
+
if (!this.closed &&
|
|
3032
|
+
this.query === activeQuery &&
|
|
3033
|
+
this.shouldRecoverInterruptedQueryAbort(error, consecutiveInterruptAbortRecoveries)) {
|
|
3034
|
+
consecutiveInterruptAbortRecoveries += 1;
|
|
3035
|
+
this.logger.debug({ recoveries: consecutiveInterruptAbortRecoveries }, "Recovering Claude query pump after interrupt abort");
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
if (!this.closed && this.query === activeQuery) {
|
|
3039
|
+
await this.awaitRecentStderrAfterProcessExit(error);
|
|
3040
|
+
this.failActiveTurns(error instanceof Error ? error.message : "Claude stream failed");
|
|
3041
|
+
}
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
finally {
|
|
3047
|
+
if (this.query === activeQuery) {
|
|
3048
|
+
this.query = null;
|
|
3049
|
+
this.input = null;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
shouldSuppressStaleResult(message) {
|
|
3054
|
+
// Suppress stale results from interrupted requests. The cancel path already
|
|
3055
|
+
// emitted the terminal event; this result is leftover from the killed API
|
|
3056
|
+
// request. Consume the flag on ANY result so it doesn't linger.
|
|
3057
|
+
if (message.type === "result" && this.pendingInterruptAbort) {
|
|
3058
|
+
this.pendingInterruptAbort = false;
|
|
3059
|
+
if (message.subtype !== "success") {
|
|
3060
|
+
this.logger.debug("Suppressing stale non-success result from interrupted request");
|
|
3061
|
+
return true;
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
if (message.type === "result" && message.subtype !== "success" && this.isAbortError(message)) {
|
|
3065
|
+
this.logger.debug("Suppressing abort result by content");
|
|
3066
|
+
return true;
|
|
3067
|
+
}
|
|
3068
|
+
return false;
|
|
3069
|
+
}
|
|
3070
|
+
isAssistantishMessage(message) {
|
|
3071
|
+
return (message.type === "assistant" ||
|
|
3072
|
+
message.type === "stream_event" ||
|
|
3073
|
+
message.type === "tool_progress" ||
|
|
3074
|
+
(message.type === "system" && message.subtype === "task_notification"));
|
|
3075
|
+
}
|
|
3076
|
+
async routeSdkMessageFromPump(message) {
|
|
3077
|
+
if (this.shouldSuppressStaleResult(message)) {
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
const isForeground = Boolean(this.activeForegroundTurnId);
|
|
3081
|
+
if (!isForeground && this.isAssistantishMessage(message)) {
|
|
3082
|
+
this.startAutonomousTurn();
|
|
3083
|
+
}
|
|
3084
|
+
if (!isForeground && !this.autonomousTurn && message.type === "result") {
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? null;
|
|
3088
|
+
const identifiers = readEventIdentifiers(message);
|
|
3089
|
+
this.rememberTranscriptProgress(message, readTranscriptUuid(message));
|
|
3090
|
+
this.logger.trace({
|
|
3091
|
+
agentId: this.agentId,
|
|
3092
|
+
provider: "claude",
|
|
3093
|
+
sessionId: this.claudeSessionId,
|
|
3094
|
+
turnId: turnId ?? undefined,
|
|
3095
|
+
messageType: message.type,
|
|
3096
|
+
identifiers,
|
|
3097
|
+
rawEvent: message,
|
|
3098
|
+
}, "provider.claude.parsed_event");
|
|
3099
|
+
const events = await this.buildPumpedMessageEvents(message, identifiers.messageId, turnId);
|
|
3100
|
+
if (events.length === 0) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
if (this.pendingInterruptAbort &&
|
|
3104
|
+
message.type === "result" &&
|
|
3105
|
+
events.some((event) => event.type === "turn_completed" || event.type === "turn_failed") &&
|
|
3106
|
+
(!this.activeForegroundTurnId || !this.foregroundHasVisibleActivity)) {
|
|
3107
|
+
this.pendingInterruptAbort = false;
|
|
3108
|
+
this.logger.debug("Suppressing stale Claude interrupt terminal result");
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
if (events.some((event) => event.type === "timeline" && event.item.type === "assistant_message")) {
|
|
3112
|
+
this.activeTurnHasAssistantText = true;
|
|
3113
|
+
}
|
|
3114
|
+
if (this.activeForegroundTurnId &&
|
|
3115
|
+
events.some((event) => event.type === "timeline" ||
|
|
3116
|
+
event.type === "permission_requested" ||
|
|
3117
|
+
event.type === "permission_resolved")) {
|
|
3118
|
+
this.foregroundHasVisibleActivity = true;
|
|
3119
|
+
}
|
|
3120
|
+
this.dispatchEvents(events);
|
|
3121
|
+
}
|
|
3122
|
+
async buildPumpedMessageEvents(message, messageIdHint, turnId) {
|
|
3123
|
+
const messageEvents = this.translateMessageToEvents(message, {
|
|
3124
|
+
suppressAssistantText: true,
|
|
3125
|
+
suppressReasoning: true,
|
|
3126
|
+
});
|
|
3127
|
+
const assistantTimelineEvents = readClaudeParentToolUseId(message)
|
|
3128
|
+
? []
|
|
3129
|
+
: this.timelineAssembler
|
|
3130
|
+
.consume({
|
|
3131
|
+
message,
|
|
3132
|
+
runId: turnId,
|
|
3133
|
+
messageIdHint,
|
|
3134
|
+
})
|
|
3135
|
+
.map((item) => ({
|
|
3136
|
+
type: "timeline",
|
|
3137
|
+
item,
|
|
3138
|
+
provider: "claude",
|
|
3139
|
+
}));
|
|
3140
|
+
return [...messageEvents, ...assistantTimelineEvents];
|
|
3141
|
+
}
|
|
3142
|
+
async handleMissingResumedConversation(message, activeQuery) {
|
|
3143
|
+
const staleResumeError = this.readMissingResumedConversationError(message);
|
|
3144
|
+
if (!staleResumeError) {
|
|
3145
|
+
return false;
|
|
3146
|
+
}
|
|
3147
|
+
this.logger.warn({
|
|
3148
|
+
error: staleResumeError,
|
|
3149
|
+
}, "Claude resumed session no longer exists; invalidating persisted session");
|
|
3150
|
+
this.failActiveTurns(staleResumeError);
|
|
3151
|
+
this.input?.end();
|
|
3152
|
+
await this.awaitWithTimeout(activeQuery.return?.(), "query pump return on missing resumed conversation");
|
|
3153
|
+
if (this.query === activeQuery) {
|
|
3154
|
+
this.query = null;
|
|
3155
|
+
this.input = null;
|
|
3156
|
+
}
|
|
3157
|
+
this.persistence = null;
|
|
3158
|
+
this.persistedHistory = [];
|
|
3159
|
+
this.historyPending = false;
|
|
3160
|
+
this.cachedRuntimeInfo = null;
|
|
3161
|
+
this.queryRestartNeeded = false;
|
|
3162
|
+
this.autonomousTurn = null;
|
|
3163
|
+
this.activeForegroundTurnId = null;
|
|
3164
|
+
this.syncTurnState("missing resumed conversation");
|
|
3165
|
+
return true;
|
|
3166
|
+
}
|
|
3167
|
+
async interruptActiveTurn() {
|
|
3168
|
+
const queryToInterrupt = this.query;
|
|
3169
|
+
if (!queryToInterrupt || typeof queryToInterrupt.interrupt !== "function") {
|
|
3170
|
+
this.logger.trace({
|
|
3171
|
+
agentId: this.agentId,
|
|
3172
|
+
provider: "claude",
|
|
3173
|
+
sessionId: this.claudeSessionId,
|
|
3174
|
+
turnId: this.activeForegroundTurnId ?? this.autonomousTurn?.id ?? undefined,
|
|
3175
|
+
}, "provider.claude.interrupt.no_query");
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
this.pendingInterruptAbort = true;
|
|
3179
|
+
try {
|
|
3180
|
+
await this.awaitWithTimeout(queryToInterrupt.interrupt(), "interruptActiveTurn query.interrupt()");
|
|
3181
|
+
}
|
|
3182
|
+
catch (error) {
|
|
3183
|
+
this.logger.warn({ err: error }, "Failed to interrupt active turn");
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
translateMessageToEvents(message, options) {
|
|
3187
|
+
const parentToolUseId = readClaudeParentToolUseId(message);
|
|
3188
|
+
if (parentToolUseId) {
|
|
3189
|
+
return this.sidechainTracker.handleMessage(message, parentToolUseId);
|
|
3190
|
+
}
|
|
3191
|
+
const events = [];
|
|
3192
|
+
if (message.type !== "system") {
|
|
3193
|
+
const sessionCapture = this.captureSessionIdFromMessage(message);
|
|
3194
|
+
if (sessionCapture.notice) {
|
|
3195
|
+
events.push({
|
|
3196
|
+
type: "timeline",
|
|
3197
|
+
provider: "claude",
|
|
3198
|
+
item: sessionCapture.notice,
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
if (sessionCapture.threadStartedSessionId) {
|
|
3202
|
+
events.push({
|
|
3203
|
+
type: "thread_started",
|
|
3204
|
+
provider: "claude",
|
|
3205
|
+
sessionId: sessionCapture.threadStartedSessionId,
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
switch (message.type) {
|
|
3210
|
+
case "system":
|
|
3211
|
+
this.appendSystemMessageEvents(message, events);
|
|
3212
|
+
break;
|
|
3213
|
+
case "user":
|
|
3214
|
+
this.appendUserMessageEvents(message, events);
|
|
3215
|
+
break;
|
|
3216
|
+
case "assistant": {
|
|
3217
|
+
const timelineItems = this.mapBlocksToTimeline(message.message.content, {
|
|
3218
|
+
suppressAssistantText: options?.suppressAssistantText ?? false,
|
|
3219
|
+
suppressReasoning: options?.suppressReasoning ?? false,
|
|
3220
|
+
});
|
|
3221
|
+
for (const item of timelineItems) {
|
|
3222
|
+
events.push({ type: "timeline", item, provider: "claude" });
|
|
3223
|
+
}
|
|
3224
|
+
break;
|
|
3225
|
+
}
|
|
3226
|
+
case "stream_event":
|
|
3227
|
+
this.appendStreamEventEvents(message, events, options);
|
|
3228
|
+
break;
|
|
3229
|
+
case "result":
|
|
3230
|
+
this.appendResultEvents(message, events);
|
|
3231
|
+
break;
|
|
3232
|
+
default:
|
|
3233
|
+
break;
|
|
3234
|
+
}
|
|
3235
|
+
return events;
|
|
3236
|
+
}
|
|
3237
|
+
emitSubmittedUserMessage(message, turnId) {
|
|
3238
|
+
const events = [];
|
|
3239
|
+
this.appendUserMessageEvents(message, events);
|
|
3240
|
+
if (events.length === 0) {
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
this.foregroundHasVisibleActivity = true;
|
|
3244
|
+
for (const event of events) {
|
|
3245
|
+
if (event.type === "timeline") {
|
|
3246
|
+
this.notifySubscribers({ ...event, turnId });
|
|
3247
|
+
}
|
|
3248
|
+
else {
|
|
3249
|
+
this.notifySubscribers(event);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
appendSystemMessageEvents(message, events) {
|
|
3254
|
+
if (message.subtype === "init") {
|
|
3255
|
+
const sessionUpdate = this.handleSystemMessage(message);
|
|
3256
|
+
if (sessionUpdate.notice) {
|
|
3257
|
+
events.push({
|
|
3258
|
+
type: "timeline",
|
|
3259
|
+
provider: "claude",
|
|
3260
|
+
item: sessionUpdate.notice,
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
if (sessionUpdate.threadStartedSessionId) {
|
|
3264
|
+
events.push({
|
|
3265
|
+
type: "thread_started",
|
|
3266
|
+
provider: "claude",
|
|
3267
|
+
sessionId: sessionUpdate.threadStartedSessionId,
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
if (message.subtype === "status") {
|
|
3273
|
+
const status = toObjectRecord(message)?.status;
|
|
3274
|
+
if (status === "compacting") {
|
|
3275
|
+
this.compacting = true;
|
|
3276
|
+
events.push({
|
|
3277
|
+
type: "timeline",
|
|
3278
|
+
item: { type: "compaction", status: "loading" },
|
|
3279
|
+
provider: "claude",
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
if (message.subtype === "compact_boundary") {
|
|
3285
|
+
const compactMetadata = readCompactionMetadata(message);
|
|
3286
|
+
events.push({
|
|
3287
|
+
type: "timeline",
|
|
3288
|
+
item: {
|
|
3289
|
+
type: "compaction",
|
|
3290
|
+
status: "completed",
|
|
3291
|
+
trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
|
|
3292
|
+
preTokens: compactMetadata?.preTokens,
|
|
3293
|
+
},
|
|
3294
|
+
provider: "claude",
|
|
3295
|
+
});
|
|
3296
|
+
events.push(this.contextUsage.buildCompactionUsageEvent(compactMetadata?.postTokens));
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
if (message.subtype === "task_notification") {
|
|
3300
|
+
this.appendTaskNotificationEvents(message, events);
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
if (message.subtype === "task_progress") {
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
appendTaskNotificationEvents(message, events) {
|
|
3308
|
+
// Expire a tracked background shell when its terminal notification lands.
|
|
3309
|
+
// Runs before the Task-subagent drop below — background shells aren't Task
|
|
3310
|
+
// calls, but feeding here keeps the signal independent of that routing.
|
|
3311
|
+
const lifecycle = extractTaskNotificationLifecycleFromSystemRecord(message);
|
|
3312
|
+
if (lifecycle) {
|
|
3313
|
+
this.backgroundTaskTracker.noteTaskNotification(lifecycle);
|
|
3314
|
+
}
|
|
3315
|
+
// TODO: subagent timelines are best-effort. Subagent task_notifications
|
|
3316
|
+
// arrive without parent_tool_use_id but with tool_use_id pointing at the
|
|
3317
|
+
// the parent's subagent tool call, so they slip past the sidechain router and pollute
|
|
3318
|
+
// the parent timeline. Drop them here; eventually thread them into the
|
|
3319
|
+
// parent tool call's sub_agent log instead.
|
|
3320
|
+
const taskUseId = message.tool_use_id;
|
|
3321
|
+
const cachedTool = taskUseId ? this.toolUseCache.get(taskUseId) : undefined;
|
|
3322
|
+
if (isClaudeSubagentToolName(cachedTool?.name)) {
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(message);
|
|
3326
|
+
if (taskNotificationItem) {
|
|
3327
|
+
events.push({
|
|
3328
|
+
type: "timeline",
|
|
3329
|
+
item: taskNotificationItem,
|
|
3330
|
+
provider: "claude",
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
appendUserMessageEvents(message, events) {
|
|
3335
|
+
if (isSyntheticUserEntry(message)) {
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
if (this.compacting) {
|
|
3339
|
+
this.compacting = false;
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
const messageId = typeof message.uuid === "string" && message.uuid.length > 0 ? message.uuid : undefined;
|
|
3343
|
+
if (messageId && this.emittedUserMessageIds.has(messageId)) {
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
this.rememberUserMessageId(messageId);
|
|
3347
|
+
this.rememberEmittedUserMessageId(messageId);
|
|
3348
|
+
const content = message.message?.content;
|
|
3349
|
+
// Expire a tracked background shell when its terminal notification arrives
|
|
3350
|
+
// as user-message content (the transcript path Claude Code uses).
|
|
3351
|
+
const backgroundLifecycle = extractTaskNotificationLifecycleFromUserContent({
|
|
3352
|
+
content,
|
|
3353
|
+
messageId,
|
|
3354
|
+
});
|
|
3355
|
+
if (backgroundLifecycle) {
|
|
3356
|
+
this.backgroundTaskTracker.noteTaskNotification(backgroundLifecycle);
|
|
3357
|
+
}
|
|
3358
|
+
const taskNotificationItem = mapTaskNotificationUserContentToToolCall({
|
|
3359
|
+
content,
|
|
3360
|
+
messageId,
|
|
3361
|
+
});
|
|
3362
|
+
if (taskNotificationItem) {
|
|
3363
|
+
events.push({
|
|
3364
|
+
type: "timeline",
|
|
3365
|
+
item: taskNotificationItem,
|
|
3366
|
+
provider: "claude",
|
|
3367
|
+
});
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
if (typeof content === "string" && content.length > 0) {
|
|
3371
|
+
if (!isClaudeTranscriptNoiseText(content)) {
|
|
3372
|
+
events.push({
|
|
3373
|
+
type: "timeline",
|
|
3374
|
+
item: {
|
|
3375
|
+
type: "user_message",
|
|
3376
|
+
text: content,
|
|
3377
|
+
...(messageId ? { messageId } : {}),
|
|
3378
|
+
},
|
|
3379
|
+
provider: "claude",
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
if (Array.isArray(content)) {
|
|
3385
|
+
this.appendUserContentArrayEvents(content, messageId, events);
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
appendUserContentArrayEvents(content, messageId, events) {
|
|
3389
|
+
const timelineItems = this.mapBlocksToTimeline(content, {
|
|
3390
|
+
textMessageType: "user_message",
|
|
3391
|
+
});
|
|
3392
|
+
for (const item of timelineItems) {
|
|
3393
|
+
if (item.type === "user_message" && messageId && !item.messageId) {
|
|
3394
|
+
events.push({
|
|
3395
|
+
type: "timeline",
|
|
3396
|
+
item: { ...item, messageId },
|
|
3397
|
+
provider: "claude",
|
|
3398
|
+
});
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
events.push({ type: "timeline", item, provider: "claude" });
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
appendStreamEventEvents(message, events, options) {
|
|
3405
|
+
const usageUpdatedEvent = this.contextUsage.buildStreamUsageEvent(message.event);
|
|
3406
|
+
if (usageUpdatedEvent) {
|
|
3407
|
+
events.push(usageUpdatedEvent);
|
|
3408
|
+
}
|
|
3409
|
+
const timelineItems = this.mapPartialEvent(message.event, {
|
|
3410
|
+
suppressAssistantText: options?.suppressAssistantText ?? false,
|
|
3411
|
+
suppressReasoning: options?.suppressReasoning ?? false,
|
|
3412
|
+
});
|
|
3413
|
+
for (const item of timelineItems) {
|
|
3414
|
+
events.push({ type: "timeline", item, provider: "claude" });
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
appendResultEvents(message, events) {
|
|
3418
|
+
const usage = this.convertUsage(message, message.modelUsage);
|
|
3419
|
+
if (message.subtype === "success") {
|
|
3420
|
+
// Built-in slash commands (e.g. /voice, /usage, "Unknown command: …")
|
|
3421
|
+
// run client-side in the Claude CLI with no model turn — output_tokens
|
|
3422
|
+
// is 0 and the user-visible text is carried in `result`. Surface it only
|
|
3423
|
+
// when the turn has not already emitted assistant text so zero-token
|
|
3424
|
+
// accounting from provider gateways does not duplicate streamed output.
|
|
3425
|
+
const resultText = typeof message.result === "string" ? message.result.trim() : "";
|
|
3426
|
+
const outputTokens = message.usage?.output_tokens;
|
|
3427
|
+
if (resultText.length > 0 && outputTokens === 0 && !this.activeTurnHasAssistantText) {
|
|
3428
|
+
events.push({
|
|
3429
|
+
type: "timeline",
|
|
3430
|
+
provider: "claude",
|
|
3431
|
+
item: {
|
|
3432
|
+
type: "assistant_message",
|
|
3433
|
+
text: resultText,
|
|
3434
|
+
messageId: message.uuid,
|
|
3435
|
+
},
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
events.push({ type: "turn_completed", provider: "claude", usage });
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
const errorMessage = "errors" in message && Array.isArray(message.errors) && message.errors.length > 0
|
|
3442
|
+
? message.errors.join("\n")
|
|
3443
|
+
: "Claude run failed";
|
|
3444
|
+
events.push(this.buildTurnFailedEvent(errorMessage));
|
|
3445
|
+
}
|
|
3446
|
+
createClaudeSessionChangedNotice(oldSessionId, newSessionId) {
|
|
3447
|
+
return {
|
|
3448
|
+
type: "assistant_message",
|
|
3449
|
+
text: `Claude switched to a new session: ${oldSessionId} -> ${newSessionId}`,
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
captureSessionIdFromMessage(message) {
|
|
3453
|
+
const msgRecord = toObjectRecord(message) ?? {};
|
|
3454
|
+
const sessionId = extractSessionIdRaw({
|
|
3455
|
+
session_id: msgRecord.session_id,
|
|
3456
|
+
sessionId: msgRecord.sessionId,
|
|
3457
|
+
session: isObjectRecord(msgRecord.session) ? { id: msgRecord.session.id } : null,
|
|
3458
|
+
}).trim();
|
|
3459
|
+
if (!sessionId) {
|
|
3460
|
+
return { threadStartedSessionId: null, notice: null };
|
|
3461
|
+
}
|
|
3462
|
+
if (this.claudeSessionId === null) {
|
|
3463
|
+
this.claudeSessionId = sessionId;
|
|
3464
|
+
this.pendingFreshSessionId = null;
|
|
3465
|
+
this.persistence = null;
|
|
3466
|
+
return { threadStartedSessionId: sessionId, notice: null };
|
|
3467
|
+
}
|
|
3468
|
+
if (this.claudeSessionId === sessionId) {
|
|
3469
|
+
this.pendingFreshSessionId = null;
|
|
3470
|
+
return { threadStartedSessionId: null, notice: null };
|
|
3471
|
+
}
|
|
3472
|
+
const oldSessionId = this.claudeSessionId;
|
|
3473
|
+
// Session ID changed mid-stream (e.g. a hook caused Claude to restart
|
|
3474
|
+
// with a new session). Accept the new ID and continue — the turn should
|
|
3475
|
+
// not be failed just because the underlying subprocess cycled.
|
|
3476
|
+
this.logger.warn({ existingSessionId: this.claudeSessionId, newSessionId: sessionId }, "Claude session ID changed in message; accepting new session");
|
|
3477
|
+
this.claudeSessionId = sessionId;
|
|
3478
|
+
this.pendingFreshSessionId = null;
|
|
3479
|
+
this.persistence = null;
|
|
3480
|
+
return {
|
|
3481
|
+
threadStartedSessionId: sessionId,
|
|
3482
|
+
notice: this.createClaudeSessionChangedNotice(oldSessionId, sessionId),
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
handleSystemMessage(message) {
|
|
3486
|
+
if (message.subtype !== "init") {
|
|
3487
|
+
return { threadStartedSessionId: null, notice: null };
|
|
3488
|
+
}
|
|
3489
|
+
const msgRecord = toObjectRecord(message) ?? {};
|
|
3490
|
+
const newSessionId = extractSessionIdRaw({
|
|
3491
|
+
session_id: msgRecord.session_id,
|
|
3492
|
+
sessionId: msgRecord.sessionId,
|
|
3493
|
+
session: isObjectRecord(msgRecord.session) ? { id: msgRecord.session.id } : null,
|
|
3494
|
+
}).trim();
|
|
3495
|
+
if (!newSessionId) {
|
|
3496
|
+
return { threadStartedSessionId: null, notice: null };
|
|
3497
|
+
}
|
|
3498
|
+
const existingSessionId = this.claudeSessionId;
|
|
3499
|
+
let threadStartedSessionId = null;
|
|
3500
|
+
let notice = null;
|
|
3501
|
+
if (existingSessionId === null) {
|
|
3502
|
+
this.claudeSessionId = newSessionId;
|
|
3503
|
+
this.pendingFreshSessionId = null;
|
|
3504
|
+
threadStartedSessionId = newSessionId;
|
|
3505
|
+
this.logger.debug({ sessionId: newSessionId }, "Claude session ID set for the first time");
|
|
3506
|
+
}
|
|
3507
|
+
else if (existingSessionId === newSessionId) {
|
|
3508
|
+
this.pendingFreshSessionId = null;
|
|
3509
|
+
this.logger.debug({ sessionId: newSessionId }, "Claude session ID unchanged (same value)");
|
|
3510
|
+
}
|
|
3511
|
+
else {
|
|
3512
|
+
// Session ID changed in an init message (e.g. a hook restarted Claude
|
|
3513
|
+
// with a new session mid-turn). Accept the new ID and continue.
|
|
3514
|
+
this.logger.warn({ existingSessionId, newSessionId }, "Claude session ID changed in init message; accepting new session");
|
|
3515
|
+
this.claudeSessionId = newSessionId;
|
|
3516
|
+
this.pendingFreshSessionId = null;
|
|
3517
|
+
threadStartedSessionId = newSessionId;
|
|
3518
|
+
notice = this.createClaudeSessionChangedNotice(existingSessionId, newSessionId);
|
|
3519
|
+
}
|
|
3520
|
+
this.availableModes = DEFAULT_MODES;
|
|
3521
|
+
this.currentMode = message.permissionMode;
|
|
3522
|
+
if (this.currentMode !== "plan") {
|
|
3523
|
+
this.planResumeMode = this.currentMode;
|
|
3524
|
+
}
|
|
3525
|
+
this.persistence = null;
|
|
3526
|
+
if (message.model) {
|
|
3527
|
+
const normalizedRuntimeModel = normalizeClaudeRuntimeModelId(message.model);
|
|
3528
|
+
this.logger.debug({ runtimeModel: message.model, normalizedRuntimeModel }, "Captured runtime model from SDK init");
|
|
3529
|
+
if (normalizedRuntimeModel) {
|
|
3530
|
+
this.lastOptionsModel = normalizedRuntimeModel;
|
|
3531
|
+
}
|
|
3532
|
+
else if (!this.lastOptionsModel) {
|
|
3533
|
+
this.lastOptionsModel = this.config.model ?? null;
|
|
3534
|
+
}
|
|
3535
|
+
this.lastRuntimeModel = message.model;
|
|
3536
|
+
this.cachedRuntimeInfo = null;
|
|
3537
|
+
}
|
|
3538
|
+
return { threadStartedSessionId, notice };
|
|
3539
|
+
}
|
|
3540
|
+
readMissingResumedConversationError(message) {
|
|
3541
|
+
if (message.type !== "result" || message.subtype !== "error_during_execution") {
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
if (!this.claudeSessionId) {
|
|
3545
|
+
return null;
|
|
3546
|
+
}
|
|
3547
|
+
const errors = "errors" in message && Array.isArray(message.errors) ? message.errors : [];
|
|
3548
|
+
for (const entry of errors) {
|
|
3549
|
+
if (typeof entry !== "string") {
|
|
3550
|
+
continue;
|
|
3551
|
+
}
|
|
3552
|
+
const match = entry.match(/^No conversation found with session ID:\s*(.+)$/);
|
|
3553
|
+
if (!match) {
|
|
3554
|
+
continue;
|
|
3555
|
+
}
|
|
3556
|
+
if (match[1]?.trim() === this.claudeSessionId) {
|
|
3557
|
+
return entry.trim();
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
return null;
|
|
3561
|
+
}
|
|
3562
|
+
convertUsage(message, modelUsage) {
|
|
3563
|
+
return this.contextUsage.buildResultUsage(message, modelUsage);
|
|
3564
|
+
}
|
|
3565
|
+
enqueueTimeline(item) {
|
|
3566
|
+
this.pushEvent({ type: "timeline", item, provider: "claude" });
|
|
3567
|
+
}
|
|
3568
|
+
flushPendingToolCalls() {
|
|
3569
|
+
for (const [id, entry] of this.toolUseCache) {
|
|
3570
|
+
if (entry.started) {
|
|
3571
|
+
this.pushToolCall(mapClaudeCanceledToolCall({
|
|
3572
|
+
name: entry.name,
|
|
3573
|
+
callId: id,
|
|
3574
|
+
input: entry.input ?? null,
|
|
3575
|
+
output: null,
|
|
3576
|
+
}));
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
this.toolUseCache.clear();
|
|
3580
|
+
this.sidechainTracker.clear();
|
|
3581
|
+
this.backgroundTaskTracker.clear();
|
|
3582
|
+
}
|
|
3583
|
+
pushToolCall(item, target) {
|
|
3584
|
+
if (!item) {
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
if (target) {
|
|
3588
|
+
target.push(item);
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
this.enqueueTimeline(item);
|
|
3592
|
+
}
|
|
3593
|
+
pushEvent(event) {
|
|
3594
|
+
this.notifySubscribers(event);
|
|
3595
|
+
}
|
|
3596
|
+
notifySubscribers(event) {
|
|
3597
|
+
const turnId = this.activeForegroundTurnId ?? this.autonomousTurn?.id;
|
|
3598
|
+
const tagged = turnId ? { ...event, turnId } : event;
|
|
3599
|
+
this.logger.trace({
|
|
3600
|
+
agentId: this.agentId,
|
|
3601
|
+
provider: "claude",
|
|
3602
|
+
sessionId: this.claudeSessionId,
|
|
3603
|
+
turnId: getAgentStreamEventTurnId(tagged),
|
|
3604
|
+
event: tagged,
|
|
3605
|
+
}, "provider.claude.event_emit");
|
|
3606
|
+
for (const callback of this.subscribers) {
|
|
3607
|
+
try {
|
|
3608
|
+
callback(tagged);
|
|
3609
|
+
}
|
|
3610
|
+
catch (error) {
|
|
3611
|
+
this.logger.warn({ err: error }, "Subscriber callback threw");
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
normalizePermissionUpdates(updates) {
|
|
3616
|
+
if (!updates || updates.length === 0) {
|
|
3617
|
+
return undefined;
|
|
3618
|
+
}
|
|
3619
|
+
const normalized = updates.filter(isPermissionUpdate);
|
|
3620
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
3621
|
+
}
|
|
3622
|
+
rejectAllPendingPermissions(error) {
|
|
3623
|
+
for (const [id, pending] of this.pendingPermissions) {
|
|
3624
|
+
pending.cleanup?.();
|
|
3625
|
+
pending.reject(error);
|
|
3626
|
+
this.pendingPermissions.delete(id);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
loadPersistedHistory(sessionId) {
|
|
3630
|
+
try {
|
|
3631
|
+
const historyPath = this.resolveHistoryPath(sessionId);
|
|
3632
|
+
if (!historyPath || !fs.existsSync(historyPath)) {
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
this.ingestPersistedHistory(fs.readFileSync(historyPath, "utf8"));
|
|
3636
|
+
}
|
|
3637
|
+
catch {
|
|
3638
|
+
// ignore history load failures
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
ingestPersistedHistory(content) {
|
|
3642
|
+
if (!content) {
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
const timeline = [];
|
|
3646
|
+
for (const line of content.split(/\r?\n/)) {
|
|
3647
|
+
this.ingestPersistedHistoryLine(line, timeline);
|
|
3648
|
+
}
|
|
3649
|
+
if (timeline.length > 0) {
|
|
3650
|
+
this.persistedHistory = [...this.persistedHistory, ...timeline];
|
|
3651
|
+
this.historyPending = true;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
ingestPersistedHistoryLine(line, timeline) {
|
|
3655
|
+
const trimmed = line.trim();
|
|
3656
|
+
if (!trimmed) {
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
let entry;
|
|
3660
|
+
try {
|
|
3661
|
+
const parsed = JSON.parse(trimmed);
|
|
3662
|
+
const record = toObjectRecord(parsed);
|
|
3663
|
+
if (!record) {
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
entry = record;
|
|
3667
|
+
}
|
|
3668
|
+
catch {
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
if (entry.isSidechain) {
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
const historyTimestamp = normalizeProviderReplayTimestamp(entry.timestamp);
|
|
3675
|
+
const items = this.convertHistoryEntry(entry);
|
|
3676
|
+
const isVisibleUserEntry = entry.type === "user" &&
|
|
3677
|
+
typeof entry.uuid === "string" &&
|
|
3678
|
+
!isSyntheticHistoryUserEntry(entry) &&
|
|
3679
|
+
!isToolResultUserEntry(entry);
|
|
3680
|
+
if (isVisibleUserEntry && typeof entry.uuid === "string") {
|
|
3681
|
+
this.rememberUserMessageId(entry.uuid);
|
|
3682
|
+
this.rememberRewindUserAnchor(entry.uuid);
|
|
3683
|
+
}
|
|
3684
|
+
if (entry.type === "assistant" && typeof entry.uuid === "string") {
|
|
3685
|
+
this.rememberRewindAssistantAnchor(entry.uuid);
|
|
3686
|
+
}
|
|
3687
|
+
if (items.length > 0) {
|
|
3688
|
+
timeline.push(...items.map((item) => ({
|
|
3689
|
+
item,
|
|
3690
|
+
timestamp: historyTimestamp ?? undefined,
|
|
3691
|
+
})));
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
resolveHistoryPath(sessionId) {
|
|
3695
|
+
const cwd = this.config.cwd;
|
|
3696
|
+
if (cwd) {
|
|
3697
|
+
const direct = resolveTranscriptPath({ cwd, sessionId });
|
|
3698
|
+
if (fs.existsSync(direct))
|
|
3699
|
+
return direct;
|
|
3700
|
+
}
|
|
3701
|
+
// The transcript may live under a different project-dir slug than the
|
|
3702
|
+
// current cwd — e.g. the session was created under a now-renamed $HOME
|
|
3703
|
+
// (/Users/marcelo/…) and is resumed from /Users/claudinho/… (or any
|
|
3704
|
+
// fallback cwd). Claude's per-cwd path no longer matches, so scan all
|
|
3705
|
+
// project dirs; without this the resumed chat hydrates empty (blank).
|
|
3706
|
+
return findTranscriptAcrossProjects(sessionId);
|
|
3707
|
+
}
|
|
3708
|
+
convertHistoryEntry(entry) {
|
|
3709
|
+
return convertClaudeHistoryEntry(entry, (content) => this.mapBlocksToTimeline(content));
|
|
3710
|
+
}
|
|
3711
|
+
// Maps Claude content blocks into AgentTimelineItems.
|
|
3712
|
+
//
|
|
3713
|
+
// textMessageType controls what type text blocks emit:
|
|
3714
|
+
// - "assistant_message" (default): one item per text block (streaming granularity)
|
|
3715
|
+
// - "user_message": coalesces all text blocks into a single user_message
|
|
3716
|
+
// (matches extractUserMessageText semantics: trim each block, join with "\n\n")
|
|
3717
|
+
//
|
|
3718
|
+
// suppressAssistantText only applies when textMessageType is "assistant_message" — user text
|
|
3719
|
+
// must never be suppressed since the TimelineAssembler only handles assistant text.
|
|
3720
|
+
//
|
|
3721
|
+
// NOTE: convertClaudeHistoryEntry uses extractUserMessageText directly instead of this function
|
|
3722
|
+
// for user entries. Both paths must produce equivalent user_message items.
|
|
3723
|
+
mapBlocksToTimeline(content, options) {
|
|
3724
|
+
const textMessageType = options?.textMessageType ?? "assistant_message";
|
|
3725
|
+
const suppressText = textMessageType === "assistant_message" && (options?.suppressAssistantText ?? false);
|
|
3726
|
+
const suppressReasoning = options?.suppressReasoning ?? false;
|
|
3727
|
+
if (typeof content === "string") {
|
|
3728
|
+
if (!content ||
|
|
3729
|
+
content === INTERRUPT_TOOL_USE_PLACEHOLDER ||
|
|
3730
|
+
isClaudeTranscriptNoiseText(content)) {
|
|
3731
|
+
return [];
|
|
3732
|
+
}
|
|
3733
|
+
if (suppressText) {
|
|
3734
|
+
return [];
|
|
3735
|
+
}
|
|
3736
|
+
return [{ type: textMessageType, text: content }];
|
|
3737
|
+
}
|
|
3738
|
+
const items = [];
|
|
3739
|
+
// User SDK entries can arrive as multiple text blocks, but Paseo treats them as one message.
|
|
3740
|
+
const userTextParts = [];
|
|
3741
|
+
for (const block of content) {
|
|
3742
|
+
if (!isClaudeContentChunk(block)) {
|
|
3743
|
+
continue;
|
|
3744
|
+
}
|
|
3745
|
+
this.mapBlockToTimeline(block, {
|
|
3746
|
+
items,
|
|
3747
|
+
userTextParts,
|
|
3748
|
+
textMessageType,
|
|
3749
|
+
suppressText,
|
|
3750
|
+
suppressReasoning,
|
|
3751
|
+
});
|
|
3752
|
+
}
|
|
3753
|
+
if (textMessageType === "user_message" && userTextParts.length > 0) {
|
|
3754
|
+
items.unshift({
|
|
3755
|
+
type: "user_message",
|
|
3756
|
+
text: userTextParts.join("\n\n"),
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
return items;
|
|
3760
|
+
}
|
|
3761
|
+
appendTextBlockToTimeline(block, context) {
|
|
3762
|
+
const { items, userTextParts, textMessageType, suppressText } = context;
|
|
3763
|
+
const text = typeof block.text === "string" ? block.text : "";
|
|
3764
|
+
if (!text || text === INTERRUPT_TOOL_USE_PLACEHOLDER || isClaudeTranscriptNoiseText(text)) {
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
if (textMessageType === "user_message") {
|
|
3768
|
+
const trimmed = text.trim();
|
|
3769
|
+
if (trimmed) {
|
|
3770
|
+
userTextParts.push(trimmed);
|
|
3771
|
+
}
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
if (!suppressText) {
|
|
3775
|
+
items.push({ type: "assistant_message", text });
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
mapBlockToTimeline(block, context) {
|
|
3779
|
+
switch (block.type) {
|
|
3780
|
+
case "text":
|
|
3781
|
+
case "text_delta":
|
|
3782
|
+
this.appendTextBlockToTimeline(block, context);
|
|
3783
|
+
break;
|
|
3784
|
+
case "thinking":
|
|
3785
|
+
case "thinking_delta":
|
|
3786
|
+
if (typeof block.thinking === "string" && block.thinking && !context.suppressReasoning) {
|
|
3787
|
+
context.items.push({ type: "reasoning", text: block.thinking });
|
|
3788
|
+
}
|
|
3789
|
+
break;
|
|
3790
|
+
case "tool_use":
|
|
3791
|
+
case "server_tool_use":
|
|
3792
|
+
case "mcp_tool_use":
|
|
3793
|
+
this.handleToolUseStart(block, context.items);
|
|
3794
|
+
break;
|
|
3795
|
+
case "tool_result":
|
|
3796
|
+
case "mcp_tool_result":
|
|
3797
|
+
case "web_fetch_tool_result":
|
|
3798
|
+
case "web_search_tool_result":
|
|
3799
|
+
case "code_execution_tool_result":
|
|
3800
|
+
case "bash_code_execution_tool_result":
|
|
3801
|
+
case "text_editor_code_execution_tool_result":
|
|
3802
|
+
this.handleToolResult(block, context.items);
|
|
3803
|
+
break;
|
|
3804
|
+
default:
|
|
3805
|
+
break;
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
handleToolUseStart(block, items) {
|
|
3809
|
+
const entry = this.upsertToolUseEntry(block);
|
|
3810
|
+
if (!entry) {
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3813
|
+
if (entry.started) {
|
|
3814
|
+
return;
|
|
3815
|
+
}
|
|
3816
|
+
entry.started = true;
|
|
3817
|
+
this.toolUseCache.set(entry.id, entry);
|
|
3818
|
+
this.pushToolCall(mapClaudeRunningToolCall({
|
|
3819
|
+
name: entry.name,
|
|
3820
|
+
callId: entry.id,
|
|
3821
|
+
input: entry.input ?? this.normalizeToolInput(block.input) ?? null,
|
|
3822
|
+
output: null,
|
|
3823
|
+
}), items);
|
|
3824
|
+
}
|
|
3825
|
+
handleToolResult(block, items) {
|
|
3826
|
+
const entry = typeof block.tool_use_id === "string" ? this.toolUseCache.get(block.tool_use_id) : undefined;
|
|
3827
|
+
const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
|
|
3828
|
+
const toolName = entry?.name ?? blockToolName ?? "tool";
|
|
3829
|
+
const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
|
|
3830
|
+
? block.tool_use_id
|
|
3831
|
+
: (entry?.id ?? null);
|
|
3832
|
+
// Pull image blocks out of the result so base64 never reaches the tool output, and render each
|
|
3833
|
+
// one as an assistant_message markdown image after the tool_call (matching how Codex emits).
|
|
3834
|
+
const { images, text } = splitClaudeToolResultImages(block.content);
|
|
3835
|
+
const output = this.buildToolOutput(text, block, entry);
|
|
3836
|
+
this.trackBackgroundShellStart(block, toolName);
|
|
3837
|
+
if (block.is_error) {
|
|
3838
|
+
this.pushToolCall(mapClaudeFailedToolCall({
|
|
3839
|
+
name: toolName,
|
|
3840
|
+
callId,
|
|
3841
|
+
input: entry?.input ?? null,
|
|
3842
|
+
output: output ?? null,
|
|
3843
|
+
error: { ...block, content: text },
|
|
3844
|
+
}), items);
|
|
3845
|
+
}
|
|
3846
|
+
else {
|
|
3847
|
+
this.pushToolCall(mapClaudeCompletedToolCall({
|
|
3848
|
+
name: toolName,
|
|
3849
|
+
callId,
|
|
3850
|
+
input: entry?.input ?? null,
|
|
3851
|
+
output: output ?? null,
|
|
3852
|
+
}), items);
|
|
3853
|
+
}
|
|
3854
|
+
for (const image of images) {
|
|
3855
|
+
const imageItem = renderProviderImageOutputAsAssistantMarkdown(image, {
|
|
3856
|
+
materialize: materializeProviderImage,
|
|
3857
|
+
});
|
|
3858
|
+
if (imageItem) {
|
|
3859
|
+
items.push(imageItem);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
if (typeof block.tool_use_id === "string") {
|
|
3863
|
+
this.toolUseCache.delete(block.tool_use_id);
|
|
3864
|
+
this.sidechainTracker.delete(block.tool_use_id);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
/**
|
|
3868
|
+
* A backgrounded Bash ("Bash" with run_in_background) returns its handle
|
|
3869
|
+
* immediately — "Command running in background with ID: <id>" — so its
|
|
3870
|
+
* tool_result is the START signal for a shell that will outlive the turn.
|
|
3871
|
+
*/
|
|
3872
|
+
trackBackgroundShellStart(block, toolName) {
|
|
3873
|
+
if (toolName !== "Bash" || block.is_error) {
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
const resultText = typeof block.content === "string" ? block.content : JSON.stringify(block.content ?? "");
|
|
3877
|
+
this.backgroundTaskTracker.noteToolResultText(resultText);
|
|
3878
|
+
}
|
|
3879
|
+
buildToolOutput(content, block, entry) {
|
|
3880
|
+
if (block.is_error) {
|
|
3881
|
+
return undefined;
|
|
3882
|
+
}
|
|
3883
|
+
const blockServer = typeof block.server === "string" ? block.server : undefined;
|
|
3884
|
+
const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
|
|
3885
|
+
const server = entry?.server ?? blockServer ?? "tool";
|
|
3886
|
+
const tool = entry?.name ?? blockToolName ?? "tool";
|
|
3887
|
+
const coercedContent = coerceToolResultContentToString(content);
|
|
3888
|
+
const input = entry?.input;
|
|
3889
|
+
// Build structured result based on tool type
|
|
3890
|
+
const structured = this.buildStructuredToolResult(server, tool, coercedContent, input);
|
|
3891
|
+
if (structured) {
|
|
3892
|
+
return structured;
|
|
3893
|
+
}
|
|
3894
|
+
// Fallback format - try to parse JSON first
|
|
3895
|
+
const result = {};
|
|
3896
|
+
if (coercedContent.length > 0) {
|
|
3897
|
+
try {
|
|
3898
|
+
// If content is a JSON string, parse it
|
|
3899
|
+
result.output = JSON.parse(coercedContent);
|
|
3900
|
+
}
|
|
3901
|
+
catch {
|
|
3902
|
+
// If not JSON, return unchanged (no extra wrapping)
|
|
3903
|
+
result.output = coercedContent;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
// Preserve file changes tracked during tool execution
|
|
3907
|
+
if (entry?.files?.length) {
|
|
3908
|
+
result.files = entry.files;
|
|
3909
|
+
}
|
|
3910
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
3911
|
+
}
|
|
3912
|
+
isCommandExecutionTool(normalizedServer, normalizedTool, input) {
|
|
3913
|
+
if (normalizedServer.includes("bash") ||
|
|
3914
|
+
normalizedServer.includes("shell") ||
|
|
3915
|
+
normalizedServer.includes("command")) {
|
|
3916
|
+
return true;
|
|
3917
|
+
}
|
|
3918
|
+
if (normalizedTool.includes("bash") ||
|
|
3919
|
+
normalizedTool.includes("shell") ||
|
|
3920
|
+
normalizedTool.includes("command")) {
|
|
3921
|
+
return true;
|
|
3922
|
+
}
|
|
3923
|
+
return Boolean(input && (typeof input.command === "string" || Array.isArray(input.command)));
|
|
3924
|
+
}
|
|
3925
|
+
static isFileWriteTool(normalizedTool) {
|
|
3926
|
+
return (normalizedTool.includes("write") ||
|
|
3927
|
+
normalizedTool === "write_file" ||
|
|
3928
|
+
normalizedTool === "create_file");
|
|
3929
|
+
}
|
|
3930
|
+
static isFileEditTool(normalizedTool) {
|
|
3931
|
+
return (normalizedTool.includes("edit") ||
|
|
3932
|
+
normalizedTool.includes("patch") ||
|
|
3933
|
+
normalizedTool === "apply_patch" ||
|
|
3934
|
+
normalizedTool === "apply_diff");
|
|
3935
|
+
}
|
|
3936
|
+
static isFileReadTool(normalizedTool) {
|
|
3937
|
+
return (normalizedTool.includes("read") ||
|
|
3938
|
+
normalizedTool === "read_file" ||
|
|
3939
|
+
normalizedTool === "view_file");
|
|
3940
|
+
}
|
|
3941
|
+
buildStructuredToolResult(server, tool, output, input) {
|
|
3942
|
+
const normalizedServer = server.toLowerCase();
|
|
3943
|
+
const normalizedTool = tool.toLowerCase();
|
|
3944
|
+
if (this.isCommandExecutionTool(normalizedServer, normalizedTool, input)) {
|
|
3945
|
+
const command = this.extractCommandText(input ?? {}) ?? "command";
|
|
3946
|
+
return {
|
|
3947
|
+
type: "command",
|
|
3948
|
+
command,
|
|
3949
|
+
output,
|
|
3950
|
+
cwd: typeof input?.cwd === "string" ? input.cwd : undefined,
|
|
3951
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
if (ClaudeAgentSession.isFileWriteTool(normalizedTool) &&
|
|
3954
|
+
input &&
|
|
3955
|
+
typeof input.file_path === "string") {
|
|
3956
|
+
return {
|
|
3957
|
+
type: "file_write",
|
|
3958
|
+
filePath: input.file_path,
|
|
3959
|
+
oldContent: "",
|
|
3960
|
+
newContent: typeof input.content === "string" ? input.content : output,
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
if (ClaudeAgentSession.isFileEditTool(normalizedTool) &&
|
|
3964
|
+
input &&
|
|
3965
|
+
typeof input.file_path === "string") {
|
|
3966
|
+
// Support both old_str/new_str and old_string/new_string parameter names
|
|
3967
|
+
const oldContent = firstStringField(input, "old_str", "old_string");
|
|
3968
|
+
const newContent = firstStringField(input, "new_str", "new_string");
|
|
3969
|
+
const diff = firstStringField(input, "patch", "diff");
|
|
3970
|
+
return {
|
|
3971
|
+
type: "file_edit",
|
|
3972
|
+
filePath: input.file_path,
|
|
3973
|
+
diff,
|
|
3974
|
+
oldContent,
|
|
3975
|
+
newContent,
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
if (ClaudeAgentSession.isFileReadTool(normalizedTool) &&
|
|
3979
|
+
input &&
|
|
3980
|
+
typeof input.file_path === "string") {
|
|
3981
|
+
return {
|
|
3982
|
+
type: "file_read",
|
|
3983
|
+
filePath: input.file_path,
|
|
3984
|
+
content: output,
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3987
|
+
return undefined;
|
|
3988
|
+
}
|
|
3989
|
+
updatePartialEventToolState(event) {
|
|
3990
|
+
if (event.type === "content_block_start") {
|
|
3991
|
+
const block = isClaudeContentChunk(event.content_block) ? event.content_block : null;
|
|
3992
|
+
if (block?.type === "tool_use" &&
|
|
3993
|
+
typeof event.index === "number" &&
|
|
3994
|
+
typeof block.id === "string") {
|
|
3995
|
+
this.toolUseIndexToId.set(event.index, block.id);
|
|
3996
|
+
this.toolUseInputBuffers.delete(block.id);
|
|
3997
|
+
}
|
|
3998
|
+
return false;
|
|
3999
|
+
}
|
|
4000
|
+
if (event.type === "content_block_delta") {
|
|
4001
|
+
const delta = isClaudeContentChunk(event.delta) ? event.delta : null;
|
|
4002
|
+
if (delta?.type === "input_json_delta") {
|
|
4003
|
+
const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : undefined;
|
|
4004
|
+
this.handleToolInputDelta(event.index, partialJson);
|
|
4005
|
+
return true;
|
|
4006
|
+
}
|
|
4007
|
+
return false;
|
|
4008
|
+
}
|
|
4009
|
+
if (event.type === "content_block_stop" && typeof event.index === "number") {
|
|
4010
|
+
const toolId = this.toolUseIndexToId.get(event.index);
|
|
4011
|
+
if (toolId) {
|
|
4012
|
+
this.toolUseIndexToId.delete(event.index);
|
|
4013
|
+
this.toolUseInputBuffers.delete(toolId);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
return false;
|
|
4017
|
+
}
|
|
4018
|
+
mapPartialEvent(event, options) {
|
|
4019
|
+
if (this.updatePartialEventToolState(event)) {
|
|
4020
|
+
return [];
|
|
4021
|
+
}
|
|
4022
|
+
switch (event.type) {
|
|
4023
|
+
case "content_block_start":
|
|
4024
|
+
return isClaudeContentChunk(event.content_block)
|
|
4025
|
+
? this.mapBlocksToTimeline([event.content_block], {
|
|
4026
|
+
suppressAssistantText: options?.suppressAssistantText,
|
|
4027
|
+
suppressReasoning: options?.suppressReasoning,
|
|
4028
|
+
})
|
|
4029
|
+
: [];
|
|
4030
|
+
case "content_block_delta":
|
|
4031
|
+
return isClaudeContentChunk(event.delta)
|
|
4032
|
+
? this.mapBlocksToTimeline([event.delta], {
|
|
4033
|
+
suppressAssistantText: options?.suppressAssistantText,
|
|
4034
|
+
suppressReasoning: options?.suppressReasoning,
|
|
4035
|
+
})
|
|
4036
|
+
: [];
|
|
4037
|
+
default:
|
|
4038
|
+
return [];
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
upsertToolUseEntry(block) {
|
|
4042
|
+
const id = typeof block.id === "string" ? block.id : undefined;
|
|
4043
|
+
if (!id) {
|
|
4044
|
+
return null;
|
|
4045
|
+
}
|
|
4046
|
+
const existing = this.toolUseCache.get(id) ?? createDefaultToolUseCacheEntry(id, block);
|
|
4047
|
+
if (typeof block.name === "string" && block.name.length > 0) {
|
|
4048
|
+
existing.name = block.name;
|
|
4049
|
+
}
|
|
4050
|
+
if (typeof block.server === "string" && block.server.length > 0) {
|
|
4051
|
+
existing.server = block.server;
|
|
4052
|
+
}
|
|
4053
|
+
else if (!existing.server) {
|
|
4054
|
+
existing.server = existing.name;
|
|
4055
|
+
}
|
|
4056
|
+
if (block.type === "tool_use" ||
|
|
4057
|
+
block.type === "mcp_tool_use" ||
|
|
4058
|
+
block.type === "server_tool_use") {
|
|
4059
|
+
const input = this.normalizeToolInput(block.input);
|
|
4060
|
+
if (input) {
|
|
4061
|
+
this.applyToolInput(existing, input);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
this.toolUseCache.set(id, existing);
|
|
4065
|
+
return existing;
|
|
4066
|
+
}
|
|
4067
|
+
handleToolInputDelta(index, partialJson) {
|
|
4068
|
+
if (typeof index !== "number" || typeof partialJson !== "string") {
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
const toolId = this.toolUseIndexToId.get(index);
|
|
4072
|
+
if (!toolId) {
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
const buffer = (this.toolUseInputBuffers.get(toolId) ?? "") + partialJson;
|
|
4076
|
+
this.toolUseInputBuffers.set(toolId, buffer);
|
|
4077
|
+
const entry = this.toolUseCache.get(toolId);
|
|
4078
|
+
const parsed = parsePartialJsonObject(buffer);
|
|
4079
|
+
if (!entry || !parsed) {
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
4082
|
+
const normalized = this.normalizeToolInput(parsed.value);
|
|
4083
|
+
if (!normalized) {
|
|
4084
|
+
return;
|
|
4085
|
+
}
|
|
4086
|
+
if (!parsed.complete && Object.keys(normalized).length === 0) {
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
4089
|
+
if (this.areToolInputsEqual(entry.input ?? undefined, normalized)) {
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
this.applyToolInput(entry, normalized);
|
|
4093
|
+
this.toolUseCache.set(toolId, entry);
|
|
4094
|
+
this.pushToolCall(mapClaudeRunningToolCall({
|
|
4095
|
+
name: entry.name,
|
|
4096
|
+
callId: toolId,
|
|
4097
|
+
input: normalized,
|
|
4098
|
+
output: null,
|
|
4099
|
+
}));
|
|
4100
|
+
}
|
|
4101
|
+
normalizeToolInput(input) {
|
|
4102
|
+
if (!isMetadata(input)) {
|
|
4103
|
+
return null;
|
|
4104
|
+
}
|
|
4105
|
+
return input;
|
|
4106
|
+
}
|
|
4107
|
+
areToolInputsEqual(left, right) {
|
|
4108
|
+
if (!left) {
|
|
4109
|
+
return false;
|
|
4110
|
+
}
|
|
4111
|
+
const leftKeys = Object.keys(left);
|
|
4112
|
+
const rightKeys = Object.keys(right);
|
|
4113
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
4114
|
+
return false;
|
|
4115
|
+
}
|
|
4116
|
+
return rightKeys.every((key) => left[key] === right[key]);
|
|
4117
|
+
}
|
|
4118
|
+
applyToolInput(entry, input) {
|
|
4119
|
+
entry.input = input;
|
|
4120
|
+
if (this.isCommandTool(entry.name, input)) {
|
|
4121
|
+
entry.classification = "command";
|
|
4122
|
+
entry.commandText = this.extractCommandText(input) ?? entry.commandText;
|
|
4123
|
+
}
|
|
4124
|
+
else {
|
|
4125
|
+
const files = this.extractFileChanges(input);
|
|
4126
|
+
if (files?.length) {
|
|
4127
|
+
entry.classification = "file_change";
|
|
4128
|
+
entry.files = files;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
isCommandTool(name, input) {
|
|
4133
|
+
const normalized = name.toLowerCase();
|
|
4134
|
+
if (normalized.includes("bash") ||
|
|
4135
|
+
normalized.includes("shell") ||
|
|
4136
|
+
normalized.includes("terminal") ||
|
|
4137
|
+
normalized.includes("command")) {
|
|
4138
|
+
return true;
|
|
4139
|
+
}
|
|
4140
|
+
if (typeof input.command === "string" || Array.isArray(input.command)) {
|
|
4141
|
+
return true;
|
|
4142
|
+
}
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
extractCommandText(input) {
|
|
4146
|
+
const command = input.command;
|
|
4147
|
+
if (typeof command === "string" && command.length > 0) {
|
|
4148
|
+
return command;
|
|
4149
|
+
}
|
|
4150
|
+
if (Array.isArray(command)) {
|
|
4151
|
+
const tokens = command.filter((value) => typeof value === "string");
|
|
4152
|
+
if (tokens.length > 0) {
|
|
4153
|
+
return tokens.join(" ");
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
if (typeof input.description === "string" && input.description.length > 0) {
|
|
4157
|
+
return input.description;
|
|
4158
|
+
}
|
|
4159
|
+
return undefined;
|
|
4160
|
+
}
|
|
4161
|
+
extractFileChanges(input) {
|
|
4162
|
+
if (typeof input.file_path === "string" && input.file_path.length > 0) {
|
|
4163
|
+
const relative = this.relativizePath(input.file_path);
|
|
4164
|
+
if (relative) {
|
|
4165
|
+
return [{ path: relative, kind: this.detectFileKind(input.file_path) }];
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
if (typeof input.patch === "string" && input.patch.length > 0) {
|
|
4169
|
+
const files = this.parsePatchFileList(input.patch);
|
|
4170
|
+
if (files.length > 0) {
|
|
4171
|
+
return files.map((entry) => ({
|
|
4172
|
+
path: this.relativizePath(entry.path) ?? entry.path,
|
|
4173
|
+
kind: entry.kind,
|
|
4174
|
+
}));
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
if (Array.isArray(input.files)) {
|
|
4178
|
+
const files = [];
|
|
4179
|
+
for (const value of input.files) {
|
|
4180
|
+
if (typeof value === "string" && value.length > 0) {
|
|
4181
|
+
files.push({
|
|
4182
|
+
path: this.relativizePath(value) ?? value,
|
|
4183
|
+
kind: this.detectFileKind(value),
|
|
4184
|
+
});
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
if (files.length > 0) {
|
|
4188
|
+
return files;
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
return undefined;
|
|
4192
|
+
}
|
|
4193
|
+
detectFileKind(filePath) {
|
|
4194
|
+
try {
|
|
4195
|
+
return fs.existsSync(filePath) ? "update" : "add";
|
|
4196
|
+
}
|
|
4197
|
+
catch {
|
|
4198
|
+
return "update";
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
relativizePath(target) {
|
|
4202
|
+
if (!target) {
|
|
4203
|
+
return undefined;
|
|
4204
|
+
}
|
|
4205
|
+
const cwd = this.config.cwd;
|
|
4206
|
+
if (cwd && target.startsWith(cwd)) {
|
|
4207
|
+
const relative = path.relative(cwd, target);
|
|
4208
|
+
return relative.length > 0 ? relative : path.basename(target);
|
|
4209
|
+
}
|
|
4210
|
+
return target;
|
|
4211
|
+
}
|
|
4212
|
+
parsePatchFileList(patch) {
|
|
4213
|
+
const files = [];
|
|
4214
|
+
const seen = new Set();
|
|
4215
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
4216
|
+
const trimmed = line.trim();
|
|
4217
|
+
let kind = null;
|
|
4218
|
+
let parsedPath = null;
|
|
4219
|
+
if (trimmed.startsWith("*** Add File:")) {
|
|
4220
|
+
kind = "add";
|
|
4221
|
+
parsedPath = trimmed.replace("*** Add File:", "").trim();
|
|
4222
|
+
}
|
|
4223
|
+
else if (trimmed.startsWith("*** Delete File:")) {
|
|
4224
|
+
kind = "delete";
|
|
4225
|
+
parsedPath = trimmed.replace("*** Delete File:", "").trim();
|
|
4226
|
+
}
|
|
4227
|
+
else if (trimmed.startsWith("*** Update File:")) {
|
|
4228
|
+
kind = "update";
|
|
4229
|
+
parsedPath = trimmed.replace("*** Update File:", "").trim();
|
|
4230
|
+
}
|
|
4231
|
+
if (kind && parsedPath && !seen.has(`${kind}:${parsedPath}`)) {
|
|
4232
|
+
seen.add(`${kind}:${parsedPath}`);
|
|
4233
|
+
files.push({ path: parsedPath, kind });
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
return files;
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
function hasToolLikeBlock(block) {
|
|
4240
|
+
if (!block || typeof block !== "object") {
|
|
4241
|
+
return false;
|
|
4242
|
+
}
|
|
4243
|
+
const type = typeof block.type === "string" ? block.type.toLowerCase() : "";
|
|
4244
|
+
return type.includes("tool");
|
|
4245
|
+
}
|
|
4246
|
+
function readCompactionMetadata(source) {
|
|
4247
|
+
const sourceRecord = toObjectRecord(source);
|
|
4248
|
+
if (!sourceRecord) {
|
|
4249
|
+
return null;
|
|
4250
|
+
}
|
|
4251
|
+
const candidates = [
|
|
4252
|
+
sourceRecord.compact_metadata,
|
|
4253
|
+
sourceRecord.compactMetadata,
|
|
4254
|
+
sourceRecord.compactionMetadata,
|
|
4255
|
+
];
|
|
4256
|
+
for (const candidate of candidates) {
|
|
4257
|
+
const metadata = toObjectRecord(candidate);
|
|
4258
|
+
if (!metadata) {
|
|
4259
|
+
continue;
|
|
4260
|
+
}
|
|
4261
|
+
const trigger = typeof metadata.trigger === "string" ? metadata.trigger : undefined;
|
|
4262
|
+
const preTokensRaw = metadata.preTokens ?? metadata.pre_tokens;
|
|
4263
|
+
const preTokens = typeof preTokensRaw === "number" ? preTokensRaw : undefined;
|
|
4264
|
+
const postTokensRaw = metadata.postTokens ?? metadata.post_tokens;
|
|
4265
|
+
const postTokens = typeof postTokensRaw === "number" ? postTokensRaw : undefined;
|
|
4266
|
+
return { trigger, preTokens, postTokens };
|
|
4267
|
+
}
|
|
4268
|
+
return null;
|
|
4269
|
+
}
|
|
4270
|
+
function normalizeHistoryBlocks(content) {
|
|
4271
|
+
if (Array.isArray(content)) {
|
|
4272
|
+
const blocks = content.filter((entry) => isClaudeContentChunk(entry));
|
|
4273
|
+
return blocks.length > 0 ? blocks : null;
|
|
4274
|
+
}
|
|
4275
|
+
if (isClaudeContentChunk(content)) {
|
|
4276
|
+
return [content];
|
|
4277
|
+
}
|
|
4278
|
+
return null;
|
|
4279
|
+
}
|
|
4280
|
+
function mapAssistantHistoryBlocksWithMessageId(entry, content, mapBlocks) {
|
|
4281
|
+
const items = mapBlocks(content);
|
|
4282
|
+
const assistantMessageId = typeof entry.uuid === "string" && entry.uuid.length > 0 ? entry.uuid : null;
|
|
4283
|
+
if (!assistantMessageId) {
|
|
4284
|
+
return items;
|
|
4285
|
+
}
|
|
4286
|
+
for (const item of items) {
|
|
4287
|
+
if (item.type === "assistant_message" && !item.messageId) {
|
|
4288
|
+
item.messageId = assistantMessageId;
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
return items;
|
|
4292
|
+
}
|
|
4293
|
+
function convertClaudeHistoryEntryPreamble(entry) {
|
|
4294
|
+
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
|
4295
|
+
const compactMetadata = readCompactionMetadata(entry);
|
|
4296
|
+
return {
|
|
4297
|
+
shortCircuit: [
|
|
4298
|
+
{
|
|
4299
|
+
type: "compaction",
|
|
4300
|
+
status: "completed",
|
|
4301
|
+
trigger: compactMetadata?.trigger === "manual" ? "manual" : "auto",
|
|
4302
|
+
preTokens: compactMetadata?.preTokens,
|
|
4303
|
+
},
|
|
4304
|
+
],
|
|
4305
|
+
};
|
|
4306
|
+
}
|
|
4307
|
+
const taskNotificationItem = mapTaskNotificationSystemRecordToToolCall(entry);
|
|
4308
|
+
if (taskNotificationItem) {
|
|
4309
|
+
return { shortCircuit: [taskNotificationItem] };
|
|
4310
|
+
}
|
|
4311
|
+
if (entry.isCompactSummary) {
|
|
4312
|
+
return { shortCircuit: [] };
|
|
4313
|
+
}
|
|
4314
|
+
if (entry.type === "user" && isSyntheticHistoryUserEntry(entry)) {
|
|
4315
|
+
return { shortCircuit: [] };
|
|
4316
|
+
}
|
|
4317
|
+
const message = entry?.message;
|
|
4318
|
+
if (!message || !("content" in message)) {
|
|
4319
|
+
return { shortCircuit: [] };
|
|
4320
|
+
}
|
|
4321
|
+
const content = message.content;
|
|
4322
|
+
if ((entry.type === "user" || entry.type === "assistant") &&
|
|
4323
|
+
isClaudeTranscriptNoiseContent(content)) {
|
|
4324
|
+
return { shortCircuit: [] };
|
|
4325
|
+
}
|
|
4326
|
+
return { proceed: { content } };
|
|
4327
|
+
}
|
|
4328
|
+
function isProviderImageMessage(item) {
|
|
4329
|
+
return item.type === "assistant_message" && isProviderImageMarkdown(item.text);
|
|
4330
|
+
}
|
|
4331
|
+
export function convertClaudeHistoryEntry(entry, mapBlocks) {
|
|
4332
|
+
const preamble = convertClaudeHistoryEntryPreamble(entry);
|
|
4333
|
+
if ("shortCircuit" in preamble) {
|
|
4334
|
+
return preamble.shortCircuit;
|
|
4335
|
+
}
|
|
4336
|
+
const { content } = preamble.proceed;
|
|
4337
|
+
const normalizedBlocks = normalizeHistoryBlocks(content);
|
|
4338
|
+
const contentValue = typeof content === "string" ? content : normalizedBlocks;
|
|
4339
|
+
const hasToolBlock = normalizedBlocks?.some((block) => hasToolLikeBlock(block)) ?? false;
|
|
4340
|
+
const userMessageId = entry.type === "user" && typeof entry.uuid === "string" && entry.uuid.length > 0
|
|
4341
|
+
? entry.uuid
|
|
4342
|
+
: null;
|
|
4343
|
+
if (entry.type === "user") {
|
|
4344
|
+
const userTaskNotificationItem = mapTaskNotificationUserContentToToolCall({
|
|
4345
|
+
content,
|
|
4346
|
+
messageId: userMessageId,
|
|
4347
|
+
});
|
|
4348
|
+
if (userTaskNotificationItem) {
|
|
4349
|
+
return [userTaskNotificationItem];
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
const timeline = [];
|
|
4353
|
+
if (entry.type === "user") {
|
|
4354
|
+
const text = extractUserMessageText(content);
|
|
4355
|
+
if (text) {
|
|
4356
|
+
timeline.push({
|
|
4357
|
+
type: "user_message",
|
|
4358
|
+
text,
|
|
4359
|
+
...(userMessageId ? { messageId: userMessageId } : {}),
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
if (hasToolBlock && normalizedBlocks) {
|
|
4364
|
+
const mapped = mapBlocks(normalizedBlocks);
|
|
4365
|
+
if (entry.type === "user") {
|
|
4366
|
+
// tool_result handling (handleToolResult) emits image markdown as an assistant_message
|
|
4367
|
+
// alongside the tool_call. User-entry text blocks also map to assistant_message in this path
|
|
4368
|
+
// and must stay suppressed, so keep tool_calls plus only the image assistant_messages.
|
|
4369
|
+
const toolItems = mapped.filter((item) => item.type === "tool_call" || isProviderImageMessage(item));
|
|
4370
|
+
return timeline.length ? [...timeline, ...toolItems] : toolItems;
|
|
4371
|
+
}
|
|
4372
|
+
return mapped;
|
|
4373
|
+
}
|
|
4374
|
+
if (entry.type === "assistant" && contentValue) {
|
|
4375
|
+
return mapAssistantHistoryBlocksWithMessageId(entry, contentValue, mapBlocks);
|
|
4376
|
+
}
|
|
4377
|
+
return timeline;
|
|
4378
|
+
}
|
|
4379
|
+
function createAsyncMessageInput() {
|
|
4380
|
+
const queue = [];
|
|
4381
|
+
const resolvers = [];
|
|
4382
|
+
let closed = false;
|
|
4383
|
+
return {
|
|
4384
|
+
push(item) {
|
|
4385
|
+
if (closed) {
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
const resolve = resolvers.shift();
|
|
4389
|
+
if (resolve) {
|
|
4390
|
+
resolve({ value: item, done: false });
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
queue.push(item);
|
|
4394
|
+
},
|
|
4395
|
+
end() {
|
|
4396
|
+
closed = true;
|
|
4397
|
+
while (resolvers.length > 0) {
|
|
4398
|
+
const resolve = resolvers.shift();
|
|
4399
|
+
resolve?.({ value: undefined, done: true });
|
|
4400
|
+
}
|
|
4401
|
+
},
|
|
4402
|
+
iterable: {
|
|
4403
|
+
[Symbol.asyncIterator]() {
|
|
4404
|
+
return {
|
|
4405
|
+
next: () => {
|
|
4406
|
+
if (queue.length > 0) {
|
|
4407
|
+
const value = queue.shift();
|
|
4408
|
+
if (value !== undefined) {
|
|
4409
|
+
return Promise.resolve({ value, done: false });
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
if (closed) {
|
|
4413
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
4414
|
+
}
|
|
4415
|
+
return new Promise((resolve) => {
|
|
4416
|
+
resolvers.push(resolve);
|
|
4417
|
+
});
|
|
4418
|
+
},
|
|
4419
|
+
};
|
|
4420
|
+
},
|
|
4421
|
+
},
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
async function pathExists(target) {
|
|
4425
|
+
try {
|
|
4426
|
+
await fsPromises.access(target);
|
|
4427
|
+
return true;
|
|
4428
|
+
}
|
|
4429
|
+
catch {
|
|
4430
|
+
return false;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
async function collectRecentClaudeSessions(root, limit) {
|
|
4434
|
+
let projectDirs;
|
|
4435
|
+
try {
|
|
4436
|
+
projectDirs = await fsPromises.readdir(root);
|
|
4437
|
+
}
|
|
4438
|
+
catch {
|
|
4439
|
+
return [];
|
|
4440
|
+
}
|
|
4441
|
+
const projectFileLists = await Promise.all(projectDirs.map(async (dirName) => {
|
|
4442
|
+
const projectPath = path.join(root, dirName);
|
|
4443
|
+
try {
|
|
4444
|
+
const stats = await fsPromises.stat(projectPath);
|
|
4445
|
+
if (!stats.isDirectory())
|
|
4446
|
+
return { projectPath, files: [] };
|
|
4447
|
+
const files = await fsPromises.readdir(projectPath);
|
|
4448
|
+
return { projectPath, files };
|
|
4449
|
+
}
|
|
4450
|
+
catch {
|
|
4451
|
+
return { projectPath, files: [] };
|
|
4452
|
+
}
|
|
4453
|
+
}));
|
|
4454
|
+
const fileEntries = projectFileLists.flatMap(({ projectPath, files }) => files.filter((f) => f.endsWith(".jsonl")).map((f) => path.join(projectPath, f)));
|
|
4455
|
+
const statResults = await Promise.all(fileEntries.map(async (fullPath) => {
|
|
4456
|
+
try {
|
|
4457
|
+
const fileStats = await fsPromises.stat(fullPath);
|
|
4458
|
+
return { path: fullPath, mtime: fileStats.mtime };
|
|
4459
|
+
}
|
|
4460
|
+
catch {
|
|
4461
|
+
return null;
|
|
4462
|
+
}
|
|
4463
|
+
}));
|
|
4464
|
+
const candidates = statResults.filter((entry) => entry !== null);
|
|
4465
|
+
return candidates.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).slice(0, limit);
|
|
4466
|
+
}
|
|
4467
|
+
function isFinishedAccumulator(acc) {
|
|
4468
|
+
return Boolean(acc.sessionId && acc.cwd && acc.title);
|
|
4469
|
+
}
|
|
4470
|
+
function applyClaudeSessionEntryToAccumulator(entryRaw, acc) {
|
|
4471
|
+
const entry = toObjectRecord(entryRaw);
|
|
4472
|
+
if (!entry) {
|
|
4473
|
+
return;
|
|
4474
|
+
}
|
|
4475
|
+
if (entry.isSidechain) {
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4478
|
+
if (entry.type === "user" && isSyntheticUserEntry(entry)) {
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
if (!acc.sessionId && typeof entry.sessionId === "string") {
|
|
4482
|
+
acc.sessionId = entry.sessionId;
|
|
4483
|
+
}
|
|
4484
|
+
if (!acc.cwd && typeof entry.cwd === "string") {
|
|
4485
|
+
acc.cwd = entry.cwd;
|
|
4486
|
+
}
|
|
4487
|
+
if (entry.type === "user" && entry.message) {
|
|
4488
|
+
const text = extractClaudeUserText(entry.message);
|
|
4489
|
+
if (text) {
|
|
4490
|
+
if (!acc.title) {
|
|
4491
|
+
acc.title = text;
|
|
4492
|
+
}
|
|
4493
|
+
const preview = normalizeImportablePromptPreview(text);
|
|
4494
|
+
acc.firstPromptPreview ?? (acc.firstPromptPreview = preview);
|
|
4495
|
+
acc.lastPromptPreview = preview;
|
|
4496
|
+
}
|
|
4497
|
+
return;
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
async function parseClaudeSessionDescriptor(filePath, mtime) {
|
|
4501
|
+
let content;
|
|
4502
|
+
try {
|
|
4503
|
+
content = await fsPromises.readFile(filePath, "utf8");
|
|
4504
|
+
}
|
|
4505
|
+
catch {
|
|
4506
|
+
return null;
|
|
4507
|
+
}
|
|
4508
|
+
const acc = {
|
|
4509
|
+
sessionId: null,
|
|
4510
|
+
cwd: null,
|
|
4511
|
+
title: null,
|
|
4512
|
+
firstPromptPreview: null,
|
|
4513
|
+
lastPromptPreview: null,
|
|
4514
|
+
};
|
|
4515
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
4516
|
+
const line = rawLine.trim();
|
|
4517
|
+
if (!line)
|
|
4518
|
+
continue;
|
|
4519
|
+
let entry;
|
|
4520
|
+
try {
|
|
4521
|
+
entry = JSON.parse(line);
|
|
4522
|
+
}
|
|
4523
|
+
catch {
|
|
4524
|
+
continue;
|
|
4525
|
+
}
|
|
4526
|
+
applyClaudeSessionEntryToAccumulator(entry, acc);
|
|
4527
|
+
if (isFinishedAccumulator(acc)) {
|
|
4528
|
+
break;
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
const { sessionId, cwd, title } = acc;
|
|
4532
|
+
if (!sessionId || !cwd) {
|
|
4533
|
+
return null;
|
|
4534
|
+
}
|
|
4535
|
+
return {
|
|
4536
|
+
providerHandleId: sessionId,
|
|
4537
|
+
cwd,
|
|
4538
|
+
title: (title ?? "").trim() || `Claude session ${sessionId.slice(0, 8)}`,
|
|
4539
|
+
firstPromptPreview: acc.firstPromptPreview,
|
|
4540
|
+
lastPromptPreview: acc.lastPromptPreview,
|
|
4541
|
+
lastActivityAt: mtime,
|
|
4542
|
+
};
|
|
4543
|
+
}
|
|
4544
|
+
function normalizeImportablePromptPreview(text) {
|
|
4545
|
+
const normalized = text.trim().replace(/\s+/g, " ");
|
|
4546
|
+
if (!normalized)
|
|
4547
|
+
return null;
|
|
4548
|
+
return normalized.length > 160 ? normalized.slice(0, 160) : normalized;
|
|
4549
|
+
}
|
|
4550
|
+
function normalizeClaudeUserPromptText(text) {
|
|
4551
|
+
const normalized = text.trim();
|
|
4552
|
+
if (!CLAUDE_COMMAND_MESSAGE_PATTERN.test(normalized)) {
|
|
4553
|
+
return normalized || null;
|
|
4554
|
+
}
|
|
4555
|
+
const command = readClaudeCommandPromptName(normalized);
|
|
4556
|
+
if (!command) {
|
|
4557
|
+
return null;
|
|
4558
|
+
}
|
|
4559
|
+
const commandArgs = normalized.match(CLAUDE_COMMAND_ARGS_PATTERN)?.[1]?.trim();
|
|
4560
|
+
if (commandArgs) {
|
|
4561
|
+
return `${command} ${commandArgs}`;
|
|
4562
|
+
}
|
|
4563
|
+
return command;
|
|
4564
|
+
}
|
|
4565
|
+
function readClaudeCommandPromptName(text) {
|
|
4566
|
+
const commandName = text.match(CLAUDE_COMMAND_NAME_PATTERN)?.[1]?.trim();
|
|
4567
|
+
if (commandName) {
|
|
4568
|
+
return commandName.startsWith("/") ? commandName : `/${commandName}`;
|
|
4569
|
+
}
|
|
4570
|
+
const commandMessage = text.match(CLAUDE_COMMAND_MESSAGE_PATTERN)?.[1]?.trim();
|
|
4571
|
+
if (!commandMessage) {
|
|
4572
|
+
return null;
|
|
4573
|
+
}
|
|
4574
|
+
return commandMessage.startsWith("/") ? commandMessage : `/${commandMessage}`;
|
|
4575
|
+
}
|
|
4576
|
+
function extractClaudeUserText(messageRaw) {
|
|
4577
|
+
const message = toObjectRecord(messageRaw);
|
|
4578
|
+
if (!message) {
|
|
4579
|
+
return null;
|
|
4580
|
+
}
|
|
4581
|
+
if (typeof message.content === "string") {
|
|
4582
|
+
const normalized = message.content.trim();
|
|
4583
|
+
if (!normalized || isClaudeTranscriptNoiseText(normalized))
|
|
4584
|
+
return null;
|
|
4585
|
+
return normalizeClaudeUserPromptText(normalized);
|
|
4586
|
+
}
|
|
4587
|
+
if (typeof message.text === "string") {
|
|
4588
|
+
const normalized = message.text.trim();
|
|
4589
|
+
if (!normalized || isClaudeTranscriptNoiseText(normalized))
|
|
4590
|
+
return null;
|
|
4591
|
+
return normalizeClaudeUserPromptText(normalized);
|
|
4592
|
+
}
|
|
4593
|
+
if (isUnknownArray(message.content)) {
|
|
4594
|
+
for (const block of message.content) {
|
|
4595
|
+
const blockRecord = toObjectRecord(block);
|
|
4596
|
+
if (blockRecord && typeof blockRecord.text === "string") {
|
|
4597
|
+
const normalized = blockRecord.text.trim();
|
|
4598
|
+
if (normalized && !isClaudeTranscriptNoiseText(normalized)) {
|
|
4599
|
+
return normalizeClaudeUserPromptText(normalized);
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
return null;
|
|
4605
|
+
}
|
|
4606
|
+
//# sourceMappingURL=agent.js.map
|