@getpaseo/server 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/daemon-runner.js +1 -1
- package/dist/scripts/daemon-runner.js.map +1 -1
- package/dist/scripts/dev-runner.js +1 -1
- package/dist/scripts/dev-runner.js.map +1 -1
- package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts.map +1 -1
- package/dist/server/client/daemon-client-relay-e2ee-transport.js.map +1 -1
- package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -1
- package/dist/server/client/daemon-client-websocket-transport.js.map +1 -1
- package/dist/server/client/daemon-client.d.ts +108 -103
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +417 -407
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
- package/dist/server/server/agent/activity-curator.js +5 -4
- package/dist/server/server/agent/activity-curator.js.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.js +13 -17
- package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +26 -26
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +1 -3
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +4 -12
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
- package/dist/server/server/agent/agent-response-loop.js +6 -6
- package/dist/server/server/agent/agent-response-loop.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +23 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +2 -4
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/dictation-debug.d.ts.map +1 -1
- package/dist/server/server/agent/dictation-debug.js.map +1 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +19 -27
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/pcm16-resampler.d.ts.map +1 -1
- package/dist/server/server/agent/pcm16-resampler.js.map +1 -1
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +4 -2
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts +2 -2
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +63 -9
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts +2 -2
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +1 -1
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/providers/claude/model-catalog.js +10 -10
- package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -1
- package/dist/server/server/agent/providers/claude/partial-json.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/partial-json.js +4 -4
- package/dist/server/server/agent/providers/claude/partial-json.js.map +1 -1
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +20 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js +230 -0
- package/dist/server/server/agent/providers/claude/sidechain-tracker.js.map +1 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +11 -0
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +37 -20
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.js.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +21 -11
- package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +23 -11
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +486 -1134
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +2 -2
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +14 -11
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +347 -163
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-rollout-timeline.js +21 -32
- package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -1
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +2 -2
- package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -1
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +2 -9
- package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +5 -5
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +277 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +149 -15
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -1
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js +1 -3
- package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -1
- package/dist/server/server/agent/stt-manager.d.ts.map +1 -1
- package/dist/server/server/agent/stt-manager.js +1 -2
- package/dist/server/server/agent/stt-manager.js.map +1 -1
- package/dist/server/server/agent/system-prompt.js +5 -5
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
- package/dist/server/server/agent/timeline-projection.js.map +1 -1
- package/dist/server/server/agent/tts-manager.d.ts.map +1 -1
- package/dist/server/server/agent/tts-manager.js +27 -9
- package/dist/server/server/agent/tts-manager.js.map +1 -1
- package/dist/server/server/agent/wait-for-agent-tracker.d.ts.map +1 -1
- package/dist/server/server/agent/wait-for-agent-tracker.js.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.js.map +1 -1
- package/dist/server/server/allowed-hosts.d.ts.map +1 -1
- package/dist/server/server/allowed-hosts.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +46 -5
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +4 -11
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/connection-offer.d.ts +1 -1
- package/dist/server/server/connection-offer.d.ts.map +1 -1
- package/dist/server/server/connection-offer.js +2 -3
- package/dist/server/server/connection-offer.js.map +1 -1
- package/dist/server/server/daemon-version.d.ts.map +1 -1
- package/dist/server/server/daemon-version.js +1 -1
- package/dist/server/server/daemon-version.js.map +1 -1
- package/dist/server/server/dictation/dictation-stream-manager.d.ts.map +1 -1
- package/dist/server/server/dictation/dictation-stream-manager.js +4 -1
- package/dist/server/server/dictation/dictation-stream-manager.js.map +1 -1
- package/dist/server/server/exports.d.ts +1 -1
- package/dist/server/server/exports.d.ts.map +1 -1
- package/dist/server/server/exports.js +1 -1
- package/dist/server/server/exports.js.map +1 -1
- package/dist/server/server/file-explorer/service.d.ts +1 -1
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +5 -8
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/index.js +1 -1
- package/dist/server/server/index.js.map +1 -1
- package/dist/server/server/logger.d.ts.map +1 -1
- package/dist/server/server/logger.js.map +1 -1
- package/dist/server/server/messages.d.ts.map +1 -1
- package/dist/server/server/messages.js.map +1 -1
- package/dist/server/server/package-version.d.ts.map +1 -1
- package/dist/server/server/package-version.js +1 -2
- package/dist/server/server/package-version.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts.map +1 -1
- package/dist/server/server/persisted-config.js.map +1 -1
- package/dist/server/server/persistence-hooks.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/pid-lock.d.ts.map +1 -1
- package/dist/server/server/pid-lock.js.map +1 -1
- package/dist/server/server/push/push-service.d.ts.map +1 -1
- package/dist/server/server/push/push-service.js.map +1 -1
- package/dist/server/server/relay-transport.d.ts.map +1 -1
- package/dist/server/server/relay-transport.js +6 -2
- package/dist/server/server/relay-transport.js.map +1 -1
- package/dist/server/server/session.d.ts +49 -37
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +1234 -998
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/speech/audio.d.ts.map +1 -1
- package/dist/server/server/speech/audio.js.map +1 -1
- package/dist/server/server/speech/providers/local/config.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/config.js +5 -14
- package/dist/server/server/speech/providers/local/config.js.map +1 -1
- package/dist/server/server/speech/providers/local/models.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/models.js +1 -1
- package/dist/server/server/speech/providers/local/models.js.map +1 -1
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js +21 -7
- package/dist/server/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -1
- package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/runtime.js +1 -23
- package/dist/server/server/speech/providers/local/runtime.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +9 -4
- package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +7 -2
- package/dist/server/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +5 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +2 -4
- package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js +1 -3
- package/dist/server/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js +2 -4
- package/dist/server/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +4 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +1 -1
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js.map +1 -1
- package/dist/server/server/speech/providers/openai/config.d.ts.map +1 -1
- package/dist/server/server/speech/providers/openai/config.js +5 -24
- package/dist/server/server/speech/providers/openai/config.js.map +1 -1
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts.map +1 -1
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +6 -3
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.js.map +1 -1
- package/dist/server/server/speech/providers/openai/runtime.d.ts.map +1 -1
- package/dist/server/server/speech/providers/openai/runtime.js +2 -6
- package/dist/server/server/speech/providers/openai/runtime.js.map +1 -1
- package/dist/server/server/speech/providers/openai/stt.d.ts.map +1 -1
- package/dist/server/server/speech/providers/openai/stt.js +1 -3
- package/dist/server/server/speech/providers/openai/stt.js.map +1 -1
- package/dist/server/server/speech/providers/openai/tts.d.ts.map +1 -1
- package/dist/server/server/speech/providers/openai/tts.js.map +1 -1
- package/dist/server/server/speech/speech-config-resolver.d.ts.map +1 -1
- package/dist/server/server/speech/speech-config-resolver.js +3 -7
- package/dist/server/server/speech/speech-config-resolver.js.map +1 -1
- package/dist/server/server/speech/speech-provider.d.ts.map +1 -1
- package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
- package/dist/server/server/speech/speech-runtime.js.map +1 -1
- package/dist/server/server/speech/turn-detection-provider.d.ts.map +1 -1
- package/dist/server/server/terminal-mcp/server.d.ts.map +1 -1
- package/dist/server/server/terminal-mcp/server.js +3 -11
- package/dist/server/server/terminal-mcp/server.js.map +1 -1
- package/dist/server/server/terminal-mcp/terminal-manager.d.ts.map +1 -1
- package/dist/server/server/terminal-mcp/terminal-manager.js +2 -14
- package/dist/server/server/terminal-mcp/terminal-manager.js.map +1 -1
- package/dist/server/server/terminal-mcp/tmux.d.ts +1 -1
- package/dist/server/server/terminal-mcp/tmux.d.ts.map +1 -1
- package/dist/server/server/terminal-mcp/tmux.js +20 -123
- package/dist/server/server/terminal-mcp/tmux.js.map +1 -1
- package/dist/server/server/utils/diff-highlighter.d.ts +11 -3
- package/dist/server/server/utils/diff-highlighter.d.ts.map +1 -1
- package/dist/server/server/utils/diff-highlighter.js +49 -36
- package/dist/server/server/utils/diff-highlighter.js.map +1 -1
- package/dist/server/server/voice/fixed-duration-pcm-ring-buffer.d.ts.map +1 -1
- package/dist/server/server/voice/fixed-duration-pcm-ring-buffer.js.map +1 -1
- package/dist/server/server/voice/voice-turn-controller.d.ts.map +1 -1
- package/dist/server/server/voice/voice-turn-controller.js +1 -3
- package/dist/server/server/voice/voice-turn-controller.js.map +1 -1
- package/dist/server/server/voice-config.d.ts.map +1 -1
- package/dist/server/server/voice-config.js.map +1 -1
- package/dist/server/server/voice-mcp-bridge.d.ts.map +1 -1
- package/dist/server/server/voice-mcp-bridge.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +1 -0
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +20 -22
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-registry-bootstrap.d.ts +3 -3
- package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
- package/dist/server/server/workspace-registry-bootstrap.js +6 -6
- package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
- package/dist/server/server/workspace-registry-model.d.ts +14 -3
- package/dist/server/server/workspace-registry-model.d.ts.map +1 -1
- package/dist/server/server/workspace-registry-model.js +40 -15
- package/dist/server/server/workspace-registry-model.js.map +1 -1
- package/dist/server/server/workspace-registry.d.ts +5 -5
- package/dist/server/server/workspace-registry.d.ts.map +1 -1
- package/dist/server/server/workspace-registry.js +16 -13
- package/dist/server/server/workspace-registry.js.map +1 -1
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +17 -6
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/shared/agent-attention-notification.d.ts.map +1 -1
- package/dist/server/shared/agent-attention-notification.js.map +1 -1
- package/dist/server/shared/agent-lifecycle.d.ts.map +1 -1
- package/dist/server/shared/daemon-endpoints.d.ts +1 -0
- package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
- package/dist/server/shared/daemon-endpoints.js +11 -2
- package/dist/server/shared/daemon-endpoints.js.map +1 -1
- package/dist/server/shared/messages.d.ts +1228 -2982
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +330 -302
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/terminal-stream-protocol.d.ts +36 -0
- package/dist/server/shared/terminal-stream-protocol.d.ts.map +1 -0
- package/dist/server/shared/terminal-stream-protocol.js +99 -0
- package/dist/server/shared/terminal-stream-protocol.js.map +1 -0
- package/dist/server/shared/tool-call-display.d.ts.map +1 -1
- package/dist/server/shared/tool-call-display.js +6 -3
- package/dist/server/shared/tool-call-display.js.map +1 -1
- package/dist/server/terminal/terminal.d.ts +5 -48
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +44 -98
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +1 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +111 -120
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/directory-suggestions.d.ts +1 -1
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
- package/dist/server/utils/directory-suggestions.js +40 -40
- package/dist/server/utils/directory-suggestions.js.map +1 -1
- package/dist/server/utils/project-icon.d.ts.map +1 -1
- package/dist/server/utils/project-icon.js +2 -11
- package/dist/server/utils/project-icon.js.map +1 -1
- package/dist/server/utils/worktree.d.ts +2 -0
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +22 -19
- package/dist/server/utils/worktree.js.map +1 -1
- package/dist/src/server/agent/activity-curator.js +5 -4
- package/dist/src/server/agent/activity-curator.js.map +1 -1
- package/dist/src/server/agent/agent-manager.js +26 -26
- package/dist/src/server/agent/agent-manager.js.map +1 -1
- package/dist/src/server/agent/agent-metadata-generator.js +1 -3
- package/dist/src/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/src/server/agent/agent-projections.js +4 -12
- package/dist/src/server/agent/agent-projections.js.map +1 -1
- package/dist/src/server/agent/agent-response-loop.js +6 -6
- package/dist/src/server/agent/agent-response-loop.js.map +1 -1
- package/dist/src/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/src/server/agent/agent-storage.js +2 -4
- package/dist/src/server/agent/agent-storage.js.map +1 -1
- package/dist/src/server/agent/dictation-debug.js.map +1 -1
- package/dist/src/server/agent/mcp-server.js +19 -27
- package/dist/src/server/agent/mcp-server.js.map +1 -1
- package/dist/src/server/agent/pcm16-resampler.js.map +1 -1
- package/dist/src/server/agent/provider-launch-config.js +4 -2
- package/dist/src/server/agent/provider-launch-config.js.map +1 -1
- package/dist/src/server/agent/provider-manifest.js +63 -9
- package/dist/src/server/agent/provider-manifest.js.map +1 -1
- package/dist/src/server/agent/provider-registry.js +1 -1
- package/dist/src/server/agent/provider-registry.js.map +1 -1
- package/dist/src/server/agent/providers/claude/model-catalog.js +10 -10
- package/dist/src/server/agent/providers/claude/model-catalog.js.map +1 -1
- package/dist/src/server/agent/providers/claude/partial-json.js +4 -4
- package/dist/src/server/agent/providers/claude/partial-json.js.map +1 -1
- package/dist/src/server/agent/providers/claude/sidechain-tracker.js +230 -0
- package/dist/src/server/agent/providers/claude/sidechain-tracker.js.map +1 -0
- package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +37 -20
- package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +1 -1
- package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +21 -11
- package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +1 -1
- package/dist/src/server/agent/providers/claude/tool-call-mapper.js +23 -11
- package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
- package/dist/src/server/agent/providers/claude-agent.js +486 -1134
- package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +2 -2
- package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -1
- package/dist/src/server/agent/providers/codex/tool-call-mapper.js +14 -11
- package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
- package/dist/src/server/agent/providers/codex-app-server-agent.js +347 -163
- package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/src/server/agent/providers/codex-rollout-timeline.js +21 -32
- package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +1 -1
- package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +2 -2
- package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -1
- package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +2 -9
- package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +1 -1
- package/dist/src/server/agent/providers/opencode-agent.js +5 -5
- package/dist/src/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/src/server/agent/providers/tool-call-detail-primitives.js +149 -15
- package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +1 -1
- package/dist/src/server/agent/providers/tool-call-mapper-utils.js +1 -3
- package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +1 -1
- package/dist/src/server/agent/stt-manager.js +1 -2
- package/dist/src/server/agent/stt-manager.js.map +1 -1
- package/dist/src/server/agent/timeline-projection.js.map +1 -1
- package/dist/src/server/agent/tts-manager.js +27 -9
- package/dist/src/server/agent/tts-manager.js.map +1 -1
- package/dist/src/server/agent/wait-for-agent-tracker.js.map +1 -1
- package/dist/src/server/agent-attention-policy.js.map +1 -1
- package/dist/src/server/allowed-hosts.js.map +1 -1
- package/dist/src/server/bootstrap.js +46 -5
- package/dist/src/server/bootstrap.js.map +1 -1
- package/dist/src/server/config.js +4 -11
- package/dist/src/server/config.js.map +1 -1
- package/dist/src/server/connection-offer.js +2 -3
- package/dist/src/server/connection-offer.js.map +1 -1
- package/dist/src/server/daemon-version.js +1 -1
- package/dist/src/server/daemon-version.js.map +1 -1
- package/dist/src/server/dictation/dictation-stream-manager.js +4 -1
- package/dist/src/server/dictation/dictation-stream-manager.js.map +1 -1
- package/dist/src/server/file-explorer/service.js +5 -8
- package/dist/src/server/file-explorer/service.js.map +1 -1
- package/dist/src/server/messages.js.map +1 -1
- package/dist/src/server/package-version.js +1 -2
- package/dist/src/server/package-version.js.map +1 -1
- package/dist/src/server/pairing-offer.js +45 -0
- package/dist/src/server/pairing-offer.js.map +1 -0
- package/dist/src/server/pairing-qr.js +45 -0
- package/dist/src/server/pairing-qr.js.map +1 -0
- package/dist/src/server/persisted-config.js.map +1 -1
- package/dist/src/server/persistence-hooks.js.map +1 -1
- package/dist/src/server/pid-lock.js.map +1 -1
- package/dist/src/server/push/push-service.js.map +1 -1
- package/dist/src/server/relay-transport.js +6 -2
- package/dist/src/server/relay-transport.js.map +1 -1
- package/dist/src/server/session.js +1234 -998
- package/dist/src/server/session.js.map +1 -1
- package/dist/src/server/speech/audio.js.map +1 -1
- package/dist/src/server/speech/providers/local/config.js +5 -14
- package/dist/src/server/speech/providers/local/config.js.map +1 -1
- package/dist/src/server/speech/providers/local/models.js +1 -1
- package/dist/src/server/speech/providers/local/models.js.map +1 -1
- package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +21 -7
- package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +1 -1
- package/dist/src/server/speech/providers/local/runtime.js +1 -23
- package/dist/src/server/speech/providers/local/runtime.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +9 -4
- package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +7 -2
- package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +5 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +2 -4
- package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +1 -3
- package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +2 -4
- package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +4 -1
- package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/silero-vad-provider.js +1 -1
- package/dist/src/server/speech/providers/local/sherpa/silero-vad-provider.js.map +1 -1
- package/dist/src/server/speech/providers/local/sherpa/silero-vad-session.js.map +1 -1
- package/dist/src/server/speech/providers/openai/config.js +5 -24
- package/dist/src/server/speech/providers/openai/config.js.map +1 -1
- package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +6 -3
- package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +1 -1
- package/dist/src/server/speech/providers/openai/runtime.js +2 -6
- package/dist/src/server/speech/providers/openai/runtime.js.map +1 -1
- package/dist/src/server/speech/providers/openai/stt.js +1 -3
- package/dist/src/server/speech/providers/openai/stt.js.map +1 -1
- package/dist/src/server/speech/providers/openai/tts.js.map +1 -1
- package/dist/src/server/speech/speech-config-resolver.js +3 -7
- package/dist/src/server/speech/speech-config-resolver.js.map +1 -1
- package/dist/src/server/speech/speech-runtime.js.map +1 -1
- package/dist/src/server/utils/diff-highlighter.js +49 -36
- package/dist/src/server/utils/diff-highlighter.js.map +1 -1
- package/dist/src/server/voice/fixed-duration-pcm-ring-buffer.js.map +1 -1
- package/dist/src/server/voice/voice-turn-controller.js +1 -3
- package/dist/src/server/voice/voice-turn-controller.js.map +1 -1
- package/dist/src/server/voice-config.js.map +1 -1
- package/dist/src/server/voice-mcp-bridge.js.map +1 -1
- package/dist/src/server/websocket-server.js +20 -22
- package/dist/src/server/websocket-server.js.map +1 -1
- package/dist/src/server/workspace-registry-bootstrap.js +6 -6
- package/dist/src/server/workspace-registry-bootstrap.js.map +1 -1
- package/dist/src/server/workspace-registry-model.js +40 -15
- package/dist/src/server/workspace-registry-model.js.map +1 -1
- package/dist/src/server/workspace-registry.js +16 -13
- package/dist/src/server/workspace-registry.js.map +1 -1
- package/dist/src/server/worktree-bootstrap.js +17 -6
- package/dist/src/server/worktree-bootstrap.js.map +1 -1
- package/dist/src/shared/agent-attention-notification.js.map +1 -1
- package/dist/src/shared/daemon-endpoints.js +11 -2
- package/dist/src/shared/daemon-endpoints.js.map +1 -1
- package/dist/src/shared/messages.js +330 -302
- package/dist/src/shared/messages.js.map +1 -1
- package/dist/src/shared/terminal-stream-protocol.js +99 -0
- package/dist/src/shared/terminal-stream-protocol.js.map +1 -0
- package/dist/src/shared/tool-call-display.js +6 -3
- package/dist/src/shared/tool-call-display.js.map +1 -1
- package/dist/src/terminal/terminal.js +44 -98
- package/dist/src/terminal/terminal.js.map +1 -1
- package/dist/src/utils/checkout-git.js +111 -120
- package/dist/src/utils/checkout-git.js.map +1 -1
- package/dist/src/utils/directory-suggestions.js +40 -40
- package/dist/src/utils/directory-suggestions.js.map +1 -1
- package/dist/src/utils/project-icon.js +2 -11
- package/dist/src/utils/project-icon.js.map +1 -1
- package/dist/src/utils/worktree.js +22 -19
- package/dist/src/utils/worktree.js.map +1 -1
- package/package.json +3 -11
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts +0 -43
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts.map +0 -1
- package/dist/server/client/daemon-client-terminal-stream-manager.js +0 -134
- package/dist/server/client/daemon-client-terminal-stream-manager.js.map +0 -1
- package/dist/server/server/utils/syntax-highlighter.d.ts +0 -10
- package/dist/server/server/utils/syntax-highlighter.d.ts.map +0 -1
- package/dist/server/server/utils/syntax-highlighter.js +0 -145
- package/dist/server/server/utils/syntax-highlighter.js.map +0 -1
- package/dist/server/shared/binary-mux.d.ts +0 -31
- package/dist/server/shared/binary-mux.d.ts.map +0 -1
- package/dist/server/shared/binary-mux.js +0 -114
- package/dist/server/shared/binary-mux.js.map +0 -1
- package/dist/server/shared/terminal-key-input.d.ts +0 -9
- package/dist/server/shared/terminal-key-input.d.ts.map +0 -1
- package/dist/server/shared/terminal-key-input.js +0 -132
- package/dist/server/shared/terminal-key-input.js.map +0 -1
- package/dist/src/server/utils/syntax-highlighter.js +0 -145
- package/dist/src/server/utils/syntax-highlighter.js.map +0 -1
- package/dist/src/shared/binary-mux.js +0 -114
- package/dist/src/shared/binary-mux.js.map +0 -1
|
@@ -1,56 +1,58 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from
|
|
2
|
-
import { watch } from
|
|
3
|
-
import { stat } from
|
|
4
|
-
import { exec } from
|
|
5
|
-
import { promisify } from
|
|
6
|
-
import { join, resolve, sep } from
|
|
7
|
-
import { homedir } from
|
|
8
|
-
import { z } from
|
|
9
|
-
import { serializeAgentStreamEvent, } from
|
|
10
|
-
import {
|
|
11
|
-
import { TTSManager } from
|
|
12
|
-
import { STTManager } from
|
|
13
|
-
import { maybePersistTtsDebugAudio } from
|
|
14
|
-
import { isPaseoDictationDebugEnabled } from
|
|
15
|
-
import { DictationStreamManager, } from
|
|
16
|
-
import { createVoiceTurnController, } from
|
|
17
|
-
import { buildConfigOverrides, buildSessionConfig, extractTimestamps } from
|
|
18
|
-
import { experimental_createMCPClient } from
|
|
19
|
-
import { buildProviderRegistry } from
|
|
20
|
-
import { scheduleAgentMetadataGeneration } from
|
|
21
|
-
import { resolveEffectiveThinkingOptionId, toAgentPayload } from
|
|
22
|
-
import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from
|
|
23
|
-
import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from
|
|
24
|
-
import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from
|
|
25
|
-
import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from
|
|
26
|
-
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from
|
|
27
|
-
import { buildProjectPlacementForCwd, deriveProjectKind, deriveProjectRootPath, deriveWorkspaceDisplayName, deriveWorkspaceKind, normalizeWorkspaceId as normalizePersistedWorkspaceId, } from
|
|
28
|
-
import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from
|
|
29
|
-
import { buildVoiceAgentMcpServerConfig, buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, } from
|
|
30
|
-
import { isVoicePermissionAllowed } from
|
|
31
|
-
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from
|
|
32
|
-
import { slugify, validateBranchSlug, listPaseoWorktrees, deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, } from
|
|
33
|
-
import { createAgentWorktree, runAsyncWorktreeBootstrap } from
|
|
34
|
-
import { getCheckoutDiff, getCheckoutShortstat, getCheckoutStatus, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, } from
|
|
35
|
-
import { getProjectIcon } from
|
|
36
|
-
import { expandTilde } from
|
|
37
|
-
import { searchHomeDirectories, searchWorkspaceEntries } from
|
|
38
|
-
import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from
|
|
39
|
-
import { toResolver } from
|
|
40
|
-
import { resolveClientMessageId } from
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import { watch } from "node:fs";
|
|
3
|
+
import { readFile, stat } from "fs/promises";
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
import { join, resolve, sep } from "path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { serializeAgentStreamEvent, } from "./messages.js";
|
|
10
|
+
import { TerminalStreamOpcode, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, decodeTerminalResizePayload, } from "../shared/terminal-stream-protocol.js";
|
|
11
|
+
import { TTSManager } from "./agent/tts-manager.js";
|
|
12
|
+
import { STTManager } from "./agent/stt-manager.js";
|
|
13
|
+
import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
|
|
14
|
+
import { isPaseoDictationDebugEnabled } from "./agent/recordings-debug.js";
|
|
15
|
+
import { DictationStreamManager, } from "./dictation/dictation-stream-manager.js";
|
|
16
|
+
import { createVoiceTurnController, } from "./voice/voice-turn-controller.js";
|
|
17
|
+
import { buildConfigOverrides, buildSessionConfig, extractTimestamps, } from "./persistence-hooks.js";
|
|
18
|
+
import { experimental_createMCPClient } from "ai";
|
|
19
|
+
import { buildProviderRegistry } from "./agent/provider-registry.js";
|
|
20
|
+
import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
|
|
21
|
+
import { resolveEffectiveThinkingOptionId, toAgentPayload } from "./agent/agent-projections.js";
|
|
22
|
+
import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from "./agent/agent-title-limits.js";
|
|
23
|
+
import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
|
|
24
|
+
import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from "./agent/timeline-projection.js";
|
|
25
|
+
import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from "./agent/agent-response-loop.js";
|
|
26
|
+
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from "./agent/provider-manifest.js";
|
|
27
|
+
import { buildProjectPlacementForCwd, detectStaleWorkspaces, deriveProjectKind, deriveProjectRootPath, deriveWorkspaceDisplayName, deriveWorkspaceKind, normalizeWorkspaceId as normalizePersistedWorkspaceId, } from "./workspace-registry-model.js";
|
|
28
|
+
import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from "./workspace-registry.js";
|
|
29
|
+
import { buildVoiceAgentMcpServerConfig, buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, } from "./voice-config.js";
|
|
30
|
+
import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
|
|
31
|
+
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
|
|
32
|
+
import { computeWorktreePath, getWorktreeSetupCommands, resolveWorktreeRuntimeEnv, slugify, validateBranchSlug, listPaseoWorktrees, deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, } from "../utils/worktree.js";
|
|
33
|
+
import { createAgentWorktree, runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
|
|
34
|
+
import { getCheckoutDiff, getCheckoutShortstat, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, resolveRepositoryDefaultBranch, } from "../utils/checkout-git.js";
|
|
35
|
+
import { getProjectIcon } from "../utils/project-icon.js";
|
|
36
|
+
import { expandTilde } from "../utils/path.js";
|
|
37
|
+
import { searchHomeDirectories, searchWorkspaceEntries } from "../utils/directory-suggestions.js";
|
|
38
|
+
import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from "./speech/providers/local/models.js";
|
|
39
|
+
import { toResolver } from "./speech/provider-resolver.js";
|
|
40
|
+
import { resolveClientMessageId } from "./client-message-id.js";
|
|
41
41
|
const execAsync = promisify(exec);
|
|
42
42
|
const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
|
|
43
43
|
const READ_ONLY_GIT_ENV = {
|
|
44
44
|
...process.env,
|
|
45
|
-
GIT_OPTIONAL_LOCKS:
|
|
45
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
46
46
|
};
|
|
47
47
|
const pendingAgentInitializations = new Map();
|
|
48
48
|
const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
|
49
49
|
const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
|
|
50
50
|
const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
51
|
+
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
|
|
52
|
+
const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
|
|
53
|
+
const TERMINAL_STREAM_HIGH_WATER_BYTES = 256 * 1024;
|
|
54
|
+
const TERMINAL_STREAM_LOW_WATER_BYTES = 16 * 1024;
|
|
55
|
+
const MAX_TERMINAL_STREAM_SLOTS = 256;
|
|
54
56
|
function deriveInitialAgentTitle(prompt) {
|
|
55
57
|
const firstContentLine = prompt
|
|
56
58
|
.split(/\r?\n/)
|
|
@@ -59,7 +61,7 @@ function deriveInitialAgentTitle(prompt) {
|
|
|
59
61
|
if (!firstContentLine) {
|
|
60
62
|
return null;
|
|
61
63
|
}
|
|
62
|
-
const normalized = firstContentLine.replace(/\s+/g,
|
|
64
|
+
const normalized = firstContentLine.replace(/\s+/g, " ").trim();
|
|
63
65
|
if (!normalized) {
|
|
64
66
|
return null;
|
|
65
67
|
}
|
|
@@ -67,7 +69,7 @@ function deriveInitialAgentTitle(prompt) {
|
|
|
67
69
|
return clamped.length > 0 ? clamped : null;
|
|
68
70
|
}
|
|
69
71
|
export function resolveCreateAgentTitles(options) {
|
|
70
|
-
const explicitTitle = typeof options.configTitle ===
|
|
72
|
+
const explicitTitle = typeof options.configTitle === "string" && options.configTitle.trim().length > 0
|
|
71
73
|
? options.configTitle.trim()
|
|
72
74
|
: null;
|
|
73
75
|
const trimmedPrompt = options.initialPrompt?.trim();
|
|
@@ -81,7 +83,7 @@ class SessionRequestError extends Error {
|
|
|
81
83
|
constructor(code, message) {
|
|
82
84
|
super(message);
|
|
83
85
|
this.code = code;
|
|
84
|
-
this.name =
|
|
86
|
+
this.name = "SessionRequestError";
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
const PCM_SAMPLE_RATE = 16000;
|
|
@@ -92,12 +94,12 @@ const MIN_STREAMING_SEGMENT_DURATION_MS = 1000;
|
|
|
92
94
|
const MIN_STREAMING_SEGMENT_BYTES = Math.round(PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS);
|
|
93
95
|
const SAFE_GIT_REF_PATTERN = /^[A-Za-z0-9._\/-]+$/;
|
|
94
96
|
const AgentIdSchema = z.string().uuid();
|
|
95
|
-
const VOICE_MCP_SERVER_NAME =
|
|
97
|
+
const VOICE_MCP_SERVER_NAME = "paseo_voice";
|
|
96
98
|
const VOICE_INTERRUPT_CONFIRMATION_MS = 500;
|
|
97
99
|
class VoiceFeatureUnavailableError extends Error {
|
|
98
100
|
constructor(context) {
|
|
99
101
|
super(context.message);
|
|
100
|
-
this.name =
|
|
102
|
+
this.name = "VoiceFeatureUnavailableError";
|
|
101
103
|
this.reasonCode = context.reasonCode;
|
|
102
104
|
this.retryable = context.retryable;
|
|
103
105
|
this.missingModelIds = [...context.missingModelIds];
|
|
@@ -108,10 +110,10 @@ function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
|
|
|
108
110
|
const wavBuffer = Buffer.alloc(headerSize + pcmBuffer.length);
|
|
109
111
|
const byteRate = (sampleRate * channels * bitsPerSample) / 8;
|
|
110
112
|
const blockAlign = (channels * bitsPerSample) / 8;
|
|
111
|
-
wavBuffer.write(
|
|
113
|
+
wavBuffer.write("RIFF", 0);
|
|
112
114
|
wavBuffer.writeUInt32LE(36 + pcmBuffer.length, 4);
|
|
113
|
-
wavBuffer.write(
|
|
114
|
-
wavBuffer.write(
|
|
115
|
+
wavBuffer.write("WAVE", 8);
|
|
116
|
+
wavBuffer.write("fmt ", 12);
|
|
115
117
|
wavBuffer.writeUInt32LE(16, 16);
|
|
116
118
|
wavBuffer.writeUInt16LE(1, 20);
|
|
117
119
|
wavBuffer.writeUInt16LE(channels, 22);
|
|
@@ -119,7 +121,7 @@ function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
|
|
|
119
121
|
wavBuffer.writeUInt32LE(byteRate, 28);
|
|
120
122
|
wavBuffer.writeUInt16LE(blockAlign, 32);
|
|
121
123
|
wavBuffer.writeUInt16LE(bitsPerSample, 34);
|
|
122
|
-
wavBuffer.write(
|
|
124
|
+
wavBuffer.write("data", 36);
|
|
123
125
|
wavBuffer.writeUInt32LE(pcmBuffer.length, 40);
|
|
124
126
|
pcmBuffer.copy(wavBuffer, 44);
|
|
125
127
|
return wavBuffer;
|
|
@@ -128,7 +130,7 @@ function coerceAgentProvider(logger, value, agentId) {
|
|
|
128
130
|
if (isValidAgentProvider(value)) {
|
|
129
131
|
return value;
|
|
130
132
|
}
|
|
131
|
-
logger.warn({ value, agentId, defaultProvider: DEFAULT_AGENT_PROVIDER }, `Unknown provider '${value}' for agent ${agentId ??
|
|
133
|
+
logger.warn({ value, agentId, defaultProvider: DEFAULT_AGENT_PROVIDER }, `Unknown provider '${value}' for agent ${agentId ?? "unknown"}; defaulting to '${DEFAULT_AGENT_PROVIDER}'`);
|
|
132
134
|
return DEFAULT_AGENT_PROVIDER;
|
|
133
135
|
}
|
|
134
136
|
function toAgentPersistenceHandle(logger, handle) {
|
|
@@ -141,7 +143,7 @@ function toAgentPersistenceHandle(logger, handle) {
|
|
|
141
143
|
return null;
|
|
142
144
|
}
|
|
143
145
|
if (!handle.sessionId) {
|
|
144
|
-
logger.warn(
|
|
146
|
+
logger.warn("Ignoring persistence handle missing sessionId");
|
|
145
147
|
return null;
|
|
146
148
|
}
|
|
147
149
|
return {
|
|
@@ -158,7 +160,7 @@ function toAgentPersistenceHandle(logger, handle) {
|
|
|
158
160
|
*/
|
|
159
161
|
export class Session {
|
|
160
162
|
constructor(options) {
|
|
161
|
-
this.processingPhase =
|
|
163
|
+
this.processingPhase = "idle";
|
|
162
164
|
// Voice mode state
|
|
163
165
|
this.isVoiceMode = false;
|
|
164
166
|
this.speechInProgress = false;
|
|
@@ -184,13 +186,13 @@ export class Session {
|
|
|
184
186
|
this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
|
|
185
187
|
this.subscribedTerminalDirectories = new Set();
|
|
186
188
|
this.unsubscribeTerminalsChanged = null;
|
|
187
|
-
this.terminalSubscriptions = new Map();
|
|
188
189
|
this.terminalExitSubscriptions = new Map();
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
191
|
-
this.
|
|
190
|
+
this.activeTerminalStreams = new Map();
|
|
191
|
+
this.terminalIdToSlot = new Map();
|
|
192
|
+
this.nextTerminalSlot = 0;
|
|
192
193
|
this.checkoutDiffSubscriptions = new Map();
|
|
193
194
|
this.checkoutDiffTargets = new Map();
|
|
195
|
+
this.workspaceGitWatchTargets = new Map();
|
|
194
196
|
this.voiceModeAgentId = null;
|
|
195
197
|
this.voiceModeBaseConfig = null;
|
|
196
198
|
this.workspaceStatePriority = {
|
|
@@ -200,11 +202,12 @@ export class Session {
|
|
|
200
202
|
attention: 3,
|
|
201
203
|
done: 4,
|
|
202
204
|
};
|
|
203
|
-
const { clientId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
|
|
205
|
+
const { clientId, onMessage, onBinaryMessage, getBinaryBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
|
|
204
206
|
this.clientId = clientId;
|
|
205
207
|
this.sessionId = uuidv4();
|
|
206
208
|
this.onMessage = onMessage;
|
|
207
209
|
this.onBinaryMessage = onBinaryMessage ?? null;
|
|
210
|
+
this.getBinaryBufferedAmount = getBinaryBufferedAmount ?? null;
|
|
208
211
|
this.onLifecycleIntent = onLifecycleIntent ?? null;
|
|
209
212
|
this.downloadTokenStore = downloadTokenStore;
|
|
210
213
|
this.pushTokenStore = pushTokenStore;
|
|
@@ -224,11 +227,11 @@ export class Session {
|
|
|
224
227
|
this.localSpeechModelsDir =
|
|
225
228
|
configuredModelsDir && configuredModelsDir.length > 0
|
|
226
229
|
? configuredModelsDir
|
|
227
|
-
: join(this.paseoHome,
|
|
230
|
+
: join(this.paseoHome, "models", "local-speech");
|
|
228
231
|
this.defaultLocalSpeechModelIds =
|
|
229
232
|
dictation?.localModels?.defaultModelIds && dictation.localModels.defaultModelIds.length > 0
|
|
230
233
|
? [...new Set(dictation.localModels.defaultModelIds)]
|
|
231
|
-
: [
|
|
234
|
+
: ["parakeet-tdt-0.6b-v2-int8", "kokoro-en-v0_19"];
|
|
232
235
|
this.registerVoiceSpeakHandler = voiceBridge?.registerVoiceSpeakHandler;
|
|
233
236
|
this.unregisterVoiceSpeakHandler = voiceBridge?.unregisterVoiceSpeakHandler;
|
|
234
237
|
this.registerVoiceCallerContext = voiceBridge?.registerVoiceCallerContext;
|
|
@@ -239,7 +242,7 @@ export class Session {
|
|
|
239
242
|
this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
|
|
240
243
|
this.abortController = new AbortController();
|
|
241
244
|
this.sessionLogger = logger.child({
|
|
242
|
-
module:
|
|
245
|
+
module: "session",
|
|
243
246
|
clientId: this.clientId,
|
|
244
247
|
sessionId: this.sessionId,
|
|
245
248
|
});
|
|
@@ -259,7 +262,7 @@ export class Session {
|
|
|
259
262
|
// Initialize agent MCP client asynchronously
|
|
260
263
|
void this.initializeAgentMcp();
|
|
261
264
|
this.subscribeToAgentEvents();
|
|
262
|
-
this.sessionLogger.trace(
|
|
265
|
+
this.sessionLogger.trace("Session created");
|
|
263
266
|
}
|
|
264
267
|
/**
|
|
265
268
|
* Get the client's current activity state
|
|
@@ -282,8 +285,7 @@ export class Session {
|
|
|
282
285
|
checkoutDiffWatcherCount,
|
|
283
286
|
checkoutDiffFallbackRefreshTargetCount,
|
|
284
287
|
terminalDirectorySubscriptionCount: this.subscribedTerminalDirectories.size,
|
|
285
|
-
terminalSubscriptionCount: this.
|
|
286
|
-
terminalStreamCount: this.terminalStreams.size,
|
|
288
|
+
terminalSubscriptionCount: this.activeTerminalStreams.size,
|
|
287
289
|
};
|
|
288
290
|
}
|
|
289
291
|
/**
|
|
@@ -296,16 +298,16 @@ export class Session {
|
|
|
296
298
|
* Normalize a user prompt (with optional image metadata) for AgentManager
|
|
297
299
|
*/
|
|
298
300
|
buildAgentPrompt(text, images) {
|
|
299
|
-
const normalized = text?.trim() ??
|
|
301
|
+
const normalized = text?.trim() ?? "";
|
|
300
302
|
if (!images || images.length === 0) {
|
|
301
303
|
return normalized;
|
|
302
304
|
}
|
|
303
305
|
const blocks = [];
|
|
304
306
|
if (normalized.length > 0) {
|
|
305
|
-
blocks.push({ type:
|
|
307
|
+
blocks.push({ type: "text", text: normalized });
|
|
306
308
|
}
|
|
307
309
|
for (const image of images) {
|
|
308
|
-
blocks.push({ type:
|
|
310
|
+
blocks.push({ type: "image", data: image.data, mimeType: image.mimeType });
|
|
309
311
|
}
|
|
310
312
|
return blocks;
|
|
311
313
|
}
|
|
@@ -316,20 +318,20 @@ export class Session {
|
|
|
316
318
|
async interruptAgentIfRunning(agentId) {
|
|
317
319
|
const snapshot = this.agentManager.getAgent(agentId);
|
|
318
320
|
if (!snapshot) {
|
|
319
|
-
this.sessionLogger.trace({ agentId },
|
|
321
|
+
this.sessionLogger.trace({ agentId }, "interruptAgentIfRunning: agent not found");
|
|
320
322
|
throw new Error(`Agent ${agentId} not found`);
|
|
321
323
|
}
|
|
322
|
-
if (snapshot.lifecycle !==
|
|
323
|
-
this.sessionLogger.trace({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) },
|
|
324
|
+
if (snapshot.lifecycle !== "running" && !snapshot.pendingRun) {
|
|
325
|
+
this.sessionLogger.trace({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) }, "interruptAgentIfRunning: skipping because agent is not running");
|
|
324
326
|
return;
|
|
325
327
|
}
|
|
326
|
-
this.sessionLogger.debug({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) },
|
|
328
|
+
this.sessionLogger.debug({ agentId, lifecycle: snapshot.lifecycle, pendingRun: Boolean(snapshot.pendingRun) }, "interruptAgentIfRunning: interrupting");
|
|
327
329
|
try {
|
|
328
330
|
const t0 = Date.now();
|
|
329
331
|
const cancelled = await this.agentManager.cancelAgentRun(agentId);
|
|
330
|
-
this.sessionLogger.debug({ agentId, cancelled, durationMs: Date.now() - t0 },
|
|
332
|
+
this.sessionLogger.debug({ agentId, cancelled, durationMs: Date.now() - t0 }, "interruptAgentIfRunning: cancelAgentRun completed");
|
|
331
333
|
if (!cancelled) {
|
|
332
|
-
this.sessionLogger.warn({ agentId },
|
|
334
|
+
this.sessionLogger.warn({ agentId }, "interruptAgentIfRunning: reported running but no active run was cancelled");
|
|
333
335
|
}
|
|
334
336
|
}
|
|
335
337
|
catch (error) {
|
|
@@ -344,7 +346,7 @@ export class Session {
|
|
|
344
346
|
if (!snapshot) {
|
|
345
347
|
return false;
|
|
346
348
|
}
|
|
347
|
-
return snapshot.lifecycle ===
|
|
349
|
+
return snapshot.lifecycle === "running" || Boolean(snapshot.pendingRun);
|
|
348
350
|
}
|
|
349
351
|
/**
|
|
350
352
|
* Start streaming an agent run and forward results via the websocket broadcast
|
|
@@ -352,21 +354,25 @@ export class Session {
|
|
|
352
354
|
startAgentStream(agentId, prompt, runOptions) {
|
|
353
355
|
this.sessionLogger.trace({
|
|
354
356
|
agentId,
|
|
355
|
-
promptType: typeof prompt ===
|
|
357
|
+
promptType: typeof prompt === "string" ? "string" : "structured",
|
|
356
358
|
hasRunOptions: Boolean(runOptions),
|
|
357
|
-
},
|
|
359
|
+
}, "startAgentStream: requested");
|
|
358
360
|
let iterator;
|
|
359
361
|
try {
|
|
360
362
|
const snapshot = this.agentManager.getAgent(agentId);
|
|
361
|
-
const shouldReplace = Boolean(snapshot && (snapshot.lifecycle ===
|
|
363
|
+
const shouldReplace = Boolean(snapshot && (snapshot.lifecycle === "running" || snapshot.pendingRun));
|
|
362
364
|
iterator = shouldReplace
|
|
363
365
|
? this.agentManager.replaceAgentRun(agentId, prompt, runOptions)
|
|
364
366
|
: this.agentManager.streamAgent(agentId, prompt, runOptions);
|
|
365
|
-
this.sessionLogger.trace({ agentId, shouldReplace },
|
|
367
|
+
this.sessionLogger.trace({ agentId, shouldReplace }, "startAgentStream: agent iterator returned");
|
|
366
368
|
}
|
|
367
369
|
catch (error) {
|
|
368
|
-
this.handleAgentRunError(agentId, error,
|
|
369
|
-
const message = error instanceof Error
|
|
370
|
+
this.handleAgentRunError(agentId, error, "Failed to start agent run");
|
|
371
|
+
const message = error instanceof Error
|
|
372
|
+
? error.message
|
|
373
|
+
: typeof error === "string"
|
|
374
|
+
? error
|
|
375
|
+
: "Unknown error";
|
|
370
376
|
return { ok: false, error: message };
|
|
371
377
|
}
|
|
372
378
|
void (async () => {
|
|
@@ -374,24 +380,24 @@ export class Session {
|
|
|
374
380
|
for await (const _ of iterator) {
|
|
375
381
|
// Events are forwarded via the session's AgentManager subscription.
|
|
376
382
|
}
|
|
377
|
-
this.sessionLogger.trace({ agentId },
|
|
383
|
+
this.sessionLogger.trace({ agentId }, "startAgentStream: iterator drained");
|
|
378
384
|
}
|
|
379
385
|
catch (error) {
|
|
380
|
-
this.sessionLogger.trace({ agentId, err: error },
|
|
381
|
-
this.handleAgentRunError(agentId, error,
|
|
386
|
+
this.sessionLogger.trace({ agentId, err: error }, "startAgentStream: iterator threw");
|
|
387
|
+
this.handleAgentRunError(agentId, error, "Agent stream failed");
|
|
382
388
|
}
|
|
383
389
|
})();
|
|
384
390
|
return { ok: true };
|
|
385
391
|
}
|
|
386
392
|
handleAgentRunError(agentId, error, context) {
|
|
387
|
-
const message = error instanceof Error ? error.message : typeof error ===
|
|
393
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
388
394
|
this.sessionLogger.error({ err: error, agentId, context }, `${context} for agent ${agentId}`);
|
|
389
395
|
this.emit({
|
|
390
|
-
type:
|
|
396
|
+
type: "activity_log",
|
|
391
397
|
payload: {
|
|
392
398
|
id: uuidv4(),
|
|
393
399
|
timestamp: new Date(),
|
|
394
|
-
type:
|
|
400
|
+
type: "error",
|
|
395
401
|
content: `${context}: ${message}`,
|
|
396
402
|
},
|
|
397
403
|
});
|
|
@@ -411,7 +417,7 @@ export class Session {
|
|
|
411
417
|
this.sessionLogger.trace({ agentToolCount }, `Agent MCP initialized with ${agentToolCount} tools`);
|
|
412
418
|
}
|
|
413
419
|
catch (error) {
|
|
414
|
-
this.sessionLogger.error({ err: error },
|
|
420
|
+
this.sessionLogger.error({ err: error }, "Failed to initialize Agent MCP");
|
|
415
421
|
}
|
|
416
422
|
}
|
|
417
423
|
/**
|
|
@@ -422,32 +428,32 @@ export class Session {
|
|
|
422
428
|
this.unsubscribeAgentEvents();
|
|
423
429
|
}
|
|
424
430
|
this.unsubscribeAgentEvents = this.agentManager.subscribe((event) => {
|
|
425
|
-
if (event.type ===
|
|
431
|
+
if (event.type === "agent_state") {
|
|
426
432
|
void this.forwardAgentUpdate(event.agent);
|
|
427
433
|
return;
|
|
428
434
|
}
|
|
429
435
|
if (this.isVoiceMode &&
|
|
430
436
|
this.voiceModeAgentId === event.agentId &&
|
|
431
|
-
event.event.type ===
|
|
437
|
+
event.event.type === "permission_requested" &&
|
|
432
438
|
isVoicePermissionAllowed(event.event.request)) {
|
|
433
439
|
const requestId = event.event.request.id;
|
|
434
440
|
void this.agentManager
|
|
435
441
|
.respondToPermission(event.agentId, requestId, {
|
|
436
|
-
behavior:
|
|
442
|
+
behavior: "allow",
|
|
437
443
|
})
|
|
438
444
|
.catch((error) => {
|
|
439
445
|
this.sessionLogger.warn({
|
|
440
446
|
err: error,
|
|
441
447
|
agentId: event.agentId,
|
|
442
448
|
requestId,
|
|
443
|
-
},
|
|
449
|
+
}, "Failed to auto-allow speak tool permission in voice mode");
|
|
444
450
|
});
|
|
445
451
|
}
|
|
446
452
|
// Reduce bandwidth/CPU on mobile: only forward high-frequency agent stream events
|
|
447
453
|
// for the focused agent, with a short grace window while backgrounded.
|
|
448
454
|
// History catch-up is handled via pull-based `fetch_agent_timeline_request`.
|
|
449
455
|
const activity = this.clientActivity;
|
|
450
|
-
if (activity?.deviceType ===
|
|
456
|
+
if (activity?.deviceType === "mobile") {
|
|
451
457
|
if (!activity.focusedAgentId) {
|
|
452
458
|
return;
|
|
453
459
|
}
|
|
@@ -469,25 +475,25 @@ export class Session {
|
|
|
469
475
|
agentId: event.agentId,
|
|
470
476
|
event: serializedEvent,
|
|
471
477
|
timestamp: new Date().toISOString(),
|
|
472
|
-
...(typeof event.seq ===
|
|
473
|
-
...(typeof event.epoch ===
|
|
478
|
+
...(typeof event.seq === "number" ? { seq: event.seq } : {}),
|
|
479
|
+
...(typeof event.epoch === "string" ? { epoch: event.epoch } : {}),
|
|
474
480
|
};
|
|
475
481
|
this.emit({
|
|
476
|
-
type:
|
|
482
|
+
type: "agent_stream",
|
|
477
483
|
payload,
|
|
478
484
|
});
|
|
479
|
-
if (event.event.type ===
|
|
485
|
+
if (event.event.type === "permission_requested") {
|
|
480
486
|
this.emit({
|
|
481
|
-
type:
|
|
487
|
+
type: "agent_permission_request",
|
|
482
488
|
payload: {
|
|
483
489
|
agentId: event.agentId,
|
|
484
490
|
request: event.event.request,
|
|
485
491
|
},
|
|
486
492
|
});
|
|
487
493
|
}
|
|
488
|
-
else if (event.event.type ===
|
|
494
|
+
else if (event.event.type === "permission_resolved") {
|
|
489
495
|
this.emit({
|
|
490
|
-
type:
|
|
496
|
+
type: "agent_permission_resolved",
|
|
491
497
|
payload: {
|
|
492
498
|
agentId: event.agentId,
|
|
493
499
|
requestId: event.event.requestId,
|
|
@@ -522,13 +528,13 @@ export class Session {
|
|
|
522
528
|
? {
|
|
523
529
|
provider: coerceAgentProvider(this.sessionLogger, record.runtimeInfo.provider, record.id),
|
|
524
530
|
sessionId: record.runtimeInfo.sessionId,
|
|
525
|
-
...(Object.prototype.hasOwnProperty.call(record.runtimeInfo,
|
|
531
|
+
...(Object.prototype.hasOwnProperty.call(record.runtimeInfo, "model")
|
|
526
532
|
? { model: record.runtimeInfo.model ?? null }
|
|
527
533
|
: {}),
|
|
528
|
-
...(Object.prototype.hasOwnProperty.call(record.runtimeInfo,
|
|
534
|
+
...(Object.prototype.hasOwnProperty.call(record.runtimeInfo, "thinkingOptionId")
|
|
529
535
|
? { thinkingOptionId: record.runtimeInfo.thinkingOptionId ?? null }
|
|
530
536
|
: {}),
|
|
531
|
-
...(Object.prototype.hasOwnProperty.call(record.runtimeInfo,
|
|
537
|
+
...(Object.prototype.hasOwnProperty.call(record.runtimeInfo, "modeId")
|
|
532
538
|
? { modeId: record.runtimeInfo.modeId ?? null }
|
|
533
539
|
: {}),
|
|
534
540
|
...(record.runtimeInfo.extra ? { extra: record.runtimeInfo.extra } : {}),
|
|
@@ -582,12 +588,12 @@ export class Session {
|
|
|
582
588
|
let snapshot;
|
|
583
589
|
if (handle) {
|
|
584
590
|
snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, extractTimestamps(record));
|
|
585
|
-
this.sessionLogger.info({ agentId, provider: record.provider },
|
|
591
|
+
this.sessionLogger.info({ agentId, provider: record.provider }, "Agent resumed from persistence");
|
|
586
592
|
}
|
|
587
593
|
else {
|
|
588
594
|
const config = buildSessionConfig(record);
|
|
589
595
|
snapshot = await this.agentManager.createAgent(config, agentId, { labels: record.labels });
|
|
590
|
-
this.sessionLogger.info({ agentId, provider: record.provider },
|
|
596
|
+
this.sessionLogger.info({ agentId, provider: record.provider }, "Agent created from stored config");
|
|
591
597
|
}
|
|
592
598
|
await this.agentManager.hydrateTimelineFromProvider(agentId);
|
|
593
599
|
return this.agentManager.getAgent(agentId) ?? snapshot;
|
|
@@ -634,7 +640,7 @@ export class Session {
|
|
|
634
640
|
return false;
|
|
635
641
|
}
|
|
636
642
|
}
|
|
637
|
-
if (typeof filter?.requiresAttention ===
|
|
643
|
+
if (typeof filter?.requiresAttention === "boolean") {
|
|
638
644
|
const requiresAttention = agent.requiresAttention ?? false;
|
|
639
645
|
if (requiresAttention !== filter.requiresAttention) {
|
|
640
646
|
return false;
|
|
@@ -649,7 +655,7 @@ export class Session {
|
|
|
649
655
|
return true;
|
|
650
656
|
}
|
|
651
657
|
getAgentUpdateTargetId(update) {
|
|
652
|
-
return update.kind ===
|
|
658
|
+
return update.kind === "remove" ? update.agentId : update.agent.id;
|
|
653
659
|
}
|
|
654
660
|
bufferOrEmitAgentUpdate(subscription, payload) {
|
|
655
661
|
if (subscription.isBootstrapping) {
|
|
@@ -657,7 +663,7 @@ export class Session {
|
|
|
657
663
|
return;
|
|
658
664
|
}
|
|
659
665
|
this.emit({
|
|
660
|
-
type:
|
|
666
|
+
type: "agent_update",
|
|
661
667
|
payload,
|
|
662
668
|
});
|
|
663
669
|
}
|
|
@@ -670,9 +676,9 @@ export class Session {
|
|
|
670
676
|
const pending = Array.from(subscription.pendingUpdatesByAgentId.values());
|
|
671
677
|
subscription.pendingUpdatesByAgentId.clear();
|
|
672
678
|
for (const payload of pending) {
|
|
673
|
-
if (payload.kind ===
|
|
679
|
+
if (payload.kind === "upsert") {
|
|
674
680
|
const snapshotUpdatedAt = options?.snapshotUpdatedAtByAgentId?.get(payload.agent.id);
|
|
675
|
-
if (typeof snapshotUpdatedAt ===
|
|
681
|
+
if (typeof snapshotUpdatedAt === "number") {
|
|
676
682
|
const updateUpdatedAt = Date.parse(payload.agent.updatedAt);
|
|
677
683
|
if (!Number.isNaN(updateUpdatedAt) && updateUpdatedAt <= snapshotUpdatedAt) {
|
|
678
684
|
continue;
|
|
@@ -680,7 +686,7 @@ export class Session {
|
|
|
680
686
|
}
|
|
681
687
|
}
|
|
682
688
|
this.emit({
|
|
683
|
-
type:
|
|
689
|
+
type: "agent_update",
|
|
684
690
|
payload,
|
|
685
691
|
});
|
|
686
692
|
}
|
|
@@ -730,6 +736,9 @@ export class Session {
|
|
|
730
736
|
const normalizedWorkspaceId = normalizePersistedWorkspaceId(workspaceId);
|
|
731
737
|
const existing = await this.workspaceRegistry.get(normalizedWorkspaceId);
|
|
732
738
|
const placement = await this.buildProjectPlacement(normalizedWorkspaceId);
|
|
739
|
+
await this.syncWorkspaceGitWatchTarget(normalizedWorkspaceId, {
|
|
740
|
+
isGit: placement.checkout.isGit,
|
|
741
|
+
});
|
|
733
742
|
const now = new Date().toISOString();
|
|
734
743
|
const nextProjectCreatedAt = existing?.createdAt ?? now;
|
|
735
744
|
const nextWorkspaceCreatedAt = existing?.createdAt ?? now;
|
|
@@ -764,9 +773,7 @@ export class Session {
|
|
|
764
773
|
}
|
|
765
774
|
await this.projectRegistry.upsert(nextProjectRecord);
|
|
766
775
|
await this.workspaceRegistry.upsert(nextWorkspaceRecord);
|
|
767
|
-
if (existing &&
|
|
768
|
-
!existing.archivedAt &&
|
|
769
|
-
existing.projectId !== nextWorkspaceRecord.projectId) {
|
|
776
|
+
if (existing && !existing.archivedAt && existing.projectId !== nextWorkspaceRecord.projectId) {
|
|
770
777
|
await this.archiveProjectRecordIfEmpty(existing.projectId, now);
|
|
771
778
|
}
|
|
772
779
|
return {
|
|
@@ -777,7 +784,30 @@ export class Session {
|
|
|
777
784
|
async reconcileActiveWorkspaceRecords() {
|
|
778
785
|
const changedWorkspaceIds = new Set();
|
|
779
786
|
const activeWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => !workspace.archivedAt);
|
|
787
|
+
const staleWorkspaceIds = await detectStaleWorkspaces({
|
|
788
|
+
activeWorkspaces,
|
|
789
|
+
agentRecords: (await this.agentStorage.list()).map((agent) => ({
|
|
790
|
+
cwd: agent.cwd,
|
|
791
|
+
archivedAt: agent.archivedAt ?? null,
|
|
792
|
+
})),
|
|
793
|
+
checkDirectoryExists: async (cwd) => {
|
|
794
|
+
try {
|
|
795
|
+
await stat(cwd);
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
});
|
|
803
|
+
for (const workspaceId of staleWorkspaceIds) {
|
|
804
|
+
await this.archiveWorkspaceRecord(workspaceId);
|
|
805
|
+
changedWorkspaceIds.add(workspaceId);
|
|
806
|
+
}
|
|
780
807
|
for (const workspace of activeWorkspaces) {
|
|
808
|
+
if (staleWorkspaceIds.has(workspace.workspaceId)) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
781
811
|
const result = await this.reconcileWorkspaceRecord(workspace.workspaceId);
|
|
782
812
|
if (result.changed) {
|
|
783
813
|
changedWorkspaceIds.add(result.workspace.workspaceId);
|
|
@@ -799,14 +829,14 @@ export class Session {
|
|
|
799
829
|
});
|
|
800
830
|
if (matches) {
|
|
801
831
|
this.bufferOrEmitAgentUpdate(subscription, {
|
|
802
|
-
kind:
|
|
832
|
+
kind: "upsert",
|
|
803
833
|
agent: payload,
|
|
804
834
|
project,
|
|
805
835
|
});
|
|
806
836
|
}
|
|
807
837
|
else {
|
|
808
838
|
this.bufferOrEmitAgentUpdate(subscription, {
|
|
809
|
-
kind:
|
|
839
|
+
kind: "remove",
|
|
810
840
|
agentId: payload.id,
|
|
811
841
|
});
|
|
812
842
|
}
|
|
@@ -814,7 +844,7 @@ export class Session {
|
|
|
814
844
|
await this.emitWorkspaceUpdateForCwd(payload.cwd);
|
|
815
845
|
}
|
|
816
846
|
catch (error) {
|
|
817
|
-
this.sessionLogger.error({ err: error },
|
|
847
|
+
this.sessionLogger.error({ err: error }, "Failed to emit agent update");
|
|
818
848
|
}
|
|
819
849
|
}
|
|
820
850
|
/**
|
|
@@ -823,48 +853,48 @@ export class Session {
|
|
|
823
853
|
async handleMessage(msg) {
|
|
824
854
|
try {
|
|
825
855
|
switch (msg.type) {
|
|
826
|
-
case
|
|
856
|
+
case "voice_audio_chunk":
|
|
827
857
|
await this.handleAudioChunk(msg);
|
|
828
858
|
break;
|
|
829
|
-
case
|
|
859
|
+
case "abort_request":
|
|
830
860
|
await this.handleAbort();
|
|
831
861
|
break;
|
|
832
|
-
case
|
|
862
|
+
case "audio_played":
|
|
833
863
|
this.handleAudioPlayed(msg.id);
|
|
834
864
|
break;
|
|
835
|
-
case
|
|
865
|
+
case "fetch_agents_request":
|
|
836
866
|
await this.handleFetchAgents(msg);
|
|
837
867
|
break;
|
|
838
|
-
case
|
|
868
|
+
case "fetch_workspaces_request":
|
|
839
869
|
await this.handleFetchWorkspacesRequest(msg);
|
|
840
870
|
break;
|
|
841
|
-
case
|
|
871
|
+
case "fetch_agent_request":
|
|
842
872
|
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
|
843
873
|
break;
|
|
844
|
-
case
|
|
874
|
+
case "delete_agent_request":
|
|
845
875
|
await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
|
|
846
876
|
break;
|
|
847
|
-
case
|
|
877
|
+
case "archive_agent_request":
|
|
848
878
|
await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
|
|
849
879
|
break;
|
|
850
|
-
case
|
|
880
|
+
case "update_agent_request":
|
|
851
881
|
await this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
|
|
852
882
|
break;
|
|
853
|
-
case
|
|
883
|
+
case "set_voice_mode":
|
|
854
884
|
await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
|
|
855
885
|
break;
|
|
856
|
-
case
|
|
886
|
+
case "send_agent_message_request":
|
|
857
887
|
await this.handleSendAgentMessageRequest(msg);
|
|
858
888
|
break;
|
|
859
|
-
case
|
|
889
|
+
case "wait_for_finish_request":
|
|
860
890
|
await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
|
|
861
891
|
break;
|
|
862
|
-
case
|
|
892
|
+
case "dictation_stream_start":
|
|
863
893
|
{
|
|
864
|
-
const unavailable = this.resolveVoiceFeatureUnavailableContext(
|
|
894
|
+
const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
|
|
865
895
|
if (unavailable) {
|
|
866
896
|
this.emit({
|
|
867
|
-
type:
|
|
897
|
+
type: "dictation_stream_error",
|
|
868
898
|
payload: {
|
|
869
899
|
dictationId: msg.dictationId,
|
|
870
900
|
error: unavailable.message,
|
|
@@ -878,7 +908,7 @@ export class Session {
|
|
|
878
908
|
}
|
|
879
909
|
await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
|
|
880
910
|
break;
|
|
881
|
-
case
|
|
911
|
+
case "dictation_stream_chunk":
|
|
882
912
|
await this.dictationStreamManager.handleChunk({
|
|
883
913
|
dictationId: msg.dictationId,
|
|
884
914
|
seq: msg.seq,
|
|
@@ -886,124 +916,127 @@ export class Session {
|
|
|
886
916
|
format: msg.format,
|
|
887
917
|
});
|
|
888
918
|
break;
|
|
889
|
-
case
|
|
919
|
+
case "dictation_stream_finish":
|
|
890
920
|
await this.dictationStreamManager.handleFinish(msg.dictationId, msg.finalSeq);
|
|
891
921
|
break;
|
|
892
|
-
case
|
|
922
|
+
case "dictation_stream_cancel":
|
|
893
923
|
this.dictationStreamManager.handleCancel(msg.dictationId);
|
|
894
924
|
break;
|
|
895
|
-
case
|
|
925
|
+
case "create_agent_request":
|
|
896
926
|
await this.handleCreateAgentRequest(msg);
|
|
897
927
|
break;
|
|
898
|
-
case
|
|
928
|
+
case "resume_agent_request":
|
|
899
929
|
await this.handleResumeAgentRequest(msg);
|
|
900
930
|
break;
|
|
901
|
-
case
|
|
931
|
+
case "refresh_agent_request":
|
|
902
932
|
await this.handleRefreshAgentRequest(msg);
|
|
903
933
|
break;
|
|
904
|
-
case
|
|
934
|
+
case "cancel_agent_request":
|
|
905
935
|
await this.handleCancelAgentRequest(msg.agentId);
|
|
906
936
|
break;
|
|
907
|
-
case
|
|
937
|
+
case "restart_server_request":
|
|
908
938
|
await this.handleRestartServerRequest(msg.requestId, msg.reason);
|
|
909
939
|
break;
|
|
910
|
-
case
|
|
940
|
+
case "shutdown_server_request":
|
|
911
941
|
await this.handleShutdownServerRequest(msg.requestId);
|
|
912
942
|
break;
|
|
913
|
-
case
|
|
943
|
+
case "fetch_agent_timeline_request":
|
|
914
944
|
await this.handleFetchAgentTimelineRequest(msg);
|
|
915
945
|
break;
|
|
916
|
-
case
|
|
946
|
+
case "set_agent_mode_request":
|
|
917
947
|
await this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
|
|
918
948
|
break;
|
|
919
|
-
case
|
|
949
|
+
case "set_agent_model_request":
|
|
920
950
|
await this.handleSetAgentModelRequest(msg.agentId, msg.modelId, msg.requestId);
|
|
921
951
|
break;
|
|
922
|
-
case
|
|
952
|
+
case "set_agent_thinking_request":
|
|
923
953
|
await this.handleSetAgentThinkingRequest(msg.agentId, msg.thinkingOptionId, msg.requestId);
|
|
924
954
|
break;
|
|
925
|
-
case
|
|
955
|
+
case "agent_permission_response":
|
|
926
956
|
await this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
|
|
927
957
|
break;
|
|
928
|
-
case
|
|
958
|
+
case "checkout_status_request":
|
|
929
959
|
await this.handleCheckoutStatusRequest(msg);
|
|
930
960
|
break;
|
|
931
|
-
case
|
|
961
|
+
case "validate_branch_request":
|
|
932
962
|
await this.handleValidateBranchRequest(msg);
|
|
933
963
|
break;
|
|
934
|
-
case
|
|
964
|
+
case "branch_suggestions_request":
|
|
935
965
|
await this.handleBranchSuggestionsRequest(msg);
|
|
936
966
|
break;
|
|
937
|
-
case
|
|
967
|
+
case "directory_suggestions_request":
|
|
938
968
|
await this.handleDirectorySuggestionsRequest(msg);
|
|
939
969
|
break;
|
|
940
|
-
case
|
|
970
|
+
case "subscribe_checkout_diff_request":
|
|
941
971
|
await this.handleSubscribeCheckoutDiffRequest(msg);
|
|
942
972
|
break;
|
|
943
|
-
case
|
|
973
|
+
case "unsubscribe_checkout_diff_request":
|
|
944
974
|
this.handleUnsubscribeCheckoutDiffRequest(msg);
|
|
945
975
|
break;
|
|
946
|
-
case
|
|
976
|
+
case "checkout_commit_request":
|
|
947
977
|
await this.handleCheckoutCommitRequest(msg);
|
|
948
978
|
break;
|
|
949
|
-
case
|
|
979
|
+
case "checkout_merge_request":
|
|
950
980
|
await this.handleCheckoutMergeRequest(msg);
|
|
951
981
|
break;
|
|
952
|
-
case
|
|
982
|
+
case "checkout_merge_from_base_request":
|
|
953
983
|
await this.handleCheckoutMergeFromBaseRequest(msg);
|
|
954
984
|
break;
|
|
955
|
-
case
|
|
985
|
+
case "checkout_push_request":
|
|
956
986
|
await this.handleCheckoutPushRequest(msg);
|
|
957
987
|
break;
|
|
958
|
-
case
|
|
988
|
+
case "checkout_pr_create_request":
|
|
959
989
|
await this.handleCheckoutPrCreateRequest(msg);
|
|
960
990
|
break;
|
|
961
|
-
case
|
|
991
|
+
case "checkout_pr_status_request":
|
|
962
992
|
await this.handleCheckoutPrStatusRequest(msg);
|
|
963
993
|
break;
|
|
964
|
-
case
|
|
994
|
+
case "paseo_worktree_list_request":
|
|
965
995
|
await this.handlePaseoWorktreeListRequest(msg);
|
|
966
996
|
break;
|
|
967
|
-
case
|
|
997
|
+
case "paseo_worktree_archive_request":
|
|
968
998
|
await this.handlePaseoWorktreeArchiveRequest(msg);
|
|
969
999
|
break;
|
|
970
|
-
case
|
|
1000
|
+
case "create_paseo_worktree_request":
|
|
1001
|
+
await this.handleCreatePaseoWorktreeRequest(msg);
|
|
1002
|
+
break;
|
|
1003
|
+
case "open_project_request":
|
|
971
1004
|
await this.handleOpenProjectRequest(msg);
|
|
972
1005
|
break;
|
|
973
|
-
case
|
|
1006
|
+
case "archive_workspace_request":
|
|
974
1007
|
await this.handleArchiveWorkspaceRequest(msg);
|
|
975
1008
|
break;
|
|
976
|
-
case
|
|
1009
|
+
case "file_explorer_request":
|
|
977
1010
|
await this.handleFileExplorerRequest(msg);
|
|
978
1011
|
break;
|
|
979
|
-
case
|
|
1012
|
+
case "project_icon_request":
|
|
980
1013
|
await this.handleProjectIconRequest(msg);
|
|
981
1014
|
break;
|
|
982
|
-
case
|
|
1015
|
+
case "file_download_token_request":
|
|
983
1016
|
await this.handleFileDownloadTokenRequest(msg);
|
|
984
1017
|
break;
|
|
985
|
-
case
|
|
1018
|
+
case "list_provider_models_request":
|
|
986
1019
|
await this.handleListProviderModelsRequest(msg);
|
|
987
1020
|
break;
|
|
988
|
-
case
|
|
1021
|
+
case "list_available_providers_request":
|
|
989
1022
|
await this.handleListAvailableProvidersRequest(msg);
|
|
990
1023
|
break;
|
|
991
|
-
case
|
|
1024
|
+
case "speech_models_list_request":
|
|
992
1025
|
await this.handleSpeechModelsListRequest(msg);
|
|
993
1026
|
break;
|
|
994
|
-
case
|
|
1027
|
+
case "speech_models_download_request":
|
|
995
1028
|
await this.handleSpeechModelsDownloadRequest(msg);
|
|
996
1029
|
break;
|
|
997
|
-
case
|
|
1030
|
+
case "clear_agent_attention":
|
|
998
1031
|
await this.handleClearAgentAttention(msg.agentId);
|
|
999
1032
|
break;
|
|
1000
|
-
case
|
|
1033
|
+
case "client_heartbeat":
|
|
1001
1034
|
this.handleClientHeartbeat(msg);
|
|
1002
1035
|
break;
|
|
1003
|
-
case
|
|
1036
|
+
case "ping": {
|
|
1004
1037
|
const now = Date.now();
|
|
1005
1038
|
this.emit({
|
|
1006
|
-
type:
|
|
1039
|
+
type: "pong",
|
|
1007
1040
|
payload: {
|
|
1008
1041
|
requestId: msg.requestId,
|
|
1009
1042
|
clientSentAt: msg.clientSentAt,
|
|
@@ -1013,160 +1046,137 @@ export class Session {
|
|
|
1013
1046
|
});
|
|
1014
1047
|
break;
|
|
1015
1048
|
}
|
|
1016
|
-
case
|
|
1049
|
+
case "list_commands_request":
|
|
1017
1050
|
await this.handleListCommandsRequest(msg);
|
|
1018
1051
|
break;
|
|
1019
|
-
case
|
|
1052
|
+
case "register_push_token":
|
|
1020
1053
|
this.handleRegisterPushToken(msg.token);
|
|
1021
1054
|
break;
|
|
1022
|
-
case
|
|
1055
|
+
case "subscribe_terminals_request":
|
|
1023
1056
|
this.handleSubscribeTerminalsRequest(msg);
|
|
1024
1057
|
break;
|
|
1025
|
-
case
|
|
1058
|
+
case "unsubscribe_terminals_request":
|
|
1026
1059
|
this.handleUnsubscribeTerminalsRequest(msg);
|
|
1027
1060
|
break;
|
|
1028
|
-
case
|
|
1061
|
+
case "list_terminals_request":
|
|
1029
1062
|
await this.handleListTerminalsRequest(msg);
|
|
1030
1063
|
break;
|
|
1031
|
-
case
|
|
1064
|
+
case "create_terminal_request":
|
|
1032
1065
|
await this.handleCreateTerminalRequest(msg);
|
|
1033
1066
|
break;
|
|
1034
|
-
case
|
|
1067
|
+
case "subscribe_terminal_request":
|
|
1035
1068
|
await this.handleSubscribeTerminalRequest(msg);
|
|
1036
1069
|
break;
|
|
1037
|
-
case
|
|
1070
|
+
case "unsubscribe_terminal_request":
|
|
1038
1071
|
this.handleUnsubscribeTerminalRequest(msg);
|
|
1039
1072
|
break;
|
|
1040
|
-
case
|
|
1073
|
+
case "terminal_input":
|
|
1041
1074
|
this.handleTerminalInput(msg);
|
|
1042
1075
|
break;
|
|
1043
|
-
case
|
|
1076
|
+
case "kill_terminal_request":
|
|
1044
1077
|
await this.handleKillTerminalRequest(msg);
|
|
1045
1078
|
break;
|
|
1046
|
-
case 'attach_terminal_stream_request':
|
|
1047
|
-
await this.handleAttachTerminalStreamRequest(msg);
|
|
1048
|
-
break;
|
|
1049
|
-
case 'detach_terminal_stream_request':
|
|
1050
|
-
this.handleDetachTerminalStreamRequest(msg);
|
|
1051
|
-
break;
|
|
1052
1079
|
}
|
|
1053
1080
|
}
|
|
1054
1081
|
catch (error) {
|
|
1055
1082
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1056
|
-
this.sessionLogger.error({ err },
|
|
1083
|
+
this.sessionLogger.error({ err }, "Error handling message");
|
|
1057
1084
|
const requestId = msg.requestId;
|
|
1058
|
-
if (typeof requestId ===
|
|
1085
|
+
if (typeof requestId === "string") {
|
|
1059
1086
|
try {
|
|
1060
1087
|
this.emit({
|
|
1061
|
-
type:
|
|
1088
|
+
type: "rpc_error",
|
|
1062
1089
|
payload: {
|
|
1063
1090
|
requestId,
|
|
1064
1091
|
requestType: msg.type,
|
|
1065
|
-
error:
|
|
1066
|
-
code:
|
|
1092
|
+
error: `Request failed: ${err.message}`,
|
|
1093
|
+
code: "handler_error",
|
|
1067
1094
|
},
|
|
1068
1095
|
});
|
|
1069
1096
|
}
|
|
1070
1097
|
catch (emitError) {
|
|
1071
|
-
this.sessionLogger.error({ err: emitError },
|
|
1098
|
+
this.sessionLogger.error({ err: emitError }, "Failed to emit rpc_error");
|
|
1072
1099
|
}
|
|
1073
1100
|
}
|
|
1074
1101
|
this.emit({
|
|
1075
|
-
type:
|
|
1102
|
+
type: "activity_log",
|
|
1076
1103
|
payload: {
|
|
1077
1104
|
id: uuidv4(),
|
|
1078
1105
|
timestamp: new Date(),
|
|
1079
|
-
type:
|
|
1106
|
+
type: "error",
|
|
1080
1107
|
content: `Error: ${err.message}`,
|
|
1081
1108
|
},
|
|
1082
1109
|
});
|
|
1083
1110
|
}
|
|
1084
1111
|
}
|
|
1085
1112
|
handleBinaryFrame(frame) {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
break;
|
|
1090
|
-
default:
|
|
1091
|
-
this.sessionLogger.warn({ channel: frame.channel, messageType: frame.messageType }, 'Unhandled binary mux channel');
|
|
1092
|
-
break;
|
|
1113
|
+
const activeStream = this.activeTerminalStreams.get(frame.slot);
|
|
1114
|
+
if (!activeStream || !this.terminalManager) {
|
|
1115
|
+
return;
|
|
1093
1116
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const binding = this.terminalStreams.get(frame.streamId);
|
|
1098
|
-
if (!binding) {
|
|
1099
|
-
this.sessionLogger.warn({ streamId: frame.streamId }, 'Terminal stream not found for input');
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
if (!this.terminalManager) {
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
const session = this.terminalManager.getTerminal(binding.terminalId);
|
|
1106
|
-
if (!session) {
|
|
1107
|
-
this.detachTerminalStream(frame.streamId, { emitExit: true });
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
const payload = frame.payload ?? new Uint8Array(0);
|
|
1111
|
-
if (payload.byteLength === 0) {
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
const text = Buffer.from(payload).toString('utf8');
|
|
1115
|
-
if (!text) {
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
session.send({ type: 'input', data: text });
|
|
1117
|
+
const terminal = this.terminalManager.getTerminal(activeStream.terminalId);
|
|
1118
|
+
if (!terminal) {
|
|
1119
|
+
this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
|
|
1119
1120
|
return;
|
|
1120
1121
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
if (!Number.isFinite(frame.offset) || frame.offset < 0) {
|
|
1122
|
+
switch (frame.opcode) {
|
|
1123
|
+
case TerminalStreamOpcode.Input: {
|
|
1124
|
+
if (frame.payload.byteLength === 0) {
|
|
1125
1125
|
return;
|
|
1126
1126
|
}
|
|
1127
|
-
const
|
|
1128
|
-
if (
|
|
1129
|
-
|
|
1130
|
-
this.flushPendingTerminalStreamChunks(frame.streamId, binding);
|
|
1127
|
+
const text = Buffer.from(frame.payload).toString("utf8");
|
|
1128
|
+
if (!text) {
|
|
1129
|
+
return;
|
|
1131
1130
|
}
|
|
1131
|
+
terminal.send({ type: "input", data: text });
|
|
1132
|
+
return;
|
|
1132
1133
|
}
|
|
1133
|
-
|
|
1134
|
+
case TerminalStreamOpcode.Resize: {
|
|
1135
|
+
const resize = decodeTerminalResizePayload(frame.payload);
|
|
1136
|
+
if (!resize) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
activeStream.needsSnapshot = true;
|
|
1140
|
+
terminal.send({ type: "resize", rows: resize.rows, cols: resize.cols });
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
default:
|
|
1144
|
+
return;
|
|
1134
1145
|
}
|
|
1135
|
-
this.sessionLogger.warn({ streamId: frame.streamId, messageType: frame.messageType }, 'Unhandled terminal binary frame');
|
|
1136
1146
|
}
|
|
1137
1147
|
async handleRestartServerRequest(requestId, reason) {
|
|
1138
1148
|
const payload = {
|
|
1139
|
-
status:
|
|
1149
|
+
status: "restart_requested",
|
|
1140
1150
|
clientId: this.clientId,
|
|
1141
1151
|
};
|
|
1142
1152
|
if (reason && reason.trim().length > 0) {
|
|
1143
1153
|
payload.reason = reason;
|
|
1144
1154
|
}
|
|
1145
1155
|
payload.requestId = requestId;
|
|
1146
|
-
this.sessionLogger.warn({ reason },
|
|
1156
|
+
this.sessionLogger.warn({ reason }, "Restart requested via websocket");
|
|
1147
1157
|
this.emit({
|
|
1148
|
-
type:
|
|
1158
|
+
type: "status",
|
|
1149
1159
|
payload,
|
|
1150
1160
|
});
|
|
1151
1161
|
this.emitLifecycleIntent({
|
|
1152
|
-
type:
|
|
1162
|
+
type: "restart",
|
|
1153
1163
|
clientId: this.clientId,
|
|
1154
1164
|
requestId,
|
|
1155
1165
|
...(reason ? { reason } : {}),
|
|
1156
1166
|
});
|
|
1157
1167
|
}
|
|
1158
1168
|
async handleShutdownServerRequest(requestId) {
|
|
1159
|
-
this.sessionLogger.warn(
|
|
1169
|
+
this.sessionLogger.warn("Shutdown requested via websocket");
|
|
1160
1170
|
this.emit({
|
|
1161
|
-
type:
|
|
1171
|
+
type: "status",
|
|
1162
1172
|
payload: {
|
|
1163
|
-
status:
|
|
1173
|
+
status: "shutdown_requested",
|
|
1164
1174
|
clientId: this.clientId,
|
|
1165
1175
|
requestId,
|
|
1166
1176
|
},
|
|
1167
1177
|
});
|
|
1168
1178
|
this.emitLifecycleIntent({
|
|
1169
|
-
type:
|
|
1179
|
+
type: "shutdown",
|
|
1170
1180
|
clientId: this.clientId,
|
|
1171
1181
|
requestId,
|
|
1172
1182
|
});
|
|
@@ -1179,7 +1189,7 @@ export class Session {
|
|
|
1179
1189
|
this.onLifecycleIntent(intent);
|
|
1180
1190
|
}
|
|
1181
1191
|
catch (error) {
|
|
1182
|
-
this.sessionLogger.error({ err: error, intent },
|
|
1192
|
+
this.sessionLogger.error({ err: error, intent }, "Lifecycle intent handler failed");
|
|
1183
1193
|
}
|
|
1184
1194
|
}
|
|
1185
1195
|
async handleDeleteAgentRequest(agentId, requestId) {
|
|
@@ -1202,7 +1212,7 @@ export class Session {
|
|
|
1202
1212
|
this.sessionLogger.error({ err: error, agentId }, `Failed to remove agent ${agentId} from registry`);
|
|
1203
1213
|
}
|
|
1204
1214
|
this.emit({
|
|
1205
|
-
type:
|
|
1215
|
+
type: "agent_deleted",
|
|
1206
1216
|
payload: {
|
|
1207
1217
|
agentId,
|
|
1208
1218
|
requestId,
|
|
@@ -1210,7 +1220,7 @@ export class Session {
|
|
|
1210
1220
|
});
|
|
1211
1221
|
if (this.agentUpdatesSubscription) {
|
|
1212
1222
|
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
|
|
1213
|
-
kind:
|
|
1223
|
+
kind: "remove",
|
|
1214
1224
|
agentId,
|
|
1215
1225
|
});
|
|
1216
1226
|
}
|
|
@@ -1222,7 +1232,7 @@ export class Session {
|
|
|
1222
1232
|
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
1223
1233
|
const { archivedAt } = await this.archiveAgentState(agentId);
|
|
1224
1234
|
this.emit({
|
|
1225
|
-
type:
|
|
1235
|
+
type: "agent_archived",
|
|
1226
1236
|
payload: {
|
|
1227
1237
|
agentId,
|
|
1228
1238
|
archivedAt,
|
|
@@ -1251,8 +1261,8 @@ export class Session {
|
|
|
1251
1261
|
throw new Error(`Agent not found in storage after snapshot: ${agentId}`);
|
|
1252
1262
|
}
|
|
1253
1263
|
}
|
|
1254
|
-
const normalizedStatus = archivedRecord.lastStatus ===
|
|
1255
|
-
?
|
|
1264
|
+
const normalizedStatus = archivedRecord.lastStatus === "running" || archivedRecord.lastStatus === "initializing"
|
|
1265
|
+
? "idle"
|
|
1256
1266
|
: archivedRecord.lastStatus;
|
|
1257
1267
|
const nextRecord = {
|
|
1258
1268
|
...archivedRecord,
|
|
@@ -1263,7 +1273,17 @@ export class Session {
|
|
|
1263
1273
|
attentionTimestamp: null,
|
|
1264
1274
|
};
|
|
1265
1275
|
await this.agentStorage.upsert(nextRecord);
|
|
1266
|
-
|
|
1276
|
+
// Unload the agent from memory — the storage record is the source of truth now.
|
|
1277
|
+
// This tears down the provider session and drops the hydrated timeline,
|
|
1278
|
+
// freeing memory. ensureAgentLoaded will re-initialize if needed later.
|
|
1279
|
+
if (this.agentManager.getAgent(agentId)) {
|
|
1280
|
+
try {
|
|
1281
|
+
await this.agentManager.closeAgent(agentId);
|
|
1282
|
+
}
|
|
1283
|
+
catch (error) {
|
|
1284
|
+
this.sessionLogger.warn({ err: error, agentId }, "Failed to close agent during archive");
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1267
1287
|
return { archivedAt, archivedRecord: nextRecord };
|
|
1268
1288
|
}
|
|
1269
1289
|
async unarchiveAgentState(agentId) {
|
|
@@ -1291,19 +1311,19 @@ export class Session {
|
|
|
1291
1311
|
this.sessionLogger.info({
|
|
1292
1312
|
agentId,
|
|
1293
1313
|
requestId,
|
|
1294
|
-
hasName: typeof name ===
|
|
1314
|
+
hasName: typeof name === "string",
|
|
1295
1315
|
labelCount: labels ? Object.keys(labels).length : 0,
|
|
1296
|
-
},
|
|
1316
|
+
}, "session: update_agent_request");
|
|
1297
1317
|
const normalizedName = name?.trim();
|
|
1298
1318
|
const normalizedLabels = labels && Object.keys(labels).length > 0 ? labels : undefined;
|
|
1299
1319
|
if (!normalizedName && !normalizedLabels) {
|
|
1300
1320
|
this.emit({
|
|
1301
|
-
type:
|
|
1321
|
+
type: "update_agent_response",
|
|
1302
1322
|
payload: {
|
|
1303
1323
|
requestId,
|
|
1304
1324
|
agentId,
|
|
1305
1325
|
accepted: false,
|
|
1306
|
-
error:
|
|
1326
|
+
error: "Nothing to update (provide name and/or labels)",
|
|
1307
1327
|
},
|
|
1308
1328
|
});
|
|
1309
1329
|
return;
|
|
@@ -1330,28 +1350,28 @@ export class Session {
|
|
|
1330
1350
|
});
|
|
1331
1351
|
}
|
|
1332
1352
|
this.emit({
|
|
1333
|
-
type:
|
|
1353
|
+
type: "update_agent_response",
|
|
1334
1354
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
1335
1355
|
});
|
|
1336
1356
|
}
|
|
1337
1357
|
catch (error) {
|
|
1338
|
-
this.sessionLogger.error({ err: error, agentId, requestId },
|
|
1358
|
+
this.sessionLogger.error({ err: error, agentId, requestId }, "session: update_agent_request error");
|
|
1339
1359
|
this.emit({
|
|
1340
|
-
type:
|
|
1360
|
+
type: "activity_log",
|
|
1341
1361
|
payload: {
|
|
1342
1362
|
id: uuidv4(),
|
|
1343
1363
|
timestamp: new Date(),
|
|
1344
|
-
type:
|
|
1364
|
+
type: "error",
|
|
1345
1365
|
content: `Failed to update agent: ${error.message}`,
|
|
1346
1366
|
},
|
|
1347
1367
|
});
|
|
1348
1368
|
this.emit({
|
|
1349
|
-
type:
|
|
1369
|
+
type: "update_agent_response",
|
|
1350
1370
|
payload: {
|
|
1351
1371
|
requestId,
|
|
1352
1372
|
agentId,
|
|
1353
1373
|
accepted: false,
|
|
1354
|
-
error: error?.message ? String(error.message) :
|
|
1374
|
+
error: error?.message ? String(error.message) : "Failed to update agent",
|
|
1355
1375
|
},
|
|
1356
1376
|
});
|
|
1357
1377
|
}
|
|
@@ -1365,7 +1385,7 @@ export class Session {
|
|
|
1365
1385
|
};
|
|
1366
1386
|
}
|
|
1367
1387
|
resolveModeReadinessState(readiness, mode) {
|
|
1368
|
-
if (mode ===
|
|
1388
|
+
if (mode === "voice_mode") {
|
|
1369
1389
|
return readiness.realtimeVoice;
|
|
1370
1390
|
}
|
|
1371
1391
|
return readiness.dictation;
|
|
@@ -1403,13 +1423,13 @@ export class Session {
|
|
|
1403
1423
|
async handleSetVoiceMode(enabled, agentId, requestId) {
|
|
1404
1424
|
const startedAt = Date.now();
|
|
1405
1425
|
try {
|
|
1406
|
-
this.sessionLogger.info({ enabled, requestedAgentId: agentId ?? null, requestId: requestId ?? null },
|
|
1426
|
+
this.sessionLogger.info({ enabled, requestedAgentId: agentId ?? null, requestId: requestId ?? null }, "set_voice_mode started");
|
|
1407
1427
|
if (enabled) {
|
|
1408
|
-
const unavailable = this.resolveVoiceFeatureUnavailableContext(
|
|
1428
|
+
const unavailable = this.resolveVoiceFeatureUnavailableContext("voice_mode");
|
|
1409
1429
|
if (unavailable) {
|
|
1410
1430
|
throw new VoiceFeatureUnavailableError(unavailable);
|
|
1411
1431
|
}
|
|
1412
|
-
const normalizedAgentId = this.parseVoiceTargetAgentId(agentId ??
|
|
1432
|
+
const normalizedAgentId = this.parseVoiceTargetAgentId(agentId ?? "", "set_voice_mode");
|
|
1413
1433
|
if (this.isVoiceMode &&
|
|
1414
1434
|
this.voiceModeAgentId &&
|
|
1415
1435
|
this.voiceModeAgentId !== normalizedAgentId) {
|
|
@@ -1417,26 +1437,26 @@ export class Session {
|
|
|
1417
1437
|
previousAgentId: this.voiceModeAgentId,
|
|
1418
1438
|
nextAgentId: normalizedAgentId,
|
|
1419
1439
|
elapsedMs: Date.now() - startedAt,
|
|
1420
|
-
},
|
|
1440
|
+
}, "set_voice_mode disabling previous active voice agent");
|
|
1421
1441
|
await this.disableVoiceModeForActiveAgent(true);
|
|
1422
1442
|
}
|
|
1423
1443
|
if (!this.isVoiceMode || this.voiceModeAgentId !== normalizedAgentId) {
|
|
1424
|
-
this.sessionLogger.info({ agentId: normalizedAgentId, elapsedMs: Date.now() - startedAt },
|
|
1444
|
+
this.sessionLogger.info({ agentId: normalizedAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode enabling voice for agent");
|
|
1425
1445
|
const refreshedAgentId = await this.enableVoiceModeForAgent(normalizedAgentId);
|
|
1426
1446
|
this.voiceModeAgentId = refreshedAgentId;
|
|
1427
|
-
this.sessionLogger.info({ agentId: refreshedAgentId, elapsedMs: Date.now() - startedAt },
|
|
1447
|
+
this.sessionLogger.info({ agentId: refreshedAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode agent enable complete");
|
|
1428
1448
|
}
|
|
1429
|
-
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt },
|
|
1449
|
+
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode starting voice turn controller");
|
|
1430
1450
|
await this.startVoiceTurnController();
|
|
1431
|
-
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt },
|
|
1451
|
+
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode voice turn controller started");
|
|
1432
1452
|
this.isVoiceMode = true;
|
|
1433
1453
|
this.sessionLogger.info({
|
|
1434
1454
|
agentId: this.voiceModeAgentId,
|
|
1435
1455
|
elapsedMs: Date.now() - startedAt,
|
|
1436
|
-
},
|
|
1456
|
+
}, "Voice mode enabled for existing agent");
|
|
1437
1457
|
if (requestId) {
|
|
1438
1458
|
this.emit({
|
|
1439
|
-
type:
|
|
1459
|
+
type: "set_voice_mode_response",
|
|
1440
1460
|
payload: {
|
|
1441
1461
|
requestId,
|
|
1442
1462
|
enabled: true,
|
|
@@ -1448,13 +1468,13 @@ export class Session {
|
|
|
1448
1468
|
}
|
|
1449
1469
|
return;
|
|
1450
1470
|
}
|
|
1451
|
-
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt },
|
|
1471
|
+
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode disabling active voice mode");
|
|
1452
1472
|
await this.disableVoiceModeForActiveAgent(true);
|
|
1453
1473
|
this.isVoiceMode = false;
|
|
1454
|
-
this.sessionLogger.info({ elapsedMs: Date.now() - startedAt },
|
|
1474
|
+
this.sessionLogger.info({ elapsedMs: Date.now() - startedAt }, "Voice mode disabled");
|
|
1455
1475
|
if (requestId) {
|
|
1456
1476
|
this.emit({
|
|
1457
|
-
type:
|
|
1477
|
+
type: "set_voice_mode_response",
|
|
1458
1478
|
payload: {
|
|
1459
1479
|
requestId,
|
|
1460
1480
|
enabled: false,
|
|
@@ -1466,17 +1486,17 @@ export class Session {
|
|
|
1466
1486
|
}
|
|
1467
1487
|
}
|
|
1468
1488
|
catch (error) {
|
|
1469
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
1489
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to set voice mode";
|
|
1470
1490
|
const unavailable = this.getVoiceFeatureUnavailableResponseMetadata(error);
|
|
1471
1491
|
this.sessionLogger.error({
|
|
1472
1492
|
err: error,
|
|
1473
1493
|
enabled,
|
|
1474
1494
|
requestedAgentId: agentId ?? null,
|
|
1475
1495
|
elapsedMs: Date.now() - startedAt,
|
|
1476
|
-
},
|
|
1496
|
+
}, "set_voice_mode failed");
|
|
1477
1497
|
if (requestId) {
|
|
1478
1498
|
this.emit({
|
|
1479
|
-
type:
|
|
1499
|
+
type: "set_voice_mode_response",
|
|
1480
1500
|
payload: {
|
|
1481
1501
|
requestId,
|
|
1482
1502
|
enabled: this.isVoiceMode,
|
|
@@ -1507,7 +1527,7 @@ export class Session {
|
|
|
1507
1527
|
buildVoiceModeMcpServers(existing, socketPath) {
|
|
1508
1528
|
const mcpStdio = this.voiceAgentMcpStdio;
|
|
1509
1529
|
if (!mcpStdio) {
|
|
1510
|
-
throw new Error(
|
|
1530
|
+
throw new Error("Voice MCP stdio bridge is not configured");
|
|
1511
1531
|
}
|
|
1512
1532
|
return {
|
|
1513
1533
|
...(existing ?? {}),
|
|
@@ -1523,14 +1543,14 @@ export class Session {
|
|
|
1523
1543
|
const startedAt = Date.now();
|
|
1524
1544
|
const ensureVoiceSocket = this.ensureVoiceMcpSocketForAgent;
|
|
1525
1545
|
if (!ensureVoiceSocket) {
|
|
1526
|
-
throw new Error(
|
|
1546
|
+
throw new Error("Voice MCP socket bridge is not configured");
|
|
1527
1547
|
}
|
|
1528
|
-
this.sessionLogger.info({ agentId },
|
|
1548
|
+
this.sessionLogger.info({ agentId }, "enableVoiceModeForAgent.ensureAgentLoaded.start");
|
|
1529
1549
|
const existing = await this.ensureAgentLoaded(agentId);
|
|
1530
|
-
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt },
|
|
1531
|
-
this.sessionLogger.info({ agentId },
|
|
1550
|
+
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.ensureAgentLoaded.done");
|
|
1551
|
+
this.sessionLogger.info({ agentId }, "enableVoiceModeForAgent.ensureVoiceSocket.start");
|
|
1532
1552
|
const socketPath = await ensureVoiceSocket(agentId);
|
|
1533
|
-
this.sessionLogger.info({ agentId, socketPath, elapsedMs: Date.now() - startedAt },
|
|
1553
|
+
this.sessionLogger.info({ agentId, socketPath, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.ensureVoiceSocket.done");
|
|
1534
1554
|
this.registerVoiceBridgeForAgent(agentId);
|
|
1535
1555
|
const baseConfig = {
|
|
1536
1556
|
systemPrompt: stripVoiceModeSystemPrompt(existing.config.systemPrompt),
|
|
@@ -1542,9 +1562,9 @@ export class Session {
|
|
|
1542
1562
|
mcpServers: this.buildVoiceModeMcpServers(baseConfig.mcpServers, socketPath),
|
|
1543
1563
|
};
|
|
1544
1564
|
try {
|
|
1545
|
-
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt },
|
|
1565
|
+
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.reloadAgentSession.start");
|
|
1546
1566
|
const refreshed = await this.agentManager.reloadAgentSession(agentId, refreshOverrides);
|
|
1547
|
-
this.sessionLogger.info({ agentId, refreshedAgentId: refreshed.id, elapsedMs: Date.now() - startedAt },
|
|
1567
|
+
this.sessionLogger.info({ agentId, refreshedAgentId: refreshed.id, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.reloadAgentSession.done");
|
|
1548
1568
|
return refreshed.id;
|
|
1549
1569
|
}
|
|
1550
1570
|
catch (error) {
|
|
@@ -1565,7 +1585,7 @@ export class Session {
|
|
|
1565
1585
|
this.unregisterVoiceSpeakHandler?.(agentId);
|
|
1566
1586
|
this.unregisterVoiceCallerContext?.(agentId);
|
|
1567
1587
|
await this.removeVoiceMcpSocketForAgent?.(agentId).catch((error) => {
|
|
1568
|
-
this.sessionLogger.warn({ err: error, agentId },
|
|
1588
|
+
this.sessionLogger.warn({ err: error, agentId }, "Failed to remove voice MCP socket bridge on disable");
|
|
1569
1589
|
});
|
|
1570
1590
|
if (restoreAgentConfig && this.voiceModeBaseConfig) {
|
|
1571
1591
|
const baseConfig = this.voiceModeBaseConfig;
|
|
@@ -1576,7 +1596,7 @@ export class Session {
|
|
|
1576
1596
|
});
|
|
1577
1597
|
}
|
|
1578
1598
|
catch (error) {
|
|
1579
|
-
this.sessionLogger.warn({ err: error, agentId },
|
|
1599
|
+
this.sessionLogger.warn({ err: error, agentId }, "Failed to restore agent config while disabling voice mode");
|
|
1580
1600
|
}
|
|
1581
1601
|
}
|
|
1582
1602
|
this.voiceModeBaseConfig = null;
|
|
@@ -1587,16 +1607,16 @@ export class Session {
|
|
|
1587
1607
|
}
|
|
1588
1608
|
async startVoiceTurnController() {
|
|
1589
1609
|
if (this.voiceTurnController) {
|
|
1590
|
-
this.sessionLogger.info(
|
|
1610
|
+
this.sessionLogger.info("startVoiceTurnController skipped: already running");
|
|
1591
1611
|
return;
|
|
1592
1612
|
}
|
|
1593
1613
|
const turnDetection = this.resolveVoiceTurnDetection();
|
|
1594
1614
|
if (!turnDetection) {
|
|
1595
|
-
throw new Error(
|
|
1615
|
+
throw new Error("Voice turn detection is not configured");
|
|
1596
1616
|
}
|
|
1597
|
-
this.sessionLogger.info({ providerId: turnDetection.id },
|
|
1617
|
+
this.sessionLogger.info({ providerId: turnDetection.id }, "startVoiceTurnController creating controller");
|
|
1598
1618
|
const controller = createVoiceTurnController({
|
|
1599
|
-
logger: this.sessionLogger.child({ component:
|
|
1619
|
+
logger: this.sessionLogger.child({ component: "voice-turn-controller" }),
|
|
1600
1620
|
turnDetection,
|
|
1601
1621
|
utteranceSink: {
|
|
1602
1622
|
submitUtterance: async ({ pcm16, format, sampleRate, startedAt, endedAt }) => {
|
|
@@ -1606,7 +1626,7 @@ export class Session {
|
|
|
1606
1626
|
startedAt,
|
|
1607
1627
|
endedAt,
|
|
1608
1628
|
durationMs: Math.max(0, endedAt - startedAt),
|
|
1609
|
-
},
|
|
1629
|
+
}, "Submitting detected voice utterance");
|
|
1610
1630
|
await this.processCompletedAudio(pcm16, format);
|
|
1611
1631
|
},
|
|
1612
1632
|
},
|
|
@@ -1618,20 +1638,20 @@ export class Session {
|
|
|
1618
1638
|
this.handleVoiceSpeechStopped();
|
|
1619
1639
|
},
|
|
1620
1640
|
onError: (error) => {
|
|
1621
|
-
this.sessionLogger.error({ err: error },
|
|
1641
|
+
this.sessionLogger.error({ err: error }, "Voice turn controller failed");
|
|
1622
1642
|
},
|
|
1623
1643
|
},
|
|
1624
1644
|
});
|
|
1625
|
-
this.sessionLogger.info(
|
|
1645
|
+
this.sessionLogger.info("startVoiceTurnController connecting controller");
|
|
1626
1646
|
await controller.start();
|
|
1627
1647
|
this.voiceTurnController = controller;
|
|
1628
|
-
this.sessionLogger.info(
|
|
1648
|
+
this.sessionLogger.info("startVoiceTurnController connected");
|
|
1629
1649
|
}
|
|
1630
1650
|
async stopVoiceTurnController() {
|
|
1631
1651
|
if (!this.voiceTurnController) {
|
|
1632
1652
|
return;
|
|
1633
1653
|
}
|
|
1634
|
-
this.clearPendingVoiceSpeechStart(
|
|
1654
|
+
this.clearPendingVoiceSpeechStart("turn-controller-stop");
|
|
1635
1655
|
const controller = this.voiceTurnController;
|
|
1636
1656
|
this.voiceTurnController = null;
|
|
1637
1657
|
await controller.stop();
|
|
@@ -1642,7 +1662,7 @@ export class Session {
|
|
|
1642
1662
|
this.pendingVoiceSpeechTimer = null;
|
|
1643
1663
|
}
|
|
1644
1664
|
if (this.pendingVoiceSpeechStartAt !== null) {
|
|
1645
|
-
this.sessionLogger.debug({ reason },
|
|
1665
|
+
this.sessionLogger.debug({ reason }, "Clearing provisional voice speech start");
|
|
1646
1666
|
this.pendingVoiceSpeechStartAt = null;
|
|
1647
1667
|
}
|
|
1648
1668
|
}
|
|
@@ -1652,16 +1672,16 @@ export class Session {
|
|
|
1652
1672
|
}
|
|
1653
1673
|
const startedAt = Date.now();
|
|
1654
1674
|
this.pendingVoiceSpeechStartAt = startedAt;
|
|
1655
|
-
this.sessionLogger.info({ confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS },
|
|
1675
|
+
this.sessionLogger.info({ confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS }, "Silero VAD provisional speech_started");
|
|
1656
1676
|
this.pendingVoiceSpeechTimer = setTimeout(() => {
|
|
1657
1677
|
this.pendingVoiceSpeechTimer = null;
|
|
1658
1678
|
if (this.pendingVoiceSpeechStartAt !== startedAt || this.speechInProgress) {
|
|
1659
1679
|
return;
|
|
1660
1680
|
}
|
|
1661
1681
|
this.pendingVoiceSpeechStartAt = null;
|
|
1662
|
-
this.sessionLogger.info(
|
|
1682
|
+
this.sessionLogger.info("voice_input_state emitting isSpeaking=true");
|
|
1663
1683
|
this.emit({
|
|
1664
|
-
type:
|
|
1684
|
+
type: "voice_input_state",
|
|
1665
1685
|
payload: {
|
|
1666
1686
|
isSpeaking: true,
|
|
1667
1687
|
},
|
|
@@ -1672,13 +1692,13 @@ export class Session {
|
|
|
1672
1692
|
handleVoiceSpeechStopped() {
|
|
1673
1693
|
if (this.pendingVoiceSpeechStartAt !== null) {
|
|
1674
1694
|
const durationMs = Date.now() - this.pendingVoiceSpeechStartAt;
|
|
1675
|
-
this.clearPendingVoiceSpeechStart(
|
|
1676
|
-
this.sessionLogger.info({ durationMs, confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS },
|
|
1695
|
+
this.clearPendingVoiceSpeechStart("speech-stopped-before-confirmation");
|
|
1696
|
+
this.sessionLogger.info({ durationMs, confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS }, "Ignoring provisional voice speech start that ended before confirmation");
|
|
1677
1697
|
return;
|
|
1678
1698
|
}
|
|
1679
|
-
this.sessionLogger.info(
|
|
1699
|
+
this.sessionLogger.info("voice_input_state emitting isSpeaking=false");
|
|
1680
1700
|
this.emit({
|
|
1681
|
-
type:
|
|
1701
|
+
type: "voice_input_state",
|
|
1682
1702
|
payload: {
|
|
1683
1703
|
isSpeaking: false,
|
|
1684
1704
|
},
|
|
@@ -1688,13 +1708,13 @@ export class Session {
|
|
|
1688
1708
|
* Handle text message to agent (with optional image attachments)
|
|
1689
1709
|
*/
|
|
1690
1710
|
async handleSendAgentMessage(agentId, text, messageId, images, runOptions) {
|
|
1691
|
-
this.sessionLogger.info({ agentId, textPreview: text.substring(0, 50), imageCount: images?.length ?? 0 }, `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` :
|
|
1711
|
+
this.sessionLogger.info({ agentId, textPreview: text.substring(0, 50), imageCount: images?.length ?? 0 }, `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}`);
|
|
1692
1712
|
await this.unarchiveAgentState(agentId);
|
|
1693
1713
|
try {
|
|
1694
1714
|
await this.ensureAgentLoaded(agentId);
|
|
1695
1715
|
}
|
|
1696
1716
|
catch (error) {
|
|
1697
|
-
this.handleAgentRunError(agentId, error,
|
|
1717
|
+
this.handleAgentRunError(agentId, error, "Failed to initialize agent before sending prompt");
|
|
1698
1718
|
return;
|
|
1699
1719
|
}
|
|
1700
1720
|
try {
|
|
@@ -1714,7 +1734,7 @@ export class Session {
|
|
|
1714
1734
|
*/
|
|
1715
1735
|
async handleCreateAgentRequest(msg) {
|
|
1716
1736
|
const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, labels, } = msg;
|
|
1717
|
-
this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` :
|
|
1737
|
+
this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ""}`);
|
|
1718
1738
|
try {
|
|
1719
1739
|
const trimmedPrompt = initialPrompt?.trim();
|
|
1720
1740
|
const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
|
|
@@ -1735,9 +1755,9 @@ export class Session {
|
|
|
1735
1755
|
throw new Error(`Agent ${snapshot.id} not found after creation`);
|
|
1736
1756
|
}
|
|
1737
1757
|
this.emit({
|
|
1738
|
-
type:
|
|
1758
|
+
type: "status",
|
|
1739
1759
|
payload: {
|
|
1740
|
-
status:
|
|
1760
|
+
status: "agent_created",
|
|
1741
1761
|
agentId: snapshot.id,
|
|
1742
1762
|
requestId,
|
|
1743
1763
|
agent: agentPayload,
|
|
@@ -1757,11 +1777,11 @@ export class Session {
|
|
|
1757
1777
|
void this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined).catch((promptError) => {
|
|
1758
1778
|
this.sessionLogger.error({ err: promptError, agentId: snapshot.id }, `Failed to run initial prompt for agent ${snapshot.id}`);
|
|
1759
1779
|
this.emit({
|
|
1760
|
-
type:
|
|
1780
|
+
type: "activity_log",
|
|
1761
1781
|
payload: {
|
|
1762
1782
|
id: uuidv4(),
|
|
1763
1783
|
timestamp: new Date(),
|
|
1764
|
-
type:
|
|
1784
|
+
type: "error",
|
|
1765
1785
|
content: `Initial prompt failed: ${promptError?.message ?? promptError}`,
|
|
1766
1786
|
},
|
|
1767
1787
|
});
|
|
@@ -1788,23 +1808,23 @@ export class Session {
|
|
|
1788
1808
|
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1789
1809
|
}
|
|
1790
1810
|
catch (error) {
|
|
1791
|
-
this.sessionLogger.error({ err: error },
|
|
1811
|
+
this.sessionLogger.error({ err: error }, "Failed to create agent");
|
|
1792
1812
|
if (requestId) {
|
|
1793
1813
|
this.emit({
|
|
1794
|
-
type:
|
|
1814
|
+
type: "status",
|
|
1795
1815
|
payload: {
|
|
1796
|
-
status:
|
|
1816
|
+
status: "agent_create_failed",
|
|
1797
1817
|
requestId,
|
|
1798
1818
|
error: error?.message ?? String(error),
|
|
1799
1819
|
},
|
|
1800
1820
|
});
|
|
1801
1821
|
}
|
|
1802
1822
|
this.emit({
|
|
1803
|
-
type:
|
|
1823
|
+
type: "activity_log",
|
|
1804
1824
|
payload: {
|
|
1805
1825
|
id: uuidv4(),
|
|
1806
1826
|
timestamp: new Date(),
|
|
1807
|
-
type:
|
|
1827
|
+
type: "error",
|
|
1808
1828
|
content: `Failed to create agent: ${error.message}`,
|
|
1809
1829
|
},
|
|
1810
1830
|
});
|
|
@@ -1813,14 +1833,14 @@ export class Session {
|
|
|
1813
1833
|
async handleResumeAgentRequest(msg) {
|
|
1814
1834
|
const { handle, overrides, requestId } = msg;
|
|
1815
1835
|
if (!handle) {
|
|
1816
|
-
this.sessionLogger.warn(
|
|
1836
|
+
this.sessionLogger.warn("Resume request missing persistence handle");
|
|
1817
1837
|
this.emit({
|
|
1818
|
-
type:
|
|
1838
|
+
type: "activity_log",
|
|
1819
1839
|
payload: {
|
|
1820
1840
|
id: uuidv4(),
|
|
1821
1841
|
timestamp: new Date(),
|
|
1822
|
-
type:
|
|
1823
|
-
content:
|
|
1842
|
+
type: "error",
|
|
1843
|
+
content: "Unable to resume agent: missing persistence handle",
|
|
1824
1844
|
},
|
|
1825
1845
|
});
|
|
1826
1846
|
return;
|
|
@@ -1839,9 +1859,9 @@ export class Session {
|
|
|
1839
1859
|
throw new Error(`Agent ${snapshot.id} not found after resume`);
|
|
1840
1860
|
}
|
|
1841
1861
|
this.emit({
|
|
1842
|
-
type:
|
|
1862
|
+
type: "status",
|
|
1843
1863
|
payload: {
|
|
1844
|
-
status:
|
|
1864
|
+
status: "agent_resumed",
|
|
1845
1865
|
agentId: snapshot.id,
|
|
1846
1866
|
requestId,
|
|
1847
1867
|
timelineSize,
|
|
@@ -1851,13 +1871,13 @@ export class Session {
|
|
|
1851
1871
|
}
|
|
1852
1872
|
}
|
|
1853
1873
|
catch (error) {
|
|
1854
|
-
this.sessionLogger.error({ err: error },
|
|
1874
|
+
this.sessionLogger.error({ err: error }, "Failed to resume agent");
|
|
1855
1875
|
this.emit({
|
|
1856
|
-
type:
|
|
1876
|
+
type: "activity_log",
|
|
1857
1877
|
payload: {
|
|
1858
1878
|
id: uuidv4(),
|
|
1859
1879
|
timestamp: new Date(),
|
|
1860
|
-
type:
|
|
1880
|
+
type: "error",
|
|
1861
1881
|
content: `Failed to resume agent: ${error.message}`,
|
|
1862
1882
|
},
|
|
1863
1883
|
});
|
|
@@ -1895,9 +1915,9 @@ export class Session {
|
|
|
1895
1915
|
const timelineSize = this.agentManager.getTimeline(agentId).length;
|
|
1896
1916
|
if (requestId) {
|
|
1897
1917
|
this.emit({
|
|
1898
|
-
type:
|
|
1918
|
+
type: "status",
|
|
1899
1919
|
payload: {
|
|
1900
|
-
status:
|
|
1920
|
+
status: "agent_refreshed",
|
|
1901
1921
|
agentId,
|
|
1902
1922
|
requestId,
|
|
1903
1923
|
timelineSize,
|
|
@@ -1908,11 +1928,11 @@ export class Session {
|
|
|
1908
1928
|
catch (error) {
|
|
1909
1929
|
this.sessionLogger.error({ err: error, agentId }, `Failed to refresh agent ${agentId}`);
|
|
1910
1930
|
this.emit({
|
|
1911
|
-
type:
|
|
1931
|
+
type: "activity_log",
|
|
1912
1932
|
payload: {
|
|
1913
1933
|
id: uuidv4(),
|
|
1914
1934
|
timestamp: new Date(),
|
|
1915
|
-
type:
|
|
1935
|
+
type: "error",
|
|
1916
1936
|
content: `Failed to refresh agent: ${error.message}`,
|
|
1917
1937
|
},
|
|
1918
1938
|
});
|
|
@@ -1924,7 +1944,7 @@ export class Session {
|
|
|
1924
1944
|
await this.interruptAgentIfRunning(agentId);
|
|
1925
1945
|
}
|
|
1926
1946
|
catch (error) {
|
|
1927
|
-
this.handleAgentRunError(agentId, error,
|
|
1947
|
+
this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
|
|
1928
1948
|
}
|
|
1929
1949
|
}
|
|
1930
1950
|
async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, _labels) {
|
|
@@ -1946,20 +1966,21 @@ export class Session {
|
|
|
1946
1966
|
}
|
|
1947
1967
|
else {
|
|
1948
1968
|
// Resolve current branch name from HEAD
|
|
1949
|
-
const { stdout } = await execAsync(
|
|
1969
|
+
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
|
|
1950
1970
|
cwd,
|
|
1951
1971
|
env: READ_ONLY_GIT_ENV,
|
|
1952
1972
|
});
|
|
1953
1973
|
targetBranch = stdout.trim();
|
|
1954
1974
|
}
|
|
1955
1975
|
if (!targetBranch) {
|
|
1956
|
-
throw new Error(
|
|
1976
|
+
throw new Error("A branch name is required when creating a worktree.");
|
|
1957
1977
|
}
|
|
1958
1978
|
this.sessionLogger.info({ worktreeSlug: normalized.worktreeSlug ?? targetBranch, branch: targetBranch }, `Creating worktree '${normalized.worktreeSlug ?? targetBranch}' for branch ${targetBranch}`);
|
|
1979
|
+
const baseBranch = normalized.baseBranch ?? (await this.resolveGitCreateBaseBranch(cwd));
|
|
1959
1980
|
const createdWorktree = await createAgentWorktree({
|
|
1960
1981
|
branchName: targetBranch,
|
|
1961
1982
|
cwd,
|
|
1962
|
-
baseBranch
|
|
1983
|
+
baseBranch,
|
|
1963
1984
|
worktreeSlug: normalized.worktreeSlug ?? targetBranch,
|
|
1964
1985
|
paseoHome: this.paseoHome,
|
|
1965
1986
|
});
|
|
@@ -1967,9 +1988,10 @@ export class Session {
|
|
|
1967
1988
|
worktreeConfig = createdWorktree;
|
|
1968
1989
|
}
|
|
1969
1990
|
else if (normalized.createNewBranch) {
|
|
1991
|
+
const baseBranch = normalized.baseBranch ?? (await this.resolveGitCreateBaseBranch(cwd));
|
|
1970
1992
|
await this.createBranchFromBase({
|
|
1971
1993
|
cwd,
|
|
1972
|
-
baseBranch
|
|
1994
|
+
baseBranch,
|
|
1973
1995
|
newBranchName: normalized.newBranchName,
|
|
1974
1996
|
});
|
|
1975
1997
|
}
|
|
@@ -1991,7 +2013,7 @@ export class Session {
|
|
|
1991
2013
|
cwd: msg.cwd ? expandTilde(msg.cwd) : undefined,
|
|
1992
2014
|
});
|
|
1993
2015
|
this.emit({
|
|
1994
|
-
type:
|
|
2016
|
+
type: "list_provider_models_response",
|
|
1995
2017
|
payload: {
|
|
1996
2018
|
provider: msg.provider,
|
|
1997
2019
|
models,
|
|
@@ -2004,7 +2026,7 @@ export class Session {
|
|
|
2004
2026
|
catch (error) {
|
|
2005
2027
|
this.sessionLogger.error({ err: error, provider: msg.provider }, `Failed to list models for ${msg.provider}`);
|
|
2006
2028
|
this.emit({
|
|
2007
|
-
type:
|
|
2029
|
+
type: "list_provider_models_response",
|
|
2008
2030
|
payload: {
|
|
2009
2031
|
provider: msg.provider,
|
|
2010
2032
|
error: error?.message ?? String(error),
|
|
@@ -2019,7 +2041,7 @@ export class Session {
|
|
|
2019
2041
|
try {
|
|
2020
2042
|
const providers = await this.agentManager.listProviderAvailability();
|
|
2021
2043
|
this.emit({
|
|
2022
|
-
type:
|
|
2044
|
+
type: "list_available_providers_response",
|
|
2023
2045
|
payload: {
|
|
2024
2046
|
providers,
|
|
2025
2047
|
error: null,
|
|
@@ -2029,9 +2051,9 @@ export class Session {
|
|
|
2029
2051
|
});
|
|
2030
2052
|
}
|
|
2031
2053
|
catch (error) {
|
|
2032
|
-
this.sessionLogger.error({ err: error },
|
|
2054
|
+
this.sessionLogger.error({ err: error }, "Failed to list provider availability");
|
|
2033
2055
|
this.emit({
|
|
2034
|
-
type:
|
|
2056
|
+
type: "list_available_providers_response",
|
|
2035
2057
|
payload: {
|
|
2036
2058
|
providers: [],
|
|
2037
2059
|
error: error?.message ?? String(error),
|
|
@@ -2071,7 +2093,7 @@ export class Session {
|
|
|
2071
2093
|
};
|
|
2072
2094
|
}));
|
|
2073
2095
|
this.emit({
|
|
2074
|
-
type:
|
|
2096
|
+
type: "speech_models_list_response",
|
|
2075
2097
|
payload: {
|
|
2076
2098
|
modelsDir,
|
|
2077
2099
|
models,
|
|
@@ -2086,11 +2108,11 @@ export class Session {
|
|
|
2086
2108
|
const invalid = modelIdsRaw.filter((id) => !allModelIds.has(id));
|
|
2087
2109
|
if (invalid.length > 0) {
|
|
2088
2110
|
this.emit({
|
|
2089
|
-
type:
|
|
2111
|
+
type: "speech_models_download_response",
|
|
2090
2112
|
payload: {
|
|
2091
2113
|
modelsDir,
|
|
2092
2114
|
downloadedModelIds: [],
|
|
2093
|
-
error: `Unknown speech model id(s): ${invalid.join(
|
|
2115
|
+
error: `Unknown speech model id(s): ${invalid.join(", ")}`,
|
|
2094
2116
|
requestId: msg.requestId,
|
|
2095
2117
|
},
|
|
2096
2118
|
});
|
|
@@ -2104,7 +2126,7 @@ export class Session {
|
|
|
2104
2126
|
logger: this.sessionLogger,
|
|
2105
2127
|
});
|
|
2106
2128
|
this.emit({
|
|
2107
|
-
type:
|
|
2129
|
+
type: "speech_models_download_response",
|
|
2108
2130
|
payload: {
|
|
2109
2131
|
modelsDir,
|
|
2110
2132
|
downloadedModelIds: modelIds,
|
|
@@ -2114,9 +2136,9 @@ export class Session {
|
|
|
2114
2136
|
});
|
|
2115
2137
|
}
|
|
2116
2138
|
catch (error) {
|
|
2117
|
-
this.sessionLogger.error({ err: error, modelIds },
|
|
2139
|
+
this.sessionLogger.error({ err: error, modelIds }, "Failed to download speech models");
|
|
2118
2140
|
this.emit({
|
|
2119
|
-
type:
|
|
2141
|
+
type: "speech_models_download_response",
|
|
2120
2142
|
payload: {
|
|
2121
2143
|
modelsDir,
|
|
2122
2144
|
downloadedModelIds: [],
|
|
@@ -2150,17 +2172,11 @@ export class Session {
|
|
|
2150
2172
|
return null;
|
|
2151
2173
|
}
|
|
2152
2174
|
if (baseBranch) {
|
|
2153
|
-
this.assertSafeGitRef(baseBranch,
|
|
2154
|
-
}
|
|
2155
|
-
if (createWorktree && !baseBranch) {
|
|
2156
|
-
throw new Error('Base branch is required when creating a worktree');
|
|
2157
|
-
}
|
|
2158
|
-
if (createNewBranch && !baseBranch) {
|
|
2159
|
-
throw new Error('Base branch is required when creating a new branch');
|
|
2175
|
+
this.assertSafeGitRef(baseBranch, "base branch");
|
|
2160
2176
|
}
|
|
2161
2177
|
if (createNewBranch) {
|
|
2162
2178
|
if (!normalizedBranchName) {
|
|
2163
|
-
throw new Error(
|
|
2179
|
+
throw new Error("New branch name is required");
|
|
2164
2180
|
}
|
|
2165
2181
|
const validation = validateBranchSlug(normalizedBranchName);
|
|
2166
2182
|
if (!validation.valid) {
|
|
@@ -2182,24 +2198,36 @@ export class Session {
|
|
|
2182
2198
|
};
|
|
2183
2199
|
}
|
|
2184
2200
|
assertSafeGitRef(ref, label) {
|
|
2185
|
-
if (!SAFE_GIT_REF_PATTERN.test(ref) || ref.includes(
|
|
2201
|
+
if (!SAFE_GIT_REF_PATTERN.test(ref) || ref.includes("..") || ref.includes("@{")) {
|
|
2186
2202
|
throw new Error(`Invalid ${label}: ${ref}`);
|
|
2187
2203
|
}
|
|
2188
2204
|
}
|
|
2205
|
+
async resolveGitCreateBaseBranch(cwd) {
|
|
2206
|
+
const checkout = await getCheckoutStatusLite(cwd, { paseoHome: this.paseoHome });
|
|
2207
|
+
if (!checkout.isGit) {
|
|
2208
|
+
throw new Error("Cannot create a worktree outside a git repository");
|
|
2209
|
+
}
|
|
2210
|
+
const repoRoot = checkout.isPaseoOwnedWorktree ? checkout.mainRepoRoot : cwd;
|
|
2211
|
+
const baseBranch = await resolveRepositoryDefaultBranch(repoRoot);
|
|
2212
|
+
if (!baseBranch) {
|
|
2213
|
+
throw new Error("Unable to resolve repository default branch");
|
|
2214
|
+
}
|
|
2215
|
+
return baseBranch;
|
|
2216
|
+
}
|
|
2189
2217
|
toCheckoutError(error) {
|
|
2190
2218
|
if (error instanceof NotGitRepoError) {
|
|
2191
|
-
return { code:
|
|
2219
|
+
return { code: "NOT_GIT_REPO", message: error.message };
|
|
2192
2220
|
}
|
|
2193
2221
|
if (error instanceof MergeConflictError) {
|
|
2194
|
-
return { code:
|
|
2222
|
+
return { code: "MERGE_CONFLICT", message: error.message };
|
|
2195
2223
|
}
|
|
2196
2224
|
if (error instanceof MergeFromBaseConflictError) {
|
|
2197
|
-
return { code:
|
|
2225
|
+
return { code: "MERGE_CONFLICT", message: error.message };
|
|
2198
2226
|
}
|
|
2199
2227
|
if (error instanceof Error) {
|
|
2200
|
-
return { code:
|
|
2228
|
+
return { code: "UNKNOWN", message: error.message };
|
|
2201
2229
|
}
|
|
2202
|
-
return { code:
|
|
2230
|
+
return { code: "UNKNOWN", message: String(error) };
|
|
2203
2231
|
}
|
|
2204
2232
|
isPathWithinRoot(rootPath, candidatePath) {
|
|
2205
2233
|
const resolvedRoot = resolve(rootPath);
|
|
@@ -2210,47 +2238,47 @@ export class Session {
|
|
|
2210
2238
|
return resolvedCandidate.startsWith(resolvedRoot + sep);
|
|
2211
2239
|
}
|
|
2212
2240
|
async generateCommitMessage(cwd) {
|
|
2213
|
-
const diff = await getCheckoutDiff(cwd, { mode:
|
|
2241
|
+
const diff = await getCheckoutDiff(cwd, { mode: "uncommitted", includeStructured: true }, { paseoHome: this.paseoHome });
|
|
2214
2242
|
const schema = z.object({
|
|
2215
2243
|
message: z
|
|
2216
2244
|
.string()
|
|
2217
2245
|
.min(1)
|
|
2218
2246
|
.max(72)
|
|
2219
|
-
.describe(
|
|
2247
|
+
.describe("Concise git commit message, imperative mood, no trailing period."),
|
|
2220
2248
|
});
|
|
2221
2249
|
const fileList = diff.structured && diff.structured.length > 0
|
|
2222
2250
|
? [
|
|
2223
|
-
|
|
2251
|
+
"Files changed:",
|
|
2224
2252
|
...diff.structured.map((file) => {
|
|
2225
|
-
const changeType = file.isNew ?
|
|
2226
|
-
const status = file.status && file.status !==
|
|
2253
|
+
const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
|
|
2254
|
+
const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
|
|
2227
2255
|
return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
|
|
2228
2256
|
}),
|
|
2229
|
-
].join(
|
|
2230
|
-
:
|
|
2257
|
+
].join("\n")
|
|
2258
|
+
: "Files changed: (unknown)";
|
|
2231
2259
|
const maxPatchChars = 120000;
|
|
2232
2260
|
const patch = diff.diff.length > maxPatchChars
|
|
2233
2261
|
? `${diff.diff.slice(0, maxPatchChars)}\n\n... (diff truncated to ${maxPatchChars} chars)\n`
|
|
2234
2262
|
: diff.diff;
|
|
2235
2263
|
const prompt = [
|
|
2236
|
-
|
|
2264
|
+
"Write a concise git commit message for the changes below.",
|
|
2237
2265
|
"Return JSON only with a single field 'message'.",
|
|
2238
|
-
|
|
2266
|
+
"",
|
|
2239
2267
|
fileList,
|
|
2240
|
-
|
|
2241
|
-
patch.length > 0 ? patch :
|
|
2242
|
-
].join(
|
|
2268
|
+
"",
|
|
2269
|
+
patch.length > 0 ? patch : "(No diff available)",
|
|
2270
|
+
].join("\n");
|
|
2243
2271
|
try {
|
|
2244
2272
|
const result = await generateStructuredAgentResponseWithFallback({
|
|
2245
2273
|
manager: this.agentManager,
|
|
2246
2274
|
cwd,
|
|
2247
2275
|
prompt,
|
|
2248
2276
|
schema,
|
|
2249
|
-
schemaName:
|
|
2277
|
+
schemaName: "CommitMessage",
|
|
2250
2278
|
maxRetries: 2,
|
|
2251
2279
|
providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
|
|
2252
2280
|
agentConfigOverrides: {
|
|
2253
|
-
title:
|
|
2281
|
+
title: "Commit generator",
|
|
2254
2282
|
internal: true,
|
|
2255
2283
|
},
|
|
2256
2284
|
});
|
|
@@ -2259,14 +2287,14 @@ export class Session {
|
|
|
2259
2287
|
catch (error) {
|
|
2260
2288
|
if (error instanceof StructuredAgentResponseError ||
|
|
2261
2289
|
error instanceof StructuredAgentFallbackError) {
|
|
2262
|
-
return
|
|
2290
|
+
return "Update files";
|
|
2263
2291
|
}
|
|
2264
2292
|
throw error;
|
|
2265
2293
|
}
|
|
2266
2294
|
}
|
|
2267
2295
|
async generatePullRequestText(cwd, baseRef) {
|
|
2268
2296
|
const diff = await getCheckoutDiff(cwd, {
|
|
2269
|
-
mode:
|
|
2297
|
+
mode: "base",
|
|
2270
2298
|
baseRef,
|
|
2271
2299
|
includeStructured: true,
|
|
2272
2300
|
}, { paseoHome: this.paseoHome });
|
|
@@ -2276,37 +2304,37 @@ export class Session {
|
|
|
2276
2304
|
});
|
|
2277
2305
|
const fileList = diff.structured && diff.structured.length > 0
|
|
2278
2306
|
? [
|
|
2279
|
-
|
|
2307
|
+
"Files changed:",
|
|
2280
2308
|
...diff.structured.map((file) => {
|
|
2281
|
-
const changeType = file.isNew ?
|
|
2282
|
-
const status = file.status && file.status !==
|
|
2309
|
+
const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
|
|
2310
|
+
const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
|
|
2283
2311
|
return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
|
|
2284
2312
|
}),
|
|
2285
|
-
].join(
|
|
2286
|
-
:
|
|
2313
|
+
].join("\n")
|
|
2314
|
+
: "Files changed: (unknown)";
|
|
2287
2315
|
const maxPatchChars = 200000;
|
|
2288
2316
|
const patch = diff.diff.length > maxPatchChars
|
|
2289
2317
|
? `${diff.diff.slice(0, maxPatchChars)}\n\n... (diff truncated to ${maxPatchChars} chars)\n`
|
|
2290
2318
|
: diff.diff;
|
|
2291
2319
|
const prompt = [
|
|
2292
|
-
|
|
2320
|
+
"Write a pull request title and body for the changes below.",
|
|
2293
2321
|
"Return JSON only with fields 'title' and 'body'.",
|
|
2294
|
-
|
|
2322
|
+
"",
|
|
2295
2323
|
fileList,
|
|
2296
|
-
|
|
2297
|
-
patch.length > 0 ? patch :
|
|
2298
|
-
].join(
|
|
2324
|
+
"",
|
|
2325
|
+
patch.length > 0 ? patch : "(No diff available)",
|
|
2326
|
+
].join("\n");
|
|
2299
2327
|
try {
|
|
2300
2328
|
return await generateStructuredAgentResponseWithFallback({
|
|
2301
2329
|
manager: this.agentManager,
|
|
2302
2330
|
cwd,
|
|
2303
2331
|
prompt,
|
|
2304
2332
|
schema,
|
|
2305
|
-
schemaName:
|
|
2333
|
+
schemaName: "PullRequest",
|
|
2306
2334
|
maxRetries: 2,
|
|
2307
2335
|
providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
|
|
2308
2336
|
agentConfigOverrides: {
|
|
2309
|
-
title:
|
|
2337
|
+
title: "PR generator",
|
|
2310
2338
|
internal: true,
|
|
2311
2339
|
},
|
|
2312
2340
|
});
|
|
@@ -2315,8 +2343,8 @@ export class Session {
|
|
|
2315
2343
|
if (error instanceof StructuredAgentResponseError ||
|
|
2316
2344
|
error instanceof StructuredAgentFallbackError) {
|
|
2317
2345
|
return {
|
|
2318
|
-
title:
|
|
2319
|
-
body:
|
|
2346
|
+
title: "Update changes",
|
|
2347
|
+
body: "Automated PR generated by Paseo.",
|
|
2320
2348
|
};
|
|
2321
2349
|
}
|
|
2322
2350
|
throw error;
|
|
@@ -2325,12 +2353,12 @@ export class Session {
|
|
|
2325
2353
|
async ensureCleanWorkingTree(cwd) {
|
|
2326
2354
|
const dirty = await this.isWorkingTreeDirty(cwd);
|
|
2327
2355
|
if (dirty) {
|
|
2328
|
-
throw new Error(
|
|
2356
|
+
throw new Error("Working directory has uncommitted changes. Commit or stash before switching branches.");
|
|
2329
2357
|
}
|
|
2330
2358
|
}
|
|
2331
2359
|
async isWorkingTreeDirty(cwd) {
|
|
2332
2360
|
try {
|
|
2333
|
-
const { stdout } = await execAsync(
|
|
2361
|
+
const { stdout } = await execAsync("git status --porcelain", {
|
|
2334
2362
|
cwd,
|
|
2335
2363
|
env: READ_ONLY_GIT_ENV,
|
|
2336
2364
|
});
|
|
@@ -2341,14 +2369,14 @@ export class Session {
|
|
|
2341
2369
|
}
|
|
2342
2370
|
}
|
|
2343
2371
|
async checkoutExistingBranch(cwd, branch) {
|
|
2344
|
-
this.assertSafeGitRef(branch,
|
|
2372
|
+
this.assertSafeGitRef(branch, "branch");
|
|
2345
2373
|
try {
|
|
2346
2374
|
await execAsync(`git rev-parse --verify ${branch}`, { cwd });
|
|
2347
2375
|
}
|
|
2348
2376
|
catch (error) {
|
|
2349
2377
|
throw new Error(`Branch not found: ${branch}`);
|
|
2350
2378
|
}
|
|
2351
|
-
const { stdout } = await execAsync(
|
|
2379
|
+
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
|
|
2352
2380
|
cwd,
|
|
2353
2381
|
});
|
|
2354
2382
|
const current = stdout.trim();
|
|
@@ -2360,7 +2388,7 @@ export class Session {
|
|
|
2360
2388
|
}
|
|
2361
2389
|
async createBranchFromBase(params) {
|
|
2362
2390
|
const { cwd, baseBranch, newBranchName } = params;
|
|
2363
|
-
this.assertSafeGitRef(baseBranch,
|
|
2391
|
+
this.assertSafeGitRef(baseBranch, "base branch");
|
|
2364
2392
|
try {
|
|
2365
2393
|
await execAsync(`git rev-parse --verify ${baseBranch}`, { cwd });
|
|
2366
2394
|
}
|
|
@@ -2391,97 +2419,97 @@ export class Session {
|
|
|
2391
2419
|
* Handle set agent mode request
|
|
2392
2420
|
*/
|
|
2393
2421
|
async handleSetAgentModeRequest(agentId, modeId, requestId) {
|
|
2394
|
-
this.sessionLogger.info({ agentId, modeId, requestId },
|
|
2422
|
+
this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request");
|
|
2395
2423
|
try {
|
|
2396
2424
|
await this.agentManager.setAgentMode(agentId, modeId);
|
|
2397
|
-
this.sessionLogger.info({ agentId, modeId, requestId },
|
|
2425
|
+
this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request success");
|
|
2398
2426
|
this.emit({
|
|
2399
|
-
type:
|
|
2427
|
+
type: "set_agent_mode_response",
|
|
2400
2428
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
2401
2429
|
});
|
|
2402
2430
|
}
|
|
2403
2431
|
catch (error) {
|
|
2404
|
-
this.sessionLogger.error({ err: error, agentId, modeId, requestId },
|
|
2432
|
+
this.sessionLogger.error({ err: error, agentId, modeId, requestId }, "session: set_agent_mode_request error");
|
|
2405
2433
|
this.emit({
|
|
2406
|
-
type:
|
|
2434
|
+
type: "activity_log",
|
|
2407
2435
|
payload: {
|
|
2408
2436
|
id: uuidv4(),
|
|
2409
2437
|
timestamp: new Date(),
|
|
2410
|
-
type:
|
|
2438
|
+
type: "error",
|
|
2411
2439
|
content: `Failed to set agent mode: ${error.message}`,
|
|
2412
2440
|
},
|
|
2413
2441
|
});
|
|
2414
2442
|
this.emit({
|
|
2415
|
-
type:
|
|
2443
|
+
type: "set_agent_mode_response",
|
|
2416
2444
|
payload: {
|
|
2417
2445
|
requestId,
|
|
2418
2446
|
agentId,
|
|
2419
2447
|
accepted: false,
|
|
2420
|
-
error: error?.message ? String(error.message) :
|
|
2448
|
+
error: error?.message ? String(error.message) : "Failed to set agent mode",
|
|
2421
2449
|
},
|
|
2422
2450
|
});
|
|
2423
2451
|
}
|
|
2424
2452
|
}
|
|
2425
2453
|
async handleSetAgentModelRequest(agentId, modelId, requestId) {
|
|
2426
|
-
this.sessionLogger.info({ agentId, modelId, requestId },
|
|
2454
|
+
this.sessionLogger.info({ agentId, modelId, requestId }, "session: set_agent_model_request");
|
|
2427
2455
|
try {
|
|
2428
2456
|
await this.agentManager.setAgentModel(agentId, modelId);
|
|
2429
|
-
this.sessionLogger.info({ agentId, modelId, requestId },
|
|
2457
|
+
this.sessionLogger.info({ agentId, modelId, requestId }, "session: set_agent_model_request success");
|
|
2430
2458
|
this.emit({
|
|
2431
|
-
type:
|
|
2459
|
+
type: "set_agent_model_response",
|
|
2432
2460
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
2433
2461
|
});
|
|
2434
2462
|
}
|
|
2435
2463
|
catch (error) {
|
|
2436
|
-
this.sessionLogger.error({ err: error, agentId, modelId, requestId },
|
|
2464
|
+
this.sessionLogger.error({ err: error, agentId, modelId, requestId }, "session: set_agent_model_request error");
|
|
2437
2465
|
this.emit({
|
|
2438
|
-
type:
|
|
2466
|
+
type: "activity_log",
|
|
2439
2467
|
payload: {
|
|
2440
2468
|
id: uuidv4(),
|
|
2441
2469
|
timestamp: new Date(),
|
|
2442
|
-
type:
|
|
2470
|
+
type: "error",
|
|
2443
2471
|
content: `Failed to set agent model: ${error.message}`,
|
|
2444
2472
|
},
|
|
2445
2473
|
});
|
|
2446
2474
|
this.emit({
|
|
2447
|
-
type:
|
|
2475
|
+
type: "set_agent_model_response",
|
|
2448
2476
|
payload: {
|
|
2449
2477
|
requestId,
|
|
2450
2478
|
agentId,
|
|
2451
2479
|
accepted: false,
|
|
2452
|
-
error: error?.message ? String(error.message) :
|
|
2480
|
+
error: error?.message ? String(error.message) : "Failed to set agent model",
|
|
2453
2481
|
},
|
|
2454
2482
|
});
|
|
2455
2483
|
}
|
|
2456
2484
|
}
|
|
2457
2485
|
async handleSetAgentThinkingRequest(agentId, thinkingOptionId, requestId) {
|
|
2458
|
-
this.sessionLogger.info({ agentId, thinkingOptionId, requestId },
|
|
2486
|
+
this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request");
|
|
2459
2487
|
try {
|
|
2460
2488
|
await this.agentManager.setAgentThinkingOption(agentId, thinkingOptionId);
|
|
2461
|
-
this.sessionLogger.info({ agentId, thinkingOptionId, requestId },
|
|
2489
|
+
this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request success");
|
|
2462
2490
|
this.emit({
|
|
2463
|
-
type:
|
|
2491
|
+
type: "set_agent_thinking_response",
|
|
2464
2492
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
2465
2493
|
});
|
|
2466
2494
|
}
|
|
2467
2495
|
catch (error) {
|
|
2468
|
-
this.sessionLogger.error({ err: error, agentId, thinkingOptionId, requestId },
|
|
2496
|
+
this.sessionLogger.error({ err: error, agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request error");
|
|
2469
2497
|
this.emit({
|
|
2470
|
-
type:
|
|
2498
|
+
type: "activity_log",
|
|
2471
2499
|
payload: {
|
|
2472
2500
|
id: uuidv4(),
|
|
2473
2501
|
timestamp: new Date(),
|
|
2474
|
-
type:
|
|
2502
|
+
type: "error",
|
|
2475
2503
|
content: `Failed to set agent thinking option: ${error.message}`,
|
|
2476
2504
|
},
|
|
2477
2505
|
});
|
|
2478
2506
|
this.emit({
|
|
2479
|
-
type:
|
|
2507
|
+
type: "set_agent_thinking_response",
|
|
2480
2508
|
payload: {
|
|
2481
2509
|
requestId,
|
|
2482
2510
|
agentId,
|
|
2483
2511
|
accepted: false,
|
|
2484
|
-
error: error?.message ? String(error.message) :
|
|
2512
|
+
error: error?.message ? String(error.message) : "Failed to set agent thinking option",
|
|
2485
2513
|
},
|
|
2486
2514
|
});
|
|
2487
2515
|
}
|
|
@@ -2495,7 +2523,7 @@ export class Session {
|
|
|
2495
2523
|
await Promise.all(agentIds.map((id) => this.agentManager.clearAgentAttention(id)));
|
|
2496
2524
|
}
|
|
2497
2525
|
catch (error) {
|
|
2498
|
-
this.sessionLogger.error({ err: error, agentIds },
|
|
2526
|
+
this.sessionLogger.error({ err: error, agentIds }, "Failed to clear agent attention");
|
|
2499
2527
|
// Don't throw - this is not critical
|
|
2500
2528
|
}
|
|
2501
2529
|
}
|
|
@@ -2519,7 +2547,7 @@ export class Session {
|
|
|
2519
2547
|
*/
|
|
2520
2548
|
handleRegisterPushToken(token) {
|
|
2521
2549
|
this.pushTokenStore.addToken(token);
|
|
2522
|
-
this.sessionLogger.info(
|
|
2550
|
+
this.sessionLogger.info("Registered push token");
|
|
2523
2551
|
}
|
|
2524
2552
|
/**
|
|
2525
2553
|
* Handle list commands request for an agent
|
|
@@ -2533,7 +2561,7 @@ export class Session {
|
|
|
2533
2561
|
if (agent?.session?.listCommands) {
|
|
2534
2562
|
const commands = await agent.session.listCommands();
|
|
2535
2563
|
this.emit({
|
|
2536
|
-
type:
|
|
2564
|
+
type: "list_commands_response",
|
|
2537
2565
|
payload: {
|
|
2538
2566
|
agentId,
|
|
2539
2567
|
commands,
|
|
@@ -2555,7 +2583,7 @@ export class Session {
|
|
|
2555
2583
|
};
|
|
2556
2584
|
const commands = await this.agentManager.listDraftCommands(sessionConfig);
|
|
2557
2585
|
this.emit({
|
|
2558
|
-
type:
|
|
2586
|
+
type: "list_commands_response",
|
|
2559
2587
|
payload: {
|
|
2560
2588
|
agentId,
|
|
2561
2589
|
commands,
|
|
@@ -2566,7 +2594,7 @@ export class Session {
|
|
|
2566
2594
|
return;
|
|
2567
2595
|
}
|
|
2568
2596
|
this.emit({
|
|
2569
|
-
type:
|
|
2597
|
+
type: "list_commands_response",
|
|
2570
2598
|
payload: {
|
|
2571
2599
|
agentId,
|
|
2572
2600
|
commands: [],
|
|
@@ -2576,9 +2604,9 @@ export class Session {
|
|
|
2576
2604
|
});
|
|
2577
2605
|
}
|
|
2578
2606
|
catch (error) {
|
|
2579
|
-
this.sessionLogger.error({ err: error, agentId, draftConfig },
|
|
2607
|
+
this.sessionLogger.error({ err: error, agentId, draftConfig }, "Failed to list commands");
|
|
2580
2608
|
this.emit({
|
|
2581
|
-
type:
|
|
2609
|
+
type: "list_commands_response",
|
|
2582
2610
|
payload: {
|
|
2583
2611
|
agentId,
|
|
2584
2612
|
commands: [],
|
|
@@ -2598,13 +2626,13 @@ export class Session {
|
|
|
2598
2626
|
this.sessionLogger.debug({ agentId }, `Permission response forwarded to agent ${agentId}`);
|
|
2599
2627
|
}
|
|
2600
2628
|
catch (error) {
|
|
2601
|
-
this.sessionLogger.error({ err: error, agentId, requestId },
|
|
2629
|
+
this.sessionLogger.error({ err: error, agentId, requestId }, "Failed to respond to permission");
|
|
2602
2630
|
this.emit({
|
|
2603
|
-
type:
|
|
2631
|
+
type: "activity_log",
|
|
2604
2632
|
payload: {
|
|
2605
2633
|
id: uuidv4(),
|
|
2606
2634
|
timestamp: new Date(),
|
|
2607
|
-
type:
|
|
2635
|
+
type: "error",
|
|
2608
2636
|
content: `Failed to respond to permission: ${error.message}`,
|
|
2609
2637
|
},
|
|
2610
2638
|
});
|
|
@@ -2618,7 +2646,7 @@ export class Session {
|
|
|
2618
2646
|
const status = await getCheckoutStatus(resolvedCwd, { paseoHome: this.paseoHome });
|
|
2619
2647
|
if (!status.isGit) {
|
|
2620
2648
|
this.emit({
|
|
2621
|
-
type:
|
|
2649
|
+
type: "checkout_status_response",
|
|
2622
2650
|
payload: {
|
|
2623
2651
|
cwd,
|
|
2624
2652
|
isGit: false,
|
|
@@ -2640,7 +2668,7 @@ export class Session {
|
|
|
2640
2668
|
}
|
|
2641
2669
|
if (status.isPaseoOwnedWorktree) {
|
|
2642
2670
|
this.emit({
|
|
2643
|
-
type:
|
|
2671
|
+
type: "checkout_status_response",
|
|
2644
2672
|
payload: {
|
|
2645
2673
|
cwd,
|
|
2646
2674
|
isGit: true,
|
|
@@ -2662,7 +2690,7 @@ export class Session {
|
|
|
2662
2690
|
return;
|
|
2663
2691
|
}
|
|
2664
2692
|
this.emit({
|
|
2665
|
-
type:
|
|
2693
|
+
type: "checkout_status_response",
|
|
2666
2694
|
payload: {
|
|
2667
2695
|
cwd,
|
|
2668
2696
|
isGit: true,
|
|
@@ -2683,7 +2711,7 @@ export class Session {
|
|
|
2683
2711
|
}
|
|
2684
2712
|
catch (error) {
|
|
2685
2713
|
this.emit({
|
|
2686
|
-
type:
|
|
2714
|
+
type: "checkout_status_response",
|
|
2687
2715
|
payload: {
|
|
2688
2716
|
cwd,
|
|
2689
2717
|
isGit: false,
|
|
@@ -2714,7 +2742,7 @@ export class Session {
|
|
|
2714
2742
|
env: READ_ONLY_GIT_ENV,
|
|
2715
2743
|
});
|
|
2716
2744
|
this.emit({
|
|
2717
|
-
type:
|
|
2745
|
+
type: "validate_branch_response",
|
|
2718
2746
|
payload: {
|
|
2719
2747
|
exists: true,
|
|
2720
2748
|
resolvedRef: branchName,
|
|
@@ -2735,7 +2763,7 @@ export class Session {
|
|
|
2735
2763
|
env: READ_ONLY_GIT_ENV,
|
|
2736
2764
|
});
|
|
2737
2765
|
this.emit({
|
|
2738
|
-
type:
|
|
2766
|
+
type: "validate_branch_response",
|
|
2739
2767
|
payload: {
|
|
2740
2768
|
exists: true,
|
|
2741
2769
|
resolvedRef: `origin/${branchName}`,
|
|
@@ -2751,7 +2779,7 @@ export class Session {
|
|
|
2751
2779
|
}
|
|
2752
2780
|
// Branch not found anywhere
|
|
2753
2781
|
this.emit({
|
|
2754
|
-
type:
|
|
2782
|
+
type: "validate_branch_response",
|
|
2755
2783
|
payload: {
|
|
2756
2784
|
exists: false,
|
|
2757
2785
|
resolvedRef: null,
|
|
@@ -2763,7 +2791,7 @@ export class Session {
|
|
|
2763
2791
|
}
|
|
2764
2792
|
catch (error) {
|
|
2765
2793
|
this.emit({
|
|
2766
|
-
type:
|
|
2794
|
+
type: "validate_branch_response",
|
|
2767
2795
|
payload: {
|
|
2768
2796
|
exists: false,
|
|
2769
2797
|
resolvedRef: null,
|
|
@@ -2780,7 +2808,7 @@ export class Session {
|
|
|
2780
2808
|
const resolvedCwd = expandTilde(cwd);
|
|
2781
2809
|
const branches = await listBranchSuggestions(resolvedCwd, { query, limit });
|
|
2782
2810
|
this.emit({
|
|
2783
|
-
type:
|
|
2811
|
+
type: "branch_suggestions_response",
|
|
2784
2812
|
payload: {
|
|
2785
2813
|
branches,
|
|
2786
2814
|
error: null,
|
|
@@ -2790,7 +2818,7 @@ export class Session {
|
|
|
2790
2818
|
}
|
|
2791
2819
|
catch (error) {
|
|
2792
2820
|
this.emit({
|
|
2793
|
-
type:
|
|
2821
|
+
type: "branch_suggestions_response",
|
|
2794
2822
|
payload: {
|
|
2795
2823
|
branches: [],
|
|
2796
2824
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -2815,12 +2843,12 @@ export class Session {
|
|
|
2815
2843
|
homeDir: process.env.HOME ?? homedir(),
|
|
2816
2844
|
query,
|
|
2817
2845
|
limit,
|
|
2818
|
-
})).map((path) => ({ path, kind:
|
|
2846
|
+
})).map((path) => ({ path, kind: "directory" }));
|
|
2819
2847
|
const directories = entries
|
|
2820
|
-
.filter((entry) => entry.kind ===
|
|
2848
|
+
.filter((entry) => entry.kind === "directory")
|
|
2821
2849
|
.map((entry) => entry.path);
|
|
2822
2850
|
this.emit({
|
|
2823
|
-
type:
|
|
2851
|
+
type: "directory_suggestions_response",
|
|
2824
2852
|
payload: {
|
|
2825
2853
|
directories,
|
|
2826
2854
|
entries,
|
|
@@ -2831,7 +2859,7 @@ export class Session {
|
|
|
2831
2859
|
}
|
|
2832
2860
|
catch (error) {
|
|
2833
2861
|
this.emit({
|
|
2834
|
-
type:
|
|
2862
|
+
type: "directory_suggestions_response",
|
|
2835
2863
|
payload: {
|
|
2836
2864
|
directories: [],
|
|
2837
2865
|
entries: [],
|
|
@@ -2842,17 +2870,17 @@ export class Session {
|
|
|
2842
2870
|
}
|
|
2843
2871
|
}
|
|
2844
2872
|
normalizeCheckoutDiffCompare(compare) {
|
|
2845
|
-
if (compare.mode ===
|
|
2846
|
-
return { mode:
|
|
2873
|
+
if (compare.mode === "uncommitted") {
|
|
2874
|
+
return { mode: "uncommitted" };
|
|
2847
2875
|
}
|
|
2848
2876
|
const trimmedBaseRef = compare.baseRef?.trim();
|
|
2849
|
-
return trimmedBaseRef ? { mode:
|
|
2877
|
+
return trimmedBaseRef ? { mode: "base", baseRef: trimmedBaseRef } : { mode: "base" };
|
|
2850
2878
|
}
|
|
2851
2879
|
buildCheckoutDiffTargetKey(cwd, compare) {
|
|
2852
2880
|
return JSON.stringify([
|
|
2853
2881
|
cwd,
|
|
2854
2882
|
compare.mode,
|
|
2855
|
-
compare.mode ===
|
|
2883
|
+
compare.mode === "base" ? (compare.baseRef ?? "") : "",
|
|
2856
2884
|
]);
|
|
2857
2885
|
}
|
|
2858
2886
|
closeCheckoutDiffWatchTarget(target) {
|
|
@@ -2887,7 +2915,7 @@ export class Session {
|
|
|
2887
2915
|
}
|
|
2888
2916
|
async resolveCheckoutGitDir(cwd) {
|
|
2889
2917
|
try {
|
|
2890
|
-
const { stdout } = await execAsync(
|
|
2918
|
+
const { stdout } = await execAsync("git rev-parse --absolute-git-dir", {
|
|
2891
2919
|
cwd,
|
|
2892
2920
|
env: READ_ONLY_GIT_ENV,
|
|
2893
2921
|
});
|
|
@@ -2898,9 +2926,150 @@ export class Session {
|
|
|
2898
2926
|
return null;
|
|
2899
2927
|
}
|
|
2900
2928
|
}
|
|
2929
|
+
async resolveWorkspaceGitRefsRoot(gitDir) {
|
|
2930
|
+
try {
|
|
2931
|
+
const commonDir = (await readFile(join(gitDir, "commondir"), "utf8")).trim();
|
|
2932
|
+
if (commonDir.length > 0) {
|
|
2933
|
+
return resolve(gitDir, commonDir);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
catch {
|
|
2937
|
+
// Regular repos do not have a commondir file.
|
|
2938
|
+
}
|
|
2939
|
+
return gitDir;
|
|
2940
|
+
}
|
|
2941
|
+
closeWorkspaceGitWatchTarget(target) {
|
|
2942
|
+
if (target.debounceTimer) {
|
|
2943
|
+
clearTimeout(target.debounceTimer);
|
|
2944
|
+
target.debounceTimer = null;
|
|
2945
|
+
}
|
|
2946
|
+
for (const watcher of target.watchers) {
|
|
2947
|
+
watcher.close();
|
|
2948
|
+
}
|
|
2949
|
+
target.watchers = [];
|
|
2950
|
+
}
|
|
2951
|
+
removeWorkspaceGitWatchTarget(cwd) {
|
|
2952
|
+
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
2953
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
2954
|
+
if (!target) {
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
this.closeWorkspaceGitWatchTarget(target);
|
|
2958
|
+
this.workspaceGitWatchTargets.delete(workspaceId);
|
|
2959
|
+
}
|
|
2960
|
+
workspaceGitDescriptorFingerprint(workspace) {
|
|
2961
|
+
if (!workspace) {
|
|
2962
|
+
return WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT;
|
|
2963
|
+
}
|
|
2964
|
+
return JSON.stringify([
|
|
2965
|
+
workspace.name,
|
|
2966
|
+
workspace.diffStat ? [workspace.diffStat.additions, workspace.diffStat.deletions] : null,
|
|
2967
|
+
]);
|
|
2968
|
+
}
|
|
2969
|
+
shouldSkipWorkspaceGitWatchUpdate(workspaceId, workspace) {
|
|
2970
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
2971
|
+
if (!target) {
|
|
2972
|
+
return false;
|
|
2973
|
+
}
|
|
2974
|
+
const nextFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
|
|
2975
|
+
if (target.latestFingerprint === nextFingerprint) {
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
target.latestFingerprint = nextFingerprint;
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
rememberWorkspaceGitWatchFingerprint(workspaceId, workspace) {
|
|
2982
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
2983
|
+
if (!target) {
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
target.latestFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
|
|
2987
|
+
}
|
|
2988
|
+
primeWorkspaceGitWatchFingerprints(workspaces) {
|
|
2989
|
+
for (const workspace of workspaces) {
|
|
2990
|
+
this.rememberWorkspaceGitWatchFingerprint(workspace.id, workspace);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
scheduleWorkspaceGitWatchRefresh(target) {
|
|
2994
|
+
if (target.debounceTimer) {
|
|
2995
|
+
clearTimeout(target.debounceTimer);
|
|
2996
|
+
}
|
|
2997
|
+
target.debounceTimer = setTimeout(() => {
|
|
2998
|
+
target.debounceTimer = null;
|
|
2999
|
+
void this.refreshWorkspaceGitWatchTarget(target);
|
|
3000
|
+
}, WORKSPACE_GIT_WATCH_DEBOUNCE_MS);
|
|
3001
|
+
}
|
|
3002
|
+
async refreshWorkspaceGitWatchTarget(target) {
|
|
3003
|
+
if (target.refreshPromise) {
|
|
3004
|
+
target.refreshQueued = true;
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
target.refreshPromise = (async () => {
|
|
3008
|
+
do {
|
|
3009
|
+
target.refreshQueued = false;
|
|
3010
|
+
await this.emitWorkspaceUpdateForCwd(target.cwd, {
|
|
3011
|
+
dedupeGitState: true,
|
|
3012
|
+
});
|
|
3013
|
+
} while (target.refreshQueued);
|
|
3014
|
+
})();
|
|
3015
|
+
try {
|
|
3016
|
+
await target.refreshPromise;
|
|
3017
|
+
}
|
|
3018
|
+
finally {
|
|
3019
|
+
target.refreshPromise = null;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
async ensureWorkspaceGitWatchTarget(cwd) {
|
|
3023
|
+
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
3024
|
+
if (this.workspaceGitWatchTargets.has(workspaceId)) {
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const gitDir = await this.resolveCheckoutGitDir(cwd);
|
|
3028
|
+
if (!gitDir) {
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
const refsRoot = await this.resolveWorkspaceGitRefsRoot(gitDir);
|
|
3032
|
+
const target = {
|
|
3033
|
+
cwd: workspaceId,
|
|
3034
|
+
watchers: [],
|
|
3035
|
+
debounceTimer: null,
|
|
3036
|
+
refreshPromise: null,
|
|
3037
|
+
refreshQueued: false,
|
|
3038
|
+
latestFingerprint: null,
|
|
3039
|
+
};
|
|
3040
|
+
for (const watchPath of new Set([join(gitDir, "HEAD"), join(refsRoot, "refs", "heads")])) {
|
|
3041
|
+
let watcher = null;
|
|
3042
|
+
try {
|
|
3043
|
+
watcher = watch(watchPath, { recursive: false }, () => {
|
|
3044
|
+
this.scheduleWorkspaceGitWatchRefresh(target);
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
catch (error) {
|
|
3048
|
+
this.sessionLogger.warn({ err: error, cwd, watchPath }, "Failed to start workspace git watcher");
|
|
3049
|
+
}
|
|
3050
|
+
if (!watcher) {
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
watcher.on("error", (error) => {
|
|
3054
|
+
this.sessionLogger.warn({ err: error, cwd, watchPath }, "Workspace git watcher error");
|
|
3055
|
+
});
|
|
3056
|
+
target.watchers.push(watcher);
|
|
3057
|
+
}
|
|
3058
|
+
if (target.watchers.length === 0) {
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
this.workspaceGitWatchTargets.set(workspaceId, target);
|
|
3062
|
+
}
|
|
3063
|
+
async syncWorkspaceGitWatchTarget(cwd, options) {
|
|
3064
|
+
if (!options.isGit) {
|
|
3065
|
+
this.removeWorkspaceGitWatchTarget(cwd);
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
await this.ensureWorkspaceGitWatchTarget(cwd);
|
|
3069
|
+
}
|
|
2901
3070
|
async resolveCheckoutWatchRoot(cwd) {
|
|
2902
3071
|
try {
|
|
2903
|
-
const { stdout } = await execAsync(
|
|
3072
|
+
const { stdout } = await execAsync("git rev-parse --path-format=absolute --show-toplevel", {
|
|
2904
3073
|
cwd,
|
|
2905
3074
|
env: READ_ONLY_GIT_ENV,
|
|
2906
3075
|
});
|
|
@@ -2926,7 +3095,7 @@ export class Session {
|
|
|
2926
3095
|
}
|
|
2927
3096
|
for (const subscriptionId of target.subscriptions) {
|
|
2928
3097
|
this.emit({
|
|
2929
|
-
type:
|
|
3098
|
+
type: "checkout_diff_update",
|
|
2930
3099
|
payload: {
|
|
2931
3100
|
subscriptionId,
|
|
2932
3101
|
...snapshot,
|
|
@@ -3019,7 +3188,7 @@ export class Session {
|
|
|
3019
3188
|
watchPaths.add(gitDir);
|
|
3020
3189
|
}
|
|
3021
3190
|
let hasRecursiveRepoCoverage = false;
|
|
3022
|
-
const allowRecursiveRepoWatch = process.platform !==
|
|
3191
|
+
const allowRecursiveRepoWatch = process.platform !== "linux";
|
|
3023
3192
|
for (const watchPath of watchPaths) {
|
|
3024
3193
|
const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
|
|
3025
3194
|
const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
|
|
@@ -3040,21 +3209,21 @@ export class Session {
|
|
|
3040
3209
|
if (shouldTryRecursive) {
|
|
3041
3210
|
try {
|
|
3042
3211
|
watcher = createWatcher(false);
|
|
3043
|
-
this.sessionLogger.warn({ err: error, watchPath, cwd, compare },
|
|
3212
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff recursive watch unavailable; using non-recursive fallback");
|
|
3044
3213
|
}
|
|
3045
3214
|
catch (fallbackError) {
|
|
3046
|
-
this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare },
|
|
3215
|
+
this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
|
|
3047
3216
|
}
|
|
3048
3217
|
}
|
|
3049
3218
|
else {
|
|
3050
|
-
this.sessionLogger.warn({ err: error, watchPath, cwd, compare },
|
|
3219
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
|
|
3051
3220
|
}
|
|
3052
3221
|
}
|
|
3053
3222
|
if (!watcher) {
|
|
3054
3223
|
continue;
|
|
3055
3224
|
}
|
|
3056
|
-
watcher.on(
|
|
3057
|
-
this.sessionLogger.warn({ err: error, watchPath, cwd, compare },
|
|
3225
|
+
watcher.on("error", (error) => {
|
|
3226
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff watcher error");
|
|
3058
3227
|
});
|
|
3059
3228
|
target.watchers.push(watcher);
|
|
3060
3229
|
if (watchPath === repoWatchPath && watcherIsRecursive) {
|
|
@@ -3070,8 +3239,8 @@ export class Session {
|
|
|
3070
3239
|
cwd,
|
|
3071
3240
|
compare,
|
|
3072
3241
|
intervalMs: CHECKOUT_DIFF_FALLBACK_REFRESH_MS,
|
|
3073
|
-
reason: target.watchers.length === 0 ?
|
|
3074
|
-
},
|
|
3242
|
+
reason: target.watchers.length === 0 ? "no_watchers" : "missing_recursive_repo_root_coverage",
|
|
3243
|
+
}, "Checkout diff watchers unavailable; using timed refresh fallback");
|
|
3075
3244
|
}
|
|
3076
3245
|
this.checkoutDiffTargets.set(targetKey, target);
|
|
3077
3246
|
return target;
|
|
@@ -3092,7 +3261,7 @@ export class Session {
|
|
|
3092
3261
|
target.latestPayload = snapshot;
|
|
3093
3262
|
target.latestFingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
|
|
3094
3263
|
this.emit({
|
|
3095
|
-
type:
|
|
3264
|
+
type: "subscribe_checkout_diff_response",
|
|
3096
3265
|
payload: {
|
|
3097
3266
|
subscriptionId: msg.subscriptionId,
|
|
3098
3267
|
...snapshot,
|
|
@@ -3115,12 +3284,12 @@ export class Session {
|
|
|
3115
3284
|
async handleCheckoutCommitRequest(msg) {
|
|
3116
3285
|
const { cwd, requestId } = msg;
|
|
3117
3286
|
try {
|
|
3118
|
-
let message = msg.message?.trim() ??
|
|
3287
|
+
let message = msg.message?.trim() ?? "";
|
|
3119
3288
|
if (!message) {
|
|
3120
3289
|
message = await this.generateCommitMessage(cwd);
|
|
3121
3290
|
}
|
|
3122
3291
|
if (!message) {
|
|
3123
|
-
throw new Error(
|
|
3292
|
+
throw new Error("Commit message is required");
|
|
3124
3293
|
}
|
|
3125
3294
|
await commitChanges(cwd, {
|
|
3126
3295
|
message,
|
|
@@ -3128,7 +3297,7 @@ export class Session {
|
|
|
3128
3297
|
});
|
|
3129
3298
|
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3130
3299
|
this.emit({
|
|
3131
|
-
type:
|
|
3300
|
+
type: "checkout_commit_response",
|
|
3132
3301
|
payload: {
|
|
3133
3302
|
cwd,
|
|
3134
3303
|
success: true,
|
|
@@ -3139,7 +3308,7 @@ export class Session {
|
|
|
3139
3308
|
}
|
|
3140
3309
|
catch (error) {
|
|
3141
3310
|
this.emit({
|
|
3142
|
-
type:
|
|
3311
|
+
type: "checkout_commit_response",
|
|
3143
3312
|
payload: {
|
|
3144
3313
|
cwd,
|
|
3145
3314
|
success: false,
|
|
@@ -3155,13 +3324,13 @@ export class Session {
|
|
|
3155
3324
|
const status = await getCheckoutStatus(cwd, { paseoHome: this.paseoHome });
|
|
3156
3325
|
if (!status.isGit) {
|
|
3157
3326
|
try {
|
|
3158
|
-
await execAsync(
|
|
3327
|
+
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
3159
3328
|
cwd,
|
|
3160
3329
|
env: READ_ONLY_GIT_ENV,
|
|
3161
3330
|
});
|
|
3162
3331
|
}
|
|
3163
3332
|
catch (error) {
|
|
3164
|
-
const details = typeof error?.stderr ===
|
|
3333
|
+
const details = typeof error?.stderr === "string"
|
|
3165
3334
|
? String(error.stderr).trim()
|
|
3166
3335
|
: error instanceof Error
|
|
3167
3336
|
? error.message
|
|
@@ -3170,28 +3339,28 @@ export class Session {
|
|
|
3170
3339
|
}
|
|
3171
3340
|
}
|
|
3172
3341
|
if (msg.requireCleanTarget) {
|
|
3173
|
-
const { stdout } = await execAsync(
|
|
3342
|
+
const { stdout } = await execAsync("git status --porcelain", {
|
|
3174
3343
|
cwd,
|
|
3175
3344
|
env: READ_ONLY_GIT_ENV,
|
|
3176
3345
|
});
|
|
3177
3346
|
if (stdout.trim().length > 0) {
|
|
3178
|
-
throw new Error(
|
|
3347
|
+
throw new Error("Working directory has uncommitted changes.");
|
|
3179
3348
|
}
|
|
3180
3349
|
}
|
|
3181
3350
|
let baseRef = msg.baseRef ?? (status.isGit ? status.baseRef : null);
|
|
3182
3351
|
if (!baseRef) {
|
|
3183
|
-
throw new Error(
|
|
3352
|
+
throw new Error("Base branch is required for merge");
|
|
3184
3353
|
}
|
|
3185
|
-
if (baseRef.startsWith(
|
|
3186
|
-
baseRef = baseRef.slice(
|
|
3354
|
+
if (baseRef.startsWith("origin/")) {
|
|
3355
|
+
baseRef = baseRef.slice("origin/".length);
|
|
3187
3356
|
}
|
|
3188
3357
|
await mergeToBase(cwd, {
|
|
3189
3358
|
baseRef,
|
|
3190
|
-
mode: msg.strategy ===
|
|
3359
|
+
mode: msg.strategy === "squash" ? "squash" : "merge",
|
|
3191
3360
|
}, { paseoHome: this.paseoHome });
|
|
3192
3361
|
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3193
3362
|
this.emit({
|
|
3194
|
-
type:
|
|
3363
|
+
type: "checkout_merge_response",
|
|
3195
3364
|
payload: {
|
|
3196
3365
|
cwd,
|
|
3197
3366
|
success: true,
|
|
@@ -3202,7 +3371,7 @@ export class Session {
|
|
|
3202
3371
|
}
|
|
3203
3372
|
catch (error) {
|
|
3204
3373
|
this.emit({
|
|
3205
|
-
type:
|
|
3374
|
+
type: "checkout_merge_response",
|
|
3206
3375
|
payload: {
|
|
3207
3376
|
cwd,
|
|
3208
3377
|
success: false,
|
|
@@ -3216,12 +3385,12 @@ export class Session {
|
|
|
3216
3385
|
const { cwd, requestId } = msg;
|
|
3217
3386
|
try {
|
|
3218
3387
|
if (msg.requireCleanTarget ?? true) {
|
|
3219
|
-
const { stdout } = await execAsync(
|
|
3388
|
+
const { stdout } = await execAsync("git status --porcelain", {
|
|
3220
3389
|
cwd,
|
|
3221
3390
|
env: READ_ONLY_GIT_ENV,
|
|
3222
3391
|
});
|
|
3223
3392
|
if (stdout.trim().length > 0) {
|
|
3224
|
-
throw new Error(
|
|
3393
|
+
throw new Error("Working directory has uncommitted changes.");
|
|
3225
3394
|
}
|
|
3226
3395
|
}
|
|
3227
3396
|
await mergeFromBase(cwd, {
|
|
@@ -3230,7 +3399,7 @@ export class Session {
|
|
|
3230
3399
|
});
|
|
3231
3400
|
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3232
3401
|
this.emit({
|
|
3233
|
-
type:
|
|
3402
|
+
type: "checkout_merge_from_base_response",
|
|
3234
3403
|
payload: {
|
|
3235
3404
|
cwd,
|
|
3236
3405
|
success: true,
|
|
@@ -3241,7 +3410,7 @@ export class Session {
|
|
|
3241
3410
|
}
|
|
3242
3411
|
catch (error) {
|
|
3243
3412
|
this.emit({
|
|
3244
|
-
type:
|
|
3413
|
+
type: "checkout_merge_from_base_response",
|
|
3245
3414
|
payload: {
|
|
3246
3415
|
cwd,
|
|
3247
3416
|
success: false,
|
|
@@ -3256,7 +3425,7 @@ export class Session {
|
|
|
3256
3425
|
try {
|
|
3257
3426
|
await pushCurrentBranch(cwd);
|
|
3258
3427
|
this.emit({
|
|
3259
|
-
type:
|
|
3428
|
+
type: "checkout_push_response",
|
|
3260
3429
|
payload: {
|
|
3261
3430
|
cwd,
|
|
3262
3431
|
success: true,
|
|
@@ -3267,7 +3436,7 @@ export class Session {
|
|
|
3267
3436
|
}
|
|
3268
3437
|
catch (error) {
|
|
3269
3438
|
this.emit({
|
|
3270
|
-
type:
|
|
3439
|
+
type: "checkout_push_response",
|
|
3271
3440
|
payload: {
|
|
3272
3441
|
cwd,
|
|
3273
3442
|
success: false,
|
|
@@ -3280,8 +3449,8 @@ export class Session {
|
|
|
3280
3449
|
async handleCheckoutPrCreateRequest(msg) {
|
|
3281
3450
|
const { cwd, requestId } = msg;
|
|
3282
3451
|
try {
|
|
3283
|
-
let title = msg.title?.trim() ??
|
|
3284
|
-
let body = msg.body?.trim() ??
|
|
3452
|
+
let title = msg.title?.trim() ?? "";
|
|
3453
|
+
let body = msg.body?.trim() ?? "";
|
|
3285
3454
|
if (!title || !body) {
|
|
3286
3455
|
const generated = await this.generatePullRequestText(cwd, msg.baseRef);
|
|
3287
3456
|
if (!title)
|
|
@@ -3295,7 +3464,7 @@ export class Session {
|
|
|
3295
3464
|
base: msg.baseRef,
|
|
3296
3465
|
});
|
|
3297
3466
|
this.emit({
|
|
3298
|
-
type:
|
|
3467
|
+
type: "checkout_pr_create_response",
|
|
3299
3468
|
payload: {
|
|
3300
3469
|
cwd,
|
|
3301
3470
|
url: result.url ?? null,
|
|
@@ -3307,7 +3476,7 @@ export class Session {
|
|
|
3307
3476
|
}
|
|
3308
3477
|
catch (error) {
|
|
3309
3478
|
this.emit({
|
|
3310
|
-
type:
|
|
3479
|
+
type: "checkout_pr_create_response",
|
|
3311
3480
|
payload: {
|
|
3312
3481
|
cwd,
|
|
3313
3482
|
url: null,
|
|
@@ -3323,7 +3492,7 @@ export class Session {
|
|
|
3323
3492
|
try {
|
|
3324
3493
|
const prStatus = await getPullRequestStatus(cwd);
|
|
3325
3494
|
this.emit({
|
|
3326
|
-
type:
|
|
3495
|
+
type: "checkout_pr_status_response",
|
|
3327
3496
|
payload: {
|
|
3328
3497
|
cwd,
|
|
3329
3498
|
status: prStatus.status,
|
|
@@ -3335,7 +3504,7 @@ export class Session {
|
|
|
3335
3504
|
}
|
|
3336
3505
|
catch (error) {
|
|
3337
3506
|
this.emit({
|
|
3338
|
-
type:
|
|
3507
|
+
type: "checkout_pr_status_response",
|
|
3339
3508
|
payload: {
|
|
3340
3509
|
cwd,
|
|
3341
3510
|
status: null,
|
|
@@ -3351,10 +3520,10 @@ export class Session {
|
|
|
3351
3520
|
const cwd = msg.repoRoot ?? msg.cwd;
|
|
3352
3521
|
if (!cwd) {
|
|
3353
3522
|
this.emit({
|
|
3354
|
-
type:
|
|
3523
|
+
type: "paseo_worktree_list_response",
|
|
3355
3524
|
payload: {
|
|
3356
3525
|
worktrees: [],
|
|
3357
|
-
error: { code:
|
|
3526
|
+
error: { code: "UNKNOWN", message: "cwd or repoRoot is required" },
|
|
3358
3527
|
requestId,
|
|
3359
3528
|
},
|
|
3360
3529
|
});
|
|
@@ -3363,7 +3532,7 @@ export class Session {
|
|
|
3363
3532
|
try {
|
|
3364
3533
|
const worktrees = await listPaseoWorktrees({ cwd, paseoHome: this.paseoHome });
|
|
3365
3534
|
this.emit({
|
|
3366
|
-
type:
|
|
3535
|
+
type: "paseo_worktree_list_response",
|
|
3367
3536
|
payload: {
|
|
3368
3537
|
worktrees: worktrees.map((entry) => ({
|
|
3369
3538
|
worktreePath: entry.path,
|
|
@@ -3378,7 +3547,7 @@ export class Session {
|
|
|
3378
3547
|
}
|
|
3379
3548
|
catch (error) {
|
|
3380
3549
|
this.emit({
|
|
3381
|
-
type:
|
|
3550
|
+
type: "paseo_worktree_list_response",
|
|
3382
3551
|
payload: {
|
|
3383
3552
|
worktrees: [],
|
|
3384
3553
|
error: this.toCheckoutError(error),
|
|
@@ -3443,7 +3612,7 @@ export class Session {
|
|
|
3443
3612
|
}
|
|
3444
3613
|
for (const agentId of removedAgents) {
|
|
3445
3614
|
this.emit({
|
|
3446
|
-
type:
|
|
3615
|
+
type: "agent_deleted",
|
|
3447
3616
|
payload: {
|
|
3448
3617
|
agentId,
|
|
3449
3618
|
requestId: options.requestId,
|
|
@@ -3460,7 +3629,7 @@ export class Session {
|
|
|
3460
3629
|
try {
|
|
3461
3630
|
if (!targetPath) {
|
|
3462
3631
|
if (!repoRoot || !msg.branchName) {
|
|
3463
|
-
throw new Error(
|
|
3632
|
+
throw new Error("worktreePath or repoRoot+branchName is required");
|
|
3464
3633
|
}
|
|
3465
3634
|
const worktrees = await listPaseoWorktrees({ cwd: repoRoot, paseoHome: this.paseoHome });
|
|
3466
3635
|
const match = worktrees.find((entry) => entry.branchName === msg.branchName);
|
|
@@ -3474,13 +3643,13 @@ export class Session {
|
|
|
3474
3643
|
});
|
|
3475
3644
|
if (!ownership.allowed) {
|
|
3476
3645
|
this.emit({
|
|
3477
|
-
type:
|
|
3646
|
+
type: "paseo_worktree_archive_response",
|
|
3478
3647
|
payload: {
|
|
3479
3648
|
success: false,
|
|
3480
3649
|
removedAgents: [],
|
|
3481
3650
|
error: {
|
|
3482
|
-
code:
|
|
3483
|
-
message:
|
|
3651
|
+
code: "NOT_ALLOWED",
|
|
3652
|
+
message: "Worktree is not a Paseo-owned worktree",
|
|
3484
3653
|
},
|
|
3485
3654
|
requestId,
|
|
3486
3655
|
},
|
|
@@ -3489,7 +3658,7 @@ export class Session {
|
|
|
3489
3658
|
}
|
|
3490
3659
|
repoRoot = ownership.repoRoot ?? repoRoot ?? null;
|
|
3491
3660
|
if (!repoRoot) {
|
|
3492
|
-
throw new Error(
|
|
3661
|
+
throw new Error("Unable to resolve repo root for worktree");
|
|
3493
3662
|
}
|
|
3494
3663
|
const removedAgents = await this.archivePaseoWorktree({
|
|
3495
3664
|
targetPath,
|
|
@@ -3497,7 +3666,7 @@ export class Session {
|
|
|
3497
3666
|
requestId,
|
|
3498
3667
|
});
|
|
3499
3668
|
this.emit({
|
|
3500
|
-
type:
|
|
3669
|
+
type: "paseo_worktree_archive_response",
|
|
3501
3670
|
payload: {
|
|
3502
3671
|
success: true,
|
|
3503
3672
|
removedAgents,
|
|
@@ -3508,7 +3677,7 @@ export class Session {
|
|
|
3508
3677
|
}
|
|
3509
3678
|
catch (error) {
|
|
3510
3679
|
this.emit({
|
|
3511
|
-
type:
|
|
3680
|
+
type: "paseo_worktree_archive_response",
|
|
3512
3681
|
payload: {
|
|
3513
3682
|
success: false,
|
|
3514
3683
|
removedAgents: [],
|
|
@@ -3522,31 +3691,31 @@ export class Session {
|
|
|
3522
3691
|
* Handle read-only file explorer requests scoped to a workspace cwd
|
|
3523
3692
|
*/
|
|
3524
3693
|
async handleFileExplorerRequest(request) {
|
|
3525
|
-
const { cwd: workspaceCwd, path: requestedPath =
|
|
3694
|
+
const { cwd: workspaceCwd, path: requestedPath = ".", mode, requestId } = request;
|
|
3526
3695
|
const cwd = workspaceCwd.trim();
|
|
3527
3696
|
if (!cwd) {
|
|
3528
3697
|
this.emit({
|
|
3529
|
-
type:
|
|
3698
|
+
type: "file_explorer_response",
|
|
3530
3699
|
payload: {
|
|
3531
3700
|
cwd: workspaceCwd,
|
|
3532
3701
|
path: requestedPath,
|
|
3533
3702
|
mode,
|
|
3534
3703
|
directory: null,
|
|
3535
3704
|
file: null,
|
|
3536
|
-
error:
|
|
3705
|
+
error: "cwd is required",
|
|
3537
3706
|
requestId,
|
|
3538
3707
|
},
|
|
3539
3708
|
});
|
|
3540
3709
|
return;
|
|
3541
3710
|
}
|
|
3542
3711
|
try {
|
|
3543
|
-
if (mode ===
|
|
3712
|
+
if (mode === "list") {
|
|
3544
3713
|
const directory = await listDirectoryEntries({
|
|
3545
3714
|
root: cwd,
|
|
3546
3715
|
relativePath: requestedPath,
|
|
3547
3716
|
});
|
|
3548
3717
|
this.emit({
|
|
3549
|
-
type:
|
|
3718
|
+
type: "file_explorer_response",
|
|
3550
3719
|
payload: {
|
|
3551
3720
|
cwd,
|
|
3552
3721
|
path: directory.path,
|
|
@@ -3564,7 +3733,7 @@ export class Session {
|
|
|
3564
3733
|
relativePath: requestedPath,
|
|
3565
3734
|
});
|
|
3566
3735
|
this.emit({
|
|
3567
|
-
type:
|
|
3736
|
+
type: "file_explorer_response",
|
|
3568
3737
|
payload: {
|
|
3569
3738
|
cwd,
|
|
3570
3739
|
path: file.path,
|
|
@@ -3580,7 +3749,7 @@ export class Session {
|
|
|
3580
3749
|
catch (error) {
|
|
3581
3750
|
this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to fulfill file explorer request for workspace ${cwd}`);
|
|
3582
3751
|
this.emit({
|
|
3583
|
-
type:
|
|
3752
|
+
type: "file_explorer_response",
|
|
3584
3753
|
payload: {
|
|
3585
3754
|
cwd,
|
|
3586
3755
|
path: requestedPath,
|
|
@@ -3601,7 +3770,7 @@ export class Session {
|
|
|
3601
3770
|
try {
|
|
3602
3771
|
const icon = await getProjectIcon(cwd);
|
|
3603
3772
|
this.emit({
|
|
3604
|
-
type:
|
|
3773
|
+
type: "project_icon_response",
|
|
3605
3774
|
payload: {
|
|
3606
3775
|
cwd,
|
|
3607
3776
|
icon,
|
|
@@ -3612,7 +3781,7 @@ export class Session {
|
|
|
3612
3781
|
}
|
|
3613
3782
|
catch (error) {
|
|
3614
3783
|
this.emit({
|
|
3615
|
-
type:
|
|
3784
|
+
type: "project_icon_response",
|
|
3616
3785
|
payload: {
|
|
3617
3786
|
cwd,
|
|
3618
3787
|
icon: null,
|
|
@@ -3630,7 +3799,7 @@ export class Session {
|
|
|
3630
3799
|
const cwd = workspaceCwd.trim();
|
|
3631
3800
|
if (!cwd) {
|
|
3632
3801
|
this.emit({
|
|
3633
|
-
type:
|
|
3802
|
+
type: "file_download_token_response",
|
|
3634
3803
|
payload: {
|
|
3635
3804
|
cwd: workspaceCwd,
|
|
3636
3805
|
path: requestedPath,
|
|
@@ -3638,7 +3807,7 @@ export class Session {
|
|
|
3638
3807
|
fileName: null,
|
|
3639
3808
|
mimeType: null,
|
|
3640
3809
|
size: null,
|
|
3641
|
-
error:
|
|
3810
|
+
error: "cwd is required",
|
|
3642
3811
|
requestId,
|
|
3643
3812
|
},
|
|
3644
3813
|
});
|
|
@@ -3658,7 +3827,7 @@ export class Session {
|
|
|
3658
3827
|
size: info.size,
|
|
3659
3828
|
});
|
|
3660
3829
|
this.emit({
|
|
3661
|
-
type:
|
|
3830
|
+
type: "file_download_token_response",
|
|
3662
3831
|
payload: {
|
|
3663
3832
|
cwd,
|
|
3664
3833
|
path: info.path,
|
|
@@ -3674,7 +3843,7 @@ export class Session {
|
|
|
3674
3843
|
catch (error) {
|
|
3675
3844
|
this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to issue download token for workspace ${cwd}`);
|
|
3676
3845
|
this.emit({
|
|
3677
|
-
type:
|
|
3846
|
+
type: "file_download_token_response",
|
|
3678
3847
|
payload: {
|
|
3679
3848
|
cwd,
|
|
3680
3849
|
path: requestedPath,
|
|
@@ -3713,7 +3882,7 @@ export class Session {
|
|
|
3713
3882
|
async resolveAgentIdentifier(identifier) {
|
|
3714
3883
|
const trimmed = identifier.trim();
|
|
3715
3884
|
if (!trimmed) {
|
|
3716
|
-
return { ok: false, error:
|
|
3885
|
+
return { ok: false, error: "Agent identifier cannot be empty" };
|
|
3717
3886
|
}
|
|
3718
3887
|
const stored = await this.agentStorage.list();
|
|
3719
3888
|
const storedRecords = stored.filter((record) => !record.internal);
|
|
@@ -3737,7 +3906,7 @@ export class Session {
|
|
|
3737
3906
|
error: `Agent identifier "${trimmed}" is ambiguous (${prefixMatches
|
|
3738
3907
|
.slice(0, 5)
|
|
3739
3908
|
.map((id) => id.slice(0, 8))
|
|
3740
|
-
.join(
|
|
3909
|
+
.join(", ")}${prefixMatches.length > 5 ? ", …" : ""})`,
|
|
3741
3910
|
};
|
|
3742
3911
|
}
|
|
3743
3912
|
const titleMatches = storedRecords.filter((record) => record.title === trimmed);
|
|
@@ -3750,7 +3919,7 @@ export class Session {
|
|
|
3750
3919
|
error: `Agent title "${trimmed}" is ambiguous (${titleMatches
|
|
3751
3920
|
.slice(0, 5)
|
|
3752
3921
|
.map((r) => r.id.slice(0, 8))
|
|
3753
|
-
.join(
|
|
3922
|
+
.join(", ")}${titleMatches.length > 5 ? ", …" : ""})`,
|
|
3754
3923
|
};
|
|
3755
3924
|
}
|
|
3756
3925
|
return { ok: false, error: `Agent not found: ${trimmed}` };
|
|
@@ -3767,7 +3936,7 @@ export class Session {
|
|
|
3767
3936
|
return this.buildStoredAgentPayload(record);
|
|
3768
3937
|
}
|
|
3769
3938
|
normalizeFetchAgentsSort(sort) {
|
|
3770
|
-
const fallback = [{ key:
|
|
3939
|
+
const fallback = [{ key: "updated_at", direction: "desc" }];
|
|
3771
3940
|
if (!sort || sort.length === 0) {
|
|
3772
3941
|
return fallback;
|
|
3773
3942
|
}
|
|
@@ -3785,42 +3954,42 @@ export class Session {
|
|
|
3785
3954
|
getStatusPriority(agent) {
|
|
3786
3955
|
const attentionReason = agent.attentionReason ?? null;
|
|
3787
3956
|
const hasPendingPermission = (agent.pendingPermissions?.length ?? 0) > 0;
|
|
3788
|
-
if (hasPendingPermission || attentionReason ===
|
|
3957
|
+
if (hasPendingPermission || attentionReason === "permission") {
|
|
3789
3958
|
return 0;
|
|
3790
3959
|
}
|
|
3791
|
-
if (agent.status ===
|
|
3960
|
+
if (agent.status === "error" || attentionReason === "error") {
|
|
3792
3961
|
return 1;
|
|
3793
3962
|
}
|
|
3794
|
-
if (agent.status ===
|
|
3963
|
+
if (agent.status === "running") {
|
|
3795
3964
|
return 2;
|
|
3796
3965
|
}
|
|
3797
|
-
if (agent.status ===
|
|
3966
|
+
if (agent.status === "initializing") {
|
|
3798
3967
|
return 3;
|
|
3799
3968
|
}
|
|
3800
3969
|
return 4;
|
|
3801
3970
|
}
|
|
3802
3971
|
getFetchAgentsSortValue(entry, key) {
|
|
3803
3972
|
switch (key) {
|
|
3804
|
-
case
|
|
3973
|
+
case "status_priority":
|
|
3805
3974
|
return this.getStatusPriority(entry.agent);
|
|
3806
|
-
case
|
|
3975
|
+
case "created_at":
|
|
3807
3976
|
return Date.parse(entry.agent.createdAt);
|
|
3808
|
-
case
|
|
3977
|
+
case "updated_at":
|
|
3809
3978
|
return Date.parse(entry.agent.updatedAt);
|
|
3810
|
-
case
|
|
3811
|
-
return entry.agent.title?.toLocaleLowerCase() ??
|
|
3979
|
+
case "title":
|
|
3980
|
+
return entry.agent.title?.toLocaleLowerCase() ?? "";
|
|
3812
3981
|
}
|
|
3813
3982
|
}
|
|
3814
3983
|
getFetchAgentsSortValueFromAgent(agent, key) {
|
|
3815
3984
|
switch (key) {
|
|
3816
|
-
case
|
|
3985
|
+
case "status_priority":
|
|
3817
3986
|
return this.getStatusPriority(agent);
|
|
3818
|
-
case
|
|
3987
|
+
case "created_at":
|
|
3819
3988
|
return Date.parse(agent.createdAt);
|
|
3820
|
-
case
|
|
3989
|
+
case "updated_at":
|
|
3821
3990
|
return Date.parse(agent.updatedAt);
|
|
3822
|
-
case
|
|
3823
|
-
return agent.title?.toLocaleLowerCase() ??
|
|
3991
|
+
case "title":
|
|
3992
|
+
return agent.title?.toLocaleLowerCase() ?? "";
|
|
3824
3993
|
}
|
|
3825
3994
|
}
|
|
3826
3995
|
compareSortValues(left, right) {
|
|
@@ -3833,7 +4002,7 @@ export class Session {
|
|
|
3833
4002
|
if (right === null) {
|
|
3834
4003
|
return 1;
|
|
3835
4004
|
}
|
|
3836
|
-
if (typeof left ===
|
|
4005
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3837
4006
|
return left < right ? -1 : 1;
|
|
3838
4007
|
}
|
|
3839
4008
|
return String(left).localeCompare(String(right));
|
|
@@ -3846,7 +4015,7 @@ export class Session {
|
|
|
3846
4015
|
if (base === 0) {
|
|
3847
4016
|
continue;
|
|
3848
4017
|
}
|
|
3849
|
-
return spec.direction ===
|
|
4018
|
+
return spec.direction === "asc" ? base : -base;
|
|
3850
4019
|
}
|
|
3851
4020
|
return left.id.localeCompare(right.id);
|
|
3852
4021
|
}
|
|
@@ -3859,48 +4028,48 @@ export class Session {
|
|
|
3859
4028
|
sort,
|
|
3860
4029
|
values,
|
|
3861
4030
|
id: entry.agent.id,
|
|
3862
|
-
}),
|
|
4031
|
+
}), "utf8").toString("base64url");
|
|
3863
4032
|
}
|
|
3864
4033
|
decodeFetchAgentsCursor(cursor, sort) {
|
|
3865
4034
|
let parsed;
|
|
3866
4035
|
try {
|
|
3867
|
-
parsed = JSON.parse(Buffer.from(cursor,
|
|
4036
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3868
4037
|
}
|
|
3869
4038
|
catch {
|
|
3870
|
-
throw new SessionRequestError(
|
|
4039
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3871
4040
|
}
|
|
3872
|
-
if (!parsed || typeof parsed !==
|
|
3873
|
-
throw new SessionRequestError(
|
|
4041
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4042
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3874
4043
|
}
|
|
3875
4044
|
const payload = parsed;
|
|
3876
|
-
if (!Array.isArray(payload.sort) || typeof payload.id !==
|
|
3877
|
-
throw new SessionRequestError(
|
|
4045
|
+
if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
|
|
4046
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3878
4047
|
}
|
|
3879
|
-
if (!payload.values || typeof payload.values !==
|
|
3880
|
-
throw new SessionRequestError(
|
|
4048
|
+
if (!payload.values || typeof payload.values !== "object") {
|
|
4049
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3881
4050
|
}
|
|
3882
4051
|
const cursorSort = [];
|
|
3883
4052
|
for (const item of payload.sort) {
|
|
3884
4053
|
if (!item ||
|
|
3885
|
-
typeof item !==
|
|
3886
|
-
typeof item.key !==
|
|
3887
|
-
typeof item.direction !==
|
|
3888
|
-
throw new SessionRequestError(
|
|
4054
|
+
typeof item !== "object" ||
|
|
4055
|
+
typeof item.key !== "string" ||
|
|
4056
|
+
typeof item.direction !== "string") {
|
|
4057
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3889
4058
|
}
|
|
3890
4059
|
const key = item.key;
|
|
3891
4060
|
const direction = item.direction;
|
|
3892
|
-
if ((key !==
|
|
3893
|
-
key !==
|
|
3894
|
-
key !==
|
|
3895
|
-
key !==
|
|
3896
|
-
(direction !==
|
|
3897
|
-
throw new SessionRequestError(
|
|
4061
|
+
if ((key !== "status_priority" &&
|
|
4062
|
+
key !== "created_at" &&
|
|
4063
|
+
key !== "updated_at" &&
|
|
4064
|
+
key !== "title") ||
|
|
4065
|
+
(direction !== "asc" && direction !== "desc")) {
|
|
4066
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3898
4067
|
}
|
|
3899
4068
|
cursorSort.push({ key, direction });
|
|
3900
4069
|
}
|
|
3901
4070
|
if (cursorSort.length !== sort.length ||
|
|
3902
4071
|
cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
|
|
3903
|
-
throw new SessionRequestError(
|
|
4072
|
+
throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
|
|
3904
4073
|
}
|
|
3905
4074
|
return {
|
|
3906
4075
|
sort: cursorSort,
|
|
@@ -3916,7 +4085,7 @@ export class Session {
|
|
|
3916
4085
|
if (base === 0) {
|
|
3917
4086
|
continue;
|
|
3918
4087
|
}
|
|
3919
|
-
return spec.direction ===
|
|
4088
|
+
return spec.direction === "asc" ? base : -base;
|
|
3920
4089
|
}
|
|
3921
4090
|
return agent.id.localeCompare(cursor.id);
|
|
3922
4091
|
}
|
|
@@ -3982,19 +4151,19 @@ export class Session {
|
|
|
3982
4151
|
}
|
|
3983
4152
|
deriveWorkspaceStateBucket(agent) {
|
|
3984
4153
|
const pendingPermissionCount = agent.pendingPermissions?.length ?? 0;
|
|
3985
|
-
if (pendingPermissionCount > 0 || agent.attentionReason ===
|
|
3986
|
-
return
|
|
4154
|
+
if (pendingPermissionCount > 0 || agent.attentionReason === "permission") {
|
|
4155
|
+
return "needs_input";
|
|
3987
4156
|
}
|
|
3988
|
-
if (agent.status ===
|
|
3989
|
-
return
|
|
4157
|
+
if (agent.status === "error" || agent.attentionReason === "error") {
|
|
4158
|
+
return "failed";
|
|
3990
4159
|
}
|
|
3991
|
-
if (agent.status ===
|
|
3992
|
-
return
|
|
4160
|
+
if (agent.status === "running" || agent.status === "initializing") {
|
|
4161
|
+
return "running";
|
|
3993
4162
|
}
|
|
3994
4163
|
if (agent.requiresAttention) {
|
|
3995
|
-
return
|
|
4164
|
+
return "attention";
|
|
3996
4165
|
}
|
|
3997
|
-
return
|
|
4166
|
+
return "done";
|
|
3998
4167
|
}
|
|
3999
4168
|
accumulateLatestActivityAt(current, agent) {
|
|
4000
4169
|
const candidateRaw = agent.lastUserMessageAt ?? agent.updatedAt;
|
|
@@ -4036,10 +4205,10 @@ export class Session {
|
|
|
4036
4205
|
projectId: workspace.projectId,
|
|
4037
4206
|
projectDisplayName: resolvedProjectRecord?.displayName ?? workspace.projectId,
|
|
4038
4207
|
projectRootPath: resolvedProjectRecord?.rootPath ?? workspace.cwd,
|
|
4039
|
-
projectKind: resolvedProjectRecord?.kind ??
|
|
4208
|
+
projectKind: resolvedProjectRecord?.kind ?? "non_git",
|
|
4040
4209
|
workspaceKind: workspace.kind,
|
|
4041
4210
|
name: displayName,
|
|
4042
|
-
status:
|
|
4211
|
+
status: "done",
|
|
4043
4212
|
activityAt: null,
|
|
4044
4213
|
diffStat,
|
|
4045
4214
|
};
|
|
@@ -4080,7 +4249,7 @@ export class Session {
|
|
|
4080
4249
|
return this.listWorkspaceDescriptorsSnapshot();
|
|
4081
4250
|
}
|
|
4082
4251
|
normalizeFetchWorkspacesSort(sort) {
|
|
4083
|
-
const fallback = [{ key:
|
|
4252
|
+
const fallback = [{ key: "activity_at", direction: "desc" }];
|
|
4084
4253
|
if (!sort || sort.length === 0) {
|
|
4085
4254
|
return fallback;
|
|
4086
4255
|
}
|
|
@@ -4097,13 +4266,13 @@ export class Session {
|
|
|
4097
4266
|
}
|
|
4098
4267
|
getFetchWorkspacesSortValue(workspace, key) {
|
|
4099
4268
|
switch (key) {
|
|
4100
|
-
case
|
|
4269
|
+
case "status_priority":
|
|
4101
4270
|
return this.workspaceStatePriority[workspace.status];
|
|
4102
|
-
case
|
|
4271
|
+
case "activity_at":
|
|
4103
4272
|
return workspace.activityAt ? Date.parse(workspace.activityAt) : null;
|
|
4104
|
-
case
|
|
4273
|
+
case "name":
|
|
4105
4274
|
return workspace.name.toLocaleLowerCase();
|
|
4106
|
-
case
|
|
4275
|
+
case "project_id":
|
|
4107
4276
|
return workspace.projectId.toLocaleLowerCase();
|
|
4108
4277
|
}
|
|
4109
4278
|
}
|
|
@@ -4115,7 +4284,7 @@ export class Session {
|
|
|
4115
4284
|
if (base === 0) {
|
|
4116
4285
|
continue;
|
|
4117
4286
|
}
|
|
4118
|
-
return spec.direction ===
|
|
4287
|
+
return spec.direction === "asc" ? base : -base;
|
|
4119
4288
|
}
|
|
4120
4289
|
return left.id.localeCompare(right.id);
|
|
4121
4290
|
}
|
|
@@ -4128,48 +4297,48 @@ export class Session {
|
|
|
4128
4297
|
sort,
|
|
4129
4298
|
values,
|
|
4130
4299
|
id: entry.id,
|
|
4131
|
-
}),
|
|
4300
|
+
}), "utf8").toString("base64url");
|
|
4132
4301
|
}
|
|
4133
4302
|
decodeFetchWorkspacesCursor(cursor, sort) {
|
|
4134
4303
|
let parsed;
|
|
4135
4304
|
try {
|
|
4136
|
-
parsed = JSON.parse(Buffer.from(cursor,
|
|
4305
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
4137
4306
|
}
|
|
4138
4307
|
catch {
|
|
4139
|
-
throw new SessionRequestError(
|
|
4308
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4140
4309
|
}
|
|
4141
|
-
if (!parsed || typeof parsed !==
|
|
4142
|
-
throw new SessionRequestError(
|
|
4310
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4311
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4143
4312
|
}
|
|
4144
4313
|
const payload = parsed;
|
|
4145
|
-
if (!Array.isArray(payload.sort) || typeof payload.id !==
|
|
4146
|
-
throw new SessionRequestError(
|
|
4314
|
+
if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
|
|
4315
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4147
4316
|
}
|
|
4148
|
-
if (!payload.values || typeof payload.values !==
|
|
4149
|
-
throw new SessionRequestError(
|
|
4317
|
+
if (!payload.values || typeof payload.values !== "object") {
|
|
4318
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4150
4319
|
}
|
|
4151
4320
|
const cursorSort = [];
|
|
4152
4321
|
for (const item of payload.sort) {
|
|
4153
4322
|
if (!item ||
|
|
4154
|
-
typeof item !==
|
|
4155
|
-
typeof item.key !==
|
|
4156
|
-
typeof item.direction !==
|
|
4157
|
-
throw new SessionRequestError(
|
|
4323
|
+
typeof item !== "object" ||
|
|
4324
|
+
typeof item.key !== "string" ||
|
|
4325
|
+
typeof item.direction !== "string") {
|
|
4326
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4158
4327
|
}
|
|
4159
4328
|
const key = item.key;
|
|
4160
4329
|
const direction = item.direction;
|
|
4161
|
-
if ((key !==
|
|
4162
|
-
key !==
|
|
4163
|
-
key !==
|
|
4164
|
-
key !==
|
|
4165
|
-
(direction !==
|
|
4166
|
-
throw new SessionRequestError(
|
|
4330
|
+
if ((key !== "status_priority" &&
|
|
4331
|
+
key !== "activity_at" &&
|
|
4332
|
+
key !== "name" &&
|
|
4333
|
+
key !== "project_id") ||
|
|
4334
|
+
(direction !== "asc" && direction !== "desc")) {
|
|
4335
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4167
4336
|
}
|
|
4168
4337
|
cursorSort.push({ key, direction });
|
|
4169
4338
|
}
|
|
4170
4339
|
if (cursorSort.length !== sort.length ||
|
|
4171
4340
|
cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
|
|
4172
|
-
throw new SessionRequestError(
|
|
4341
|
+
throw new SessionRequestError("invalid_cursor", "fetch_workspaces cursor does not match current sort");
|
|
4173
4342
|
}
|
|
4174
4343
|
return {
|
|
4175
4344
|
sort: cursorSort,
|
|
@@ -4185,7 +4354,7 @@ export class Session {
|
|
|
4185
4354
|
if (base === 0) {
|
|
4186
4355
|
continue;
|
|
4187
4356
|
}
|
|
4188
|
-
return spec.direction ===
|
|
4357
|
+
return spec.direction === "asc" ? base : -base;
|
|
4189
4358
|
}
|
|
4190
4359
|
return workspace.id.localeCompare(cursor.id);
|
|
4191
4360
|
}
|
|
@@ -4241,12 +4410,12 @@ export class Session {
|
|
|
4241
4410
|
}
|
|
4242
4411
|
bufferOrEmitWorkspaceUpdate(subscription, payload) {
|
|
4243
4412
|
if (subscription.isBootstrapping) {
|
|
4244
|
-
const workspaceId = payload.kind ===
|
|
4413
|
+
const workspaceId = payload.kind === "upsert" ? payload.workspace.id : payload.id;
|
|
4245
4414
|
subscription.pendingUpdatesByWorkspaceId.set(workspaceId, payload);
|
|
4246
4415
|
return;
|
|
4247
4416
|
}
|
|
4248
4417
|
this.emit({
|
|
4249
|
-
type:
|
|
4418
|
+
type: "workspace_update",
|
|
4250
4419
|
payload,
|
|
4251
4420
|
});
|
|
4252
4421
|
}
|
|
@@ -4259,19 +4428,20 @@ export class Session {
|
|
|
4259
4428
|
const pending = Array.from(subscription.pendingUpdatesByWorkspaceId.values());
|
|
4260
4429
|
subscription.pendingUpdatesByWorkspaceId.clear();
|
|
4261
4430
|
for (const payload of pending) {
|
|
4262
|
-
if (payload.kind ===
|
|
4431
|
+
if (payload.kind === "upsert") {
|
|
4263
4432
|
const snapshotLatestActivity = options?.snapshotLatestActivityByWorkspaceId?.get(payload.workspace.id);
|
|
4264
|
-
if (typeof snapshotLatestActivity ===
|
|
4433
|
+
if (typeof snapshotLatestActivity === "number") {
|
|
4265
4434
|
const updateLatestActivity = payload.workspace.activityAt
|
|
4266
4435
|
? Date.parse(payload.workspace.activityAt)
|
|
4267
4436
|
: Number.NEGATIVE_INFINITY;
|
|
4268
|
-
if (!Number.isNaN(updateLatestActivity) &&
|
|
4437
|
+
if (!Number.isNaN(updateLatestActivity) &&
|
|
4438
|
+
updateLatestActivity <= snapshotLatestActivity) {
|
|
4269
4439
|
continue;
|
|
4270
4440
|
}
|
|
4271
4441
|
}
|
|
4272
4442
|
}
|
|
4273
4443
|
this.emit({
|
|
4274
|
-
type:
|
|
4444
|
+
type: "workspace_update",
|
|
4275
4445
|
payload,
|
|
4276
4446
|
});
|
|
4277
4447
|
}
|
|
@@ -4280,19 +4450,62 @@ export class Session {
|
|
|
4280
4450
|
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
4281
4451
|
return (await this.reconcileWorkspaceRecord(workspaceId)).workspace;
|
|
4282
4452
|
}
|
|
4453
|
+
async registerPendingWorktreeWorkspace(options) {
|
|
4454
|
+
const workspaceId = normalizePersistedWorkspaceId(options.worktreePath);
|
|
4455
|
+
const basePlacement = await this.buildProjectPlacement(options.repoRoot);
|
|
4456
|
+
const placement = {
|
|
4457
|
+
...basePlacement,
|
|
4458
|
+
checkout: {
|
|
4459
|
+
cwd: workspaceId,
|
|
4460
|
+
isGit: true,
|
|
4461
|
+
currentBranch: options.branchName,
|
|
4462
|
+
remoteUrl: basePlacement.checkout.remoteUrl,
|
|
4463
|
+
isPaseoOwnedWorktree: true,
|
|
4464
|
+
mainRepoRoot: options.repoRoot,
|
|
4465
|
+
},
|
|
4466
|
+
};
|
|
4467
|
+
const now = new Date().toISOString();
|
|
4468
|
+
const existingWorkspace = await this.workspaceRegistry.get(workspaceId);
|
|
4469
|
+
const existingProject = await this.projectRegistry.get(placement.projectKey);
|
|
4470
|
+
const nextProjectRecord = this.buildPersistedProjectRecord({
|
|
4471
|
+
workspaceId,
|
|
4472
|
+
placement,
|
|
4473
|
+
createdAt: existingProject?.createdAt ?? now,
|
|
4474
|
+
updatedAt: now,
|
|
4475
|
+
});
|
|
4476
|
+
const nextWorkspaceRecord = this.buildPersistedWorkspaceRecord({
|
|
4477
|
+
workspaceId,
|
|
4478
|
+
placement,
|
|
4479
|
+
createdAt: existingWorkspace?.createdAt ?? now,
|
|
4480
|
+
updatedAt: now,
|
|
4481
|
+
});
|
|
4482
|
+
await this.projectRegistry.upsert(nextProjectRecord);
|
|
4483
|
+
await this.workspaceRegistry.upsert(nextWorkspaceRecord);
|
|
4484
|
+
await this.syncWorkspaceGitWatchTarget(workspaceId, {
|
|
4485
|
+
isGit: placement.checkout.isGit,
|
|
4486
|
+
});
|
|
4487
|
+
if (existingWorkspace &&
|
|
4488
|
+
!existingWorkspace.archivedAt &&
|
|
4489
|
+
existingWorkspace.projectId !== nextWorkspaceRecord.projectId) {
|
|
4490
|
+
await this.archiveProjectRecordIfEmpty(existingWorkspace.projectId, now);
|
|
4491
|
+
}
|
|
4492
|
+
return nextWorkspaceRecord;
|
|
4493
|
+
}
|
|
4283
4494
|
async archiveWorkspaceRecord(workspaceId, archivedAt) {
|
|
4284
4495
|
const existing = await this.workspaceRegistry.get(workspaceId);
|
|
4285
4496
|
if (!existing || existing.archivedAt) {
|
|
4497
|
+
this.removeWorkspaceGitWatchTarget(workspaceId);
|
|
4286
4498
|
return;
|
|
4287
4499
|
}
|
|
4288
4500
|
const nextArchivedAt = archivedAt ?? new Date().toISOString();
|
|
4289
4501
|
await this.workspaceRegistry.archive(workspaceId, nextArchivedAt);
|
|
4502
|
+
this.removeWorkspaceGitWatchTarget(workspaceId);
|
|
4290
4503
|
const siblingWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === existing.projectId && !workspace.archivedAt);
|
|
4291
4504
|
if (siblingWorkspaces.length === 0) {
|
|
4292
4505
|
await this.projectRegistry.archive(existing.projectId, nextArchivedAt);
|
|
4293
4506
|
}
|
|
4294
4507
|
}
|
|
4295
|
-
async emitWorkspaceUpdateForCwd(cwd) {
|
|
4508
|
+
async emitWorkspaceUpdateForCwd(cwd, options) {
|
|
4296
4509
|
const subscription = this.workspaceUpdatesSubscription;
|
|
4297
4510
|
if (!subscription) {
|
|
4298
4511
|
return;
|
|
@@ -4304,16 +4517,24 @@ export class Session {
|
|
|
4304
4517
|
const workspaceIdsToEmit = new Set([workspaceId, ...changedWorkspaceIds]);
|
|
4305
4518
|
for (const nextWorkspaceId of workspaceIdsToEmit) {
|
|
4306
4519
|
const workspace = descriptorsByWorkspaceId.get(nextWorkspaceId);
|
|
4307
|
-
|
|
4520
|
+
const nextWorkspace = workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
|
|
4521
|
+
? workspace
|
|
4522
|
+
: null;
|
|
4523
|
+
if (options?.dedupeGitState &&
|
|
4524
|
+
this.shouldSkipWorkspaceGitWatchUpdate(nextWorkspaceId, nextWorkspace)) {
|
|
4525
|
+
continue;
|
|
4526
|
+
}
|
|
4527
|
+
this.rememberWorkspaceGitWatchFingerprint(nextWorkspaceId, nextWorkspace);
|
|
4528
|
+
if (!nextWorkspace) {
|
|
4308
4529
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4309
|
-
kind:
|
|
4530
|
+
kind: "remove",
|
|
4310
4531
|
id: nextWorkspaceId,
|
|
4311
4532
|
});
|
|
4312
4533
|
continue;
|
|
4313
4534
|
}
|
|
4314
4535
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4315
|
-
kind:
|
|
4316
|
-
workspace,
|
|
4536
|
+
kind: "upsert",
|
|
4537
|
+
workspace: nextWorkspace,
|
|
4317
4538
|
});
|
|
4318
4539
|
}
|
|
4319
4540
|
}
|
|
@@ -4335,16 +4556,20 @@ export class Session {
|
|
|
4335
4556
|
const descriptorsByWorkspaceId = new Map(all.map((entry) => [entry.id, entry]));
|
|
4336
4557
|
for (const workspaceId of uniqueWorkspaceCwds) {
|
|
4337
4558
|
const workspace = descriptorsByWorkspaceId.get(workspaceId);
|
|
4338
|
-
|
|
4559
|
+
const nextWorkspace = workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
|
|
4560
|
+
? workspace
|
|
4561
|
+
: null;
|
|
4562
|
+
this.rememberWorkspaceGitWatchFingerprint(workspaceId, nextWorkspace);
|
|
4563
|
+
if (!nextWorkspace) {
|
|
4339
4564
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4340
|
-
kind:
|
|
4565
|
+
kind: "remove",
|
|
4341
4566
|
id: workspaceId,
|
|
4342
4567
|
});
|
|
4343
4568
|
continue;
|
|
4344
4569
|
}
|
|
4345
4570
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4346
|
-
kind:
|
|
4347
|
-
workspace,
|
|
4571
|
+
kind: "upsert",
|
|
4572
|
+
workspace: nextWorkspace,
|
|
4348
4573
|
});
|
|
4349
4574
|
}
|
|
4350
4575
|
}
|
|
@@ -4373,7 +4598,7 @@ export class Session {
|
|
|
4373
4598
|
}
|
|
4374
4599
|
}
|
|
4375
4600
|
this.emit({
|
|
4376
|
-
type:
|
|
4601
|
+
type: "fetch_agents_response",
|
|
4377
4602
|
payload: {
|
|
4378
4603
|
requestId: request.requestId,
|
|
4379
4604
|
...(subscriptionId ? { subscriptionId } : {}),
|
|
@@ -4388,11 +4613,11 @@ export class Session {
|
|
|
4388
4613
|
if (subscriptionId && this.agentUpdatesSubscription?.subscriptionId === subscriptionId) {
|
|
4389
4614
|
this.agentUpdatesSubscription = null;
|
|
4390
4615
|
}
|
|
4391
|
-
const code = error instanceof SessionRequestError ? error.code :
|
|
4392
|
-
const message = error instanceof Error ? error.message :
|
|
4393
|
-
this.sessionLogger.error({ err: error },
|
|
4616
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
|
|
4617
|
+
const message = error instanceof Error ? error.message : "Failed to fetch agents";
|
|
4618
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_agents_request");
|
|
4394
4619
|
this.emit({
|
|
4395
|
-
type:
|
|
4620
|
+
type: "rpc_error",
|
|
4396
4621
|
payload: {
|
|
4397
4622
|
requestId: request.requestId,
|
|
4398
4623
|
requestType: request.type,
|
|
@@ -4419,6 +4644,7 @@ export class Session {
|
|
|
4419
4644
|
};
|
|
4420
4645
|
}
|
|
4421
4646
|
const payload = await this.listFetchWorkspacesEntries(request);
|
|
4647
|
+
this.primeWorkspaceGitWatchFingerprints(payload.entries);
|
|
4422
4648
|
const snapshotLatestActivityByWorkspaceId = new Map();
|
|
4423
4649
|
for (const entry of payload.entries) {
|
|
4424
4650
|
const parsedLatestActivity = entry.activityAt
|
|
@@ -4429,7 +4655,7 @@ export class Session {
|
|
|
4429
4655
|
}
|
|
4430
4656
|
}
|
|
4431
4657
|
this.emit({
|
|
4432
|
-
type:
|
|
4658
|
+
type: "fetch_workspaces_response",
|
|
4433
4659
|
payload: {
|
|
4434
4660
|
requestId: request.requestId,
|
|
4435
4661
|
...(subscriptionId ? { subscriptionId } : {}),
|
|
@@ -4444,11 +4670,11 @@ export class Session {
|
|
|
4444
4670
|
if (subscriptionId && this.workspaceUpdatesSubscription?.subscriptionId === subscriptionId) {
|
|
4445
4671
|
this.workspaceUpdatesSubscription = null;
|
|
4446
4672
|
}
|
|
4447
|
-
const code = error instanceof SessionRequestError ? error.code :
|
|
4448
|
-
const message = error instanceof Error ? error.message :
|
|
4449
|
-
this.sessionLogger.error({ err: error },
|
|
4673
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_workspaces_failed";
|
|
4674
|
+
const message = error instanceof Error ? error.message : "Failed to fetch workspaces";
|
|
4675
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_workspaces_request");
|
|
4450
4676
|
this.emit({
|
|
4451
|
-
type:
|
|
4677
|
+
type: "rpc_error",
|
|
4452
4678
|
payload: {
|
|
4453
4679
|
requestId: request.requestId,
|
|
4454
4680
|
requestType: request.type,
|
|
@@ -4464,7 +4690,7 @@ export class Session {
|
|
|
4464
4690
|
await this.emitWorkspaceUpdateForCwd(workspace.cwd);
|
|
4465
4691
|
const descriptor = await this.describeWorkspaceRecord(workspace);
|
|
4466
4692
|
this.emit({
|
|
4467
|
-
type:
|
|
4693
|
+
type: "open_project_response",
|
|
4468
4694
|
payload: {
|
|
4469
4695
|
requestId: request.requestId,
|
|
4470
4696
|
workspace: descriptor,
|
|
@@ -4473,10 +4699,10 @@ export class Session {
|
|
|
4473
4699
|
});
|
|
4474
4700
|
}
|
|
4475
4701
|
catch (error) {
|
|
4476
|
-
const message = error instanceof Error ? error.message :
|
|
4477
|
-
this.sessionLogger.error({ err: error, cwd: request.cwd },
|
|
4702
|
+
const message = error instanceof Error ? error.message : "Failed to open project";
|
|
4703
|
+
this.sessionLogger.error({ err: error, cwd: request.cwd }, "Failed to open project");
|
|
4478
4704
|
this.emit({
|
|
4479
|
-
type:
|
|
4705
|
+
type: "open_project_response",
|
|
4480
4706
|
payload: {
|
|
4481
4707
|
requestId: request.requestId,
|
|
4482
4708
|
workspace: null,
|
|
@@ -4485,20 +4711,123 @@ export class Session {
|
|
|
4485
4711
|
});
|
|
4486
4712
|
}
|
|
4487
4713
|
}
|
|
4714
|
+
async handleCreatePaseoWorktreeRequest(request) {
|
|
4715
|
+
try {
|
|
4716
|
+
const checkout = await getCheckoutStatusLite(request.cwd, { paseoHome: this.paseoHome });
|
|
4717
|
+
if (!checkout.isGit) {
|
|
4718
|
+
throw new Error("Create worktree requires a git repository");
|
|
4719
|
+
}
|
|
4720
|
+
const repoRoot = checkout.isPaseoOwnedWorktree ? checkout.mainRepoRoot : request.cwd;
|
|
4721
|
+
const baseBranch = await resolveRepositoryDefaultBranch(repoRoot);
|
|
4722
|
+
if (!baseBranch) {
|
|
4723
|
+
throw new Error("Unable to resolve repository default branch");
|
|
4724
|
+
}
|
|
4725
|
+
const normalizedSlug = request.worktreeSlug ? slugify(request.worktreeSlug) : uuidv4();
|
|
4726
|
+
const validation = validateBranchSlug(normalizedSlug);
|
|
4727
|
+
if (!validation.valid) {
|
|
4728
|
+
throw new Error(`Invalid worktree name: ${validation.error}`);
|
|
4729
|
+
}
|
|
4730
|
+
const worktreePath = await computeWorktreePath(repoRoot, normalizedSlug, this.paseoHome);
|
|
4731
|
+
const workspace = await this.registerPendingWorktreeWorkspace({
|
|
4732
|
+
repoRoot,
|
|
4733
|
+
worktreePath,
|
|
4734
|
+
branchName: normalizedSlug,
|
|
4735
|
+
});
|
|
4736
|
+
const descriptor = await this.describeWorkspaceRecord(workspace);
|
|
4737
|
+
this.emit({
|
|
4738
|
+
type: "create_paseo_worktree_response",
|
|
4739
|
+
payload: {
|
|
4740
|
+
workspace: descriptor,
|
|
4741
|
+
error: null,
|
|
4742
|
+
setupTerminalId: null,
|
|
4743
|
+
requestId: request.requestId,
|
|
4744
|
+
},
|
|
4745
|
+
});
|
|
4746
|
+
void this.createPaseoWorktreeInBackground({
|
|
4747
|
+
requestCwd: request.cwd,
|
|
4748
|
+
repoRoot,
|
|
4749
|
+
baseBranch,
|
|
4750
|
+
slug: normalizedSlug,
|
|
4751
|
+
worktreePath,
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
catch (error) {
|
|
4755
|
+
const message = error instanceof Error ? error.message : "Failed to create worktree";
|
|
4756
|
+
this.sessionLogger.error({ err: error, cwd: request.cwd, worktreeSlug: request.worktreeSlug }, "Failed to create worktree");
|
|
4757
|
+
this.emit({
|
|
4758
|
+
type: "create_paseo_worktree_response",
|
|
4759
|
+
payload: {
|
|
4760
|
+
workspace: null,
|
|
4761
|
+
error: message,
|
|
4762
|
+
setupTerminalId: null,
|
|
4763
|
+
requestId: request.requestId,
|
|
4764
|
+
},
|
|
4765
|
+
});
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
async createPaseoWorktreeInBackground(options) {
|
|
4769
|
+
let setupTerminalId = null;
|
|
4770
|
+
try {
|
|
4771
|
+
await createAgentWorktree({
|
|
4772
|
+
cwd: options.repoRoot,
|
|
4773
|
+
branchName: options.slug,
|
|
4774
|
+
baseBranch: options.baseBranch,
|
|
4775
|
+
worktreeSlug: options.slug,
|
|
4776
|
+
paseoHome: this.paseoHome,
|
|
4777
|
+
});
|
|
4778
|
+
const setupCommands = getWorktreeSetupCommands(options.worktreePath);
|
|
4779
|
+
if (setupCommands.length > 0 && this.terminalManager) {
|
|
4780
|
+
const runtimeEnv = await resolveWorktreeRuntimeEnv({
|
|
4781
|
+
worktreePath: options.worktreePath,
|
|
4782
|
+
branchName: options.slug,
|
|
4783
|
+
repoRootPath: options.repoRoot,
|
|
4784
|
+
});
|
|
4785
|
+
this.terminalManager.registerCwdEnv({
|
|
4786
|
+
cwd: options.worktreePath,
|
|
4787
|
+
env: runtimeEnv,
|
|
4788
|
+
});
|
|
4789
|
+
const terminal = await this.terminalManager.createTerminal({
|
|
4790
|
+
cwd: options.worktreePath,
|
|
4791
|
+
name: `setup-${options.slug}`,
|
|
4792
|
+
env: runtimeEnv,
|
|
4793
|
+
});
|
|
4794
|
+
setupTerminalId = terminal.id;
|
|
4795
|
+
for (const command of setupCommands) {
|
|
4796
|
+
terminal.send({
|
|
4797
|
+
type: "input",
|
|
4798
|
+
data: `${command}\r`,
|
|
4799
|
+
});
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
catch (error) {
|
|
4804
|
+
this.sessionLogger.error({
|
|
4805
|
+
err: error,
|
|
4806
|
+
cwd: options.requestCwd,
|
|
4807
|
+
repoRoot: options.repoRoot,
|
|
4808
|
+
worktreeSlug: options.slug,
|
|
4809
|
+
worktreePath: options.worktreePath,
|
|
4810
|
+
setupTerminalId,
|
|
4811
|
+
}, "Background worktree creation failed");
|
|
4812
|
+
}
|
|
4813
|
+
finally {
|
|
4814
|
+
await this.emitWorkspaceUpdateForCwd(options.worktreePath);
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4488
4817
|
async handleArchiveWorkspaceRequest(request) {
|
|
4489
4818
|
try {
|
|
4490
4819
|
const existing = await this.workspaceRegistry.get(request.workspaceId);
|
|
4491
4820
|
if (!existing) {
|
|
4492
4821
|
throw new Error(`Workspace not found: ${request.workspaceId}`);
|
|
4493
4822
|
}
|
|
4494
|
-
if (existing.kind ===
|
|
4495
|
-
throw new Error(
|
|
4823
|
+
if (existing.kind === "worktree") {
|
|
4824
|
+
throw new Error("Use worktree archive for Paseo worktrees");
|
|
4496
4825
|
}
|
|
4497
4826
|
const archivedAt = new Date().toISOString();
|
|
4498
4827
|
await this.archiveWorkspaceRecord(request.workspaceId, archivedAt);
|
|
4499
4828
|
await this.emitWorkspaceUpdateForCwd(existing.cwd);
|
|
4500
4829
|
this.emit({
|
|
4501
|
-
type:
|
|
4830
|
+
type: "archive_workspace_response",
|
|
4502
4831
|
payload: {
|
|
4503
4832
|
requestId: request.requestId,
|
|
4504
4833
|
workspaceId: request.workspaceId,
|
|
@@ -4508,10 +4837,10 @@ export class Session {
|
|
|
4508
4837
|
});
|
|
4509
4838
|
}
|
|
4510
4839
|
catch (error) {
|
|
4511
|
-
const message = error instanceof Error ? error.message :
|
|
4512
|
-
this.sessionLogger.error({ err: error, workspaceId: request.workspaceId },
|
|
4840
|
+
const message = error instanceof Error ? error.message : "Failed to archive workspace";
|
|
4841
|
+
this.sessionLogger.error({ err: error, workspaceId: request.workspaceId }, "Failed to archive workspace");
|
|
4513
4842
|
this.emit({
|
|
4514
|
-
type:
|
|
4843
|
+
type: "archive_workspace_response",
|
|
4515
4844
|
payload: {
|
|
4516
4845
|
requestId: request.requestId,
|
|
4517
4846
|
workspaceId: request.workspaceId,
|
|
@@ -4525,7 +4854,7 @@ export class Session {
|
|
|
4525
4854
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
4526
4855
|
if (!resolved.ok) {
|
|
4527
4856
|
this.emit({
|
|
4528
|
-
type:
|
|
4857
|
+
type: "fetch_agent_response",
|
|
4529
4858
|
payload: { requestId, agent: null, project: null, error: resolved.error },
|
|
4530
4859
|
});
|
|
4531
4860
|
return;
|
|
@@ -4533,7 +4862,7 @@ export class Session {
|
|
|
4533
4862
|
const agent = await this.getAgentPayloadById(resolved.agentId);
|
|
4534
4863
|
if (!agent) {
|
|
4535
4864
|
this.emit({
|
|
4536
|
-
type:
|
|
4865
|
+
type: "fetch_agent_response",
|
|
4537
4866
|
payload: {
|
|
4538
4867
|
requestId,
|
|
4539
4868
|
agent: null,
|
|
@@ -4545,18 +4874,18 @@ export class Session {
|
|
|
4545
4874
|
}
|
|
4546
4875
|
const project = await this.buildProjectPlacement(agent.cwd);
|
|
4547
4876
|
this.emit({
|
|
4548
|
-
type:
|
|
4877
|
+
type: "fetch_agent_response",
|
|
4549
4878
|
payload: { requestId, agent, project, error: null },
|
|
4550
4879
|
});
|
|
4551
4880
|
}
|
|
4552
4881
|
async handleFetchAgentTimelineRequest(msg) {
|
|
4553
|
-
const direction = msg.direction ?? (msg.cursor ?
|
|
4554
|
-
const projection = msg.projection ??
|
|
4882
|
+
const direction = msg.direction ?? (msg.cursor ? "after" : "tail");
|
|
4883
|
+
const projection = msg.projection ?? "projected";
|
|
4555
4884
|
const requestedLimit = msg.limit;
|
|
4556
|
-
const limit = requestedLimit ?? (direction ===
|
|
4557
|
-
const shouldLimitByProjectedWindow = projection ===
|
|
4558
|
-
direction ===
|
|
4559
|
-
typeof requestedLimit ===
|
|
4885
|
+
const limit = requestedLimit ?? (direction === "after" ? 0 : undefined);
|
|
4886
|
+
const shouldLimitByProjectedWindow = projection === "canonical" &&
|
|
4887
|
+
direction === "tail" &&
|
|
4888
|
+
typeof requestedLimit === "number" &&
|
|
4560
4889
|
requestedLimit > 0;
|
|
4561
4890
|
const cursor = msg.cursor
|
|
4562
4891
|
? {
|
|
@@ -4570,7 +4899,7 @@ export class Session {
|
|
|
4570
4899
|
let timeline = this.agentManager.fetchTimeline(msg.agentId, {
|
|
4571
4900
|
direction,
|
|
4572
4901
|
cursor,
|
|
4573
|
-
limit: shouldLimitByProjectedWindow && typeof requestedLimit ===
|
|
4902
|
+
limit: shouldLimitByProjectedWindow && typeof requestedLimit === "number"
|
|
4574
4903
|
? Math.max(1, Math.floor(requestedLimit))
|
|
4575
4904
|
: limit,
|
|
4576
4905
|
});
|
|
@@ -4596,7 +4925,7 @@ export class Session {
|
|
|
4596
4925
|
const startsAtLoadedBoundary = firstLoadedRow != null &&
|
|
4597
4926
|
firstSelectedRow != null &&
|
|
4598
4927
|
firstSelectedRow.seq === firstLoadedRow.seq;
|
|
4599
|
-
const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type ===
|
|
4928
|
+
const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
|
|
4600
4929
|
if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
|
|
4601
4930
|
break;
|
|
4602
4931
|
}
|
|
@@ -4636,7 +4965,7 @@ export class Session {
|
|
|
4636
4965
|
entries = projectTimelineRows(timeline.rows, snapshot.provider, projection);
|
|
4637
4966
|
}
|
|
4638
4967
|
this.emit({
|
|
4639
|
-
type:
|
|
4968
|
+
type: "fetch_agent_timeline_response",
|
|
4640
4969
|
payload: {
|
|
4641
4970
|
requestId: msg.requestId,
|
|
4642
4971
|
agentId: msg.agentId,
|
|
@@ -4658,16 +4987,16 @@ export class Session {
|
|
|
4658
4987
|
});
|
|
4659
4988
|
}
|
|
4660
4989
|
catch (error) {
|
|
4661
|
-
this.sessionLogger.error({ err: error, agentId: msg.agentId },
|
|
4990
|
+
this.sessionLogger.error({ err: error, agentId: msg.agentId }, "Failed to handle fetch_agent_timeline_request");
|
|
4662
4991
|
this.emit({
|
|
4663
|
-
type:
|
|
4992
|
+
type: "fetch_agent_timeline_response",
|
|
4664
4993
|
payload: {
|
|
4665
4994
|
requestId: msg.requestId,
|
|
4666
4995
|
agentId: msg.agentId,
|
|
4667
4996
|
agent: null,
|
|
4668
4997
|
direction,
|
|
4669
4998
|
projection,
|
|
4670
|
-
epoch:
|
|
4999
|
+
epoch: "",
|
|
4671
5000
|
reset: false,
|
|
4672
5001
|
staleCursor: false,
|
|
4673
5002
|
gap: false,
|
|
@@ -4686,7 +5015,7 @@ export class Session {
|
|
|
4686
5015
|
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
4687
5016
|
if (!resolved.ok) {
|
|
4688
5017
|
this.emit({
|
|
4689
|
-
type:
|
|
5018
|
+
type: "send_agent_message_response",
|
|
4690
5019
|
payload: {
|
|
4691
5020
|
requestId: msg.requestId,
|
|
4692
5021
|
agentId: msg.agentId,
|
|
@@ -4707,13 +5036,13 @@ export class Session {
|
|
|
4707
5036
|
});
|
|
4708
5037
|
}
|
|
4709
5038
|
catch (error) {
|
|
4710
|
-
this.sessionLogger.error({ err: error, agentId },
|
|
5039
|
+
this.sessionLogger.error({ err: error, agentId }, "Failed to record user message for send_agent_message_request");
|
|
4711
5040
|
}
|
|
4712
5041
|
const prompt = this.buildAgentPrompt(msg.text, msg.images);
|
|
4713
5042
|
const started = this.startAgentStream(agentId, prompt);
|
|
4714
5043
|
if (!started.ok) {
|
|
4715
5044
|
this.emit({
|
|
4716
|
-
type:
|
|
5045
|
+
type: "send_agent_message_response",
|
|
4717
5046
|
payload: {
|
|
4718
5047
|
requestId: msg.requestId,
|
|
4719
5048
|
agentId,
|
|
@@ -4725,18 +5054,18 @@ export class Session {
|
|
|
4725
5054
|
}
|
|
4726
5055
|
const startAbort = new AbortController();
|
|
4727
5056
|
const startTimeoutMs = 15000;
|
|
4728
|
-
const startTimeout = setTimeout(() => startAbort.abort(
|
|
5057
|
+
const startTimeout = setTimeout(() => startAbort.abort("timeout"), startTimeoutMs);
|
|
4729
5058
|
try {
|
|
4730
5059
|
await this.agentManager.waitForAgentRunStart(agentId, { signal: startAbort.signal });
|
|
4731
5060
|
}
|
|
4732
5061
|
catch (error) {
|
|
4733
5062
|
const message = error instanceof Error
|
|
4734
5063
|
? error.message
|
|
4735
|
-
: typeof error ===
|
|
5064
|
+
: typeof error === "string"
|
|
4736
5065
|
? error
|
|
4737
|
-
:
|
|
5066
|
+
: "Unknown error";
|
|
4738
5067
|
this.emit({
|
|
4739
|
-
type:
|
|
5068
|
+
type: "send_agent_message_response",
|
|
4740
5069
|
payload: {
|
|
4741
5070
|
requestId: msg.requestId,
|
|
4742
5071
|
agentId,
|
|
@@ -4750,7 +5079,7 @@ export class Session {
|
|
|
4750
5079
|
clearTimeout(startTimeout);
|
|
4751
5080
|
}
|
|
4752
5081
|
this.emit({
|
|
4753
|
-
type:
|
|
5082
|
+
type: "send_agent_message_response",
|
|
4754
5083
|
payload: {
|
|
4755
5084
|
requestId: msg.requestId,
|
|
4756
5085
|
agentId,
|
|
@@ -4760,9 +5089,13 @@ export class Session {
|
|
|
4760
5089
|
});
|
|
4761
5090
|
}
|
|
4762
5091
|
catch (error) {
|
|
4763
|
-
const message = error instanceof Error
|
|
5092
|
+
const message = error instanceof Error
|
|
5093
|
+
? error.message
|
|
5094
|
+
: typeof error === "string"
|
|
5095
|
+
? error
|
|
5096
|
+
: "Unknown error";
|
|
4764
5097
|
this.emit({
|
|
4765
|
-
type:
|
|
5098
|
+
type: "send_agent_message_response",
|
|
4766
5099
|
payload: {
|
|
4767
5100
|
requestId: msg.requestId,
|
|
4768
5101
|
agentId: resolved.agentId,
|
|
@@ -4776,10 +5109,10 @@ export class Session {
|
|
|
4776
5109
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
4777
5110
|
if (!resolved.ok) {
|
|
4778
5111
|
this.emit({
|
|
4779
|
-
type:
|
|
5112
|
+
type: "wait_for_finish_response",
|
|
4780
5113
|
payload: {
|
|
4781
5114
|
requestId,
|
|
4782
|
-
status:
|
|
5115
|
+
status: "error",
|
|
4783
5116
|
final: null,
|
|
4784
5117
|
error: resolved.error,
|
|
4785
5118
|
lastMessage: null,
|
|
@@ -4793,10 +5126,10 @@ export class Session {
|
|
|
4793
5126
|
const record = await this.agentStorage.get(agentId);
|
|
4794
5127
|
if (!record || record.internal) {
|
|
4795
5128
|
this.emit({
|
|
4796
|
-
type:
|
|
5129
|
+
type: "wait_for_finish_response",
|
|
4797
5130
|
payload: {
|
|
4798
5131
|
requestId,
|
|
4799
|
-
status:
|
|
5132
|
+
status: "error",
|
|
4800
5133
|
final: null,
|
|
4801
5134
|
error: `Agent not found: ${agentId}`,
|
|
4802
5135
|
lastMessage: null,
|
|
@@ -4805,22 +5138,22 @@ export class Session {
|
|
|
4805
5138
|
return;
|
|
4806
5139
|
}
|
|
4807
5140
|
const final = this.buildStoredAgentPayload(record);
|
|
4808
|
-
const status = record.attentionReason ===
|
|
4809
|
-
?
|
|
4810
|
-
: record.lastStatus ===
|
|
4811
|
-
?
|
|
4812
|
-
:
|
|
5141
|
+
const status = record.attentionReason === "permission"
|
|
5142
|
+
? "permission"
|
|
5143
|
+
: record.lastStatus === "error"
|
|
5144
|
+
? "error"
|
|
5145
|
+
: "idle";
|
|
4813
5146
|
this.emit({
|
|
4814
|
-
type:
|
|
5147
|
+
type: "wait_for_finish_response",
|
|
4815
5148
|
payload: { requestId, status, final, error: null, lastMessage: null },
|
|
4816
5149
|
});
|
|
4817
5150
|
return;
|
|
4818
5151
|
}
|
|
4819
5152
|
const abortController = new AbortController();
|
|
4820
|
-
const hasTimeout = typeof timeoutMs ===
|
|
5153
|
+
const hasTimeout = typeof timeoutMs === "number" && timeoutMs > 0;
|
|
4821
5154
|
const timeoutHandle = hasTimeout
|
|
4822
5155
|
? setTimeout(() => {
|
|
4823
|
-
abortController.abort(
|
|
5156
|
+
abortController.abort("timeout");
|
|
4824
5157
|
}, timeoutMs)
|
|
4825
5158
|
: null;
|
|
4826
5159
|
try {
|
|
@@ -4831,28 +5164,32 @@ export class Session {
|
|
|
4831
5164
|
if (!final) {
|
|
4832
5165
|
throw new Error(`Agent ${agentId} disappeared while waiting`);
|
|
4833
5166
|
}
|
|
4834
|
-
let status = result.permission
|
|
5167
|
+
let status = result.permission
|
|
5168
|
+
? "permission"
|
|
5169
|
+
: result.status === "error"
|
|
5170
|
+
? "error"
|
|
5171
|
+
: "idle";
|
|
4835
5172
|
this.emit({
|
|
4836
|
-
type:
|
|
5173
|
+
type: "wait_for_finish_response",
|
|
4837
5174
|
payload: { requestId, status, final, error: null, lastMessage: result.lastMessage },
|
|
4838
5175
|
});
|
|
4839
5176
|
}
|
|
4840
5177
|
catch (error) {
|
|
4841
5178
|
const isAbort = error instanceof Error &&
|
|
4842
|
-
(error.name ===
|
|
5179
|
+
(error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
|
|
4843
5180
|
if (!isAbort) {
|
|
4844
5181
|
const message = error instanceof Error
|
|
4845
5182
|
? error.message
|
|
4846
|
-
: typeof error ===
|
|
5183
|
+
: typeof error === "string"
|
|
4847
5184
|
? error
|
|
4848
|
-
:
|
|
4849
|
-
this.sessionLogger.error({ err: error, agentId },
|
|
5185
|
+
: "Unknown error";
|
|
5186
|
+
this.sessionLogger.error({ err: error, agentId }, "wait_for_finish_request failed");
|
|
4850
5187
|
const final = await this.getAgentPayloadById(agentId);
|
|
4851
5188
|
this.emit({
|
|
4852
|
-
type:
|
|
5189
|
+
type: "wait_for_finish_response",
|
|
4853
5190
|
payload: {
|
|
4854
5191
|
requestId,
|
|
4855
|
-
status:
|
|
5192
|
+
status: "error",
|
|
4856
5193
|
final,
|
|
4857
5194
|
error: message,
|
|
4858
5195
|
lastMessage: null,
|
|
@@ -4865,8 +5202,8 @@ export class Session {
|
|
|
4865
5202
|
throw new Error(`Agent ${agentId} disappeared while waiting`);
|
|
4866
5203
|
}
|
|
4867
5204
|
this.emit({
|
|
4868
|
-
type:
|
|
4869
|
-
payload: { requestId, status:
|
|
5205
|
+
type: "wait_for_finish_response",
|
|
5206
|
+
payload: { requestId, status: "timeout", final, error: null, lastMessage: null },
|
|
4870
5207
|
});
|
|
4871
5208
|
}
|
|
4872
5209
|
finally {
|
|
@@ -4880,31 +5217,30 @@ export class Session {
|
|
|
4880
5217
|
*/
|
|
4881
5218
|
async handleAudioChunk(msg) {
|
|
4882
5219
|
if (!this.isVoiceMode) {
|
|
4883
|
-
this.sessionLogger.warn(
|
|
5220
|
+
this.sessionLogger.warn("Received voice_audio_chunk while voice mode is disabled; transcript will be emitted but voice assistant turn is skipped");
|
|
4884
5221
|
}
|
|
4885
|
-
const chunkFormat = msg.format ||
|
|
5222
|
+
const chunkFormat = msg.format || "audio/wav";
|
|
4886
5223
|
if (this.isVoiceMode) {
|
|
4887
5224
|
if (!this.voiceTurnController) {
|
|
4888
|
-
throw new Error(
|
|
5225
|
+
throw new Error("Voice mode is enabled but the voice turn controller is not running");
|
|
4889
5226
|
}
|
|
4890
|
-
const chunkBytes = Buffer.byteLength(msg.audio,
|
|
5227
|
+
const chunkBytes = Buffer.byteLength(msg.audio, "base64");
|
|
4891
5228
|
this.voiceInputChunkCount += 1;
|
|
4892
5229
|
this.voiceInputBytes += chunkBytes;
|
|
4893
5230
|
if (this.voiceInputChunkCount === 1) {
|
|
4894
5231
|
this.sessionLogger.info({
|
|
4895
5232
|
format: chunkFormat,
|
|
4896
5233
|
audioBytes: chunkBytes,
|
|
4897
|
-
},
|
|
5234
|
+
}, "Received first voice_audio_chunk for active voice mode");
|
|
4898
5235
|
}
|
|
4899
5236
|
const now = Date.now();
|
|
4900
|
-
if (this.voiceInputChunkCount % 50 === 0 ||
|
|
4901
|
-
now - this.voiceInputWindowStartedAt >= 1000) {
|
|
5237
|
+
if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
|
|
4902
5238
|
this.sessionLogger.info({
|
|
4903
5239
|
chunkCount: this.voiceInputChunkCount,
|
|
4904
5240
|
audioBytes: this.voiceInputBytes,
|
|
4905
5241
|
windowMs: now - this.voiceInputWindowStartedAt,
|
|
4906
5242
|
format: chunkFormat,
|
|
4907
|
-
},
|
|
5243
|
+
}, "Voice input chunk summary");
|
|
4908
5244
|
this.voiceInputWindowStartedAt = now;
|
|
4909
5245
|
this.voiceInputChunkCount = 0;
|
|
4910
5246
|
this.voiceInputBytes = 0;
|
|
@@ -4915,8 +5251,8 @@ export class Session {
|
|
|
4915
5251
|
});
|
|
4916
5252
|
return;
|
|
4917
5253
|
}
|
|
4918
|
-
const chunkBuffer = Buffer.from(msg.audio,
|
|
4919
|
-
const isPCMChunk = chunkFormat.toLowerCase().includes(
|
|
5254
|
+
const chunkBuffer = Buffer.from(msg.audio, "base64");
|
|
5255
|
+
const isPCMChunk = chunkFormat.toLowerCase().includes("pcm");
|
|
4920
5256
|
if (!this.audioBuffer) {
|
|
4921
5257
|
this.audioBuffer = {
|
|
4922
5258
|
chunks: [],
|
|
@@ -4928,7 +5264,7 @@ export class Session {
|
|
|
4928
5264
|
// If the format changes mid-stream, flush what we have first
|
|
4929
5265
|
if (this.audioBuffer.isPCM !== isPCMChunk) {
|
|
4930
5266
|
this.sessionLogger.debug({
|
|
4931
|
-
oldFormat: this.audioBuffer.isPCM ?
|
|
5267
|
+
oldFormat: this.audioBuffer.isPCM ? "pcm" : this.audioBuffer.format,
|
|
4932
5268
|
newFormat: chunkFormat,
|
|
4933
5269
|
}, `Audio format changed mid-stream, flushing current buffer`);
|
|
4934
5270
|
const finalized = this.finalizeBufferedAudio();
|
|
@@ -4984,7 +5320,7 @@ export class Session {
|
|
|
4984
5320
|
const wavBuffer = convertPCMToWavBuffer(pcmBuffer, PCM_SAMPLE_RATE, PCM_CHANNELS, PCM_BITS_PER_SAMPLE);
|
|
4985
5321
|
return {
|
|
4986
5322
|
audio: wavBuffer,
|
|
4987
|
-
format:
|
|
5323
|
+
format: "audio/wav",
|
|
4988
5324
|
};
|
|
4989
5325
|
}
|
|
4990
5326
|
return {
|
|
@@ -4993,7 +5329,7 @@ export class Session {
|
|
|
4993
5329
|
};
|
|
4994
5330
|
}
|
|
4995
5331
|
async processCompletedAudio(audio, format) {
|
|
4996
|
-
if (this.processingPhase ===
|
|
5332
|
+
if (this.processingPhase === "transcribing") {
|
|
4997
5333
|
this.sessionLogger.debug({ phase: this.processingPhase, segmentCount: this.pendingAudioSegments.length + 1 }, `Buffering audio segment (phase: ${this.processingPhase})`);
|
|
4998
5334
|
this.pendingAudioSegments.push({
|
|
4999
5335
|
audio,
|
|
@@ -5019,7 +5355,7 @@ export class Session {
|
|
|
5019
5355
|
await this.processAudio(audio, format);
|
|
5020
5356
|
}
|
|
5021
5357
|
async flushPendingAudioSegments(reason) {
|
|
5022
|
-
if (this.processingPhase ===
|
|
5358
|
+
if (this.processingPhase === "transcribing" || this.pendingAudioSegments.length === 0) {
|
|
5023
5359
|
return;
|
|
5024
5360
|
}
|
|
5025
5361
|
const pendingSegments = [...this.pendingAudioSegments];
|
|
@@ -5034,21 +5370,21 @@ export class Session {
|
|
|
5034
5370
|
* Process audio through STT and then LLM
|
|
5035
5371
|
*/
|
|
5036
5372
|
async processAudio(audio, format) {
|
|
5037
|
-
this.setPhase(
|
|
5373
|
+
this.setPhase("transcribing");
|
|
5038
5374
|
this.emit({
|
|
5039
|
-
type:
|
|
5375
|
+
type: "activity_log",
|
|
5040
5376
|
payload: {
|
|
5041
5377
|
id: uuidv4(),
|
|
5042
5378
|
timestamp: new Date(),
|
|
5043
|
-
type:
|
|
5044
|
-
content:
|
|
5379
|
+
type: "system",
|
|
5380
|
+
content: "Transcribing audio...",
|
|
5045
5381
|
},
|
|
5046
5382
|
});
|
|
5047
5383
|
try {
|
|
5048
5384
|
const requestId = uuidv4();
|
|
5049
5385
|
const result = await this.sttManager.transcribe(audio, format, {
|
|
5050
5386
|
requestId,
|
|
5051
|
-
label: this.isVoiceMode ?
|
|
5387
|
+
label: this.isVoiceMode ? "voice" : "buffered",
|
|
5052
5388
|
});
|
|
5053
5389
|
const transcriptText = result.text.trim();
|
|
5054
5390
|
this.sessionLogger.info({
|
|
@@ -5056,7 +5392,7 @@ export class Session {
|
|
|
5056
5392
|
isVoiceMode: this.isVoiceMode,
|
|
5057
5393
|
transcriptLength: transcriptText.length,
|
|
5058
5394
|
transcript: transcriptText,
|
|
5059
|
-
},
|
|
5395
|
+
}, "Transcription result");
|
|
5060
5396
|
await this.handleTranscriptionResultPayload({
|
|
5061
5397
|
text: result.text,
|
|
5062
5398
|
language: result.language,
|
|
@@ -5070,15 +5406,15 @@ export class Session {
|
|
|
5070
5406
|
});
|
|
5071
5407
|
}
|
|
5072
5408
|
catch (error) {
|
|
5073
|
-
this.setPhase(
|
|
5074
|
-
this.clearSpeechInProgress(
|
|
5075
|
-
await this.flushPendingAudioSegments(
|
|
5409
|
+
this.setPhase("idle");
|
|
5410
|
+
this.clearSpeechInProgress("transcription error");
|
|
5411
|
+
await this.flushPendingAudioSegments("transcription error");
|
|
5076
5412
|
this.emit({
|
|
5077
|
-
type:
|
|
5413
|
+
type: "activity_log",
|
|
5078
5414
|
payload: {
|
|
5079
5415
|
id: uuidv4(),
|
|
5080
5416
|
timestamp: new Date(),
|
|
5081
|
-
type:
|
|
5417
|
+
type: "error",
|
|
5082
5418
|
content: `Transcription error: ${error.message}`,
|
|
5083
5419
|
},
|
|
5084
5420
|
});
|
|
@@ -5088,7 +5424,7 @@ export class Session {
|
|
|
5088
5424
|
async handleTranscriptionResultPayload(result) {
|
|
5089
5425
|
const transcriptText = result.text.trim();
|
|
5090
5426
|
this.emit({
|
|
5091
|
-
type:
|
|
5427
|
+
type: "transcription_result",
|
|
5092
5428
|
payload: {
|
|
5093
5429
|
text: result.text,
|
|
5094
5430
|
...(result.language ? { language: result.language } : {}),
|
|
@@ -5104,21 +5440,21 @@ export class Session {
|
|
|
5104
5440
|
},
|
|
5105
5441
|
});
|
|
5106
5442
|
if (!transcriptText) {
|
|
5107
|
-
this.sessionLogger.debug(
|
|
5108
|
-
this.setPhase(
|
|
5109
|
-
this.clearSpeechInProgress(
|
|
5110
|
-
await this.flushPendingAudioSegments(
|
|
5443
|
+
this.sessionLogger.debug("Empty transcription (false positive), not aborting");
|
|
5444
|
+
this.setPhase("idle");
|
|
5445
|
+
this.clearSpeechInProgress("empty transcription");
|
|
5446
|
+
await this.flushPendingAudioSegments("empty transcription");
|
|
5111
5447
|
return;
|
|
5112
5448
|
}
|
|
5113
5449
|
// Has content - abort any in-progress stream now
|
|
5114
5450
|
this.createAbortController();
|
|
5115
5451
|
if (result.debugRecordingPath) {
|
|
5116
5452
|
this.emit({
|
|
5117
|
-
type:
|
|
5453
|
+
type: "activity_log",
|
|
5118
5454
|
payload: {
|
|
5119
5455
|
id: uuidv4(),
|
|
5120
5456
|
timestamp: new Date(),
|
|
5121
|
-
type:
|
|
5457
|
+
type: "system",
|
|
5122
5458
|
content: `Saved input audio: ${result.debugRecordingPath}`,
|
|
5123
5459
|
metadata: {
|
|
5124
5460
|
recordingPath: result.debugRecordingPath,
|
|
@@ -5129,11 +5465,11 @@ export class Session {
|
|
|
5129
5465
|
});
|
|
5130
5466
|
}
|
|
5131
5467
|
this.emit({
|
|
5132
|
-
type:
|
|
5468
|
+
type: "activity_log",
|
|
5133
5469
|
payload: {
|
|
5134
5470
|
id: uuidv4(),
|
|
5135
5471
|
timestamp: new Date(),
|
|
5136
|
-
type:
|
|
5472
|
+
type: "transcript",
|
|
5137
5473
|
content: result.text,
|
|
5138
5474
|
metadata: {
|
|
5139
5475
|
...(result.language ? { language: result.language } : {}),
|
|
@@ -5141,23 +5477,23 @@ export class Session {
|
|
|
5141
5477
|
},
|
|
5142
5478
|
},
|
|
5143
5479
|
});
|
|
5144
|
-
this.clearSpeechInProgress(
|
|
5145
|
-
this.setPhase(
|
|
5480
|
+
this.clearSpeechInProgress("transcription complete");
|
|
5481
|
+
this.setPhase("idle");
|
|
5146
5482
|
if (!this.isVoiceMode) {
|
|
5147
|
-
this.sessionLogger.debug({ requestId: result.requestId },
|
|
5148
|
-
await this.flushPendingAudioSegments(
|
|
5483
|
+
this.sessionLogger.debug({ requestId: result.requestId }, "Skipping voice agent processing because voice mode is disabled");
|
|
5484
|
+
await this.flushPendingAudioSegments("voice mode disabled");
|
|
5149
5485
|
return;
|
|
5150
5486
|
}
|
|
5151
5487
|
const agentId = this.voiceModeAgentId;
|
|
5152
5488
|
if (!agentId) {
|
|
5153
|
-
this.sessionLogger.warn({ requestId: result.requestId },
|
|
5154
|
-
await this.flushPendingAudioSegments(
|
|
5489
|
+
this.sessionLogger.warn({ requestId: result.requestId }, "Skipping voice agent processing because no agent is currently voice-enabled");
|
|
5490
|
+
await this.flushPendingAudioSegments("no active voice agent");
|
|
5155
5491
|
return;
|
|
5156
5492
|
}
|
|
5157
5493
|
// Route voice utterances through the same send path as regular text input:
|
|
5158
5494
|
// interrupt-if-running, record message, then start a new stream.
|
|
5159
5495
|
await this.handleSendAgentMessage(agentId, result.text);
|
|
5160
|
-
await this.flushPendingAudioSegments(
|
|
5496
|
+
await this.flushPendingAudioSegments("transcription complete");
|
|
5161
5497
|
}
|
|
5162
5498
|
registerVoiceBridgeForAgent(agentId) {
|
|
5163
5499
|
this.registerVoiceSpeakHandler?.(agentId, async ({ text, signal }) => {
|
|
@@ -5165,16 +5501,16 @@ export class Session {
|
|
|
5165
5501
|
agentId,
|
|
5166
5502
|
textLength: text.length,
|
|
5167
5503
|
preview: text.slice(0, 160),
|
|
5168
|
-
},
|
|
5504
|
+
}, "Voice speak tool call received by session handler");
|
|
5169
5505
|
const abortSignal = signal ?? this.abortController.signal;
|
|
5170
5506
|
await this.ttsManager.generateAndWaitForPlayback(text, (msg) => this.emit(msg), abortSignal, true);
|
|
5171
|
-
this.sessionLogger.info({ agentId, textLength: text.length },
|
|
5507
|
+
this.sessionLogger.info({ agentId, textLength: text.length }, "Voice speak tool call finished playback");
|
|
5172
5508
|
this.emit({
|
|
5173
|
-
type:
|
|
5509
|
+
type: "activity_log",
|
|
5174
5510
|
payload: {
|
|
5175
5511
|
id: uuidv4(),
|
|
5176
5512
|
timestamp: new Date(),
|
|
5177
|
-
type:
|
|
5513
|
+
type: "assistant",
|
|
5178
5514
|
content: text,
|
|
5179
5515
|
},
|
|
5180
5516
|
});
|
|
@@ -5191,24 +5527,24 @@ export class Session {
|
|
|
5191
5527
|
async handleAbort() {
|
|
5192
5528
|
this.sessionLogger.info({ phase: this.processingPhase }, `Abort request, phase: ${this.processingPhase}`);
|
|
5193
5529
|
this.abortController.abort();
|
|
5194
|
-
this.ttsManager.cancelPendingPlaybacks(
|
|
5530
|
+
this.ttsManager.cancelPendingPlaybacks("abort request");
|
|
5195
5531
|
// Voice abort should always interrupt active agent output immediately.
|
|
5196
5532
|
if (this.isVoiceMode && this.voiceModeAgentId) {
|
|
5197
5533
|
try {
|
|
5198
5534
|
await this.interruptAgentIfRunning(this.voiceModeAgentId);
|
|
5199
5535
|
}
|
|
5200
5536
|
catch (error) {
|
|
5201
|
-
this.sessionLogger.warn({ err: error, agentId: this.voiceModeAgentId },
|
|
5537
|
+
this.sessionLogger.warn({ err: error, agentId: this.voiceModeAgentId }, "Failed to interrupt active voice-mode agent on abort");
|
|
5202
5538
|
}
|
|
5203
5539
|
}
|
|
5204
|
-
if (this.processingPhase ===
|
|
5540
|
+
if (this.processingPhase === "transcribing") {
|
|
5205
5541
|
// Still in STT phase - we'll buffer the next audio
|
|
5206
|
-
this.sessionLogger.debug(
|
|
5542
|
+
this.sessionLogger.debug("Will buffer next audio (currently transcribing)");
|
|
5207
5543
|
// Phase stays as 'transcribing', handleAudioChunk will handle buffering
|
|
5208
5544
|
return;
|
|
5209
5545
|
}
|
|
5210
5546
|
// Reset phase to idle and clear pending non-voice buffers.
|
|
5211
|
-
this.setPhase(
|
|
5547
|
+
this.setPhase("idle");
|
|
5212
5548
|
this.pendingAudioSegments = [];
|
|
5213
5549
|
this.clearBufferTimeout();
|
|
5214
5550
|
}
|
|
@@ -5229,20 +5565,20 @@ export class Session {
|
|
|
5229
5565
|
const phaseBeforeAbort = this.processingPhase;
|
|
5230
5566
|
const hadActiveStream = this.hasActiveAgentRun(this.voiceModeAgentId);
|
|
5231
5567
|
this.speechInProgress = true;
|
|
5232
|
-
this.sessionLogger.debug(
|
|
5568
|
+
this.sessionLogger.debug("Voice speech detected – aborting playback and active agent run");
|
|
5233
5569
|
if (this.pendingAudioSegments.length > 0) {
|
|
5234
5570
|
this.sessionLogger.debug({ segmentCount: this.pendingAudioSegments.length }, `Dropping ${this.pendingAudioSegments.length} buffered audio segment(s) due to voice speech`);
|
|
5235
5571
|
this.pendingAudioSegments = [];
|
|
5236
5572
|
}
|
|
5237
5573
|
if (this.audioBuffer) {
|
|
5238
|
-
this.sessionLogger.debug({ chunks: this.audioBuffer.chunks.length, pcmBytes: this.audioBuffer.totalPCMBytes }, `Clearing partial audio buffer (${this.audioBuffer.chunks.length} chunk(s)${this.audioBuffer.isPCM ? `, ${this.audioBuffer.totalPCMBytes} PCM bytes` :
|
|
5574
|
+
this.sessionLogger.debug({ chunks: this.audioBuffer.chunks.length, pcmBytes: this.audioBuffer.totalPCMBytes }, `Clearing partial audio buffer (${this.audioBuffer.chunks.length} chunk(s)${this.audioBuffer.isPCM ? `, ${this.audioBuffer.totalPCMBytes} PCM bytes` : ""})`);
|
|
5239
5575
|
this.audioBuffer = null;
|
|
5240
5576
|
}
|
|
5241
5577
|
this.clearBufferTimeout();
|
|
5242
5578
|
this.abortController.abort();
|
|
5243
5579
|
await this.handleAbort();
|
|
5244
5580
|
const latencyMs = Date.now() - chunkReceivedAt;
|
|
5245
|
-
this.sessionLogger.debug({ latencyMs, phaseBeforeAbort, hadActiveStream },
|
|
5581
|
+
this.sessionLogger.debug({ latencyMs, phaseBeforeAbort, hadActiveStream }, "[Telemetry] barge_in.llm_abort_latency");
|
|
5246
5582
|
}
|
|
5247
5583
|
/**
|
|
5248
5584
|
* Clear speech-in-progress flag once the user turn has completed
|
|
@@ -5277,9 +5613,9 @@ export class Session {
|
|
|
5277
5613
|
setBufferTimeout() {
|
|
5278
5614
|
this.clearBufferTimeout();
|
|
5279
5615
|
this.bufferTimeout = setTimeout(async () => {
|
|
5280
|
-
this.sessionLogger.debug(
|
|
5281
|
-
if (this.processingPhase ===
|
|
5282
|
-
this.sessionLogger.debug({ segmentCount: this.pendingAudioSegments.length },
|
|
5616
|
+
this.sessionLogger.debug("Buffer timeout reached, processing pending segments");
|
|
5617
|
+
if (this.processingPhase === "transcribing") {
|
|
5618
|
+
this.sessionLogger.debug({ segmentCount: this.pendingAudioSegments.length }, "Buffer timeout deferred because transcription is still in progress");
|
|
5283
5619
|
this.setBufferTimeout();
|
|
5284
5620
|
return;
|
|
5285
5621
|
}
|
|
@@ -5305,15 +5641,15 @@ export class Session {
|
|
|
5305
5641
|
* Emit a message to the client
|
|
5306
5642
|
*/
|
|
5307
5643
|
emit(msg) {
|
|
5308
|
-
if (msg.type ===
|
|
5644
|
+
if (msg.type === "audio_output" &&
|
|
5309
5645
|
(process.env.TTS_DEBUG_AUDIO_DIR || isPaseoDictationDebugEnabled()) &&
|
|
5310
5646
|
msg.payload.groupId &&
|
|
5311
|
-
typeof msg.payload.audio ===
|
|
5647
|
+
typeof msg.payload.audio === "string") {
|
|
5312
5648
|
const groupId = msg.payload.groupId;
|
|
5313
5649
|
const existing = this.ttsDebugStreams.get(groupId) ??
|
|
5314
5650
|
{ format: msg.payload.format, chunks: [] };
|
|
5315
5651
|
try {
|
|
5316
|
-
existing.chunks.push(Buffer.from(msg.payload.audio,
|
|
5652
|
+
existing.chunks.push(Buffer.from(msg.payload.audio, "base64"));
|
|
5317
5653
|
existing.format = msg.payload.format;
|
|
5318
5654
|
this.ttsDebugStreams.set(groupId, existing);
|
|
5319
5655
|
}
|
|
@@ -5328,11 +5664,11 @@ export class Session {
|
|
|
5328
5664
|
const recordingPath = await maybePersistTtsDebugAudio(Buffer.concat(final.chunks), { sessionId: this.sessionId, groupId, format: final.format }, this.sessionLogger);
|
|
5329
5665
|
if (recordingPath) {
|
|
5330
5666
|
this.onMessage({
|
|
5331
|
-
type:
|
|
5667
|
+
type: "activity_log",
|
|
5332
5668
|
payload: {
|
|
5333
5669
|
id: uuidv4(),
|
|
5334
5670
|
timestamp: new Date(),
|
|
5335
|
-
type:
|
|
5671
|
+
type: "system",
|
|
5336
5672
|
content: `Saved TTS audio: ${recordingPath}`,
|
|
5337
5673
|
metadata: { recordingPath, format: final.format, groupId },
|
|
5338
5674
|
},
|
|
@@ -5352,14 +5688,14 @@ export class Session {
|
|
|
5352
5688
|
this.onBinaryMessage(frame);
|
|
5353
5689
|
}
|
|
5354
5690
|
catch (error) {
|
|
5355
|
-
this.sessionLogger.error({ err: error },
|
|
5691
|
+
this.sessionLogger.error({ err: error }, "Failed to emit binary frame");
|
|
5356
5692
|
}
|
|
5357
5693
|
}
|
|
5358
5694
|
/**
|
|
5359
5695
|
* Clean up session resources
|
|
5360
5696
|
*/
|
|
5361
5697
|
async cleanup() {
|
|
5362
|
-
this.sessionLogger.trace(
|
|
5698
|
+
this.sessionLogger.trace("Cleaning up");
|
|
5363
5699
|
if (this.unsubscribeAgentEvents) {
|
|
5364
5700
|
this.unsubscribeAgentEvents();
|
|
5365
5701
|
this.unsubscribeAgentEvents = null;
|
|
@@ -5382,7 +5718,7 @@ export class Session {
|
|
|
5382
5718
|
await this.agentMcpClient.close();
|
|
5383
5719
|
}
|
|
5384
5720
|
catch (error) {
|
|
5385
|
-
this.sessionLogger.error({ err: error },
|
|
5721
|
+
this.sessionLogger.error({ err: error }, "Failed to close Agent MCP client");
|
|
5386
5722
|
}
|
|
5387
5723
|
this.agentMcpClient = null;
|
|
5388
5724
|
this.agentTools = null;
|
|
@@ -5395,20 +5731,20 @@ export class Session {
|
|
|
5395
5731
|
this.unsubscribeTerminalsChanged = null;
|
|
5396
5732
|
}
|
|
5397
5733
|
this.subscribedTerminalDirectories.clear();
|
|
5398
|
-
for (const unsubscribe of this.terminalSubscriptions.values()) {
|
|
5399
|
-
unsubscribe();
|
|
5400
|
-
}
|
|
5401
|
-
this.terminalSubscriptions.clear();
|
|
5402
5734
|
for (const unsubscribeExit of this.terminalExitSubscriptions.values()) {
|
|
5403
5735
|
unsubscribeExit();
|
|
5404
5736
|
}
|
|
5405
5737
|
this.terminalExitSubscriptions.clear();
|
|
5406
|
-
this.
|
|
5738
|
+
this.disposeTerminalSubscriptions();
|
|
5407
5739
|
for (const target of this.checkoutDiffTargets.values()) {
|
|
5408
5740
|
this.closeCheckoutDiffWatchTarget(target);
|
|
5409
5741
|
}
|
|
5410
5742
|
this.checkoutDiffTargets.clear();
|
|
5411
5743
|
this.checkoutDiffSubscriptions.clear();
|
|
5744
|
+
for (const target of this.workspaceGitWatchTargets.values()) {
|
|
5745
|
+
this.closeWorkspaceGitWatchTarget(target);
|
|
5746
|
+
}
|
|
5747
|
+
this.workspaceGitWatchTargets.clear();
|
|
5412
5748
|
}
|
|
5413
5749
|
// ============================================================================
|
|
5414
5750
|
// Terminal Handlers
|
|
@@ -5428,24 +5764,11 @@ export class Session {
|
|
|
5428
5764
|
unsubscribeExit();
|
|
5429
5765
|
this.terminalExitSubscriptions.delete(terminalId);
|
|
5430
5766
|
}
|
|
5431
|
-
|
|
5432
|
-
if (unsubscribe) {
|
|
5433
|
-
try {
|
|
5434
|
-
unsubscribe();
|
|
5435
|
-
}
|
|
5436
|
-
catch (error) {
|
|
5437
|
-
this.sessionLogger.warn({ err: error, terminalId }, 'Failed to unsubscribe terminal after process exit');
|
|
5438
|
-
}
|
|
5439
|
-
this.terminalSubscriptions.delete(terminalId);
|
|
5440
|
-
}
|
|
5441
|
-
const streamId = this.terminalStreamByTerminalId.get(terminalId);
|
|
5442
|
-
if (typeof streamId === 'number') {
|
|
5443
|
-
this.detachTerminalStream(streamId, { emitExit: true });
|
|
5444
|
-
}
|
|
5767
|
+
this.detachTerminalStream(terminalId, { emitExit: true });
|
|
5445
5768
|
}
|
|
5446
5769
|
emitTerminalsChangedSnapshot(input) {
|
|
5447
5770
|
this.emit({
|
|
5448
|
-
type:
|
|
5771
|
+
type: "terminals_changed",
|
|
5449
5772
|
payload: {
|
|
5450
5773
|
cwd: input.cwd,
|
|
5451
5774
|
terminals: input.terminals,
|
|
@@ -5492,13 +5815,13 @@ export class Session {
|
|
|
5492
5815
|
});
|
|
5493
5816
|
}
|
|
5494
5817
|
catch (error) {
|
|
5495
|
-
this.sessionLogger.warn({ err: error, cwd },
|
|
5818
|
+
this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
|
|
5496
5819
|
}
|
|
5497
5820
|
}
|
|
5498
5821
|
async handleListTerminalsRequest(msg) {
|
|
5499
5822
|
if (!this.terminalManager) {
|
|
5500
5823
|
this.emit({
|
|
5501
|
-
type:
|
|
5824
|
+
type: "list_terminals_response",
|
|
5502
5825
|
payload: {
|
|
5503
5826
|
cwd: msg.cwd,
|
|
5504
5827
|
terminals: [],
|
|
@@ -5513,7 +5836,7 @@ export class Session {
|
|
|
5513
5836
|
this.ensureTerminalExitSubscription(terminal);
|
|
5514
5837
|
}
|
|
5515
5838
|
this.emit({
|
|
5516
|
-
type:
|
|
5839
|
+
type: "list_terminals_response",
|
|
5517
5840
|
payload: {
|
|
5518
5841
|
cwd: msg.cwd,
|
|
5519
5842
|
terminals: terminals.map((t) => ({ id: t.id, name: t.name })),
|
|
@@ -5522,9 +5845,9 @@ export class Session {
|
|
|
5522
5845
|
});
|
|
5523
5846
|
}
|
|
5524
5847
|
catch (error) {
|
|
5525
|
-
this.sessionLogger.error({ err: error, cwd: msg.cwd },
|
|
5848
|
+
this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to list terminals");
|
|
5526
5849
|
this.emit({
|
|
5527
|
-
type:
|
|
5850
|
+
type: "list_terminals_response",
|
|
5528
5851
|
payload: {
|
|
5529
5852
|
cwd: msg.cwd,
|
|
5530
5853
|
terminals: [],
|
|
@@ -5536,10 +5859,10 @@ export class Session {
|
|
|
5536
5859
|
async handleCreateTerminalRequest(msg) {
|
|
5537
5860
|
if (!this.terminalManager) {
|
|
5538
5861
|
this.emit({
|
|
5539
|
-
type:
|
|
5862
|
+
type: "create_terminal_response",
|
|
5540
5863
|
payload: {
|
|
5541
5864
|
terminal: null,
|
|
5542
|
-
error:
|
|
5865
|
+
error: "Terminal manager not available",
|
|
5543
5866
|
requestId: msg.requestId,
|
|
5544
5867
|
},
|
|
5545
5868
|
});
|
|
@@ -5552,7 +5875,7 @@ export class Session {
|
|
|
5552
5875
|
});
|
|
5553
5876
|
this.ensureTerminalExitSubscription(session);
|
|
5554
5877
|
this.emit({
|
|
5555
|
-
type:
|
|
5878
|
+
type: "create_terminal_response",
|
|
5556
5879
|
payload: {
|
|
5557
5880
|
terminal: { id: session.id, name: session.name, cwd: session.cwd },
|
|
5558
5881
|
error: null,
|
|
@@ -5561,9 +5884,9 @@ export class Session {
|
|
|
5561
5884
|
});
|
|
5562
5885
|
}
|
|
5563
5886
|
catch (error) {
|
|
5564
|
-
this.sessionLogger.error({ err: error, cwd: msg.cwd },
|
|
5887
|
+
this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to create terminal");
|
|
5565
5888
|
this.emit({
|
|
5566
|
-
type:
|
|
5889
|
+
type: "create_terminal_response",
|
|
5567
5890
|
payload: {
|
|
5568
5891
|
terminal: null,
|
|
5569
5892
|
error: error.message,
|
|
@@ -5575,11 +5898,10 @@ export class Session {
|
|
|
5575
5898
|
async handleSubscribeTerminalRequest(msg) {
|
|
5576
5899
|
if (!this.terminalManager) {
|
|
5577
5900
|
this.emit({
|
|
5578
|
-
type:
|
|
5901
|
+
type: "subscribe_terminal_response",
|
|
5579
5902
|
payload: {
|
|
5580
5903
|
terminalId: msg.terminalId,
|
|
5581
|
-
|
|
5582
|
-
error: 'Terminal manager not available',
|
|
5904
|
+
error: "Terminal manager not available",
|
|
5583
5905
|
requestId: msg.requestId,
|
|
5584
5906
|
},
|
|
5585
5907
|
});
|
|
@@ -5588,52 +5910,44 @@ export class Session {
|
|
|
5588
5910
|
const session = this.terminalManager.getTerminal(msg.terminalId);
|
|
5589
5911
|
if (!session) {
|
|
5590
5912
|
this.emit({
|
|
5591
|
-
type:
|
|
5913
|
+
type: "subscribe_terminal_response",
|
|
5592
5914
|
payload: {
|
|
5593
5915
|
terminalId: msg.terminalId,
|
|
5594
|
-
|
|
5595
|
-
error: 'Terminal not found',
|
|
5916
|
+
error: "Terminal not found",
|
|
5596
5917
|
requestId: msg.requestId,
|
|
5597
5918
|
},
|
|
5598
5919
|
});
|
|
5599
5920
|
return;
|
|
5600
5921
|
}
|
|
5601
5922
|
this.ensureTerminalExitSubscription(session);
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5923
|
+
const slot = this.bindActiveTerminalStream(session);
|
|
5924
|
+
if (slot === null) {
|
|
5925
|
+
this.sessionLogger.warn({
|
|
5926
|
+
terminalId: msg.terminalId,
|
|
5927
|
+
activeTerminalStreamCount: this.activeTerminalStreams.size,
|
|
5928
|
+
}, "Terminal stream slot exhaustion");
|
|
5929
|
+
this.emit({
|
|
5930
|
+
type: "subscribe_terminal_response",
|
|
5931
|
+
payload: {
|
|
5932
|
+
terminalId: msg.terminalId,
|
|
5933
|
+
error: "No terminal stream slots available",
|
|
5934
|
+
requestId: msg.requestId,
|
|
5935
|
+
},
|
|
5936
|
+
});
|
|
5937
|
+
return;
|
|
5606
5938
|
}
|
|
5607
|
-
// Subscribe to terminal updates
|
|
5608
|
-
const unsubscribe = session.subscribe((serverMsg) => {
|
|
5609
|
-
if (serverMsg.type === 'full') {
|
|
5610
|
-
this.emit({
|
|
5611
|
-
type: 'terminal_output',
|
|
5612
|
-
payload: {
|
|
5613
|
-
terminalId: msg.terminalId,
|
|
5614
|
-
state: serverMsg.state,
|
|
5615
|
-
},
|
|
5616
|
-
});
|
|
5617
|
-
}
|
|
5618
|
-
});
|
|
5619
|
-
this.terminalSubscriptions.set(msg.terminalId, unsubscribe);
|
|
5620
|
-
// Send initial state
|
|
5621
5939
|
this.emit({
|
|
5622
|
-
type:
|
|
5940
|
+
type: "subscribe_terminal_response",
|
|
5623
5941
|
payload: {
|
|
5624
5942
|
terminalId: msg.terminalId,
|
|
5625
|
-
|
|
5943
|
+
slot,
|
|
5626
5944
|
error: null,
|
|
5627
5945
|
requestId: msg.requestId,
|
|
5628
5946
|
},
|
|
5629
5947
|
});
|
|
5630
5948
|
}
|
|
5631
5949
|
handleUnsubscribeTerminalRequest(msg) {
|
|
5632
|
-
|
|
5633
|
-
if (unsubscribe) {
|
|
5634
|
-
unsubscribe();
|
|
5635
|
-
this.terminalSubscriptions.delete(msg.terminalId);
|
|
5636
|
-
}
|
|
5950
|
+
this.detachTerminalStream(msg.terminalId, { emitExit: false });
|
|
5637
5951
|
}
|
|
5638
5952
|
handleTerminalInput(msg) {
|
|
5639
5953
|
if (!this.terminalManager) {
|
|
@@ -5641,22 +5955,14 @@ export class Session {
|
|
|
5641
5955
|
}
|
|
5642
5956
|
const session = this.terminalManager.getTerminal(msg.terminalId);
|
|
5643
5957
|
if (!session) {
|
|
5644
|
-
this.sessionLogger.warn({ terminalId: msg.terminalId },
|
|
5958
|
+
this.sessionLogger.warn({ terminalId: msg.terminalId }, "Terminal not found for input");
|
|
5645
5959
|
return;
|
|
5646
5960
|
}
|
|
5647
5961
|
this.ensureTerminalExitSubscription(session);
|
|
5648
5962
|
session.send(msg.message);
|
|
5649
5963
|
}
|
|
5650
5964
|
killTrackedTerminal(terminalId, options) {
|
|
5651
|
-
|
|
5652
|
-
if (unsubscribe) {
|
|
5653
|
-
unsubscribe();
|
|
5654
|
-
this.terminalSubscriptions.delete(terminalId);
|
|
5655
|
-
}
|
|
5656
|
-
const streamId = this.terminalStreamByTerminalId.get(terminalId);
|
|
5657
|
-
if (typeof streamId === 'number') {
|
|
5658
|
-
this.detachTerminalStream(streamId, { emitExit: options?.emitExit ?? true });
|
|
5659
|
-
}
|
|
5965
|
+
this.detachTerminalStream(terminalId, { emitExit: options?.emitExit ?? true });
|
|
5660
5966
|
this.terminalManager?.killTerminal(terminalId);
|
|
5661
5967
|
}
|
|
5662
5968
|
async killTerminalsUnderPath(rootPath) {
|
|
@@ -5678,18 +5984,18 @@ export class Session {
|
|
|
5678
5984
|
catch (error) {
|
|
5679
5985
|
const message = error instanceof Error ? error.message : String(error);
|
|
5680
5986
|
cleanupErrors.push({ cwd: terminalCwd, message });
|
|
5681
|
-
this.sessionLogger.warn({ err: error, cwd: terminalCwd },
|
|
5987
|
+
this.sessionLogger.warn({ err: error, cwd: terminalCwd }, "Failed to clean up worktree terminals during archive");
|
|
5682
5988
|
}
|
|
5683
5989
|
}
|
|
5684
5990
|
if (cleanupErrors.length > 0) {
|
|
5685
|
-
const details = cleanupErrors.map((entry) => `${entry.cwd}: ${entry.message}`).join(
|
|
5991
|
+
const details = cleanupErrors.map((entry) => `${entry.cwd}: ${entry.message}`).join("; ");
|
|
5686
5992
|
throw new Error(`Failed to clean up worktree terminals during archive (${details})`);
|
|
5687
5993
|
}
|
|
5688
5994
|
}
|
|
5689
5995
|
async handleKillTerminalRequest(msg) {
|
|
5690
5996
|
if (!this.terminalManager) {
|
|
5691
5997
|
this.emit({
|
|
5692
|
-
type:
|
|
5998
|
+
type: "kill_terminal_response",
|
|
5693
5999
|
payload: {
|
|
5694
6000
|
terminalId: msg.terminalId,
|
|
5695
6001
|
success: false,
|
|
@@ -5700,7 +6006,7 @@ export class Session {
|
|
|
5700
6006
|
}
|
|
5701
6007
|
this.killTrackedTerminal(msg.terminalId, { emitExit: true });
|
|
5702
6008
|
this.emit({
|
|
5703
|
-
type:
|
|
6009
|
+
type: "kill_terminal_response",
|
|
5704
6010
|
payload: {
|
|
5705
6011
|
terminalId: msg.terminalId,
|
|
5706
6012
|
success: true,
|
|
@@ -5708,219 +6014,149 @@ export class Session {
|
|
|
5708
6014
|
},
|
|
5709
6015
|
});
|
|
5710
6016
|
}
|
|
5711
|
-
|
|
5712
|
-
if (!this.
|
|
5713
|
-
|
|
5714
|
-
type: 'attach_terminal_stream_response',
|
|
5715
|
-
payload: {
|
|
5716
|
-
terminalId: msg.terminalId,
|
|
5717
|
-
streamId: null,
|
|
5718
|
-
replayedFrom: 0,
|
|
5719
|
-
currentOffset: 0,
|
|
5720
|
-
earliestAvailableOffset: 0,
|
|
5721
|
-
reset: true,
|
|
5722
|
-
error: 'Terminal streaming not available',
|
|
5723
|
-
requestId: msg.requestId,
|
|
5724
|
-
},
|
|
5725
|
-
});
|
|
5726
|
-
return;
|
|
6017
|
+
bindActiveTerminalStream(terminal) {
|
|
6018
|
+
if (!this.onBinaryMessage) {
|
|
6019
|
+
return null;
|
|
5727
6020
|
}
|
|
5728
|
-
const
|
|
5729
|
-
if (
|
|
5730
|
-
this.
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
},
|
|
5742
|
-
});
|
|
5743
|
-
return;
|
|
6021
|
+
const existingSlot = this.terminalIdToSlot.get(terminal.id);
|
|
6022
|
+
if (typeof existingSlot === "number") {
|
|
6023
|
+
const existingStream = this.activeTerminalStreams.get(existingSlot);
|
|
6024
|
+
if (existingStream) {
|
|
6025
|
+
existingStream.needsSnapshot = true;
|
|
6026
|
+
this.trySendTerminalSnapshot(existingStream);
|
|
6027
|
+
return existingSlot;
|
|
6028
|
+
}
|
|
6029
|
+
this.terminalIdToSlot.delete(terminal.id);
|
|
6030
|
+
}
|
|
6031
|
+
const slot = this.allocateTerminalSlot();
|
|
6032
|
+
if (slot === null) {
|
|
6033
|
+
return null;
|
|
5744
6034
|
}
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
type: 'resize',
|
|
5749
|
-
rows: msg.rows ?? state.rows,
|
|
5750
|
-
cols: msg.cols ?? state.cols,
|
|
5751
|
-
});
|
|
5752
|
-
}
|
|
5753
|
-
const existingStreamId = this.terminalStreamByTerminalId.get(msg.terminalId);
|
|
5754
|
-
if (typeof existingStreamId === 'number') {
|
|
5755
|
-
// Replacing an active stream can happen when multiple UI surfaces attach to the
|
|
5756
|
-
// same terminal. Emit exit for the replaced stream so stale listeners reconnect
|
|
5757
|
-
// instead of continuing to send input to an invalid stream id.
|
|
5758
|
-
this.detachTerminalStream(existingStreamId, { emitExit: true });
|
|
5759
|
-
}
|
|
5760
|
-
const streamId = this.allocateTerminalStreamId();
|
|
5761
|
-
const requestedResumeOffset = typeof msg.resumeOffset === 'number'
|
|
5762
|
-
? msg.resumeOffset
|
|
5763
|
-
: 0;
|
|
5764
|
-
const initialOffset = Math.max(0, Math.floor(requestedResumeOffset));
|
|
5765
|
-
const binding = {
|
|
5766
|
-
terminalId: msg.terminalId,
|
|
6035
|
+
const activeStream = {
|
|
6036
|
+
terminalId: terminal.id,
|
|
6037
|
+
slot,
|
|
5767
6038
|
unsubscribe: () => { },
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
pendingChunks: [],
|
|
5771
|
-
pendingBytes: 0,
|
|
6039
|
+
needsSnapshot: true,
|
|
6040
|
+
snapshotRetryTimer: null,
|
|
5772
6041
|
};
|
|
5773
|
-
this.
|
|
5774
|
-
this.
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
}, { fromOffset: requestedResumeOffset });
|
|
5789
|
-
}
|
|
5790
|
-
catch (error) {
|
|
5791
|
-
this.terminalStreams.delete(streamId);
|
|
5792
|
-
this.terminalStreamByTerminalId.delete(msg.terminalId);
|
|
5793
|
-
throw error;
|
|
5794
|
-
}
|
|
5795
|
-
binding.unsubscribe = rawSub.unsubscribe;
|
|
5796
|
-
binding.lastAckOffset = rawSub.replayedFrom;
|
|
5797
|
-
if (binding.lastOutputOffset < rawSub.replayedFrom) {
|
|
5798
|
-
binding.lastOutputOffset = rawSub.replayedFrom;
|
|
5799
|
-
}
|
|
5800
|
-
this.flushPendingTerminalStreamChunks(streamId, binding);
|
|
5801
|
-
this.emit({
|
|
5802
|
-
type: 'attach_terminal_stream_response',
|
|
5803
|
-
payload: {
|
|
5804
|
-
terminalId: msg.terminalId,
|
|
5805
|
-
streamId,
|
|
5806
|
-
replayedFrom: rawSub.replayedFrom,
|
|
5807
|
-
currentOffset: rawSub.currentOffset,
|
|
5808
|
-
earliestAvailableOffset: rawSub.earliestAvailableOffset,
|
|
5809
|
-
reset: rawSub.reset,
|
|
5810
|
-
error: null,
|
|
5811
|
-
requestId: msg.requestId,
|
|
5812
|
-
},
|
|
5813
|
-
});
|
|
5814
|
-
}
|
|
5815
|
-
getTerminalStreamChunkByteLength(chunk) {
|
|
5816
|
-
return Math.max(0, chunk.endOffset - chunk.startOffset);
|
|
5817
|
-
}
|
|
5818
|
-
canEmitTerminalStreamChunk(binding, chunk) {
|
|
5819
|
-
return chunk.startOffset < binding.lastAckOffset + TERMINAL_STREAM_WINDOW_BYTES;
|
|
5820
|
-
}
|
|
5821
|
-
emitTerminalStreamChunk(streamId, binding, chunk) {
|
|
5822
|
-
const payload = new Uint8Array(Buffer.from(chunk.data, 'utf8'));
|
|
5823
|
-
this.emitBinary({
|
|
5824
|
-
channel: BinaryMuxChannel.Terminal,
|
|
5825
|
-
messageType: TerminalBinaryMessageType.OutputUtf8,
|
|
5826
|
-
streamId,
|
|
5827
|
-
offset: chunk.startOffset,
|
|
5828
|
-
flags: chunk.replay ? TerminalBinaryFlags.Replay : 0,
|
|
5829
|
-
payload,
|
|
5830
|
-
});
|
|
5831
|
-
binding.lastOutputOffset = chunk.endOffset;
|
|
5832
|
-
}
|
|
5833
|
-
enqueueOrEmitTerminalStreamChunk(streamId, binding, chunk) {
|
|
5834
|
-
const chunkBytes = this.getTerminalStreamChunkByteLength(chunk);
|
|
5835
|
-
if (binding.pendingChunks.length > 0 || !this.canEmitTerminalStreamChunk(binding, chunk)) {
|
|
5836
|
-
if (binding.pendingChunks.length >= TERMINAL_STREAM_MAX_PENDING_CHUNKS ||
|
|
5837
|
-
binding.pendingBytes + chunkBytes > TERMINAL_STREAM_MAX_PENDING_BYTES) {
|
|
5838
|
-
this.sessionLogger.warn({
|
|
5839
|
-
streamId,
|
|
5840
|
-
pendingChunks: binding.pendingChunks.length,
|
|
5841
|
-
pendingBytes: binding.pendingBytes,
|
|
5842
|
-
chunkBytes,
|
|
5843
|
-
}, 'Terminal stream pending buffer overflow; closing stream');
|
|
5844
|
-
this.detachTerminalStream(streamId, { emitExit: true });
|
|
6042
|
+
this.activeTerminalStreams.set(slot, activeStream);
|
|
6043
|
+
this.terminalIdToSlot.set(terminal.id, slot);
|
|
6044
|
+
activeStream.unsubscribe = terminal.subscribe((message) => {
|
|
6045
|
+
if (this.activeTerminalStreams.get(slot) !== activeStream) {
|
|
6046
|
+
return;
|
|
6047
|
+
}
|
|
6048
|
+
if (message.type === "snapshot") {
|
|
6049
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6050
|
+
return;
|
|
6051
|
+
}
|
|
6052
|
+
if (activeStream.needsSnapshot || message.data.length === 0) {
|
|
6053
|
+
return;
|
|
6054
|
+
}
|
|
6055
|
+
if (this.getCurrentBinaryBufferedAmount() >= TERMINAL_STREAM_HIGH_WATER_BYTES) {
|
|
6056
|
+
this.markAllActiveTerminalStreamsForSnapshot();
|
|
5845
6057
|
return;
|
|
5846
6058
|
}
|
|
5847
|
-
|
|
5848
|
-
|
|
6059
|
+
this.emitBinary(encodeTerminalStreamFrame({
|
|
6060
|
+
opcode: TerminalStreamOpcode.Output,
|
|
6061
|
+
slot,
|
|
6062
|
+
payload: new Uint8Array(Buffer.from(message.data, "utf8")),
|
|
6063
|
+
}));
|
|
6064
|
+
if (this.getCurrentBinaryBufferedAmount() >= TERMINAL_STREAM_HIGH_WATER_BYTES) {
|
|
6065
|
+
this.markAllActiveTerminalStreamsForSnapshot();
|
|
6066
|
+
}
|
|
6067
|
+
});
|
|
6068
|
+
return slot;
|
|
6069
|
+
}
|
|
6070
|
+
trySendTerminalSnapshot(activeStream) {
|
|
6071
|
+
if (this.activeTerminalStreams.get(activeStream.slot) !== activeStream ||
|
|
6072
|
+
!activeStream.needsSnapshot) {
|
|
5849
6073
|
return;
|
|
5850
6074
|
}
|
|
5851
|
-
this.
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
break;
|
|
6075
|
+
if (this.getCurrentBinaryBufferedAmount() > TERMINAL_STREAM_LOW_WATER_BYTES) {
|
|
6076
|
+
if (!activeStream.snapshotRetryTimer) {
|
|
6077
|
+
activeStream.snapshotRetryTimer = setTimeout(() => {
|
|
6078
|
+
activeStream.snapshotRetryTimer = null;
|
|
6079
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6080
|
+
}, 33);
|
|
5858
6081
|
}
|
|
5859
|
-
|
|
5860
|
-
binding.pendingBytes -= this.getTerminalStreamChunkByteLength(next);
|
|
5861
|
-
if (binding.pendingBytes < 0) {
|
|
5862
|
-
binding.pendingBytes = 0;
|
|
5863
|
-
}
|
|
5864
|
-
this.emitTerminalStreamChunk(streamId, binding, next);
|
|
6082
|
+
return;
|
|
5865
6083
|
}
|
|
6084
|
+
if (activeStream.snapshotRetryTimer) {
|
|
6085
|
+
clearTimeout(activeStream.snapshotRetryTimer);
|
|
6086
|
+
activeStream.snapshotRetryTimer = null;
|
|
6087
|
+
}
|
|
6088
|
+
const terminal = this.terminalManager?.getTerminal(activeStream.terminalId);
|
|
6089
|
+
if (!terminal) {
|
|
6090
|
+
this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
|
|
6091
|
+
return;
|
|
6092
|
+
}
|
|
6093
|
+
activeStream.needsSnapshot = false;
|
|
6094
|
+
this.emitBinary(encodeTerminalStreamFrame({
|
|
6095
|
+
opcode: TerminalStreamOpcode.Snapshot,
|
|
6096
|
+
slot: activeStream.slot,
|
|
6097
|
+
payload: encodeTerminalSnapshotPayload(terminal.getState()),
|
|
6098
|
+
}));
|
|
5866
6099
|
}
|
|
5867
|
-
|
|
5868
|
-
const
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
streamId: msg.streamId,
|
|
5873
|
-
success,
|
|
5874
|
-
requestId: msg.requestId,
|
|
5875
|
-
},
|
|
5876
|
-
});
|
|
6100
|
+
markAllActiveTerminalStreamsForSnapshot() {
|
|
6101
|
+
for (const activeStream of this.activeTerminalStreams.values()) {
|
|
6102
|
+
activeStream.needsSnapshot = true;
|
|
6103
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6104
|
+
}
|
|
5877
6105
|
}
|
|
5878
|
-
|
|
5879
|
-
for (
|
|
5880
|
-
this.
|
|
6106
|
+
allocateTerminalSlot() {
|
|
6107
|
+
for (let attempt = 0; attempt < MAX_TERMINAL_STREAM_SLOTS; attempt += 1) {
|
|
6108
|
+
const slot = (this.nextTerminalSlot + attempt) % MAX_TERMINAL_STREAM_SLOTS;
|
|
6109
|
+
if (this.activeTerminalStreams.has(slot)) {
|
|
6110
|
+
continue;
|
|
6111
|
+
}
|
|
6112
|
+
this.nextTerminalSlot = (slot + 1) % MAX_TERMINAL_STREAM_SLOTS;
|
|
6113
|
+
return slot;
|
|
5881
6114
|
}
|
|
6115
|
+
return null;
|
|
5882
6116
|
}
|
|
5883
|
-
detachTerminalStream(
|
|
5884
|
-
const
|
|
5885
|
-
if (
|
|
6117
|
+
detachTerminalStream(terminalId, options) {
|
|
6118
|
+
const slot = this.terminalIdToSlot.get(terminalId);
|
|
6119
|
+
if (typeof slot !== "number") {
|
|
5886
6120
|
return false;
|
|
5887
6121
|
}
|
|
6122
|
+
const activeStream = this.activeTerminalStreams.get(slot);
|
|
6123
|
+
if (!activeStream) {
|
|
6124
|
+
this.terminalIdToSlot.delete(terminalId);
|
|
6125
|
+
return false;
|
|
6126
|
+
}
|
|
6127
|
+
this.activeTerminalStreams.delete(slot);
|
|
6128
|
+
this.terminalIdToSlot.delete(terminalId);
|
|
6129
|
+
if (activeStream.snapshotRetryTimer) {
|
|
6130
|
+
clearTimeout(activeStream.snapshotRetryTimer);
|
|
6131
|
+
activeStream.snapshotRetryTimer = null;
|
|
6132
|
+
}
|
|
5888
6133
|
try {
|
|
5889
|
-
|
|
6134
|
+
activeStream.unsubscribe();
|
|
5890
6135
|
}
|
|
5891
6136
|
catch (error) {
|
|
5892
|
-
this.sessionLogger.warn({ err: error
|
|
5893
|
-
}
|
|
5894
|
-
this.terminalStreams.delete(streamId);
|
|
5895
|
-
if (this.terminalStreamByTerminalId.get(binding.terminalId) === streamId) {
|
|
5896
|
-
this.terminalStreamByTerminalId.delete(binding.terminalId);
|
|
6137
|
+
this.sessionLogger.warn({ err: error }, "Failed to unsubscribe terminal stream");
|
|
5897
6138
|
}
|
|
5898
6139
|
if (options?.emitExit) {
|
|
5899
6140
|
this.emit({
|
|
5900
|
-
type:
|
|
6141
|
+
type: "terminal_stream_exit",
|
|
5901
6142
|
payload: {
|
|
5902
|
-
|
|
5903
|
-
terminalId: binding.terminalId,
|
|
6143
|
+
terminalId: activeStream.terminalId,
|
|
5904
6144
|
},
|
|
5905
6145
|
});
|
|
5906
6146
|
}
|
|
5907
6147
|
return true;
|
|
5908
6148
|
}
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
if (!this.terminalStreams.has(candidate)) {
|
|
5919
|
-
return candidate;
|
|
5920
|
-
}
|
|
5921
|
-
attempts += 1;
|
|
6149
|
+
disposeTerminalSubscriptions() {
|
|
6150
|
+
for (const terminalId of [...this.terminalIdToSlot.keys()]) {
|
|
6151
|
+
this.detachTerminalStream(terminalId, { emitExit: false });
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
getCurrentBinaryBufferedAmount() {
|
|
6155
|
+
const bufferedAmount = this.getBinaryBufferedAmount?.() ?? 0;
|
|
6156
|
+
if (!Number.isFinite(bufferedAmount) || bufferedAmount < 0) {
|
|
6157
|
+
return 0;
|
|
5922
6158
|
}
|
|
5923
|
-
|
|
6159
|
+
return Math.floor(bufferedAmount);
|
|
5924
6160
|
}
|
|
5925
6161
|
}
|
|
5926
6162
|
//# sourceMappingURL=session.js.map
|