@getpaseo/server 0.1.30 → 0.1.33
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 +488 -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 +10 -10
- 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 +1240 -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 +9 -48
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +49 -126
- 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 +488 -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 +1240 -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 +49 -126
- 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,136 @@ 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
|
+
terminal.send({ type: "resize", rows: resize.rows, cols: resize.cols });
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
default:
|
|
1143
|
+
return;
|
|
1134
1144
|
}
|
|
1135
|
-
this.sessionLogger.warn({ streamId: frame.streamId, messageType: frame.messageType }, 'Unhandled terminal binary frame');
|
|
1136
1145
|
}
|
|
1137
1146
|
async handleRestartServerRequest(requestId, reason) {
|
|
1138
1147
|
const payload = {
|
|
1139
|
-
status:
|
|
1148
|
+
status: "restart_requested",
|
|
1140
1149
|
clientId: this.clientId,
|
|
1141
1150
|
};
|
|
1142
1151
|
if (reason && reason.trim().length > 0) {
|
|
1143
1152
|
payload.reason = reason;
|
|
1144
1153
|
}
|
|
1145
1154
|
payload.requestId = requestId;
|
|
1146
|
-
this.sessionLogger.warn({ reason },
|
|
1155
|
+
this.sessionLogger.warn({ reason }, "Restart requested via websocket");
|
|
1147
1156
|
this.emit({
|
|
1148
|
-
type:
|
|
1157
|
+
type: "status",
|
|
1149
1158
|
payload,
|
|
1150
1159
|
});
|
|
1151
1160
|
this.emitLifecycleIntent({
|
|
1152
|
-
type:
|
|
1161
|
+
type: "restart",
|
|
1153
1162
|
clientId: this.clientId,
|
|
1154
1163
|
requestId,
|
|
1155
1164
|
...(reason ? { reason } : {}),
|
|
1156
1165
|
});
|
|
1157
1166
|
}
|
|
1158
1167
|
async handleShutdownServerRequest(requestId) {
|
|
1159
|
-
this.sessionLogger.warn(
|
|
1168
|
+
this.sessionLogger.warn("Shutdown requested via websocket");
|
|
1160
1169
|
this.emit({
|
|
1161
|
-
type:
|
|
1170
|
+
type: "status",
|
|
1162
1171
|
payload: {
|
|
1163
|
-
status:
|
|
1172
|
+
status: "shutdown_requested",
|
|
1164
1173
|
clientId: this.clientId,
|
|
1165
1174
|
requestId,
|
|
1166
1175
|
},
|
|
1167
1176
|
});
|
|
1168
1177
|
this.emitLifecycleIntent({
|
|
1169
|
-
type:
|
|
1178
|
+
type: "shutdown",
|
|
1170
1179
|
clientId: this.clientId,
|
|
1171
1180
|
requestId,
|
|
1172
1181
|
});
|
|
@@ -1179,7 +1188,7 @@ export class Session {
|
|
|
1179
1188
|
this.onLifecycleIntent(intent);
|
|
1180
1189
|
}
|
|
1181
1190
|
catch (error) {
|
|
1182
|
-
this.sessionLogger.error({ err: error, intent },
|
|
1191
|
+
this.sessionLogger.error({ err: error, intent }, "Lifecycle intent handler failed");
|
|
1183
1192
|
}
|
|
1184
1193
|
}
|
|
1185
1194
|
async handleDeleteAgentRequest(agentId, requestId) {
|
|
@@ -1202,7 +1211,7 @@ export class Session {
|
|
|
1202
1211
|
this.sessionLogger.error({ err: error, agentId }, `Failed to remove agent ${agentId} from registry`);
|
|
1203
1212
|
}
|
|
1204
1213
|
this.emit({
|
|
1205
|
-
type:
|
|
1214
|
+
type: "agent_deleted",
|
|
1206
1215
|
payload: {
|
|
1207
1216
|
agentId,
|
|
1208
1217
|
requestId,
|
|
@@ -1210,7 +1219,7 @@ export class Session {
|
|
|
1210
1219
|
});
|
|
1211
1220
|
if (this.agentUpdatesSubscription) {
|
|
1212
1221
|
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
|
|
1213
|
-
kind:
|
|
1222
|
+
kind: "remove",
|
|
1214
1223
|
agentId,
|
|
1215
1224
|
});
|
|
1216
1225
|
}
|
|
@@ -1222,7 +1231,7 @@ export class Session {
|
|
|
1222
1231
|
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
1223
1232
|
const { archivedAt } = await this.archiveAgentState(agentId);
|
|
1224
1233
|
this.emit({
|
|
1225
|
-
type:
|
|
1234
|
+
type: "agent_archived",
|
|
1226
1235
|
payload: {
|
|
1227
1236
|
agentId,
|
|
1228
1237
|
archivedAt,
|
|
@@ -1251,8 +1260,8 @@ export class Session {
|
|
|
1251
1260
|
throw new Error(`Agent not found in storage after snapshot: ${agentId}`);
|
|
1252
1261
|
}
|
|
1253
1262
|
}
|
|
1254
|
-
const normalizedStatus = archivedRecord.lastStatus ===
|
|
1255
|
-
?
|
|
1263
|
+
const normalizedStatus = archivedRecord.lastStatus === "running" || archivedRecord.lastStatus === "initializing"
|
|
1264
|
+
? "idle"
|
|
1256
1265
|
: archivedRecord.lastStatus;
|
|
1257
1266
|
const nextRecord = {
|
|
1258
1267
|
...archivedRecord,
|
|
@@ -1263,7 +1272,17 @@ export class Session {
|
|
|
1263
1272
|
attentionTimestamp: null,
|
|
1264
1273
|
};
|
|
1265
1274
|
await this.agentStorage.upsert(nextRecord);
|
|
1266
|
-
|
|
1275
|
+
// Unload the agent from memory — the storage record is the source of truth now.
|
|
1276
|
+
// This tears down the provider session and drops the hydrated timeline,
|
|
1277
|
+
// freeing memory. ensureAgentLoaded will re-initialize if needed later.
|
|
1278
|
+
if (this.agentManager.getAgent(agentId)) {
|
|
1279
|
+
try {
|
|
1280
|
+
await this.agentManager.closeAgent(agentId);
|
|
1281
|
+
}
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
this.sessionLogger.warn({ err: error, agentId }, "Failed to close agent during archive");
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1267
1286
|
return { archivedAt, archivedRecord: nextRecord };
|
|
1268
1287
|
}
|
|
1269
1288
|
async unarchiveAgentState(agentId) {
|
|
@@ -1291,19 +1310,19 @@ export class Session {
|
|
|
1291
1310
|
this.sessionLogger.info({
|
|
1292
1311
|
agentId,
|
|
1293
1312
|
requestId,
|
|
1294
|
-
hasName: typeof name ===
|
|
1313
|
+
hasName: typeof name === "string",
|
|
1295
1314
|
labelCount: labels ? Object.keys(labels).length : 0,
|
|
1296
|
-
},
|
|
1315
|
+
}, "session: update_agent_request");
|
|
1297
1316
|
const normalizedName = name?.trim();
|
|
1298
1317
|
const normalizedLabels = labels && Object.keys(labels).length > 0 ? labels : undefined;
|
|
1299
1318
|
if (!normalizedName && !normalizedLabels) {
|
|
1300
1319
|
this.emit({
|
|
1301
|
-
type:
|
|
1320
|
+
type: "update_agent_response",
|
|
1302
1321
|
payload: {
|
|
1303
1322
|
requestId,
|
|
1304
1323
|
agentId,
|
|
1305
1324
|
accepted: false,
|
|
1306
|
-
error:
|
|
1325
|
+
error: "Nothing to update (provide name and/or labels)",
|
|
1307
1326
|
},
|
|
1308
1327
|
});
|
|
1309
1328
|
return;
|
|
@@ -1330,28 +1349,28 @@ export class Session {
|
|
|
1330
1349
|
});
|
|
1331
1350
|
}
|
|
1332
1351
|
this.emit({
|
|
1333
|
-
type:
|
|
1352
|
+
type: "update_agent_response",
|
|
1334
1353
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
1335
1354
|
});
|
|
1336
1355
|
}
|
|
1337
1356
|
catch (error) {
|
|
1338
|
-
this.sessionLogger.error({ err: error, agentId, requestId },
|
|
1357
|
+
this.sessionLogger.error({ err: error, agentId, requestId }, "session: update_agent_request error");
|
|
1339
1358
|
this.emit({
|
|
1340
|
-
type:
|
|
1359
|
+
type: "activity_log",
|
|
1341
1360
|
payload: {
|
|
1342
1361
|
id: uuidv4(),
|
|
1343
1362
|
timestamp: new Date(),
|
|
1344
|
-
type:
|
|
1363
|
+
type: "error",
|
|
1345
1364
|
content: `Failed to update agent: ${error.message}`,
|
|
1346
1365
|
},
|
|
1347
1366
|
});
|
|
1348
1367
|
this.emit({
|
|
1349
|
-
type:
|
|
1368
|
+
type: "update_agent_response",
|
|
1350
1369
|
payload: {
|
|
1351
1370
|
requestId,
|
|
1352
1371
|
agentId,
|
|
1353
1372
|
accepted: false,
|
|
1354
|
-
error: error?.message ? String(error.message) :
|
|
1373
|
+
error: error?.message ? String(error.message) : "Failed to update agent",
|
|
1355
1374
|
},
|
|
1356
1375
|
});
|
|
1357
1376
|
}
|
|
@@ -1365,7 +1384,7 @@ export class Session {
|
|
|
1365
1384
|
};
|
|
1366
1385
|
}
|
|
1367
1386
|
resolveModeReadinessState(readiness, mode) {
|
|
1368
|
-
if (mode ===
|
|
1387
|
+
if (mode === "voice_mode") {
|
|
1369
1388
|
return readiness.realtimeVoice;
|
|
1370
1389
|
}
|
|
1371
1390
|
return readiness.dictation;
|
|
@@ -1403,13 +1422,13 @@ export class Session {
|
|
|
1403
1422
|
async handleSetVoiceMode(enabled, agentId, requestId) {
|
|
1404
1423
|
const startedAt = Date.now();
|
|
1405
1424
|
try {
|
|
1406
|
-
this.sessionLogger.info({ enabled, requestedAgentId: agentId ?? null, requestId: requestId ?? null },
|
|
1425
|
+
this.sessionLogger.info({ enabled, requestedAgentId: agentId ?? null, requestId: requestId ?? null }, "set_voice_mode started");
|
|
1407
1426
|
if (enabled) {
|
|
1408
|
-
const unavailable = this.resolveVoiceFeatureUnavailableContext(
|
|
1427
|
+
const unavailable = this.resolveVoiceFeatureUnavailableContext("voice_mode");
|
|
1409
1428
|
if (unavailable) {
|
|
1410
1429
|
throw new VoiceFeatureUnavailableError(unavailable);
|
|
1411
1430
|
}
|
|
1412
|
-
const normalizedAgentId = this.parseVoiceTargetAgentId(agentId ??
|
|
1431
|
+
const normalizedAgentId = this.parseVoiceTargetAgentId(agentId ?? "", "set_voice_mode");
|
|
1413
1432
|
if (this.isVoiceMode &&
|
|
1414
1433
|
this.voiceModeAgentId &&
|
|
1415
1434
|
this.voiceModeAgentId !== normalizedAgentId) {
|
|
@@ -1417,26 +1436,26 @@ export class Session {
|
|
|
1417
1436
|
previousAgentId: this.voiceModeAgentId,
|
|
1418
1437
|
nextAgentId: normalizedAgentId,
|
|
1419
1438
|
elapsedMs: Date.now() - startedAt,
|
|
1420
|
-
},
|
|
1439
|
+
}, "set_voice_mode disabling previous active voice agent");
|
|
1421
1440
|
await this.disableVoiceModeForActiveAgent(true);
|
|
1422
1441
|
}
|
|
1423
1442
|
if (!this.isVoiceMode || this.voiceModeAgentId !== normalizedAgentId) {
|
|
1424
|
-
this.sessionLogger.info({ agentId: normalizedAgentId, elapsedMs: Date.now() - startedAt },
|
|
1443
|
+
this.sessionLogger.info({ agentId: normalizedAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode enabling voice for agent");
|
|
1425
1444
|
const refreshedAgentId = await this.enableVoiceModeForAgent(normalizedAgentId);
|
|
1426
1445
|
this.voiceModeAgentId = refreshedAgentId;
|
|
1427
|
-
this.sessionLogger.info({ agentId: refreshedAgentId, elapsedMs: Date.now() - startedAt },
|
|
1446
|
+
this.sessionLogger.info({ agentId: refreshedAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode agent enable complete");
|
|
1428
1447
|
}
|
|
1429
|
-
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt },
|
|
1448
|
+
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode starting voice turn controller");
|
|
1430
1449
|
await this.startVoiceTurnController();
|
|
1431
|
-
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt },
|
|
1450
|
+
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode voice turn controller started");
|
|
1432
1451
|
this.isVoiceMode = true;
|
|
1433
1452
|
this.sessionLogger.info({
|
|
1434
1453
|
agentId: this.voiceModeAgentId,
|
|
1435
1454
|
elapsedMs: Date.now() - startedAt,
|
|
1436
|
-
},
|
|
1455
|
+
}, "Voice mode enabled for existing agent");
|
|
1437
1456
|
if (requestId) {
|
|
1438
1457
|
this.emit({
|
|
1439
|
-
type:
|
|
1458
|
+
type: "set_voice_mode_response",
|
|
1440
1459
|
payload: {
|
|
1441
1460
|
requestId,
|
|
1442
1461
|
enabled: true,
|
|
@@ -1448,13 +1467,13 @@ export class Session {
|
|
|
1448
1467
|
}
|
|
1449
1468
|
return;
|
|
1450
1469
|
}
|
|
1451
|
-
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt },
|
|
1470
|
+
this.sessionLogger.info({ agentId: this.voiceModeAgentId, elapsedMs: Date.now() - startedAt }, "set_voice_mode disabling active voice mode");
|
|
1452
1471
|
await this.disableVoiceModeForActiveAgent(true);
|
|
1453
1472
|
this.isVoiceMode = false;
|
|
1454
|
-
this.sessionLogger.info({ elapsedMs: Date.now() - startedAt },
|
|
1473
|
+
this.sessionLogger.info({ elapsedMs: Date.now() - startedAt }, "Voice mode disabled");
|
|
1455
1474
|
if (requestId) {
|
|
1456
1475
|
this.emit({
|
|
1457
|
-
type:
|
|
1476
|
+
type: "set_voice_mode_response",
|
|
1458
1477
|
payload: {
|
|
1459
1478
|
requestId,
|
|
1460
1479
|
enabled: false,
|
|
@@ -1466,17 +1485,17 @@ export class Session {
|
|
|
1466
1485
|
}
|
|
1467
1486
|
}
|
|
1468
1487
|
catch (error) {
|
|
1469
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
1488
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to set voice mode";
|
|
1470
1489
|
const unavailable = this.getVoiceFeatureUnavailableResponseMetadata(error);
|
|
1471
1490
|
this.sessionLogger.error({
|
|
1472
1491
|
err: error,
|
|
1473
1492
|
enabled,
|
|
1474
1493
|
requestedAgentId: agentId ?? null,
|
|
1475
1494
|
elapsedMs: Date.now() - startedAt,
|
|
1476
|
-
},
|
|
1495
|
+
}, "set_voice_mode failed");
|
|
1477
1496
|
if (requestId) {
|
|
1478
1497
|
this.emit({
|
|
1479
|
-
type:
|
|
1498
|
+
type: "set_voice_mode_response",
|
|
1480
1499
|
payload: {
|
|
1481
1500
|
requestId,
|
|
1482
1501
|
enabled: this.isVoiceMode,
|
|
@@ -1507,7 +1526,7 @@ export class Session {
|
|
|
1507
1526
|
buildVoiceModeMcpServers(existing, socketPath) {
|
|
1508
1527
|
const mcpStdio = this.voiceAgentMcpStdio;
|
|
1509
1528
|
if (!mcpStdio) {
|
|
1510
|
-
throw new Error(
|
|
1529
|
+
throw new Error("Voice MCP stdio bridge is not configured");
|
|
1511
1530
|
}
|
|
1512
1531
|
return {
|
|
1513
1532
|
...(existing ?? {}),
|
|
@@ -1523,14 +1542,14 @@ export class Session {
|
|
|
1523
1542
|
const startedAt = Date.now();
|
|
1524
1543
|
const ensureVoiceSocket = this.ensureVoiceMcpSocketForAgent;
|
|
1525
1544
|
if (!ensureVoiceSocket) {
|
|
1526
|
-
throw new Error(
|
|
1545
|
+
throw new Error("Voice MCP socket bridge is not configured");
|
|
1527
1546
|
}
|
|
1528
|
-
this.sessionLogger.info({ agentId },
|
|
1547
|
+
this.sessionLogger.info({ agentId }, "enableVoiceModeForAgent.ensureAgentLoaded.start");
|
|
1529
1548
|
const existing = await this.ensureAgentLoaded(agentId);
|
|
1530
|
-
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt },
|
|
1531
|
-
this.sessionLogger.info({ agentId },
|
|
1549
|
+
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.ensureAgentLoaded.done");
|
|
1550
|
+
this.sessionLogger.info({ agentId }, "enableVoiceModeForAgent.ensureVoiceSocket.start");
|
|
1532
1551
|
const socketPath = await ensureVoiceSocket(agentId);
|
|
1533
|
-
this.sessionLogger.info({ agentId, socketPath, elapsedMs: Date.now() - startedAt },
|
|
1552
|
+
this.sessionLogger.info({ agentId, socketPath, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.ensureVoiceSocket.done");
|
|
1534
1553
|
this.registerVoiceBridgeForAgent(agentId);
|
|
1535
1554
|
const baseConfig = {
|
|
1536
1555
|
systemPrompt: stripVoiceModeSystemPrompt(existing.config.systemPrompt),
|
|
@@ -1542,9 +1561,9 @@ export class Session {
|
|
|
1542
1561
|
mcpServers: this.buildVoiceModeMcpServers(baseConfig.mcpServers, socketPath),
|
|
1543
1562
|
};
|
|
1544
1563
|
try {
|
|
1545
|
-
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt },
|
|
1564
|
+
this.sessionLogger.info({ agentId, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.reloadAgentSession.start");
|
|
1546
1565
|
const refreshed = await this.agentManager.reloadAgentSession(agentId, refreshOverrides);
|
|
1547
|
-
this.sessionLogger.info({ agentId, refreshedAgentId: refreshed.id, elapsedMs: Date.now() - startedAt },
|
|
1566
|
+
this.sessionLogger.info({ agentId, refreshedAgentId: refreshed.id, elapsedMs: Date.now() - startedAt }, "enableVoiceModeForAgent.reloadAgentSession.done");
|
|
1548
1567
|
return refreshed.id;
|
|
1549
1568
|
}
|
|
1550
1569
|
catch (error) {
|
|
@@ -1565,7 +1584,7 @@ export class Session {
|
|
|
1565
1584
|
this.unregisterVoiceSpeakHandler?.(agentId);
|
|
1566
1585
|
this.unregisterVoiceCallerContext?.(agentId);
|
|
1567
1586
|
await this.removeVoiceMcpSocketForAgent?.(agentId).catch((error) => {
|
|
1568
|
-
this.sessionLogger.warn({ err: error, agentId },
|
|
1587
|
+
this.sessionLogger.warn({ err: error, agentId }, "Failed to remove voice MCP socket bridge on disable");
|
|
1569
1588
|
});
|
|
1570
1589
|
if (restoreAgentConfig && this.voiceModeBaseConfig) {
|
|
1571
1590
|
const baseConfig = this.voiceModeBaseConfig;
|
|
@@ -1576,7 +1595,7 @@ export class Session {
|
|
|
1576
1595
|
});
|
|
1577
1596
|
}
|
|
1578
1597
|
catch (error) {
|
|
1579
|
-
this.sessionLogger.warn({ err: error, agentId },
|
|
1598
|
+
this.sessionLogger.warn({ err: error, agentId }, "Failed to restore agent config while disabling voice mode");
|
|
1580
1599
|
}
|
|
1581
1600
|
}
|
|
1582
1601
|
this.voiceModeBaseConfig = null;
|
|
@@ -1587,16 +1606,16 @@ export class Session {
|
|
|
1587
1606
|
}
|
|
1588
1607
|
async startVoiceTurnController() {
|
|
1589
1608
|
if (this.voiceTurnController) {
|
|
1590
|
-
this.sessionLogger.info(
|
|
1609
|
+
this.sessionLogger.info("startVoiceTurnController skipped: already running");
|
|
1591
1610
|
return;
|
|
1592
1611
|
}
|
|
1593
1612
|
const turnDetection = this.resolveVoiceTurnDetection();
|
|
1594
1613
|
if (!turnDetection) {
|
|
1595
|
-
throw new Error(
|
|
1614
|
+
throw new Error("Voice turn detection is not configured");
|
|
1596
1615
|
}
|
|
1597
|
-
this.sessionLogger.info({ providerId: turnDetection.id },
|
|
1616
|
+
this.sessionLogger.info({ providerId: turnDetection.id }, "startVoiceTurnController creating controller");
|
|
1598
1617
|
const controller = createVoiceTurnController({
|
|
1599
|
-
logger: this.sessionLogger.child({ component:
|
|
1618
|
+
logger: this.sessionLogger.child({ component: "voice-turn-controller" }),
|
|
1600
1619
|
turnDetection,
|
|
1601
1620
|
utteranceSink: {
|
|
1602
1621
|
submitUtterance: async ({ pcm16, format, sampleRate, startedAt, endedAt }) => {
|
|
@@ -1606,7 +1625,7 @@ export class Session {
|
|
|
1606
1625
|
startedAt,
|
|
1607
1626
|
endedAt,
|
|
1608
1627
|
durationMs: Math.max(0, endedAt - startedAt),
|
|
1609
|
-
},
|
|
1628
|
+
}, "Submitting detected voice utterance");
|
|
1610
1629
|
await this.processCompletedAudio(pcm16, format);
|
|
1611
1630
|
},
|
|
1612
1631
|
},
|
|
@@ -1618,20 +1637,20 @@ export class Session {
|
|
|
1618
1637
|
this.handleVoiceSpeechStopped();
|
|
1619
1638
|
},
|
|
1620
1639
|
onError: (error) => {
|
|
1621
|
-
this.sessionLogger.error({ err: error },
|
|
1640
|
+
this.sessionLogger.error({ err: error }, "Voice turn controller failed");
|
|
1622
1641
|
},
|
|
1623
1642
|
},
|
|
1624
1643
|
});
|
|
1625
|
-
this.sessionLogger.info(
|
|
1644
|
+
this.sessionLogger.info("startVoiceTurnController connecting controller");
|
|
1626
1645
|
await controller.start();
|
|
1627
1646
|
this.voiceTurnController = controller;
|
|
1628
|
-
this.sessionLogger.info(
|
|
1647
|
+
this.sessionLogger.info("startVoiceTurnController connected");
|
|
1629
1648
|
}
|
|
1630
1649
|
async stopVoiceTurnController() {
|
|
1631
1650
|
if (!this.voiceTurnController) {
|
|
1632
1651
|
return;
|
|
1633
1652
|
}
|
|
1634
|
-
this.clearPendingVoiceSpeechStart(
|
|
1653
|
+
this.clearPendingVoiceSpeechStart("turn-controller-stop");
|
|
1635
1654
|
const controller = this.voiceTurnController;
|
|
1636
1655
|
this.voiceTurnController = null;
|
|
1637
1656
|
await controller.stop();
|
|
@@ -1642,7 +1661,7 @@ export class Session {
|
|
|
1642
1661
|
this.pendingVoiceSpeechTimer = null;
|
|
1643
1662
|
}
|
|
1644
1663
|
if (this.pendingVoiceSpeechStartAt !== null) {
|
|
1645
|
-
this.sessionLogger.debug({ reason },
|
|
1664
|
+
this.sessionLogger.debug({ reason }, "Clearing provisional voice speech start");
|
|
1646
1665
|
this.pendingVoiceSpeechStartAt = null;
|
|
1647
1666
|
}
|
|
1648
1667
|
}
|
|
@@ -1652,16 +1671,16 @@ export class Session {
|
|
|
1652
1671
|
}
|
|
1653
1672
|
const startedAt = Date.now();
|
|
1654
1673
|
this.pendingVoiceSpeechStartAt = startedAt;
|
|
1655
|
-
this.sessionLogger.info({ confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS },
|
|
1674
|
+
this.sessionLogger.info({ confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS }, "Silero VAD provisional speech_started");
|
|
1656
1675
|
this.pendingVoiceSpeechTimer = setTimeout(() => {
|
|
1657
1676
|
this.pendingVoiceSpeechTimer = null;
|
|
1658
1677
|
if (this.pendingVoiceSpeechStartAt !== startedAt || this.speechInProgress) {
|
|
1659
1678
|
return;
|
|
1660
1679
|
}
|
|
1661
1680
|
this.pendingVoiceSpeechStartAt = null;
|
|
1662
|
-
this.sessionLogger.info(
|
|
1681
|
+
this.sessionLogger.info("voice_input_state emitting isSpeaking=true");
|
|
1663
1682
|
this.emit({
|
|
1664
|
-
type:
|
|
1683
|
+
type: "voice_input_state",
|
|
1665
1684
|
payload: {
|
|
1666
1685
|
isSpeaking: true,
|
|
1667
1686
|
},
|
|
@@ -1672,13 +1691,13 @@ export class Session {
|
|
|
1672
1691
|
handleVoiceSpeechStopped() {
|
|
1673
1692
|
if (this.pendingVoiceSpeechStartAt !== null) {
|
|
1674
1693
|
const durationMs = Date.now() - this.pendingVoiceSpeechStartAt;
|
|
1675
|
-
this.clearPendingVoiceSpeechStart(
|
|
1676
|
-
this.sessionLogger.info({ durationMs, confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS },
|
|
1694
|
+
this.clearPendingVoiceSpeechStart("speech-stopped-before-confirmation");
|
|
1695
|
+
this.sessionLogger.info({ durationMs, confirmationMs: VOICE_INTERRUPT_CONFIRMATION_MS }, "Ignoring provisional voice speech start that ended before confirmation");
|
|
1677
1696
|
return;
|
|
1678
1697
|
}
|
|
1679
|
-
this.sessionLogger.info(
|
|
1698
|
+
this.sessionLogger.info("voice_input_state emitting isSpeaking=false");
|
|
1680
1699
|
this.emit({
|
|
1681
|
-
type:
|
|
1700
|
+
type: "voice_input_state",
|
|
1682
1701
|
payload: {
|
|
1683
1702
|
isSpeaking: false,
|
|
1684
1703
|
},
|
|
@@ -1688,13 +1707,13 @@ export class Session {
|
|
|
1688
1707
|
* Handle text message to agent (with optional image attachments)
|
|
1689
1708
|
*/
|
|
1690
1709
|
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)` :
|
|
1710
|
+
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
1711
|
await this.unarchiveAgentState(agentId);
|
|
1693
1712
|
try {
|
|
1694
1713
|
await this.ensureAgentLoaded(agentId);
|
|
1695
1714
|
}
|
|
1696
1715
|
catch (error) {
|
|
1697
|
-
this.handleAgentRunError(agentId, error,
|
|
1716
|
+
this.handleAgentRunError(agentId, error, "Failed to initialize agent before sending prompt");
|
|
1698
1717
|
return;
|
|
1699
1718
|
}
|
|
1700
1719
|
try {
|
|
@@ -1714,7 +1733,7 @@ export class Session {
|
|
|
1714
1733
|
*/
|
|
1715
1734
|
async handleCreateAgentRequest(msg) {
|
|
1716
1735
|
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}` :
|
|
1736
|
+
this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ""}`);
|
|
1718
1737
|
try {
|
|
1719
1738
|
const trimmedPrompt = initialPrompt?.trim();
|
|
1720
1739
|
const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
|
|
@@ -1735,9 +1754,9 @@ export class Session {
|
|
|
1735
1754
|
throw new Error(`Agent ${snapshot.id} not found after creation`);
|
|
1736
1755
|
}
|
|
1737
1756
|
this.emit({
|
|
1738
|
-
type:
|
|
1757
|
+
type: "status",
|
|
1739
1758
|
payload: {
|
|
1740
|
-
status:
|
|
1759
|
+
status: "agent_created",
|
|
1741
1760
|
agentId: snapshot.id,
|
|
1742
1761
|
requestId,
|
|
1743
1762
|
agent: agentPayload,
|
|
@@ -1757,11 +1776,11 @@ export class Session {
|
|
|
1757
1776
|
void this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined).catch((promptError) => {
|
|
1758
1777
|
this.sessionLogger.error({ err: promptError, agentId: snapshot.id }, `Failed to run initial prompt for agent ${snapshot.id}`);
|
|
1759
1778
|
this.emit({
|
|
1760
|
-
type:
|
|
1779
|
+
type: "activity_log",
|
|
1761
1780
|
payload: {
|
|
1762
1781
|
id: uuidv4(),
|
|
1763
1782
|
timestamp: new Date(),
|
|
1764
|
-
type:
|
|
1783
|
+
type: "error",
|
|
1765
1784
|
content: `Initial prompt failed: ${promptError?.message ?? promptError}`,
|
|
1766
1785
|
},
|
|
1767
1786
|
});
|
|
@@ -1788,23 +1807,23 @@ export class Session {
|
|
|
1788
1807
|
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1789
1808
|
}
|
|
1790
1809
|
catch (error) {
|
|
1791
|
-
this.sessionLogger.error({ err: error },
|
|
1810
|
+
this.sessionLogger.error({ err: error }, "Failed to create agent");
|
|
1792
1811
|
if (requestId) {
|
|
1793
1812
|
this.emit({
|
|
1794
|
-
type:
|
|
1813
|
+
type: "status",
|
|
1795
1814
|
payload: {
|
|
1796
|
-
status:
|
|
1815
|
+
status: "agent_create_failed",
|
|
1797
1816
|
requestId,
|
|
1798
1817
|
error: error?.message ?? String(error),
|
|
1799
1818
|
},
|
|
1800
1819
|
});
|
|
1801
1820
|
}
|
|
1802
1821
|
this.emit({
|
|
1803
|
-
type:
|
|
1822
|
+
type: "activity_log",
|
|
1804
1823
|
payload: {
|
|
1805
1824
|
id: uuidv4(),
|
|
1806
1825
|
timestamp: new Date(),
|
|
1807
|
-
type:
|
|
1826
|
+
type: "error",
|
|
1808
1827
|
content: `Failed to create agent: ${error.message}`,
|
|
1809
1828
|
},
|
|
1810
1829
|
});
|
|
@@ -1813,14 +1832,14 @@ export class Session {
|
|
|
1813
1832
|
async handleResumeAgentRequest(msg) {
|
|
1814
1833
|
const { handle, overrides, requestId } = msg;
|
|
1815
1834
|
if (!handle) {
|
|
1816
|
-
this.sessionLogger.warn(
|
|
1835
|
+
this.sessionLogger.warn("Resume request missing persistence handle");
|
|
1817
1836
|
this.emit({
|
|
1818
|
-
type:
|
|
1837
|
+
type: "activity_log",
|
|
1819
1838
|
payload: {
|
|
1820
1839
|
id: uuidv4(),
|
|
1821
1840
|
timestamp: new Date(),
|
|
1822
|
-
type:
|
|
1823
|
-
content:
|
|
1841
|
+
type: "error",
|
|
1842
|
+
content: "Unable to resume agent: missing persistence handle",
|
|
1824
1843
|
},
|
|
1825
1844
|
});
|
|
1826
1845
|
return;
|
|
@@ -1839,9 +1858,9 @@ export class Session {
|
|
|
1839
1858
|
throw new Error(`Agent ${snapshot.id} not found after resume`);
|
|
1840
1859
|
}
|
|
1841
1860
|
this.emit({
|
|
1842
|
-
type:
|
|
1861
|
+
type: "status",
|
|
1843
1862
|
payload: {
|
|
1844
|
-
status:
|
|
1863
|
+
status: "agent_resumed",
|
|
1845
1864
|
agentId: snapshot.id,
|
|
1846
1865
|
requestId,
|
|
1847
1866
|
timelineSize,
|
|
@@ -1851,13 +1870,13 @@ export class Session {
|
|
|
1851
1870
|
}
|
|
1852
1871
|
}
|
|
1853
1872
|
catch (error) {
|
|
1854
|
-
this.sessionLogger.error({ err: error },
|
|
1873
|
+
this.sessionLogger.error({ err: error }, "Failed to resume agent");
|
|
1855
1874
|
this.emit({
|
|
1856
|
-
type:
|
|
1875
|
+
type: "activity_log",
|
|
1857
1876
|
payload: {
|
|
1858
1877
|
id: uuidv4(),
|
|
1859
1878
|
timestamp: new Date(),
|
|
1860
|
-
type:
|
|
1879
|
+
type: "error",
|
|
1861
1880
|
content: `Failed to resume agent: ${error.message}`,
|
|
1862
1881
|
},
|
|
1863
1882
|
});
|
|
@@ -1895,9 +1914,9 @@ export class Session {
|
|
|
1895
1914
|
const timelineSize = this.agentManager.getTimeline(agentId).length;
|
|
1896
1915
|
if (requestId) {
|
|
1897
1916
|
this.emit({
|
|
1898
|
-
type:
|
|
1917
|
+
type: "status",
|
|
1899
1918
|
payload: {
|
|
1900
|
-
status:
|
|
1919
|
+
status: "agent_refreshed",
|
|
1901
1920
|
agentId,
|
|
1902
1921
|
requestId,
|
|
1903
1922
|
timelineSize,
|
|
@@ -1908,11 +1927,11 @@ export class Session {
|
|
|
1908
1927
|
catch (error) {
|
|
1909
1928
|
this.sessionLogger.error({ err: error, agentId }, `Failed to refresh agent ${agentId}`);
|
|
1910
1929
|
this.emit({
|
|
1911
|
-
type:
|
|
1930
|
+
type: "activity_log",
|
|
1912
1931
|
payload: {
|
|
1913
1932
|
id: uuidv4(),
|
|
1914
1933
|
timestamp: new Date(),
|
|
1915
|
-
type:
|
|
1934
|
+
type: "error",
|
|
1916
1935
|
content: `Failed to refresh agent: ${error.message}`,
|
|
1917
1936
|
},
|
|
1918
1937
|
});
|
|
@@ -1924,7 +1943,7 @@ export class Session {
|
|
|
1924
1943
|
await this.interruptAgentIfRunning(agentId);
|
|
1925
1944
|
}
|
|
1926
1945
|
catch (error) {
|
|
1927
|
-
this.handleAgentRunError(agentId, error,
|
|
1946
|
+
this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
|
|
1928
1947
|
}
|
|
1929
1948
|
}
|
|
1930
1949
|
async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, _labels) {
|
|
@@ -1946,20 +1965,21 @@ export class Session {
|
|
|
1946
1965
|
}
|
|
1947
1966
|
else {
|
|
1948
1967
|
// Resolve current branch name from HEAD
|
|
1949
|
-
const { stdout } = await execAsync(
|
|
1968
|
+
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
|
|
1950
1969
|
cwd,
|
|
1951
1970
|
env: READ_ONLY_GIT_ENV,
|
|
1952
1971
|
});
|
|
1953
1972
|
targetBranch = stdout.trim();
|
|
1954
1973
|
}
|
|
1955
1974
|
if (!targetBranch) {
|
|
1956
|
-
throw new Error(
|
|
1975
|
+
throw new Error("A branch name is required when creating a worktree.");
|
|
1957
1976
|
}
|
|
1958
1977
|
this.sessionLogger.info({ worktreeSlug: normalized.worktreeSlug ?? targetBranch, branch: targetBranch }, `Creating worktree '${normalized.worktreeSlug ?? targetBranch}' for branch ${targetBranch}`);
|
|
1978
|
+
const baseBranch = normalized.baseBranch ?? (await this.resolveGitCreateBaseBranch(cwd));
|
|
1959
1979
|
const createdWorktree = await createAgentWorktree({
|
|
1960
1980
|
branchName: targetBranch,
|
|
1961
1981
|
cwd,
|
|
1962
|
-
baseBranch
|
|
1982
|
+
baseBranch,
|
|
1963
1983
|
worktreeSlug: normalized.worktreeSlug ?? targetBranch,
|
|
1964
1984
|
paseoHome: this.paseoHome,
|
|
1965
1985
|
});
|
|
@@ -1967,9 +1987,10 @@ export class Session {
|
|
|
1967
1987
|
worktreeConfig = createdWorktree;
|
|
1968
1988
|
}
|
|
1969
1989
|
else if (normalized.createNewBranch) {
|
|
1990
|
+
const baseBranch = normalized.baseBranch ?? (await this.resolveGitCreateBaseBranch(cwd));
|
|
1970
1991
|
await this.createBranchFromBase({
|
|
1971
1992
|
cwd,
|
|
1972
|
-
baseBranch
|
|
1993
|
+
baseBranch,
|
|
1973
1994
|
newBranchName: normalized.newBranchName,
|
|
1974
1995
|
});
|
|
1975
1996
|
}
|
|
@@ -1991,7 +2012,7 @@ export class Session {
|
|
|
1991
2012
|
cwd: msg.cwd ? expandTilde(msg.cwd) : undefined,
|
|
1992
2013
|
});
|
|
1993
2014
|
this.emit({
|
|
1994
|
-
type:
|
|
2015
|
+
type: "list_provider_models_response",
|
|
1995
2016
|
payload: {
|
|
1996
2017
|
provider: msg.provider,
|
|
1997
2018
|
models,
|
|
@@ -2004,7 +2025,7 @@ export class Session {
|
|
|
2004
2025
|
catch (error) {
|
|
2005
2026
|
this.sessionLogger.error({ err: error, provider: msg.provider }, `Failed to list models for ${msg.provider}`);
|
|
2006
2027
|
this.emit({
|
|
2007
|
-
type:
|
|
2028
|
+
type: "list_provider_models_response",
|
|
2008
2029
|
payload: {
|
|
2009
2030
|
provider: msg.provider,
|
|
2010
2031
|
error: error?.message ?? String(error),
|
|
@@ -2019,7 +2040,7 @@ export class Session {
|
|
|
2019
2040
|
try {
|
|
2020
2041
|
const providers = await this.agentManager.listProviderAvailability();
|
|
2021
2042
|
this.emit({
|
|
2022
|
-
type:
|
|
2043
|
+
type: "list_available_providers_response",
|
|
2023
2044
|
payload: {
|
|
2024
2045
|
providers,
|
|
2025
2046
|
error: null,
|
|
@@ -2029,9 +2050,9 @@ export class Session {
|
|
|
2029
2050
|
});
|
|
2030
2051
|
}
|
|
2031
2052
|
catch (error) {
|
|
2032
|
-
this.sessionLogger.error({ err: error },
|
|
2053
|
+
this.sessionLogger.error({ err: error }, "Failed to list provider availability");
|
|
2033
2054
|
this.emit({
|
|
2034
|
-
type:
|
|
2055
|
+
type: "list_available_providers_response",
|
|
2035
2056
|
payload: {
|
|
2036
2057
|
providers: [],
|
|
2037
2058
|
error: error?.message ?? String(error),
|
|
@@ -2071,7 +2092,7 @@ export class Session {
|
|
|
2071
2092
|
};
|
|
2072
2093
|
}));
|
|
2073
2094
|
this.emit({
|
|
2074
|
-
type:
|
|
2095
|
+
type: "speech_models_list_response",
|
|
2075
2096
|
payload: {
|
|
2076
2097
|
modelsDir,
|
|
2077
2098
|
models,
|
|
@@ -2086,11 +2107,11 @@ export class Session {
|
|
|
2086
2107
|
const invalid = modelIdsRaw.filter((id) => !allModelIds.has(id));
|
|
2087
2108
|
if (invalid.length > 0) {
|
|
2088
2109
|
this.emit({
|
|
2089
|
-
type:
|
|
2110
|
+
type: "speech_models_download_response",
|
|
2090
2111
|
payload: {
|
|
2091
2112
|
modelsDir,
|
|
2092
2113
|
downloadedModelIds: [],
|
|
2093
|
-
error: `Unknown speech model id(s): ${invalid.join(
|
|
2114
|
+
error: `Unknown speech model id(s): ${invalid.join(", ")}`,
|
|
2094
2115
|
requestId: msg.requestId,
|
|
2095
2116
|
},
|
|
2096
2117
|
});
|
|
@@ -2104,7 +2125,7 @@ export class Session {
|
|
|
2104
2125
|
logger: this.sessionLogger,
|
|
2105
2126
|
});
|
|
2106
2127
|
this.emit({
|
|
2107
|
-
type:
|
|
2128
|
+
type: "speech_models_download_response",
|
|
2108
2129
|
payload: {
|
|
2109
2130
|
modelsDir,
|
|
2110
2131
|
downloadedModelIds: modelIds,
|
|
@@ -2114,9 +2135,9 @@ export class Session {
|
|
|
2114
2135
|
});
|
|
2115
2136
|
}
|
|
2116
2137
|
catch (error) {
|
|
2117
|
-
this.sessionLogger.error({ err: error, modelIds },
|
|
2138
|
+
this.sessionLogger.error({ err: error, modelIds }, "Failed to download speech models");
|
|
2118
2139
|
this.emit({
|
|
2119
|
-
type:
|
|
2140
|
+
type: "speech_models_download_response",
|
|
2120
2141
|
payload: {
|
|
2121
2142
|
modelsDir,
|
|
2122
2143
|
downloadedModelIds: [],
|
|
@@ -2150,17 +2171,11 @@ export class Session {
|
|
|
2150
2171
|
return null;
|
|
2151
2172
|
}
|
|
2152
2173
|
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');
|
|
2174
|
+
this.assertSafeGitRef(baseBranch, "base branch");
|
|
2160
2175
|
}
|
|
2161
2176
|
if (createNewBranch) {
|
|
2162
2177
|
if (!normalizedBranchName) {
|
|
2163
|
-
throw new Error(
|
|
2178
|
+
throw new Error("New branch name is required");
|
|
2164
2179
|
}
|
|
2165
2180
|
const validation = validateBranchSlug(normalizedBranchName);
|
|
2166
2181
|
if (!validation.valid) {
|
|
@@ -2182,24 +2197,36 @@ export class Session {
|
|
|
2182
2197
|
};
|
|
2183
2198
|
}
|
|
2184
2199
|
assertSafeGitRef(ref, label) {
|
|
2185
|
-
if (!SAFE_GIT_REF_PATTERN.test(ref) || ref.includes(
|
|
2200
|
+
if (!SAFE_GIT_REF_PATTERN.test(ref) || ref.includes("..") || ref.includes("@{")) {
|
|
2186
2201
|
throw new Error(`Invalid ${label}: ${ref}`);
|
|
2187
2202
|
}
|
|
2188
2203
|
}
|
|
2204
|
+
async resolveGitCreateBaseBranch(cwd) {
|
|
2205
|
+
const checkout = await getCheckoutStatusLite(cwd, { paseoHome: this.paseoHome });
|
|
2206
|
+
if (!checkout.isGit) {
|
|
2207
|
+
throw new Error("Cannot create a worktree outside a git repository");
|
|
2208
|
+
}
|
|
2209
|
+
const repoRoot = checkout.isPaseoOwnedWorktree ? checkout.mainRepoRoot : cwd;
|
|
2210
|
+
const baseBranch = await resolveRepositoryDefaultBranch(repoRoot);
|
|
2211
|
+
if (!baseBranch) {
|
|
2212
|
+
throw new Error("Unable to resolve repository default branch");
|
|
2213
|
+
}
|
|
2214
|
+
return baseBranch;
|
|
2215
|
+
}
|
|
2189
2216
|
toCheckoutError(error) {
|
|
2190
2217
|
if (error instanceof NotGitRepoError) {
|
|
2191
|
-
return { code:
|
|
2218
|
+
return { code: "NOT_GIT_REPO", message: error.message };
|
|
2192
2219
|
}
|
|
2193
2220
|
if (error instanceof MergeConflictError) {
|
|
2194
|
-
return { code:
|
|
2221
|
+
return { code: "MERGE_CONFLICT", message: error.message };
|
|
2195
2222
|
}
|
|
2196
2223
|
if (error instanceof MergeFromBaseConflictError) {
|
|
2197
|
-
return { code:
|
|
2224
|
+
return { code: "MERGE_CONFLICT", message: error.message };
|
|
2198
2225
|
}
|
|
2199
2226
|
if (error instanceof Error) {
|
|
2200
|
-
return { code:
|
|
2227
|
+
return { code: "UNKNOWN", message: error.message };
|
|
2201
2228
|
}
|
|
2202
|
-
return { code:
|
|
2229
|
+
return { code: "UNKNOWN", message: String(error) };
|
|
2203
2230
|
}
|
|
2204
2231
|
isPathWithinRoot(rootPath, candidatePath) {
|
|
2205
2232
|
const resolvedRoot = resolve(rootPath);
|
|
@@ -2210,47 +2237,47 @@ export class Session {
|
|
|
2210
2237
|
return resolvedCandidate.startsWith(resolvedRoot + sep);
|
|
2211
2238
|
}
|
|
2212
2239
|
async generateCommitMessage(cwd) {
|
|
2213
|
-
const diff = await getCheckoutDiff(cwd, { mode:
|
|
2240
|
+
const diff = await getCheckoutDiff(cwd, { mode: "uncommitted", includeStructured: true }, { paseoHome: this.paseoHome });
|
|
2214
2241
|
const schema = z.object({
|
|
2215
2242
|
message: z
|
|
2216
2243
|
.string()
|
|
2217
2244
|
.min(1)
|
|
2218
2245
|
.max(72)
|
|
2219
|
-
.describe(
|
|
2246
|
+
.describe("Concise git commit message, imperative mood, no trailing period."),
|
|
2220
2247
|
});
|
|
2221
2248
|
const fileList = diff.structured && diff.structured.length > 0
|
|
2222
2249
|
? [
|
|
2223
|
-
|
|
2250
|
+
"Files changed:",
|
|
2224
2251
|
...diff.structured.map((file) => {
|
|
2225
|
-
const changeType = file.isNew ?
|
|
2226
|
-
const status = file.status && file.status !==
|
|
2252
|
+
const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
|
|
2253
|
+
const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
|
|
2227
2254
|
return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
|
|
2228
2255
|
}),
|
|
2229
|
-
].join(
|
|
2230
|
-
:
|
|
2256
|
+
].join("\n")
|
|
2257
|
+
: "Files changed: (unknown)";
|
|
2231
2258
|
const maxPatchChars = 120000;
|
|
2232
2259
|
const patch = diff.diff.length > maxPatchChars
|
|
2233
2260
|
? `${diff.diff.slice(0, maxPatchChars)}\n\n... (diff truncated to ${maxPatchChars} chars)\n`
|
|
2234
2261
|
: diff.diff;
|
|
2235
2262
|
const prompt = [
|
|
2236
|
-
|
|
2263
|
+
"Write a concise git commit message for the changes below.",
|
|
2237
2264
|
"Return JSON only with a single field 'message'.",
|
|
2238
|
-
|
|
2265
|
+
"",
|
|
2239
2266
|
fileList,
|
|
2240
|
-
|
|
2241
|
-
patch.length > 0 ? patch :
|
|
2242
|
-
].join(
|
|
2267
|
+
"",
|
|
2268
|
+
patch.length > 0 ? patch : "(No diff available)",
|
|
2269
|
+
].join("\n");
|
|
2243
2270
|
try {
|
|
2244
2271
|
const result = await generateStructuredAgentResponseWithFallback({
|
|
2245
2272
|
manager: this.agentManager,
|
|
2246
2273
|
cwd,
|
|
2247
2274
|
prompt,
|
|
2248
2275
|
schema,
|
|
2249
|
-
schemaName:
|
|
2276
|
+
schemaName: "CommitMessage",
|
|
2250
2277
|
maxRetries: 2,
|
|
2251
2278
|
providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
|
|
2252
2279
|
agentConfigOverrides: {
|
|
2253
|
-
title:
|
|
2280
|
+
title: "Commit generator",
|
|
2254
2281
|
internal: true,
|
|
2255
2282
|
},
|
|
2256
2283
|
});
|
|
@@ -2259,14 +2286,14 @@ export class Session {
|
|
|
2259
2286
|
catch (error) {
|
|
2260
2287
|
if (error instanceof StructuredAgentResponseError ||
|
|
2261
2288
|
error instanceof StructuredAgentFallbackError) {
|
|
2262
|
-
return
|
|
2289
|
+
return "Update files";
|
|
2263
2290
|
}
|
|
2264
2291
|
throw error;
|
|
2265
2292
|
}
|
|
2266
2293
|
}
|
|
2267
2294
|
async generatePullRequestText(cwd, baseRef) {
|
|
2268
2295
|
const diff = await getCheckoutDiff(cwd, {
|
|
2269
|
-
mode:
|
|
2296
|
+
mode: "base",
|
|
2270
2297
|
baseRef,
|
|
2271
2298
|
includeStructured: true,
|
|
2272
2299
|
}, { paseoHome: this.paseoHome });
|
|
@@ -2276,37 +2303,37 @@ export class Session {
|
|
|
2276
2303
|
});
|
|
2277
2304
|
const fileList = diff.structured && diff.structured.length > 0
|
|
2278
2305
|
? [
|
|
2279
|
-
|
|
2306
|
+
"Files changed:",
|
|
2280
2307
|
...diff.structured.map((file) => {
|
|
2281
|
-
const changeType = file.isNew ?
|
|
2282
|
-
const status = file.status && file.status !==
|
|
2308
|
+
const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M";
|
|
2309
|
+
const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
|
|
2283
2310
|
return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
|
|
2284
2311
|
}),
|
|
2285
|
-
].join(
|
|
2286
|
-
:
|
|
2312
|
+
].join("\n")
|
|
2313
|
+
: "Files changed: (unknown)";
|
|
2287
2314
|
const maxPatchChars = 200000;
|
|
2288
2315
|
const patch = diff.diff.length > maxPatchChars
|
|
2289
2316
|
? `${diff.diff.slice(0, maxPatchChars)}\n\n... (diff truncated to ${maxPatchChars} chars)\n`
|
|
2290
2317
|
: diff.diff;
|
|
2291
2318
|
const prompt = [
|
|
2292
|
-
|
|
2319
|
+
"Write a pull request title and body for the changes below.",
|
|
2293
2320
|
"Return JSON only with fields 'title' and 'body'.",
|
|
2294
|
-
|
|
2321
|
+
"",
|
|
2295
2322
|
fileList,
|
|
2296
|
-
|
|
2297
|
-
patch.length > 0 ? patch :
|
|
2298
|
-
].join(
|
|
2323
|
+
"",
|
|
2324
|
+
patch.length > 0 ? patch : "(No diff available)",
|
|
2325
|
+
].join("\n");
|
|
2299
2326
|
try {
|
|
2300
2327
|
return await generateStructuredAgentResponseWithFallback({
|
|
2301
2328
|
manager: this.agentManager,
|
|
2302
2329
|
cwd,
|
|
2303
2330
|
prompt,
|
|
2304
2331
|
schema,
|
|
2305
|
-
schemaName:
|
|
2332
|
+
schemaName: "PullRequest",
|
|
2306
2333
|
maxRetries: 2,
|
|
2307
2334
|
providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
|
|
2308
2335
|
agentConfigOverrides: {
|
|
2309
|
-
title:
|
|
2336
|
+
title: "PR generator",
|
|
2310
2337
|
internal: true,
|
|
2311
2338
|
},
|
|
2312
2339
|
});
|
|
@@ -2315,8 +2342,8 @@ export class Session {
|
|
|
2315
2342
|
if (error instanceof StructuredAgentResponseError ||
|
|
2316
2343
|
error instanceof StructuredAgentFallbackError) {
|
|
2317
2344
|
return {
|
|
2318
|
-
title:
|
|
2319
|
-
body:
|
|
2345
|
+
title: "Update changes",
|
|
2346
|
+
body: "Automated PR generated by Paseo.",
|
|
2320
2347
|
};
|
|
2321
2348
|
}
|
|
2322
2349
|
throw error;
|
|
@@ -2325,12 +2352,12 @@ export class Session {
|
|
|
2325
2352
|
async ensureCleanWorkingTree(cwd) {
|
|
2326
2353
|
const dirty = await this.isWorkingTreeDirty(cwd);
|
|
2327
2354
|
if (dirty) {
|
|
2328
|
-
throw new Error(
|
|
2355
|
+
throw new Error("Working directory has uncommitted changes. Commit or stash before switching branches.");
|
|
2329
2356
|
}
|
|
2330
2357
|
}
|
|
2331
2358
|
async isWorkingTreeDirty(cwd) {
|
|
2332
2359
|
try {
|
|
2333
|
-
const { stdout } = await execAsync(
|
|
2360
|
+
const { stdout } = await execAsync("git status --porcelain", {
|
|
2334
2361
|
cwd,
|
|
2335
2362
|
env: READ_ONLY_GIT_ENV,
|
|
2336
2363
|
});
|
|
@@ -2341,14 +2368,14 @@ export class Session {
|
|
|
2341
2368
|
}
|
|
2342
2369
|
}
|
|
2343
2370
|
async checkoutExistingBranch(cwd, branch) {
|
|
2344
|
-
this.assertSafeGitRef(branch,
|
|
2371
|
+
this.assertSafeGitRef(branch, "branch");
|
|
2345
2372
|
try {
|
|
2346
2373
|
await execAsync(`git rev-parse --verify ${branch}`, { cwd });
|
|
2347
2374
|
}
|
|
2348
2375
|
catch (error) {
|
|
2349
2376
|
throw new Error(`Branch not found: ${branch}`);
|
|
2350
2377
|
}
|
|
2351
|
-
const { stdout } = await execAsync(
|
|
2378
|
+
const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
|
|
2352
2379
|
cwd,
|
|
2353
2380
|
});
|
|
2354
2381
|
const current = stdout.trim();
|
|
@@ -2360,7 +2387,7 @@ export class Session {
|
|
|
2360
2387
|
}
|
|
2361
2388
|
async createBranchFromBase(params) {
|
|
2362
2389
|
const { cwd, baseBranch, newBranchName } = params;
|
|
2363
|
-
this.assertSafeGitRef(baseBranch,
|
|
2390
|
+
this.assertSafeGitRef(baseBranch, "base branch");
|
|
2364
2391
|
try {
|
|
2365
2392
|
await execAsync(`git rev-parse --verify ${baseBranch}`, { cwd });
|
|
2366
2393
|
}
|
|
@@ -2391,97 +2418,97 @@ export class Session {
|
|
|
2391
2418
|
* Handle set agent mode request
|
|
2392
2419
|
*/
|
|
2393
2420
|
async handleSetAgentModeRequest(agentId, modeId, requestId) {
|
|
2394
|
-
this.sessionLogger.info({ agentId, modeId, requestId },
|
|
2421
|
+
this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request");
|
|
2395
2422
|
try {
|
|
2396
2423
|
await this.agentManager.setAgentMode(agentId, modeId);
|
|
2397
|
-
this.sessionLogger.info({ agentId, modeId, requestId },
|
|
2424
|
+
this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request success");
|
|
2398
2425
|
this.emit({
|
|
2399
|
-
type:
|
|
2426
|
+
type: "set_agent_mode_response",
|
|
2400
2427
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
2401
2428
|
});
|
|
2402
2429
|
}
|
|
2403
2430
|
catch (error) {
|
|
2404
|
-
this.sessionLogger.error({ err: error, agentId, modeId, requestId },
|
|
2431
|
+
this.sessionLogger.error({ err: error, agentId, modeId, requestId }, "session: set_agent_mode_request error");
|
|
2405
2432
|
this.emit({
|
|
2406
|
-
type:
|
|
2433
|
+
type: "activity_log",
|
|
2407
2434
|
payload: {
|
|
2408
2435
|
id: uuidv4(),
|
|
2409
2436
|
timestamp: new Date(),
|
|
2410
|
-
type:
|
|
2437
|
+
type: "error",
|
|
2411
2438
|
content: `Failed to set agent mode: ${error.message}`,
|
|
2412
2439
|
},
|
|
2413
2440
|
});
|
|
2414
2441
|
this.emit({
|
|
2415
|
-
type:
|
|
2442
|
+
type: "set_agent_mode_response",
|
|
2416
2443
|
payload: {
|
|
2417
2444
|
requestId,
|
|
2418
2445
|
agentId,
|
|
2419
2446
|
accepted: false,
|
|
2420
|
-
error: error?.message ? String(error.message) :
|
|
2447
|
+
error: error?.message ? String(error.message) : "Failed to set agent mode",
|
|
2421
2448
|
},
|
|
2422
2449
|
});
|
|
2423
2450
|
}
|
|
2424
2451
|
}
|
|
2425
2452
|
async handleSetAgentModelRequest(agentId, modelId, requestId) {
|
|
2426
|
-
this.sessionLogger.info({ agentId, modelId, requestId },
|
|
2453
|
+
this.sessionLogger.info({ agentId, modelId, requestId }, "session: set_agent_model_request");
|
|
2427
2454
|
try {
|
|
2428
2455
|
await this.agentManager.setAgentModel(agentId, modelId);
|
|
2429
|
-
this.sessionLogger.info({ agentId, modelId, requestId },
|
|
2456
|
+
this.sessionLogger.info({ agentId, modelId, requestId }, "session: set_agent_model_request success");
|
|
2430
2457
|
this.emit({
|
|
2431
|
-
type:
|
|
2458
|
+
type: "set_agent_model_response",
|
|
2432
2459
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
2433
2460
|
});
|
|
2434
2461
|
}
|
|
2435
2462
|
catch (error) {
|
|
2436
|
-
this.sessionLogger.error({ err: error, agentId, modelId, requestId },
|
|
2463
|
+
this.sessionLogger.error({ err: error, agentId, modelId, requestId }, "session: set_agent_model_request error");
|
|
2437
2464
|
this.emit({
|
|
2438
|
-
type:
|
|
2465
|
+
type: "activity_log",
|
|
2439
2466
|
payload: {
|
|
2440
2467
|
id: uuidv4(),
|
|
2441
2468
|
timestamp: new Date(),
|
|
2442
|
-
type:
|
|
2469
|
+
type: "error",
|
|
2443
2470
|
content: `Failed to set agent model: ${error.message}`,
|
|
2444
2471
|
},
|
|
2445
2472
|
});
|
|
2446
2473
|
this.emit({
|
|
2447
|
-
type:
|
|
2474
|
+
type: "set_agent_model_response",
|
|
2448
2475
|
payload: {
|
|
2449
2476
|
requestId,
|
|
2450
2477
|
agentId,
|
|
2451
2478
|
accepted: false,
|
|
2452
|
-
error: error?.message ? String(error.message) :
|
|
2479
|
+
error: error?.message ? String(error.message) : "Failed to set agent model",
|
|
2453
2480
|
},
|
|
2454
2481
|
});
|
|
2455
2482
|
}
|
|
2456
2483
|
}
|
|
2457
2484
|
async handleSetAgentThinkingRequest(agentId, thinkingOptionId, requestId) {
|
|
2458
|
-
this.sessionLogger.info({ agentId, thinkingOptionId, requestId },
|
|
2485
|
+
this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request");
|
|
2459
2486
|
try {
|
|
2460
2487
|
await this.agentManager.setAgentThinkingOption(agentId, thinkingOptionId);
|
|
2461
|
-
this.sessionLogger.info({ agentId, thinkingOptionId, requestId },
|
|
2488
|
+
this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request success");
|
|
2462
2489
|
this.emit({
|
|
2463
|
-
type:
|
|
2490
|
+
type: "set_agent_thinking_response",
|
|
2464
2491
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
2465
2492
|
});
|
|
2466
2493
|
}
|
|
2467
2494
|
catch (error) {
|
|
2468
|
-
this.sessionLogger.error({ err: error, agentId, thinkingOptionId, requestId },
|
|
2495
|
+
this.sessionLogger.error({ err: error, agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request error");
|
|
2469
2496
|
this.emit({
|
|
2470
|
-
type:
|
|
2497
|
+
type: "activity_log",
|
|
2471
2498
|
payload: {
|
|
2472
2499
|
id: uuidv4(),
|
|
2473
2500
|
timestamp: new Date(),
|
|
2474
|
-
type:
|
|
2501
|
+
type: "error",
|
|
2475
2502
|
content: `Failed to set agent thinking option: ${error.message}`,
|
|
2476
2503
|
},
|
|
2477
2504
|
});
|
|
2478
2505
|
this.emit({
|
|
2479
|
-
type:
|
|
2506
|
+
type: "set_agent_thinking_response",
|
|
2480
2507
|
payload: {
|
|
2481
2508
|
requestId,
|
|
2482
2509
|
agentId,
|
|
2483
2510
|
accepted: false,
|
|
2484
|
-
error: error?.message ? String(error.message) :
|
|
2511
|
+
error: error?.message ? String(error.message) : "Failed to set agent thinking option",
|
|
2485
2512
|
},
|
|
2486
2513
|
});
|
|
2487
2514
|
}
|
|
@@ -2495,7 +2522,7 @@ export class Session {
|
|
|
2495
2522
|
await Promise.all(agentIds.map((id) => this.agentManager.clearAgentAttention(id)));
|
|
2496
2523
|
}
|
|
2497
2524
|
catch (error) {
|
|
2498
|
-
this.sessionLogger.error({ err: error, agentIds },
|
|
2525
|
+
this.sessionLogger.error({ err: error, agentIds }, "Failed to clear agent attention");
|
|
2499
2526
|
// Don't throw - this is not critical
|
|
2500
2527
|
}
|
|
2501
2528
|
}
|
|
@@ -2519,7 +2546,7 @@ export class Session {
|
|
|
2519
2546
|
*/
|
|
2520
2547
|
handleRegisterPushToken(token) {
|
|
2521
2548
|
this.pushTokenStore.addToken(token);
|
|
2522
|
-
this.sessionLogger.info(
|
|
2549
|
+
this.sessionLogger.info("Registered push token");
|
|
2523
2550
|
}
|
|
2524
2551
|
/**
|
|
2525
2552
|
* Handle list commands request for an agent
|
|
@@ -2533,7 +2560,7 @@ export class Session {
|
|
|
2533
2560
|
if (agent?.session?.listCommands) {
|
|
2534
2561
|
const commands = await agent.session.listCommands();
|
|
2535
2562
|
this.emit({
|
|
2536
|
-
type:
|
|
2563
|
+
type: "list_commands_response",
|
|
2537
2564
|
payload: {
|
|
2538
2565
|
agentId,
|
|
2539
2566
|
commands,
|
|
@@ -2555,7 +2582,7 @@ export class Session {
|
|
|
2555
2582
|
};
|
|
2556
2583
|
const commands = await this.agentManager.listDraftCommands(sessionConfig);
|
|
2557
2584
|
this.emit({
|
|
2558
|
-
type:
|
|
2585
|
+
type: "list_commands_response",
|
|
2559
2586
|
payload: {
|
|
2560
2587
|
agentId,
|
|
2561
2588
|
commands,
|
|
@@ -2566,7 +2593,7 @@ export class Session {
|
|
|
2566
2593
|
return;
|
|
2567
2594
|
}
|
|
2568
2595
|
this.emit({
|
|
2569
|
-
type:
|
|
2596
|
+
type: "list_commands_response",
|
|
2570
2597
|
payload: {
|
|
2571
2598
|
agentId,
|
|
2572
2599
|
commands: [],
|
|
@@ -2576,9 +2603,9 @@ export class Session {
|
|
|
2576
2603
|
});
|
|
2577
2604
|
}
|
|
2578
2605
|
catch (error) {
|
|
2579
|
-
this.sessionLogger.error({ err: error, agentId, draftConfig },
|
|
2606
|
+
this.sessionLogger.error({ err: error, agentId, draftConfig }, "Failed to list commands");
|
|
2580
2607
|
this.emit({
|
|
2581
|
-
type:
|
|
2608
|
+
type: "list_commands_response",
|
|
2582
2609
|
payload: {
|
|
2583
2610
|
agentId,
|
|
2584
2611
|
commands: [],
|
|
@@ -2598,13 +2625,13 @@ export class Session {
|
|
|
2598
2625
|
this.sessionLogger.debug({ agentId }, `Permission response forwarded to agent ${agentId}`);
|
|
2599
2626
|
}
|
|
2600
2627
|
catch (error) {
|
|
2601
|
-
this.sessionLogger.error({ err: error, agentId, requestId },
|
|
2628
|
+
this.sessionLogger.error({ err: error, agentId, requestId }, "Failed to respond to permission");
|
|
2602
2629
|
this.emit({
|
|
2603
|
-
type:
|
|
2630
|
+
type: "activity_log",
|
|
2604
2631
|
payload: {
|
|
2605
2632
|
id: uuidv4(),
|
|
2606
2633
|
timestamp: new Date(),
|
|
2607
|
-
type:
|
|
2634
|
+
type: "error",
|
|
2608
2635
|
content: `Failed to respond to permission: ${error.message}`,
|
|
2609
2636
|
},
|
|
2610
2637
|
});
|
|
@@ -2618,7 +2645,7 @@ export class Session {
|
|
|
2618
2645
|
const status = await getCheckoutStatus(resolvedCwd, { paseoHome: this.paseoHome });
|
|
2619
2646
|
if (!status.isGit) {
|
|
2620
2647
|
this.emit({
|
|
2621
|
-
type:
|
|
2648
|
+
type: "checkout_status_response",
|
|
2622
2649
|
payload: {
|
|
2623
2650
|
cwd,
|
|
2624
2651
|
isGit: false,
|
|
@@ -2640,7 +2667,7 @@ export class Session {
|
|
|
2640
2667
|
}
|
|
2641
2668
|
if (status.isPaseoOwnedWorktree) {
|
|
2642
2669
|
this.emit({
|
|
2643
|
-
type:
|
|
2670
|
+
type: "checkout_status_response",
|
|
2644
2671
|
payload: {
|
|
2645
2672
|
cwd,
|
|
2646
2673
|
isGit: true,
|
|
@@ -2662,7 +2689,7 @@ export class Session {
|
|
|
2662
2689
|
return;
|
|
2663
2690
|
}
|
|
2664
2691
|
this.emit({
|
|
2665
|
-
type:
|
|
2692
|
+
type: "checkout_status_response",
|
|
2666
2693
|
payload: {
|
|
2667
2694
|
cwd,
|
|
2668
2695
|
isGit: true,
|
|
@@ -2683,7 +2710,7 @@ export class Session {
|
|
|
2683
2710
|
}
|
|
2684
2711
|
catch (error) {
|
|
2685
2712
|
this.emit({
|
|
2686
|
-
type:
|
|
2713
|
+
type: "checkout_status_response",
|
|
2687
2714
|
payload: {
|
|
2688
2715
|
cwd,
|
|
2689
2716
|
isGit: false,
|
|
@@ -2714,7 +2741,7 @@ export class Session {
|
|
|
2714
2741
|
env: READ_ONLY_GIT_ENV,
|
|
2715
2742
|
});
|
|
2716
2743
|
this.emit({
|
|
2717
|
-
type:
|
|
2744
|
+
type: "validate_branch_response",
|
|
2718
2745
|
payload: {
|
|
2719
2746
|
exists: true,
|
|
2720
2747
|
resolvedRef: branchName,
|
|
@@ -2735,7 +2762,7 @@ export class Session {
|
|
|
2735
2762
|
env: READ_ONLY_GIT_ENV,
|
|
2736
2763
|
});
|
|
2737
2764
|
this.emit({
|
|
2738
|
-
type:
|
|
2765
|
+
type: "validate_branch_response",
|
|
2739
2766
|
payload: {
|
|
2740
2767
|
exists: true,
|
|
2741
2768
|
resolvedRef: `origin/${branchName}`,
|
|
@@ -2751,7 +2778,7 @@ export class Session {
|
|
|
2751
2778
|
}
|
|
2752
2779
|
// Branch not found anywhere
|
|
2753
2780
|
this.emit({
|
|
2754
|
-
type:
|
|
2781
|
+
type: "validate_branch_response",
|
|
2755
2782
|
payload: {
|
|
2756
2783
|
exists: false,
|
|
2757
2784
|
resolvedRef: null,
|
|
@@ -2763,7 +2790,7 @@ export class Session {
|
|
|
2763
2790
|
}
|
|
2764
2791
|
catch (error) {
|
|
2765
2792
|
this.emit({
|
|
2766
|
-
type:
|
|
2793
|
+
type: "validate_branch_response",
|
|
2767
2794
|
payload: {
|
|
2768
2795
|
exists: false,
|
|
2769
2796
|
resolvedRef: null,
|
|
@@ -2780,7 +2807,7 @@ export class Session {
|
|
|
2780
2807
|
const resolvedCwd = expandTilde(cwd);
|
|
2781
2808
|
const branches = await listBranchSuggestions(resolvedCwd, { query, limit });
|
|
2782
2809
|
this.emit({
|
|
2783
|
-
type:
|
|
2810
|
+
type: "branch_suggestions_response",
|
|
2784
2811
|
payload: {
|
|
2785
2812
|
branches,
|
|
2786
2813
|
error: null,
|
|
@@ -2790,7 +2817,7 @@ export class Session {
|
|
|
2790
2817
|
}
|
|
2791
2818
|
catch (error) {
|
|
2792
2819
|
this.emit({
|
|
2793
|
-
type:
|
|
2820
|
+
type: "branch_suggestions_response",
|
|
2794
2821
|
payload: {
|
|
2795
2822
|
branches: [],
|
|
2796
2823
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -2815,12 +2842,12 @@ export class Session {
|
|
|
2815
2842
|
homeDir: process.env.HOME ?? homedir(),
|
|
2816
2843
|
query,
|
|
2817
2844
|
limit,
|
|
2818
|
-
})).map((path) => ({ path, kind:
|
|
2845
|
+
})).map((path) => ({ path, kind: "directory" }));
|
|
2819
2846
|
const directories = entries
|
|
2820
|
-
.filter((entry) => entry.kind ===
|
|
2847
|
+
.filter((entry) => entry.kind === "directory")
|
|
2821
2848
|
.map((entry) => entry.path);
|
|
2822
2849
|
this.emit({
|
|
2823
|
-
type:
|
|
2850
|
+
type: "directory_suggestions_response",
|
|
2824
2851
|
payload: {
|
|
2825
2852
|
directories,
|
|
2826
2853
|
entries,
|
|
@@ -2831,7 +2858,7 @@ export class Session {
|
|
|
2831
2858
|
}
|
|
2832
2859
|
catch (error) {
|
|
2833
2860
|
this.emit({
|
|
2834
|
-
type:
|
|
2861
|
+
type: "directory_suggestions_response",
|
|
2835
2862
|
payload: {
|
|
2836
2863
|
directories: [],
|
|
2837
2864
|
entries: [],
|
|
@@ -2842,17 +2869,17 @@ export class Session {
|
|
|
2842
2869
|
}
|
|
2843
2870
|
}
|
|
2844
2871
|
normalizeCheckoutDiffCompare(compare) {
|
|
2845
|
-
if (compare.mode ===
|
|
2846
|
-
return { mode:
|
|
2872
|
+
if (compare.mode === "uncommitted") {
|
|
2873
|
+
return { mode: "uncommitted" };
|
|
2847
2874
|
}
|
|
2848
2875
|
const trimmedBaseRef = compare.baseRef?.trim();
|
|
2849
|
-
return trimmedBaseRef ? { mode:
|
|
2876
|
+
return trimmedBaseRef ? { mode: "base", baseRef: trimmedBaseRef } : { mode: "base" };
|
|
2850
2877
|
}
|
|
2851
2878
|
buildCheckoutDiffTargetKey(cwd, compare) {
|
|
2852
2879
|
return JSON.stringify([
|
|
2853
2880
|
cwd,
|
|
2854
2881
|
compare.mode,
|
|
2855
|
-
compare.mode ===
|
|
2882
|
+
compare.mode === "base" ? (compare.baseRef ?? "") : "",
|
|
2856
2883
|
]);
|
|
2857
2884
|
}
|
|
2858
2885
|
closeCheckoutDiffWatchTarget(target) {
|
|
@@ -2887,7 +2914,7 @@ export class Session {
|
|
|
2887
2914
|
}
|
|
2888
2915
|
async resolveCheckoutGitDir(cwd) {
|
|
2889
2916
|
try {
|
|
2890
|
-
const { stdout } = await execAsync(
|
|
2917
|
+
const { stdout } = await execAsync("git rev-parse --absolute-git-dir", {
|
|
2891
2918
|
cwd,
|
|
2892
2919
|
env: READ_ONLY_GIT_ENV,
|
|
2893
2920
|
});
|
|
@@ -2898,9 +2925,150 @@ export class Session {
|
|
|
2898
2925
|
return null;
|
|
2899
2926
|
}
|
|
2900
2927
|
}
|
|
2928
|
+
async resolveWorkspaceGitRefsRoot(gitDir) {
|
|
2929
|
+
try {
|
|
2930
|
+
const commonDir = (await readFile(join(gitDir, "commondir"), "utf8")).trim();
|
|
2931
|
+
if (commonDir.length > 0) {
|
|
2932
|
+
return resolve(gitDir, commonDir);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
catch {
|
|
2936
|
+
// Regular repos do not have a commondir file.
|
|
2937
|
+
}
|
|
2938
|
+
return gitDir;
|
|
2939
|
+
}
|
|
2940
|
+
closeWorkspaceGitWatchTarget(target) {
|
|
2941
|
+
if (target.debounceTimer) {
|
|
2942
|
+
clearTimeout(target.debounceTimer);
|
|
2943
|
+
target.debounceTimer = null;
|
|
2944
|
+
}
|
|
2945
|
+
for (const watcher of target.watchers) {
|
|
2946
|
+
watcher.close();
|
|
2947
|
+
}
|
|
2948
|
+
target.watchers = [];
|
|
2949
|
+
}
|
|
2950
|
+
removeWorkspaceGitWatchTarget(cwd) {
|
|
2951
|
+
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
2952
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
2953
|
+
if (!target) {
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
this.closeWorkspaceGitWatchTarget(target);
|
|
2957
|
+
this.workspaceGitWatchTargets.delete(workspaceId);
|
|
2958
|
+
}
|
|
2959
|
+
workspaceGitDescriptorFingerprint(workspace) {
|
|
2960
|
+
if (!workspace) {
|
|
2961
|
+
return WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT;
|
|
2962
|
+
}
|
|
2963
|
+
return JSON.stringify([
|
|
2964
|
+
workspace.name,
|
|
2965
|
+
workspace.diffStat ? [workspace.diffStat.additions, workspace.diffStat.deletions] : null,
|
|
2966
|
+
]);
|
|
2967
|
+
}
|
|
2968
|
+
shouldSkipWorkspaceGitWatchUpdate(workspaceId, workspace) {
|
|
2969
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
2970
|
+
if (!target) {
|
|
2971
|
+
return false;
|
|
2972
|
+
}
|
|
2973
|
+
const nextFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
|
|
2974
|
+
if (target.latestFingerprint === nextFingerprint) {
|
|
2975
|
+
return true;
|
|
2976
|
+
}
|
|
2977
|
+
target.latestFingerprint = nextFingerprint;
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
rememberWorkspaceGitWatchFingerprint(workspaceId, workspace) {
|
|
2981
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
2982
|
+
if (!target) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
target.latestFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
|
|
2986
|
+
}
|
|
2987
|
+
primeWorkspaceGitWatchFingerprints(workspaces) {
|
|
2988
|
+
for (const workspace of workspaces) {
|
|
2989
|
+
this.rememberWorkspaceGitWatchFingerprint(workspace.id, workspace);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
scheduleWorkspaceGitWatchRefresh(target) {
|
|
2993
|
+
if (target.debounceTimer) {
|
|
2994
|
+
clearTimeout(target.debounceTimer);
|
|
2995
|
+
}
|
|
2996
|
+
target.debounceTimer = setTimeout(() => {
|
|
2997
|
+
target.debounceTimer = null;
|
|
2998
|
+
void this.refreshWorkspaceGitWatchTarget(target);
|
|
2999
|
+
}, WORKSPACE_GIT_WATCH_DEBOUNCE_MS);
|
|
3000
|
+
}
|
|
3001
|
+
async refreshWorkspaceGitWatchTarget(target) {
|
|
3002
|
+
if (target.refreshPromise) {
|
|
3003
|
+
target.refreshQueued = true;
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
target.refreshPromise = (async () => {
|
|
3007
|
+
do {
|
|
3008
|
+
target.refreshQueued = false;
|
|
3009
|
+
await this.emitWorkspaceUpdateForCwd(target.cwd, {
|
|
3010
|
+
dedupeGitState: true,
|
|
3011
|
+
});
|
|
3012
|
+
} while (target.refreshQueued);
|
|
3013
|
+
})();
|
|
3014
|
+
try {
|
|
3015
|
+
await target.refreshPromise;
|
|
3016
|
+
}
|
|
3017
|
+
finally {
|
|
3018
|
+
target.refreshPromise = null;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
async ensureWorkspaceGitWatchTarget(cwd) {
|
|
3022
|
+
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
3023
|
+
if (this.workspaceGitWatchTargets.has(workspaceId)) {
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
3026
|
+
const gitDir = await this.resolveCheckoutGitDir(cwd);
|
|
3027
|
+
if (!gitDir) {
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
const refsRoot = await this.resolveWorkspaceGitRefsRoot(gitDir);
|
|
3031
|
+
const target = {
|
|
3032
|
+
cwd: workspaceId,
|
|
3033
|
+
watchers: [],
|
|
3034
|
+
debounceTimer: null,
|
|
3035
|
+
refreshPromise: null,
|
|
3036
|
+
refreshQueued: false,
|
|
3037
|
+
latestFingerprint: null,
|
|
3038
|
+
};
|
|
3039
|
+
for (const watchPath of new Set([join(gitDir, "HEAD"), join(refsRoot, "refs", "heads")])) {
|
|
3040
|
+
let watcher = null;
|
|
3041
|
+
try {
|
|
3042
|
+
watcher = watch(watchPath, { recursive: false }, () => {
|
|
3043
|
+
this.scheduleWorkspaceGitWatchRefresh(target);
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
catch (error) {
|
|
3047
|
+
this.sessionLogger.warn({ err: error, cwd, watchPath }, "Failed to start workspace git watcher");
|
|
3048
|
+
}
|
|
3049
|
+
if (!watcher) {
|
|
3050
|
+
continue;
|
|
3051
|
+
}
|
|
3052
|
+
watcher.on("error", (error) => {
|
|
3053
|
+
this.sessionLogger.warn({ err: error, cwd, watchPath }, "Workspace git watcher error");
|
|
3054
|
+
});
|
|
3055
|
+
target.watchers.push(watcher);
|
|
3056
|
+
}
|
|
3057
|
+
if (target.watchers.length === 0) {
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
this.workspaceGitWatchTargets.set(workspaceId, target);
|
|
3061
|
+
}
|
|
3062
|
+
async syncWorkspaceGitWatchTarget(cwd, options) {
|
|
3063
|
+
if (!options.isGit) {
|
|
3064
|
+
this.removeWorkspaceGitWatchTarget(cwd);
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
await this.ensureWorkspaceGitWatchTarget(cwd);
|
|
3068
|
+
}
|
|
2901
3069
|
async resolveCheckoutWatchRoot(cwd) {
|
|
2902
3070
|
try {
|
|
2903
|
-
const { stdout } = await execAsync(
|
|
3071
|
+
const { stdout } = await execAsync("git rev-parse --path-format=absolute --show-toplevel", {
|
|
2904
3072
|
cwd,
|
|
2905
3073
|
env: READ_ONLY_GIT_ENV,
|
|
2906
3074
|
});
|
|
@@ -2926,7 +3094,7 @@ export class Session {
|
|
|
2926
3094
|
}
|
|
2927
3095
|
for (const subscriptionId of target.subscriptions) {
|
|
2928
3096
|
this.emit({
|
|
2929
|
-
type:
|
|
3097
|
+
type: "checkout_diff_update",
|
|
2930
3098
|
payload: {
|
|
2931
3099
|
subscriptionId,
|
|
2932
3100
|
...snapshot,
|
|
@@ -3019,7 +3187,7 @@ export class Session {
|
|
|
3019
3187
|
watchPaths.add(gitDir);
|
|
3020
3188
|
}
|
|
3021
3189
|
let hasRecursiveRepoCoverage = false;
|
|
3022
|
-
const allowRecursiveRepoWatch = process.platform !==
|
|
3190
|
+
const allowRecursiveRepoWatch = process.platform !== "linux";
|
|
3023
3191
|
for (const watchPath of watchPaths) {
|
|
3024
3192
|
const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
|
|
3025
3193
|
const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
|
|
@@ -3040,21 +3208,21 @@ export class Session {
|
|
|
3040
3208
|
if (shouldTryRecursive) {
|
|
3041
3209
|
try {
|
|
3042
3210
|
watcher = createWatcher(false);
|
|
3043
|
-
this.sessionLogger.warn({ err: error, watchPath, cwd, compare },
|
|
3211
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff recursive watch unavailable; using non-recursive fallback");
|
|
3044
3212
|
}
|
|
3045
3213
|
catch (fallbackError) {
|
|
3046
|
-
this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare },
|
|
3214
|
+
this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
|
|
3047
3215
|
}
|
|
3048
3216
|
}
|
|
3049
3217
|
else {
|
|
3050
|
-
this.sessionLogger.warn({ err: error, watchPath, cwd, compare },
|
|
3218
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
|
|
3051
3219
|
}
|
|
3052
3220
|
}
|
|
3053
3221
|
if (!watcher) {
|
|
3054
3222
|
continue;
|
|
3055
3223
|
}
|
|
3056
|
-
watcher.on(
|
|
3057
|
-
this.sessionLogger.warn({ err: error, watchPath, cwd, compare },
|
|
3224
|
+
watcher.on("error", (error) => {
|
|
3225
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff watcher error");
|
|
3058
3226
|
});
|
|
3059
3227
|
target.watchers.push(watcher);
|
|
3060
3228
|
if (watchPath === repoWatchPath && watcherIsRecursive) {
|
|
@@ -3070,8 +3238,8 @@ export class Session {
|
|
|
3070
3238
|
cwd,
|
|
3071
3239
|
compare,
|
|
3072
3240
|
intervalMs: CHECKOUT_DIFF_FALLBACK_REFRESH_MS,
|
|
3073
|
-
reason: target.watchers.length === 0 ?
|
|
3074
|
-
},
|
|
3241
|
+
reason: target.watchers.length === 0 ? "no_watchers" : "missing_recursive_repo_root_coverage",
|
|
3242
|
+
}, "Checkout diff watchers unavailable; using timed refresh fallback");
|
|
3075
3243
|
}
|
|
3076
3244
|
this.checkoutDiffTargets.set(targetKey, target);
|
|
3077
3245
|
return target;
|
|
@@ -3092,7 +3260,7 @@ export class Session {
|
|
|
3092
3260
|
target.latestPayload = snapshot;
|
|
3093
3261
|
target.latestFingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
|
|
3094
3262
|
this.emit({
|
|
3095
|
-
type:
|
|
3263
|
+
type: "subscribe_checkout_diff_response",
|
|
3096
3264
|
payload: {
|
|
3097
3265
|
subscriptionId: msg.subscriptionId,
|
|
3098
3266
|
...snapshot,
|
|
@@ -3115,12 +3283,12 @@ export class Session {
|
|
|
3115
3283
|
async handleCheckoutCommitRequest(msg) {
|
|
3116
3284
|
const { cwd, requestId } = msg;
|
|
3117
3285
|
try {
|
|
3118
|
-
let message = msg.message?.trim() ??
|
|
3286
|
+
let message = msg.message?.trim() ?? "";
|
|
3119
3287
|
if (!message) {
|
|
3120
3288
|
message = await this.generateCommitMessage(cwd);
|
|
3121
3289
|
}
|
|
3122
3290
|
if (!message) {
|
|
3123
|
-
throw new Error(
|
|
3291
|
+
throw new Error("Commit message is required");
|
|
3124
3292
|
}
|
|
3125
3293
|
await commitChanges(cwd, {
|
|
3126
3294
|
message,
|
|
@@ -3128,7 +3296,7 @@ export class Session {
|
|
|
3128
3296
|
});
|
|
3129
3297
|
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3130
3298
|
this.emit({
|
|
3131
|
-
type:
|
|
3299
|
+
type: "checkout_commit_response",
|
|
3132
3300
|
payload: {
|
|
3133
3301
|
cwd,
|
|
3134
3302
|
success: true,
|
|
@@ -3139,7 +3307,7 @@ export class Session {
|
|
|
3139
3307
|
}
|
|
3140
3308
|
catch (error) {
|
|
3141
3309
|
this.emit({
|
|
3142
|
-
type:
|
|
3310
|
+
type: "checkout_commit_response",
|
|
3143
3311
|
payload: {
|
|
3144
3312
|
cwd,
|
|
3145
3313
|
success: false,
|
|
@@ -3155,13 +3323,13 @@ export class Session {
|
|
|
3155
3323
|
const status = await getCheckoutStatus(cwd, { paseoHome: this.paseoHome });
|
|
3156
3324
|
if (!status.isGit) {
|
|
3157
3325
|
try {
|
|
3158
|
-
await execAsync(
|
|
3326
|
+
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
3159
3327
|
cwd,
|
|
3160
3328
|
env: READ_ONLY_GIT_ENV,
|
|
3161
3329
|
});
|
|
3162
3330
|
}
|
|
3163
3331
|
catch (error) {
|
|
3164
|
-
const details = typeof error?.stderr ===
|
|
3332
|
+
const details = typeof error?.stderr === "string"
|
|
3165
3333
|
? String(error.stderr).trim()
|
|
3166
3334
|
: error instanceof Error
|
|
3167
3335
|
? error.message
|
|
@@ -3170,28 +3338,28 @@ export class Session {
|
|
|
3170
3338
|
}
|
|
3171
3339
|
}
|
|
3172
3340
|
if (msg.requireCleanTarget) {
|
|
3173
|
-
const { stdout } = await execAsync(
|
|
3341
|
+
const { stdout } = await execAsync("git status --porcelain", {
|
|
3174
3342
|
cwd,
|
|
3175
3343
|
env: READ_ONLY_GIT_ENV,
|
|
3176
3344
|
});
|
|
3177
3345
|
if (stdout.trim().length > 0) {
|
|
3178
|
-
throw new Error(
|
|
3346
|
+
throw new Error("Working directory has uncommitted changes.");
|
|
3179
3347
|
}
|
|
3180
3348
|
}
|
|
3181
3349
|
let baseRef = msg.baseRef ?? (status.isGit ? status.baseRef : null);
|
|
3182
3350
|
if (!baseRef) {
|
|
3183
|
-
throw new Error(
|
|
3351
|
+
throw new Error("Base branch is required for merge");
|
|
3184
3352
|
}
|
|
3185
|
-
if (baseRef.startsWith(
|
|
3186
|
-
baseRef = baseRef.slice(
|
|
3353
|
+
if (baseRef.startsWith("origin/")) {
|
|
3354
|
+
baseRef = baseRef.slice("origin/".length);
|
|
3187
3355
|
}
|
|
3188
3356
|
await mergeToBase(cwd, {
|
|
3189
3357
|
baseRef,
|
|
3190
|
-
mode: msg.strategy ===
|
|
3358
|
+
mode: msg.strategy === "squash" ? "squash" : "merge",
|
|
3191
3359
|
}, { paseoHome: this.paseoHome });
|
|
3192
3360
|
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3193
3361
|
this.emit({
|
|
3194
|
-
type:
|
|
3362
|
+
type: "checkout_merge_response",
|
|
3195
3363
|
payload: {
|
|
3196
3364
|
cwd,
|
|
3197
3365
|
success: true,
|
|
@@ -3202,7 +3370,7 @@ export class Session {
|
|
|
3202
3370
|
}
|
|
3203
3371
|
catch (error) {
|
|
3204
3372
|
this.emit({
|
|
3205
|
-
type:
|
|
3373
|
+
type: "checkout_merge_response",
|
|
3206
3374
|
payload: {
|
|
3207
3375
|
cwd,
|
|
3208
3376
|
success: false,
|
|
@@ -3216,12 +3384,12 @@ export class Session {
|
|
|
3216
3384
|
const { cwd, requestId } = msg;
|
|
3217
3385
|
try {
|
|
3218
3386
|
if (msg.requireCleanTarget ?? true) {
|
|
3219
|
-
const { stdout } = await execAsync(
|
|
3387
|
+
const { stdout } = await execAsync("git status --porcelain", {
|
|
3220
3388
|
cwd,
|
|
3221
3389
|
env: READ_ONLY_GIT_ENV,
|
|
3222
3390
|
});
|
|
3223
3391
|
if (stdout.trim().length > 0) {
|
|
3224
|
-
throw new Error(
|
|
3392
|
+
throw new Error("Working directory has uncommitted changes.");
|
|
3225
3393
|
}
|
|
3226
3394
|
}
|
|
3227
3395
|
await mergeFromBase(cwd, {
|
|
@@ -3230,7 +3398,7 @@ export class Session {
|
|
|
3230
3398
|
});
|
|
3231
3399
|
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3232
3400
|
this.emit({
|
|
3233
|
-
type:
|
|
3401
|
+
type: "checkout_merge_from_base_response",
|
|
3234
3402
|
payload: {
|
|
3235
3403
|
cwd,
|
|
3236
3404
|
success: true,
|
|
@@ -3241,7 +3409,7 @@ export class Session {
|
|
|
3241
3409
|
}
|
|
3242
3410
|
catch (error) {
|
|
3243
3411
|
this.emit({
|
|
3244
|
-
type:
|
|
3412
|
+
type: "checkout_merge_from_base_response",
|
|
3245
3413
|
payload: {
|
|
3246
3414
|
cwd,
|
|
3247
3415
|
success: false,
|
|
@@ -3256,7 +3424,7 @@ export class Session {
|
|
|
3256
3424
|
try {
|
|
3257
3425
|
await pushCurrentBranch(cwd);
|
|
3258
3426
|
this.emit({
|
|
3259
|
-
type:
|
|
3427
|
+
type: "checkout_push_response",
|
|
3260
3428
|
payload: {
|
|
3261
3429
|
cwd,
|
|
3262
3430
|
success: true,
|
|
@@ -3267,7 +3435,7 @@ export class Session {
|
|
|
3267
3435
|
}
|
|
3268
3436
|
catch (error) {
|
|
3269
3437
|
this.emit({
|
|
3270
|
-
type:
|
|
3438
|
+
type: "checkout_push_response",
|
|
3271
3439
|
payload: {
|
|
3272
3440
|
cwd,
|
|
3273
3441
|
success: false,
|
|
@@ -3280,8 +3448,8 @@ export class Session {
|
|
|
3280
3448
|
async handleCheckoutPrCreateRequest(msg) {
|
|
3281
3449
|
const { cwd, requestId } = msg;
|
|
3282
3450
|
try {
|
|
3283
|
-
let title = msg.title?.trim() ??
|
|
3284
|
-
let body = msg.body?.trim() ??
|
|
3451
|
+
let title = msg.title?.trim() ?? "";
|
|
3452
|
+
let body = msg.body?.trim() ?? "";
|
|
3285
3453
|
if (!title || !body) {
|
|
3286
3454
|
const generated = await this.generatePullRequestText(cwd, msg.baseRef);
|
|
3287
3455
|
if (!title)
|
|
@@ -3295,7 +3463,7 @@ export class Session {
|
|
|
3295
3463
|
base: msg.baseRef,
|
|
3296
3464
|
});
|
|
3297
3465
|
this.emit({
|
|
3298
|
-
type:
|
|
3466
|
+
type: "checkout_pr_create_response",
|
|
3299
3467
|
payload: {
|
|
3300
3468
|
cwd,
|
|
3301
3469
|
url: result.url ?? null,
|
|
@@ -3307,7 +3475,7 @@ export class Session {
|
|
|
3307
3475
|
}
|
|
3308
3476
|
catch (error) {
|
|
3309
3477
|
this.emit({
|
|
3310
|
-
type:
|
|
3478
|
+
type: "checkout_pr_create_response",
|
|
3311
3479
|
payload: {
|
|
3312
3480
|
cwd,
|
|
3313
3481
|
url: null,
|
|
@@ -3323,7 +3491,7 @@ export class Session {
|
|
|
3323
3491
|
try {
|
|
3324
3492
|
const prStatus = await getPullRequestStatus(cwd);
|
|
3325
3493
|
this.emit({
|
|
3326
|
-
type:
|
|
3494
|
+
type: "checkout_pr_status_response",
|
|
3327
3495
|
payload: {
|
|
3328
3496
|
cwd,
|
|
3329
3497
|
status: prStatus.status,
|
|
@@ -3335,7 +3503,7 @@ export class Session {
|
|
|
3335
3503
|
}
|
|
3336
3504
|
catch (error) {
|
|
3337
3505
|
this.emit({
|
|
3338
|
-
type:
|
|
3506
|
+
type: "checkout_pr_status_response",
|
|
3339
3507
|
payload: {
|
|
3340
3508
|
cwd,
|
|
3341
3509
|
status: null,
|
|
@@ -3351,10 +3519,10 @@ export class Session {
|
|
|
3351
3519
|
const cwd = msg.repoRoot ?? msg.cwd;
|
|
3352
3520
|
if (!cwd) {
|
|
3353
3521
|
this.emit({
|
|
3354
|
-
type:
|
|
3522
|
+
type: "paseo_worktree_list_response",
|
|
3355
3523
|
payload: {
|
|
3356
3524
|
worktrees: [],
|
|
3357
|
-
error: { code:
|
|
3525
|
+
error: { code: "UNKNOWN", message: "cwd or repoRoot is required" },
|
|
3358
3526
|
requestId,
|
|
3359
3527
|
},
|
|
3360
3528
|
});
|
|
@@ -3363,7 +3531,7 @@ export class Session {
|
|
|
3363
3531
|
try {
|
|
3364
3532
|
const worktrees = await listPaseoWorktrees({ cwd, paseoHome: this.paseoHome });
|
|
3365
3533
|
this.emit({
|
|
3366
|
-
type:
|
|
3534
|
+
type: "paseo_worktree_list_response",
|
|
3367
3535
|
payload: {
|
|
3368
3536
|
worktrees: worktrees.map((entry) => ({
|
|
3369
3537
|
worktreePath: entry.path,
|
|
@@ -3378,7 +3546,7 @@ export class Session {
|
|
|
3378
3546
|
}
|
|
3379
3547
|
catch (error) {
|
|
3380
3548
|
this.emit({
|
|
3381
|
-
type:
|
|
3549
|
+
type: "paseo_worktree_list_response",
|
|
3382
3550
|
payload: {
|
|
3383
3551
|
worktrees: [],
|
|
3384
3552
|
error: this.toCheckoutError(error),
|
|
@@ -3443,7 +3611,7 @@ export class Session {
|
|
|
3443
3611
|
}
|
|
3444
3612
|
for (const agentId of removedAgents) {
|
|
3445
3613
|
this.emit({
|
|
3446
|
-
type:
|
|
3614
|
+
type: "agent_deleted",
|
|
3447
3615
|
payload: {
|
|
3448
3616
|
agentId,
|
|
3449
3617
|
requestId: options.requestId,
|
|
@@ -3460,7 +3628,7 @@ export class Session {
|
|
|
3460
3628
|
try {
|
|
3461
3629
|
if (!targetPath) {
|
|
3462
3630
|
if (!repoRoot || !msg.branchName) {
|
|
3463
|
-
throw new Error(
|
|
3631
|
+
throw new Error("worktreePath or repoRoot+branchName is required");
|
|
3464
3632
|
}
|
|
3465
3633
|
const worktrees = await listPaseoWorktrees({ cwd: repoRoot, paseoHome: this.paseoHome });
|
|
3466
3634
|
const match = worktrees.find((entry) => entry.branchName === msg.branchName);
|
|
@@ -3474,13 +3642,13 @@ export class Session {
|
|
|
3474
3642
|
});
|
|
3475
3643
|
if (!ownership.allowed) {
|
|
3476
3644
|
this.emit({
|
|
3477
|
-
type:
|
|
3645
|
+
type: "paseo_worktree_archive_response",
|
|
3478
3646
|
payload: {
|
|
3479
3647
|
success: false,
|
|
3480
3648
|
removedAgents: [],
|
|
3481
3649
|
error: {
|
|
3482
|
-
code:
|
|
3483
|
-
message:
|
|
3650
|
+
code: "NOT_ALLOWED",
|
|
3651
|
+
message: "Worktree is not a Paseo-owned worktree",
|
|
3484
3652
|
},
|
|
3485
3653
|
requestId,
|
|
3486
3654
|
},
|
|
@@ -3489,7 +3657,7 @@ export class Session {
|
|
|
3489
3657
|
}
|
|
3490
3658
|
repoRoot = ownership.repoRoot ?? repoRoot ?? null;
|
|
3491
3659
|
if (!repoRoot) {
|
|
3492
|
-
throw new Error(
|
|
3660
|
+
throw new Error("Unable to resolve repo root for worktree");
|
|
3493
3661
|
}
|
|
3494
3662
|
const removedAgents = await this.archivePaseoWorktree({
|
|
3495
3663
|
targetPath,
|
|
@@ -3497,7 +3665,7 @@ export class Session {
|
|
|
3497
3665
|
requestId,
|
|
3498
3666
|
});
|
|
3499
3667
|
this.emit({
|
|
3500
|
-
type:
|
|
3668
|
+
type: "paseo_worktree_archive_response",
|
|
3501
3669
|
payload: {
|
|
3502
3670
|
success: true,
|
|
3503
3671
|
removedAgents,
|
|
@@ -3508,7 +3676,7 @@ export class Session {
|
|
|
3508
3676
|
}
|
|
3509
3677
|
catch (error) {
|
|
3510
3678
|
this.emit({
|
|
3511
|
-
type:
|
|
3679
|
+
type: "paseo_worktree_archive_response",
|
|
3512
3680
|
payload: {
|
|
3513
3681
|
success: false,
|
|
3514
3682
|
removedAgents: [],
|
|
@@ -3522,31 +3690,31 @@ export class Session {
|
|
|
3522
3690
|
* Handle read-only file explorer requests scoped to a workspace cwd
|
|
3523
3691
|
*/
|
|
3524
3692
|
async handleFileExplorerRequest(request) {
|
|
3525
|
-
const { cwd: workspaceCwd, path: requestedPath =
|
|
3693
|
+
const { cwd: workspaceCwd, path: requestedPath = ".", mode, requestId } = request;
|
|
3526
3694
|
const cwd = workspaceCwd.trim();
|
|
3527
3695
|
if (!cwd) {
|
|
3528
3696
|
this.emit({
|
|
3529
|
-
type:
|
|
3697
|
+
type: "file_explorer_response",
|
|
3530
3698
|
payload: {
|
|
3531
3699
|
cwd: workspaceCwd,
|
|
3532
3700
|
path: requestedPath,
|
|
3533
3701
|
mode,
|
|
3534
3702
|
directory: null,
|
|
3535
3703
|
file: null,
|
|
3536
|
-
error:
|
|
3704
|
+
error: "cwd is required",
|
|
3537
3705
|
requestId,
|
|
3538
3706
|
},
|
|
3539
3707
|
});
|
|
3540
3708
|
return;
|
|
3541
3709
|
}
|
|
3542
3710
|
try {
|
|
3543
|
-
if (mode ===
|
|
3711
|
+
if (mode === "list") {
|
|
3544
3712
|
const directory = await listDirectoryEntries({
|
|
3545
3713
|
root: cwd,
|
|
3546
3714
|
relativePath: requestedPath,
|
|
3547
3715
|
});
|
|
3548
3716
|
this.emit({
|
|
3549
|
-
type:
|
|
3717
|
+
type: "file_explorer_response",
|
|
3550
3718
|
payload: {
|
|
3551
3719
|
cwd,
|
|
3552
3720
|
path: directory.path,
|
|
@@ -3564,7 +3732,7 @@ export class Session {
|
|
|
3564
3732
|
relativePath: requestedPath,
|
|
3565
3733
|
});
|
|
3566
3734
|
this.emit({
|
|
3567
|
-
type:
|
|
3735
|
+
type: "file_explorer_response",
|
|
3568
3736
|
payload: {
|
|
3569
3737
|
cwd,
|
|
3570
3738
|
path: file.path,
|
|
@@ -3580,7 +3748,7 @@ export class Session {
|
|
|
3580
3748
|
catch (error) {
|
|
3581
3749
|
this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to fulfill file explorer request for workspace ${cwd}`);
|
|
3582
3750
|
this.emit({
|
|
3583
|
-
type:
|
|
3751
|
+
type: "file_explorer_response",
|
|
3584
3752
|
payload: {
|
|
3585
3753
|
cwd,
|
|
3586
3754
|
path: requestedPath,
|
|
@@ -3601,7 +3769,7 @@ export class Session {
|
|
|
3601
3769
|
try {
|
|
3602
3770
|
const icon = await getProjectIcon(cwd);
|
|
3603
3771
|
this.emit({
|
|
3604
|
-
type:
|
|
3772
|
+
type: "project_icon_response",
|
|
3605
3773
|
payload: {
|
|
3606
3774
|
cwd,
|
|
3607
3775
|
icon,
|
|
@@ -3612,7 +3780,7 @@ export class Session {
|
|
|
3612
3780
|
}
|
|
3613
3781
|
catch (error) {
|
|
3614
3782
|
this.emit({
|
|
3615
|
-
type:
|
|
3783
|
+
type: "project_icon_response",
|
|
3616
3784
|
payload: {
|
|
3617
3785
|
cwd,
|
|
3618
3786
|
icon: null,
|
|
@@ -3630,7 +3798,7 @@ export class Session {
|
|
|
3630
3798
|
const cwd = workspaceCwd.trim();
|
|
3631
3799
|
if (!cwd) {
|
|
3632
3800
|
this.emit({
|
|
3633
|
-
type:
|
|
3801
|
+
type: "file_download_token_response",
|
|
3634
3802
|
payload: {
|
|
3635
3803
|
cwd: workspaceCwd,
|
|
3636
3804
|
path: requestedPath,
|
|
@@ -3638,7 +3806,7 @@ export class Session {
|
|
|
3638
3806
|
fileName: null,
|
|
3639
3807
|
mimeType: null,
|
|
3640
3808
|
size: null,
|
|
3641
|
-
error:
|
|
3809
|
+
error: "cwd is required",
|
|
3642
3810
|
requestId,
|
|
3643
3811
|
},
|
|
3644
3812
|
});
|
|
@@ -3658,7 +3826,7 @@ export class Session {
|
|
|
3658
3826
|
size: info.size,
|
|
3659
3827
|
});
|
|
3660
3828
|
this.emit({
|
|
3661
|
-
type:
|
|
3829
|
+
type: "file_download_token_response",
|
|
3662
3830
|
payload: {
|
|
3663
3831
|
cwd,
|
|
3664
3832
|
path: info.path,
|
|
@@ -3674,7 +3842,7 @@ export class Session {
|
|
|
3674
3842
|
catch (error) {
|
|
3675
3843
|
this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to issue download token for workspace ${cwd}`);
|
|
3676
3844
|
this.emit({
|
|
3677
|
-
type:
|
|
3845
|
+
type: "file_download_token_response",
|
|
3678
3846
|
payload: {
|
|
3679
3847
|
cwd,
|
|
3680
3848
|
path: requestedPath,
|
|
@@ -3713,7 +3881,7 @@ export class Session {
|
|
|
3713
3881
|
async resolveAgentIdentifier(identifier) {
|
|
3714
3882
|
const trimmed = identifier.trim();
|
|
3715
3883
|
if (!trimmed) {
|
|
3716
|
-
return { ok: false, error:
|
|
3884
|
+
return { ok: false, error: "Agent identifier cannot be empty" };
|
|
3717
3885
|
}
|
|
3718
3886
|
const stored = await this.agentStorage.list();
|
|
3719
3887
|
const storedRecords = stored.filter((record) => !record.internal);
|
|
@@ -3737,7 +3905,7 @@ export class Session {
|
|
|
3737
3905
|
error: `Agent identifier "${trimmed}" is ambiguous (${prefixMatches
|
|
3738
3906
|
.slice(0, 5)
|
|
3739
3907
|
.map((id) => id.slice(0, 8))
|
|
3740
|
-
.join(
|
|
3908
|
+
.join(", ")}${prefixMatches.length > 5 ? ", …" : ""})`,
|
|
3741
3909
|
};
|
|
3742
3910
|
}
|
|
3743
3911
|
const titleMatches = storedRecords.filter((record) => record.title === trimmed);
|
|
@@ -3750,7 +3918,7 @@ export class Session {
|
|
|
3750
3918
|
error: `Agent title "${trimmed}" is ambiguous (${titleMatches
|
|
3751
3919
|
.slice(0, 5)
|
|
3752
3920
|
.map((r) => r.id.slice(0, 8))
|
|
3753
|
-
.join(
|
|
3921
|
+
.join(", ")}${titleMatches.length > 5 ? ", …" : ""})`,
|
|
3754
3922
|
};
|
|
3755
3923
|
}
|
|
3756
3924
|
return { ok: false, error: `Agent not found: ${trimmed}` };
|
|
@@ -3767,7 +3935,7 @@ export class Session {
|
|
|
3767
3935
|
return this.buildStoredAgentPayload(record);
|
|
3768
3936
|
}
|
|
3769
3937
|
normalizeFetchAgentsSort(sort) {
|
|
3770
|
-
const fallback = [{ key:
|
|
3938
|
+
const fallback = [{ key: "updated_at", direction: "desc" }];
|
|
3771
3939
|
if (!sort || sort.length === 0) {
|
|
3772
3940
|
return fallback;
|
|
3773
3941
|
}
|
|
@@ -3785,42 +3953,42 @@ export class Session {
|
|
|
3785
3953
|
getStatusPriority(agent) {
|
|
3786
3954
|
const attentionReason = agent.attentionReason ?? null;
|
|
3787
3955
|
const hasPendingPermission = (agent.pendingPermissions?.length ?? 0) > 0;
|
|
3788
|
-
if (hasPendingPermission || attentionReason ===
|
|
3956
|
+
if (hasPendingPermission || attentionReason === "permission") {
|
|
3789
3957
|
return 0;
|
|
3790
3958
|
}
|
|
3791
|
-
if (agent.status ===
|
|
3959
|
+
if (agent.status === "error" || attentionReason === "error") {
|
|
3792
3960
|
return 1;
|
|
3793
3961
|
}
|
|
3794
|
-
if (agent.status ===
|
|
3962
|
+
if (agent.status === "running") {
|
|
3795
3963
|
return 2;
|
|
3796
3964
|
}
|
|
3797
|
-
if (agent.status ===
|
|
3965
|
+
if (agent.status === "initializing") {
|
|
3798
3966
|
return 3;
|
|
3799
3967
|
}
|
|
3800
3968
|
return 4;
|
|
3801
3969
|
}
|
|
3802
3970
|
getFetchAgentsSortValue(entry, key) {
|
|
3803
3971
|
switch (key) {
|
|
3804
|
-
case
|
|
3972
|
+
case "status_priority":
|
|
3805
3973
|
return this.getStatusPriority(entry.agent);
|
|
3806
|
-
case
|
|
3974
|
+
case "created_at":
|
|
3807
3975
|
return Date.parse(entry.agent.createdAt);
|
|
3808
|
-
case
|
|
3976
|
+
case "updated_at":
|
|
3809
3977
|
return Date.parse(entry.agent.updatedAt);
|
|
3810
|
-
case
|
|
3811
|
-
return entry.agent.title?.toLocaleLowerCase() ??
|
|
3978
|
+
case "title":
|
|
3979
|
+
return entry.agent.title?.toLocaleLowerCase() ?? "";
|
|
3812
3980
|
}
|
|
3813
3981
|
}
|
|
3814
3982
|
getFetchAgentsSortValueFromAgent(agent, key) {
|
|
3815
3983
|
switch (key) {
|
|
3816
|
-
case
|
|
3984
|
+
case "status_priority":
|
|
3817
3985
|
return this.getStatusPriority(agent);
|
|
3818
|
-
case
|
|
3986
|
+
case "created_at":
|
|
3819
3987
|
return Date.parse(agent.createdAt);
|
|
3820
|
-
case
|
|
3988
|
+
case "updated_at":
|
|
3821
3989
|
return Date.parse(agent.updatedAt);
|
|
3822
|
-
case
|
|
3823
|
-
return agent.title?.toLocaleLowerCase() ??
|
|
3990
|
+
case "title":
|
|
3991
|
+
return agent.title?.toLocaleLowerCase() ?? "";
|
|
3824
3992
|
}
|
|
3825
3993
|
}
|
|
3826
3994
|
compareSortValues(left, right) {
|
|
@@ -3833,7 +4001,7 @@ export class Session {
|
|
|
3833
4001
|
if (right === null) {
|
|
3834
4002
|
return 1;
|
|
3835
4003
|
}
|
|
3836
|
-
if (typeof left ===
|
|
4004
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3837
4005
|
return left < right ? -1 : 1;
|
|
3838
4006
|
}
|
|
3839
4007
|
return String(left).localeCompare(String(right));
|
|
@@ -3846,7 +4014,7 @@ export class Session {
|
|
|
3846
4014
|
if (base === 0) {
|
|
3847
4015
|
continue;
|
|
3848
4016
|
}
|
|
3849
|
-
return spec.direction ===
|
|
4017
|
+
return spec.direction === "asc" ? base : -base;
|
|
3850
4018
|
}
|
|
3851
4019
|
return left.id.localeCompare(right.id);
|
|
3852
4020
|
}
|
|
@@ -3859,48 +4027,48 @@ export class Session {
|
|
|
3859
4027
|
sort,
|
|
3860
4028
|
values,
|
|
3861
4029
|
id: entry.agent.id,
|
|
3862
|
-
}),
|
|
4030
|
+
}), "utf8").toString("base64url");
|
|
3863
4031
|
}
|
|
3864
4032
|
decodeFetchAgentsCursor(cursor, sort) {
|
|
3865
4033
|
let parsed;
|
|
3866
4034
|
try {
|
|
3867
|
-
parsed = JSON.parse(Buffer.from(cursor,
|
|
4035
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3868
4036
|
}
|
|
3869
4037
|
catch {
|
|
3870
|
-
throw new SessionRequestError(
|
|
4038
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3871
4039
|
}
|
|
3872
|
-
if (!parsed || typeof parsed !==
|
|
3873
|
-
throw new SessionRequestError(
|
|
4040
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4041
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3874
4042
|
}
|
|
3875
4043
|
const payload = parsed;
|
|
3876
|
-
if (!Array.isArray(payload.sort) || typeof payload.id !==
|
|
3877
|
-
throw new SessionRequestError(
|
|
4044
|
+
if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
|
|
4045
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3878
4046
|
}
|
|
3879
|
-
if (!payload.values || typeof payload.values !==
|
|
3880
|
-
throw new SessionRequestError(
|
|
4047
|
+
if (!payload.values || typeof payload.values !== "object") {
|
|
4048
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3881
4049
|
}
|
|
3882
4050
|
const cursorSort = [];
|
|
3883
4051
|
for (const item of payload.sort) {
|
|
3884
4052
|
if (!item ||
|
|
3885
|
-
typeof item !==
|
|
3886
|
-
typeof item.key !==
|
|
3887
|
-
typeof item.direction !==
|
|
3888
|
-
throw new SessionRequestError(
|
|
4053
|
+
typeof item !== "object" ||
|
|
4054
|
+
typeof item.key !== "string" ||
|
|
4055
|
+
typeof item.direction !== "string") {
|
|
4056
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3889
4057
|
}
|
|
3890
4058
|
const key = item.key;
|
|
3891
4059
|
const direction = item.direction;
|
|
3892
|
-
if ((key !==
|
|
3893
|
-
key !==
|
|
3894
|
-
key !==
|
|
3895
|
-
key !==
|
|
3896
|
-
(direction !==
|
|
3897
|
-
throw new SessionRequestError(
|
|
4060
|
+
if ((key !== "status_priority" &&
|
|
4061
|
+
key !== "created_at" &&
|
|
4062
|
+
key !== "updated_at" &&
|
|
4063
|
+
key !== "title") ||
|
|
4064
|
+
(direction !== "asc" && direction !== "desc")) {
|
|
4065
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3898
4066
|
}
|
|
3899
4067
|
cursorSort.push({ key, direction });
|
|
3900
4068
|
}
|
|
3901
4069
|
if (cursorSort.length !== sort.length ||
|
|
3902
4070
|
cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
|
|
3903
|
-
throw new SessionRequestError(
|
|
4071
|
+
throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
|
|
3904
4072
|
}
|
|
3905
4073
|
return {
|
|
3906
4074
|
sort: cursorSort,
|
|
@@ -3916,7 +4084,7 @@ export class Session {
|
|
|
3916
4084
|
if (base === 0) {
|
|
3917
4085
|
continue;
|
|
3918
4086
|
}
|
|
3919
|
-
return spec.direction ===
|
|
4087
|
+
return spec.direction === "asc" ? base : -base;
|
|
3920
4088
|
}
|
|
3921
4089
|
return agent.id.localeCompare(cursor.id);
|
|
3922
4090
|
}
|
|
@@ -3982,19 +4150,19 @@ export class Session {
|
|
|
3982
4150
|
}
|
|
3983
4151
|
deriveWorkspaceStateBucket(agent) {
|
|
3984
4152
|
const pendingPermissionCount = agent.pendingPermissions?.length ?? 0;
|
|
3985
|
-
if (pendingPermissionCount > 0 || agent.attentionReason ===
|
|
3986
|
-
return
|
|
4153
|
+
if (pendingPermissionCount > 0 || agent.attentionReason === "permission") {
|
|
4154
|
+
return "needs_input";
|
|
3987
4155
|
}
|
|
3988
|
-
if (agent.status ===
|
|
3989
|
-
return
|
|
4156
|
+
if (agent.status === "error" || agent.attentionReason === "error") {
|
|
4157
|
+
return "failed";
|
|
3990
4158
|
}
|
|
3991
|
-
if (agent.status ===
|
|
3992
|
-
return
|
|
4159
|
+
if (agent.status === "running" || agent.status === "initializing") {
|
|
4160
|
+
return "running";
|
|
3993
4161
|
}
|
|
3994
4162
|
if (agent.requiresAttention) {
|
|
3995
|
-
return
|
|
4163
|
+
return "attention";
|
|
3996
4164
|
}
|
|
3997
|
-
return
|
|
4165
|
+
return "done";
|
|
3998
4166
|
}
|
|
3999
4167
|
accumulateLatestActivityAt(current, agent) {
|
|
4000
4168
|
const candidateRaw = agent.lastUserMessageAt ?? agent.updatedAt;
|
|
@@ -4036,10 +4204,10 @@ export class Session {
|
|
|
4036
4204
|
projectId: workspace.projectId,
|
|
4037
4205
|
projectDisplayName: resolvedProjectRecord?.displayName ?? workspace.projectId,
|
|
4038
4206
|
projectRootPath: resolvedProjectRecord?.rootPath ?? workspace.cwd,
|
|
4039
|
-
projectKind: resolvedProjectRecord?.kind ??
|
|
4207
|
+
projectKind: resolvedProjectRecord?.kind ?? "non_git",
|
|
4040
4208
|
workspaceKind: workspace.kind,
|
|
4041
4209
|
name: displayName,
|
|
4042
|
-
status:
|
|
4210
|
+
status: "done",
|
|
4043
4211
|
activityAt: null,
|
|
4044
4212
|
diffStat,
|
|
4045
4213
|
};
|
|
@@ -4080,7 +4248,7 @@ export class Session {
|
|
|
4080
4248
|
return this.listWorkspaceDescriptorsSnapshot();
|
|
4081
4249
|
}
|
|
4082
4250
|
normalizeFetchWorkspacesSort(sort) {
|
|
4083
|
-
const fallback = [{ key:
|
|
4251
|
+
const fallback = [{ key: "activity_at", direction: "desc" }];
|
|
4084
4252
|
if (!sort || sort.length === 0) {
|
|
4085
4253
|
return fallback;
|
|
4086
4254
|
}
|
|
@@ -4097,13 +4265,13 @@ export class Session {
|
|
|
4097
4265
|
}
|
|
4098
4266
|
getFetchWorkspacesSortValue(workspace, key) {
|
|
4099
4267
|
switch (key) {
|
|
4100
|
-
case
|
|
4268
|
+
case "status_priority":
|
|
4101
4269
|
return this.workspaceStatePriority[workspace.status];
|
|
4102
|
-
case
|
|
4270
|
+
case "activity_at":
|
|
4103
4271
|
return workspace.activityAt ? Date.parse(workspace.activityAt) : null;
|
|
4104
|
-
case
|
|
4272
|
+
case "name":
|
|
4105
4273
|
return workspace.name.toLocaleLowerCase();
|
|
4106
|
-
case
|
|
4274
|
+
case "project_id":
|
|
4107
4275
|
return workspace.projectId.toLocaleLowerCase();
|
|
4108
4276
|
}
|
|
4109
4277
|
}
|
|
@@ -4115,7 +4283,7 @@ export class Session {
|
|
|
4115
4283
|
if (base === 0) {
|
|
4116
4284
|
continue;
|
|
4117
4285
|
}
|
|
4118
|
-
return spec.direction ===
|
|
4286
|
+
return spec.direction === "asc" ? base : -base;
|
|
4119
4287
|
}
|
|
4120
4288
|
return left.id.localeCompare(right.id);
|
|
4121
4289
|
}
|
|
@@ -4128,48 +4296,48 @@ export class Session {
|
|
|
4128
4296
|
sort,
|
|
4129
4297
|
values,
|
|
4130
4298
|
id: entry.id,
|
|
4131
|
-
}),
|
|
4299
|
+
}), "utf8").toString("base64url");
|
|
4132
4300
|
}
|
|
4133
4301
|
decodeFetchWorkspacesCursor(cursor, sort) {
|
|
4134
4302
|
let parsed;
|
|
4135
4303
|
try {
|
|
4136
|
-
parsed = JSON.parse(Buffer.from(cursor,
|
|
4304
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
4137
4305
|
}
|
|
4138
4306
|
catch {
|
|
4139
|
-
throw new SessionRequestError(
|
|
4307
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4140
4308
|
}
|
|
4141
|
-
if (!parsed || typeof parsed !==
|
|
4142
|
-
throw new SessionRequestError(
|
|
4309
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4310
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4143
4311
|
}
|
|
4144
4312
|
const payload = parsed;
|
|
4145
|
-
if (!Array.isArray(payload.sort) || typeof payload.id !==
|
|
4146
|
-
throw new SessionRequestError(
|
|
4313
|
+
if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
|
|
4314
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4147
4315
|
}
|
|
4148
|
-
if (!payload.values || typeof payload.values !==
|
|
4149
|
-
throw new SessionRequestError(
|
|
4316
|
+
if (!payload.values || typeof payload.values !== "object") {
|
|
4317
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4150
4318
|
}
|
|
4151
4319
|
const cursorSort = [];
|
|
4152
4320
|
for (const item of payload.sort) {
|
|
4153
4321
|
if (!item ||
|
|
4154
|
-
typeof item !==
|
|
4155
|
-
typeof item.key !==
|
|
4156
|
-
typeof item.direction !==
|
|
4157
|
-
throw new SessionRequestError(
|
|
4322
|
+
typeof item !== "object" ||
|
|
4323
|
+
typeof item.key !== "string" ||
|
|
4324
|
+
typeof item.direction !== "string") {
|
|
4325
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4158
4326
|
}
|
|
4159
4327
|
const key = item.key;
|
|
4160
4328
|
const direction = item.direction;
|
|
4161
|
-
if ((key !==
|
|
4162
|
-
key !==
|
|
4163
|
-
key !==
|
|
4164
|
-
key !==
|
|
4165
|
-
(direction !==
|
|
4166
|
-
throw new SessionRequestError(
|
|
4329
|
+
if ((key !== "status_priority" &&
|
|
4330
|
+
key !== "activity_at" &&
|
|
4331
|
+
key !== "name" &&
|
|
4332
|
+
key !== "project_id") ||
|
|
4333
|
+
(direction !== "asc" && direction !== "desc")) {
|
|
4334
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
|
|
4167
4335
|
}
|
|
4168
4336
|
cursorSort.push({ key, direction });
|
|
4169
4337
|
}
|
|
4170
4338
|
if (cursorSort.length !== sort.length ||
|
|
4171
4339
|
cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
|
|
4172
|
-
throw new SessionRequestError(
|
|
4340
|
+
throw new SessionRequestError("invalid_cursor", "fetch_workspaces cursor does not match current sort");
|
|
4173
4341
|
}
|
|
4174
4342
|
return {
|
|
4175
4343
|
sort: cursorSort,
|
|
@@ -4185,7 +4353,7 @@ export class Session {
|
|
|
4185
4353
|
if (base === 0) {
|
|
4186
4354
|
continue;
|
|
4187
4355
|
}
|
|
4188
|
-
return spec.direction ===
|
|
4356
|
+
return spec.direction === "asc" ? base : -base;
|
|
4189
4357
|
}
|
|
4190
4358
|
return workspace.id.localeCompare(cursor.id);
|
|
4191
4359
|
}
|
|
@@ -4241,12 +4409,12 @@ export class Session {
|
|
|
4241
4409
|
}
|
|
4242
4410
|
bufferOrEmitWorkspaceUpdate(subscription, payload) {
|
|
4243
4411
|
if (subscription.isBootstrapping) {
|
|
4244
|
-
const workspaceId = payload.kind ===
|
|
4412
|
+
const workspaceId = payload.kind === "upsert" ? payload.workspace.id : payload.id;
|
|
4245
4413
|
subscription.pendingUpdatesByWorkspaceId.set(workspaceId, payload);
|
|
4246
4414
|
return;
|
|
4247
4415
|
}
|
|
4248
4416
|
this.emit({
|
|
4249
|
-
type:
|
|
4417
|
+
type: "workspace_update",
|
|
4250
4418
|
payload,
|
|
4251
4419
|
});
|
|
4252
4420
|
}
|
|
@@ -4259,19 +4427,20 @@ export class Session {
|
|
|
4259
4427
|
const pending = Array.from(subscription.pendingUpdatesByWorkspaceId.values());
|
|
4260
4428
|
subscription.pendingUpdatesByWorkspaceId.clear();
|
|
4261
4429
|
for (const payload of pending) {
|
|
4262
|
-
if (payload.kind ===
|
|
4430
|
+
if (payload.kind === "upsert") {
|
|
4263
4431
|
const snapshotLatestActivity = options?.snapshotLatestActivityByWorkspaceId?.get(payload.workspace.id);
|
|
4264
|
-
if (typeof snapshotLatestActivity ===
|
|
4432
|
+
if (typeof snapshotLatestActivity === "number") {
|
|
4265
4433
|
const updateLatestActivity = payload.workspace.activityAt
|
|
4266
4434
|
? Date.parse(payload.workspace.activityAt)
|
|
4267
4435
|
: Number.NEGATIVE_INFINITY;
|
|
4268
|
-
if (!Number.isNaN(updateLatestActivity) &&
|
|
4436
|
+
if (!Number.isNaN(updateLatestActivity) &&
|
|
4437
|
+
updateLatestActivity <= snapshotLatestActivity) {
|
|
4269
4438
|
continue;
|
|
4270
4439
|
}
|
|
4271
4440
|
}
|
|
4272
4441
|
}
|
|
4273
4442
|
this.emit({
|
|
4274
|
-
type:
|
|
4443
|
+
type: "workspace_update",
|
|
4275
4444
|
payload,
|
|
4276
4445
|
});
|
|
4277
4446
|
}
|
|
@@ -4280,19 +4449,62 @@ export class Session {
|
|
|
4280
4449
|
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
4281
4450
|
return (await this.reconcileWorkspaceRecord(workspaceId)).workspace;
|
|
4282
4451
|
}
|
|
4452
|
+
async registerPendingWorktreeWorkspace(options) {
|
|
4453
|
+
const workspaceId = normalizePersistedWorkspaceId(options.worktreePath);
|
|
4454
|
+
const basePlacement = await this.buildProjectPlacement(options.repoRoot);
|
|
4455
|
+
const placement = {
|
|
4456
|
+
...basePlacement,
|
|
4457
|
+
checkout: {
|
|
4458
|
+
cwd: workspaceId,
|
|
4459
|
+
isGit: true,
|
|
4460
|
+
currentBranch: options.branchName,
|
|
4461
|
+
remoteUrl: basePlacement.checkout.remoteUrl,
|
|
4462
|
+
isPaseoOwnedWorktree: true,
|
|
4463
|
+
mainRepoRoot: options.repoRoot,
|
|
4464
|
+
},
|
|
4465
|
+
};
|
|
4466
|
+
const now = new Date().toISOString();
|
|
4467
|
+
const existingWorkspace = await this.workspaceRegistry.get(workspaceId);
|
|
4468
|
+
const existingProject = await this.projectRegistry.get(placement.projectKey);
|
|
4469
|
+
const nextProjectRecord = this.buildPersistedProjectRecord({
|
|
4470
|
+
workspaceId,
|
|
4471
|
+
placement,
|
|
4472
|
+
createdAt: existingProject?.createdAt ?? now,
|
|
4473
|
+
updatedAt: now,
|
|
4474
|
+
});
|
|
4475
|
+
const nextWorkspaceRecord = this.buildPersistedWorkspaceRecord({
|
|
4476
|
+
workspaceId,
|
|
4477
|
+
placement,
|
|
4478
|
+
createdAt: existingWorkspace?.createdAt ?? now,
|
|
4479
|
+
updatedAt: now,
|
|
4480
|
+
});
|
|
4481
|
+
await this.projectRegistry.upsert(nextProjectRecord);
|
|
4482
|
+
await this.workspaceRegistry.upsert(nextWorkspaceRecord);
|
|
4483
|
+
await this.syncWorkspaceGitWatchTarget(workspaceId, {
|
|
4484
|
+
isGit: placement.checkout.isGit,
|
|
4485
|
+
});
|
|
4486
|
+
if (existingWorkspace &&
|
|
4487
|
+
!existingWorkspace.archivedAt &&
|
|
4488
|
+
existingWorkspace.projectId !== nextWorkspaceRecord.projectId) {
|
|
4489
|
+
await this.archiveProjectRecordIfEmpty(existingWorkspace.projectId, now);
|
|
4490
|
+
}
|
|
4491
|
+
return nextWorkspaceRecord;
|
|
4492
|
+
}
|
|
4283
4493
|
async archiveWorkspaceRecord(workspaceId, archivedAt) {
|
|
4284
4494
|
const existing = await this.workspaceRegistry.get(workspaceId);
|
|
4285
4495
|
if (!existing || existing.archivedAt) {
|
|
4496
|
+
this.removeWorkspaceGitWatchTarget(workspaceId);
|
|
4286
4497
|
return;
|
|
4287
4498
|
}
|
|
4288
4499
|
const nextArchivedAt = archivedAt ?? new Date().toISOString();
|
|
4289
4500
|
await this.workspaceRegistry.archive(workspaceId, nextArchivedAt);
|
|
4501
|
+
this.removeWorkspaceGitWatchTarget(workspaceId);
|
|
4290
4502
|
const siblingWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === existing.projectId && !workspace.archivedAt);
|
|
4291
4503
|
if (siblingWorkspaces.length === 0) {
|
|
4292
4504
|
await this.projectRegistry.archive(existing.projectId, nextArchivedAt);
|
|
4293
4505
|
}
|
|
4294
4506
|
}
|
|
4295
|
-
async emitWorkspaceUpdateForCwd(cwd) {
|
|
4507
|
+
async emitWorkspaceUpdateForCwd(cwd, options) {
|
|
4296
4508
|
const subscription = this.workspaceUpdatesSubscription;
|
|
4297
4509
|
if (!subscription) {
|
|
4298
4510
|
return;
|
|
@@ -4304,16 +4516,24 @@ export class Session {
|
|
|
4304
4516
|
const workspaceIdsToEmit = new Set([workspaceId, ...changedWorkspaceIds]);
|
|
4305
4517
|
for (const nextWorkspaceId of workspaceIdsToEmit) {
|
|
4306
4518
|
const workspace = descriptorsByWorkspaceId.get(nextWorkspaceId);
|
|
4307
|
-
|
|
4519
|
+
const nextWorkspace = workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
|
|
4520
|
+
? workspace
|
|
4521
|
+
: null;
|
|
4522
|
+
if (options?.dedupeGitState &&
|
|
4523
|
+
this.shouldSkipWorkspaceGitWatchUpdate(nextWorkspaceId, nextWorkspace)) {
|
|
4524
|
+
continue;
|
|
4525
|
+
}
|
|
4526
|
+
this.rememberWorkspaceGitWatchFingerprint(nextWorkspaceId, nextWorkspace);
|
|
4527
|
+
if (!nextWorkspace) {
|
|
4308
4528
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4309
|
-
kind:
|
|
4529
|
+
kind: "remove",
|
|
4310
4530
|
id: nextWorkspaceId,
|
|
4311
4531
|
});
|
|
4312
4532
|
continue;
|
|
4313
4533
|
}
|
|
4314
4534
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4315
|
-
kind:
|
|
4316
|
-
workspace,
|
|
4535
|
+
kind: "upsert",
|
|
4536
|
+
workspace: nextWorkspace,
|
|
4317
4537
|
});
|
|
4318
4538
|
}
|
|
4319
4539
|
}
|
|
@@ -4335,16 +4555,20 @@ export class Session {
|
|
|
4335
4555
|
const descriptorsByWorkspaceId = new Map(all.map((entry) => [entry.id, entry]));
|
|
4336
4556
|
for (const workspaceId of uniqueWorkspaceCwds) {
|
|
4337
4557
|
const workspace = descriptorsByWorkspaceId.get(workspaceId);
|
|
4338
|
-
|
|
4558
|
+
const nextWorkspace = workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
|
|
4559
|
+
? workspace
|
|
4560
|
+
: null;
|
|
4561
|
+
this.rememberWorkspaceGitWatchFingerprint(workspaceId, nextWorkspace);
|
|
4562
|
+
if (!nextWorkspace) {
|
|
4339
4563
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4340
|
-
kind:
|
|
4564
|
+
kind: "remove",
|
|
4341
4565
|
id: workspaceId,
|
|
4342
4566
|
});
|
|
4343
4567
|
continue;
|
|
4344
4568
|
}
|
|
4345
4569
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
4346
|
-
kind:
|
|
4347
|
-
workspace,
|
|
4570
|
+
kind: "upsert",
|
|
4571
|
+
workspace: nextWorkspace,
|
|
4348
4572
|
});
|
|
4349
4573
|
}
|
|
4350
4574
|
}
|
|
@@ -4373,7 +4597,7 @@ export class Session {
|
|
|
4373
4597
|
}
|
|
4374
4598
|
}
|
|
4375
4599
|
this.emit({
|
|
4376
|
-
type:
|
|
4600
|
+
type: "fetch_agents_response",
|
|
4377
4601
|
payload: {
|
|
4378
4602
|
requestId: request.requestId,
|
|
4379
4603
|
...(subscriptionId ? { subscriptionId } : {}),
|
|
@@ -4388,11 +4612,11 @@ export class Session {
|
|
|
4388
4612
|
if (subscriptionId && this.agentUpdatesSubscription?.subscriptionId === subscriptionId) {
|
|
4389
4613
|
this.agentUpdatesSubscription = null;
|
|
4390
4614
|
}
|
|
4391
|
-
const code = error instanceof SessionRequestError ? error.code :
|
|
4392
|
-
const message = error instanceof Error ? error.message :
|
|
4393
|
-
this.sessionLogger.error({ err: error },
|
|
4615
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
|
|
4616
|
+
const message = error instanceof Error ? error.message : "Failed to fetch agents";
|
|
4617
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_agents_request");
|
|
4394
4618
|
this.emit({
|
|
4395
|
-
type:
|
|
4619
|
+
type: "rpc_error",
|
|
4396
4620
|
payload: {
|
|
4397
4621
|
requestId: request.requestId,
|
|
4398
4622
|
requestType: request.type,
|
|
@@ -4419,6 +4643,7 @@ export class Session {
|
|
|
4419
4643
|
};
|
|
4420
4644
|
}
|
|
4421
4645
|
const payload = await this.listFetchWorkspacesEntries(request);
|
|
4646
|
+
this.primeWorkspaceGitWatchFingerprints(payload.entries);
|
|
4422
4647
|
const snapshotLatestActivityByWorkspaceId = new Map();
|
|
4423
4648
|
for (const entry of payload.entries) {
|
|
4424
4649
|
const parsedLatestActivity = entry.activityAt
|
|
@@ -4429,7 +4654,7 @@ export class Session {
|
|
|
4429
4654
|
}
|
|
4430
4655
|
}
|
|
4431
4656
|
this.emit({
|
|
4432
|
-
type:
|
|
4657
|
+
type: "fetch_workspaces_response",
|
|
4433
4658
|
payload: {
|
|
4434
4659
|
requestId: request.requestId,
|
|
4435
4660
|
...(subscriptionId ? { subscriptionId } : {}),
|
|
@@ -4444,11 +4669,11 @@ export class Session {
|
|
|
4444
4669
|
if (subscriptionId && this.workspaceUpdatesSubscription?.subscriptionId === subscriptionId) {
|
|
4445
4670
|
this.workspaceUpdatesSubscription = null;
|
|
4446
4671
|
}
|
|
4447
|
-
const code = error instanceof SessionRequestError ? error.code :
|
|
4448
|
-
const message = error instanceof Error ? error.message :
|
|
4449
|
-
this.sessionLogger.error({ err: error },
|
|
4672
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_workspaces_failed";
|
|
4673
|
+
const message = error instanceof Error ? error.message : "Failed to fetch workspaces";
|
|
4674
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_workspaces_request");
|
|
4450
4675
|
this.emit({
|
|
4451
|
-
type:
|
|
4676
|
+
type: "rpc_error",
|
|
4452
4677
|
payload: {
|
|
4453
4678
|
requestId: request.requestId,
|
|
4454
4679
|
requestType: request.type,
|
|
@@ -4464,7 +4689,7 @@ export class Session {
|
|
|
4464
4689
|
await this.emitWorkspaceUpdateForCwd(workspace.cwd);
|
|
4465
4690
|
const descriptor = await this.describeWorkspaceRecord(workspace);
|
|
4466
4691
|
this.emit({
|
|
4467
|
-
type:
|
|
4692
|
+
type: "open_project_response",
|
|
4468
4693
|
payload: {
|
|
4469
4694
|
requestId: request.requestId,
|
|
4470
4695
|
workspace: descriptor,
|
|
@@ -4473,10 +4698,10 @@ export class Session {
|
|
|
4473
4698
|
});
|
|
4474
4699
|
}
|
|
4475
4700
|
catch (error) {
|
|
4476
|
-
const message = error instanceof Error ? error.message :
|
|
4477
|
-
this.sessionLogger.error({ err: error, cwd: request.cwd },
|
|
4701
|
+
const message = error instanceof Error ? error.message : "Failed to open project";
|
|
4702
|
+
this.sessionLogger.error({ err: error, cwd: request.cwd }, "Failed to open project");
|
|
4478
4703
|
this.emit({
|
|
4479
|
-
type:
|
|
4704
|
+
type: "open_project_response",
|
|
4480
4705
|
payload: {
|
|
4481
4706
|
requestId: request.requestId,
|
|
4482
4707
|
workspace: null,
|
|
@@ -4485,20 +4710,123 @@ export class Session {
|
|
|
4485
4710
|
});
|
|
4486
4711
|
}
|
|
4487
4712
|
}
|
|
4713
|
+
async handleCreatePaseoWorktreeRequest(request) {
|
|
4714
|
+
try {
|
|
4715
|
+
const checkout = await getCheckoutStatusLite(request.cwd, { paseoHome: this.paseoHome });
|
|
4716
|
+
if (!checkout.isGit) {
|
|
4717
|
+
throw new Error("Create worktree requires a git repository");
|
|
4718
|
+
}
|
|
4719
|
+
const repoRoot = checkout.isPaseoOwnedWorktree ? checkout.mainRepoRoot : request.cwd;
|
|
4720
|
+
const baseBranch = await resolveRepositoryDefaultBranch(repoRoot);
|
|
4721
|
+
if (!baseBranch) {
|
|
4722
|
+
throw new Error("Unable to resolve repository default branch");
|
|
4723
|
+
}
|
|
4724
|
+
const normalizedSlug = request.worktreeSlug ? slugify(request.worktreeSlug) : uuidv4();
|
|
4725
|
+
const validation = validateBranchSlug(normalizedSlug);
|
|
4726
|
+
if (!validation.valid) {
|
|
4727
|
+
throw new Error(`Invalid worktree name: ${validation.error}`);
|
|
4728
|
+
}
|
|
4729
|
+
const worktreePath = await computeWorktreePath(repoRoot, normalizedSlug, this.paseoHome);
|
|
4730
|
+
const workspace = await this.registerPendingWorktreeWorkspace({
|
|
4731
|
+
repoRoot,
|
|
4732
|
+
worktreePath,
|
|
4733
|
+
branchName: normalizedSlug,
|
|
4734
|
+
});
|
|
4735
|
+
const descriptor = await this.describeWorkspaceRecord(workspace);
|
|
4736
|
+
this.emit({
|
|
4737
|
+
type: "create_paseo_worktree_response",
|
|
4738
|
+
payload: {
|
|
4739
|
+
workspace: descriptor,
|
|
4740
|
+
error: null,
|
|
4741
|
+
setupTerminalId: null,
|
|
4742
|
+
requestId: request.requestId,
|
|
4743
|
+
},
|
|
4744
|
+
});
|
|
4745
|
+
void this.createPaseoWorktreeInBackground({
|
|
4746
|
+
requestCwd: request.cwd,
|
|
4747
|
+
repoRoot,
|
|
4748
|
+
baseBranch,
|
|
4749
|
+
slug: normalizedSlug,
|
|
4750
|
+
worktreePath,
|
|
4751
|
+
});
|
|
4752
|
+
}
|
|
4753
|
+
catch (error) {
|
|
4754
|
+
const message = error instanceof Error ? error.message : "Failed to create worktree";
|
|
4755
|
+
this.sessionLogger.error({ err: error, cwd: request.cwd, worktreeSlug: request.worktreeSlug }, "Failed to create worktree");
|
|
4756
|
+
this.emit({
|
|
4757
|
+
type: "create_paseo_worktree_response",
|
|
4758
|
+
payload: {
|
|
4759
|
+
workspace: null,
|
|
4760
|
+
error: message,
|
|
4761
|
+
setupTerminalId: null,
|
|
4762
|
+
requestId: request.requestId,
|
|
4763
|
+
},
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
async createPaseoWorktreeInBackground(options) {
|
|
4768
|
+
let setupTerminalId = null;
|
|
4769
|
+
try {
|
|
4770
|
+
await createAgentWorktree({
|
|
4771
|
+
cwd: options.repoRoot,
|
|
4772
|
+
branchName: options.slug,
|
|
4773
|
+
baseBranch: options.baseBranch,
|
|
4774
|
+
worktreeSlug: options.slug,
|
|
4775
|
+
paseoHome: this.paseoHome,
|
|
4776
|
+
});
|
|
4777
|
+
const setupCommands = getWorktreeSetupCommands(options.worktreePath);
|
|
4778
|
+
if (setupCommands.length > 0 && this.terminalManager) {
|
|
4779
|
+
const runtimeEnv = await resolveWorktreeRuntimeEnv({
|
|
4780
|
+
worktreePath: options.worktreePath,
|
|
4781
|
+
branchName: options.slug,
|
|
4782
|
+
repoRootPath: options.repoRoot,
|
|
4783
|
+
});
|
|
4784
|
+
this.terminalManager.registerCwdEnv({
|
|
4785
|
+
cwd: options.worktreePath,
|
|
4786
|
+
env: runtimeEnv,
|
|
4787
|
+
});
|
|
4788
|
+
const terminal = await this.terminalManager.createTerminal({
|
|
4789
|
+
cwd: options.worktreePath,
|
|
4790
|
+
name: `setup-${options.slug}`,
|
|
4791
|
+
env: runtimeEnv,
|
|
4792
|
+
});
|
|
4793
|
+
setupTerminalId = terminal.id;
|
|
4794
|
+
for (const command of setupCommands) {
|
|
4795
|
+
terminal.send({
|
|
4796
|
+
type: "input",
|
|
4797
|
+
data: `${command}\r`,
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
catch (error) {
|
|
4803
|
+
this.sessionLogger.error({
|
|
4804
|
+
err: error,
|
|
4805
|
+
cwd: options.requestCwd,
|
|
4806
|
+
repoRoot: options.repoRoot,
|
|
4807
|
+
worktreeSlug: options.slug,
|
|
4808
|
+
worktreePath: options.worktreePath,
|
|
4809
|
+
setupTerminalId,
|
|
4810
|
+
}, "Background worktree creation failed");
|
|
4811
|
+
}
|
|
4812
|
+
finally {
|
|
4813
|
+
await this.emitWorkspaceUpdateForCwd(options.worktreePath);
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4488
4816
|
async handleArchiveWorkspaceRequest(request) {
|
|
4489
4817
|
try {
|
|
4490
4818
|
const existing = await this.workspaceRegistry.get(request.workspaceId);
|
|
4491
4819
|
if (!existing) {
|
|
4492
4820
|
throw new Error(`Workspace not found: ${request.workspaceId}`);
|
|
4493
4821
|
}
|
|
4494
|
-
if (existing.kind ===
|
|
4495
|
-
throw new Error(
|
|
4822
|
+
if (existing.kind === "worktree") {
|
|
4823
|
+
throw new Error("Use worktree archive for Paseo worktrees");
|
|
4496
4824
|
}
|
|
4497
4825
|
const archivedAt = new Date().toISOString();
|
|
4498
4826
|
await this.archiveWorkspaceRecord(request.workspaceId, archivedAt);
|
|
4499
4827
|
await this.emitWorkspaceUpdateForCwd(existing.cwd);
|
|
4500
4828
|
this.emit({
|
|
4501
|
-
type:
|
|
4829
|
+
type: "archive_workspace_response",
|
|
4502
4830
|
payload: {
|
|
4503
4831
|
requestId: request.requestId,
|
|
4504
4832
|
workspaceId: request.workspaceId,
|
|
@@ -4508,10 +4836,10 @@ export class Session {
|
|
|
4508
4836
|
});
|
|
4509
4837
|
}
|
|
4510
4838
|
catch (error) {
|
|
4511
|
-
const message = error instanceof Error ? error.message :
|
|
4512
|
-
this.sessionLogger.error({ err: error, workspaceId: request.workspaceId },
|
|
4839
|
+
const message = error instanceof Error ? error.message : "Failed to archive workspace";
|
|
4840
|
+
this.sessionLogger.error({ err: error, workspaceId: request.workspaceId }, "Failed to archive workspace");
|
|
4513
4841
|
this.emit({
|
|
4514
|
-
type:
|
|
4842
|
+
type: "archive_workspace_response",
|
|
4515
4843
|
payload: {
|
|
4516
4844
|
requestId: request.requestId,
|
|
4517
4845
|
workspaceId: request.workspaceId,
|
|
@@ -4525,7 +4853,7 @@ export class Session {
|
|
|
4525
4853
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
4526
4854
|
if (!resolved.ok) {
|
|
4527
4855
|
this.emit({
|
|
4528
|
-
type:
|
|
4856
|
+
type: "fetch_agent_response",
|
|
4529
4857
|
payload: { requestId, agent: null, project: null, error: resolved.error },
|
|
4530
4858
|
});
|
|
4531
4859
|
return;
|
|
@@ -4533,7 +4861,7 @@ export class Session {
|
|
|
4533
4861
|
const agent = await this.getAgentPayloadById(resolved.agentId);
|
|
4534
4862
|
if (!agent) {
|
|
4535
4863
|
this.emit({
|
|
4536
|
-
type:
|
|
4864
|
+
type: "fetch_agent_response",
|
|
4537
4865
|
payload: {
|
|
4538
4866
|
requestId,
|
|
4539
4867
|
agent: null,
|
|
@@ -4545,18 +4873,18 @@ export class Session {
|
|
|
4545
4873
|
}
|
|
4546
4874
|
const project = await this.buildProjectPlacement(agent.cwd);
|
|
4547
4875
|
this.emit({
|
|
4548
|
-
type:
|
|
4876
|
+
type: "fetch_agent_response",
|
|
4549
4877
|
payload: { requestId, agent, project, error: null },
|
|
4550
4878
|
});
|
|
4551
4879
|
}
|
|
4552
4880
|
async handleFetchAgentTimelineRequest(msg) {
|
|
4553
|
-
const direction = msg.direction ?? (msg.cursor ?
|
|
4554
|
-
const projection = msg.projection ??
|
|
4881
|
+
const direction = msg.direction ?? (msg.cursor ? "after" : "tail");
|
|
4882
|
+
const projection = msg.projection ?? "projected";
|
|
4555
4883
|
const requestedLimit = msg.limit;
|
|
4556
|
-
const limit = requestedLimit ?? (direction ===
|
|
4557
|
-
const shouldLimitByProjectedWindow = projection ===
|
|
4558
|
-
direction ===
|
|
4559
|
-
typeof requestedLimit ===
|
|
4884
|
+
const limit = requestedLimit ?? (direction === "after" ? 0 : undefined);
|
|
4885
|
+
const shouldLimitByProjectedWindow = projection === "canonical" &&
|
|
4886
|
+
direction === "tail" &&
|
|
4887
|
+
typeof requestedLimit === "number" &&
|
|
4560
4888
|
requestedLimit > 0;
|
|
4561
4889
|
const cursor = msg.cursor
|
|
4562
4890
|
? {
|
|
@@ -4570,7 +4898,7 @@ export class Session {
|
|
|
4570
4898
|
let timeline = this.agentManager.fetchTimeline(msg.agentId, {
|
|
4571
4899
|
direction,
|
|
4572
4900
|
cursor,
|
|
4573
|
-
limit: shouldLimitByProjectedWindow && typeof requestedLimit ===
|
|
4901
|
+
limit: shouldLimitByProjectedWindow && typeof requestedLimit === "number"
|
|
4574
4902
|
? Math.max(1, Math.floor(requestedLimit))
|
|
4575
4903
|
: limit,
|
|
4576
4904
|
});
|
|
@@ -4596,7 +4924,7 @@ export class Session {
|
|
|
4596
4924
|
const startsAtLoadedBoundary = firstLoadedRow != null &&
|
|
4597
4925
|
firstSelectedRow != null &&
|
|
4598
4926
|
firstSelectedRow.seq === firstLoadedRow.seq;
|
|
4599
|
-
const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type ===
|
|
4927
|
+
const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
|
|
4600
4928
|
if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
|
|
4601
4929
|
break;
|
|
4602
4930
|
}
|
|
@@ -4636,7 +4964,7 @@ export class Session {
|
|
|
4636
4964
|
entries = projectTimelineRows(timeline.rows, snapshot.provider, projection);
|
|
4637
4965
|
}
|
|
4638
4966
|
this.emit({
|
|
4639
|
-
type:
|
|
4967
|
+
type: "fetch_agent_timeline_response",
|
|
4640
4968
|
payload: {
|
|
4641
4969
|
requestId: msg.requestId,
|
|
4642
4970
|
agentId: msg.agentId,
|
|
@@ -4658,16 +4986,16 @@ export class Session {
|
|
|
4658
4986
|
});
|
|
4659
4987
|
}
|
|
4660
4988
|
catch (error) {
|
|
4661
|
-
this.sessionLogger.error({ err: error, agentId: msg.agentId },
|
|
4989
|
+
this.sessionLogger.error({ err: error, agentId: msg.agentId }, "Failed to handle fetch_agent_timeline_request");
|
|
4662
4990
|
this.emit({
|
|
4663
|
-
type:
|
|
4991
|
+
type: "fetch_agent_timeline_response",
|
|
4664
4992
|
payload: {
|
|
4665
4993
|
requestId: msg.requestId,
|
|
4666
4994
|
agentId: msg.agentId,
|
|
4667
4995
|
agent: null,
|
|
4668
4996
|
direction,
|
|
4669
4997
|
projection,
|
|
4670
|
-
epoch:
|
|
4998
|
+
epoch: "",
|
|
4671
4999
|
reset: false,
|
|
4672
5000
|
staleCursor: false,
|
|
4673
5001
|
gap: false,
|
|
@@ -4686,7 +5014,7 @@ export class Session {
|
|
|
4686
5014
|
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
4687
5015
|
if (!resolved.ok) {
|
|
4688
5016
|
this.emit({
|
|
4689
|
-
type:
|
|
5017
|
+
type: "send_agent_message_response",
|
|
4690
5018
|
payload: {
|
|
4691
5019
|
requestId: msg.requestId,
|
|
4692
5020
|
agentId: msg.agentId,
|
|
@@ -4707,13 +5035,13 @@ export class Session {
|
|
|
4707
5035
|
});
|
|
4708
5036
|
}
|
|
4709
5037
|
catch (error) {
|
|
4710
|
-
this.sessionLogger.error({ err: error, agentId },
|
|
5038
|
+
this.sessionLogger.error({ err: error, agentId }, "Failed to record user message for send_agent_message_request");
|
|
4711
5039
|
}
|
|
4712
5040
|
const prompt = this.buildAgentPrompt(msg.text, msg.images);
|
|
4713
5041
|
const started = this.startAgentStream(agentId, prompt);
|
|
4714
5042
|
if (!started.ok) {
|
|
4715
5043
|
this.emit({
|
|
4716
|
-
type:
|
|
5044
|
+
type: "send_agent_message_response",
|
|
4717
5045
|
payload: {
|
|
4718
5046
|
requestId: msg.requestId,
|
|
4719
5047
|
agentId,
|
|
@@ -4725,18 +5053,18 @@ export class Session {
|
|
|
4725
5053
|
}
|
|
4726
5054
|
const startAbort = new AbortController();
|
|
4727
5055
|
const startTimeoutMs = 15000;
|
|
4728
|
-
const startTimeout = setTimeout(() => startAbort.abort(
|
|
5056
|
+
const startTimeout = setTimeout(() => startAbort.abort("timeout"), startTimeoutMs);
|
|
4729
5057
|
try {
|
|
4730
5058
|
await this.agentManager.waitForAgentRunStart(agentId, { signal: startAbort.signal });
|
|
4731
5059
|
}
|
|
4732
5060
|
catch (error) {
|
|
4733
5061
|
const message = error instanceof Error
|
|
4734
5062
|
? error.message
|
|
4735
|
-
: typeof error ===
|
|
5063
|
+
: typeof error === "string"
|
|
4736
5064
|
? error
|
|
4737
|
-
:
|
|
5065
|
+
: "Unknown error";
|
|
4738
5066
|
this.emit({
|
|
4739
|
-
type:
|
|
5067
|
+
type: "send_agent_message_response",
|
|
4740
5068
|
payload: {
|
|
4741
5069
|
requestId: msg.requestId,
|
|
4742
5070
|
agentId,
|
|
@@ -4750,7 +5078,7 @@ export class Session {
|
|
|
4750
5078
|
clearTimeout(startTimeout);
|
|
4751
5079
|
}
|
|
4752
5080
|
this.emit({
|
|
4753
|
-
type:
|
|
5081
|
+
type: "send_agent_message_response",
|
|
4754
5082
|
payload: {
|
|
4755
5083
|
requestId: msg.requestId,
|
|
4756
5084
|
agentId,
|
|
@@ -4760,9 +5088,13 @@ export class Session {
|
|
|
4760
5088
|
});
|
|
4761
5089
|
}
|
|
4762
5090
|
catch (error) {
|
|
4763
|
-
const message = error instanceof Error
|
|
5091
|
+
const message = error instanceof Error
|
|
5092
|
+
? error.message
|
|
5093
|
+
: typeof error === "string"
|
|
5094
|
+
? error
|
|
5095
|
+
: "Unknown error";
|
|
4764
5096
|
this.emit({
|
|
4765
|
-
type:
|
|
5097
|
+
type: "send_agent_message_response",
|
|
4766
5098
|
payload: {
|
|
4767
5099
|
requestId: msg.requestId,
|
|
4768
5100
|
agentId: resolved.agentId,
|
|
@@ -4776,10 +5108,10 @@ export class Session {
|
|
|
4776
5108
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
4777
5109
|
if (!resolved.ok) {
|
|
4778
5110
|
this.emit({
|
|
4779
|
-
type:
|
|
5111
|
+
type: "wait_for_finish_response",
|
|
4780
5112
|
payload: {
|
|
4781
5113
|
requestId,
|
|
4782
|
-
status:
|
|
5114
|
+
status: "error",
|
|
4783
5115
|
final: null,
|
|
4784
5116
|
error: resolved.error,
|
|
4785
5117
|
lastMessage: null,
|
|
@@ -4793,10 +5125,10 @@ export class Session {
|
|
|
4793
5125
|
const record = await this.agentStorage.get(agentId);
|
|
4794
5126
|
if (!record || record.internal) {
|
|
4795
5127
|
this.emit({
|
|
4796
|
-
type:
|
|
5128
|
+
type: "wait_for_finish_response",
|
|
4797
5129
|
payload: {
|
|
4798
5130
|
requestId,
|
|
4799
|
-
status:
|
|
5131
|
+
status: "error",
|
|
4800
5132
|
final: null,
|
|
4801
5133
|
error: `Agent not found: ${agentId}`,
|
|
4802
5134
|
lastMessage: null,
|
|
@@ -4805,22 +5137,22 @@ export class Session {
|
|
|
4805
5137
|
return;
|
|
4806
5138
|
}
|
|
4807
5139
|
const final = this.buildStoredAgentPayload(record);
|
|
4808
|
-
const status = record.attentionReason ===
|
|
4809
|
-
?
|
|
4810
|
-
: record.lastStatus ===
|
|
4811
|
-
?
|
|
4812
|
-
:
|
|
5140
|
+
const status = record.attentionReason === "permission"
|
|
5141
|
+
? "permission"
|
|
5142
|
+
: record.lastStatus === "error"
|
|
5143
|
+
? "error"
|
|
5144
|
+
: "idle";
|
|
4813
5145
|
this.emit({
|
|
4814
|
-
type:
|
|
5146
|
+
type: "wait_for_finish_response",
|
|
4815
5147
|
payload: { requestId, status, final, error: null, lastMessage: null },
|
|
4816
5148
|
});
|
|
4817
5149
|
return;
|
|
4818
5150
|
}
|
|
4819
5151
|
const abortController = new AbortController();
|
|
4820
|
-
const hasTimeout = typeof timeoutMs ===
|
|
5152
|
+
const hasTimeout = typeof timeoutMs === "number" && timeoutMs > 0;
|
|
4821
5153
|
const timeoutHandle = hasTimeout
|
|
4822
5154
|
? setTimeout(() => {
|
|
4823
|
-
abortController.abort(
|
|
5155
|
+
abortController.abort("timeout");
|
|
4824
5156
|
}, timeoutMs)
|
|
4825
5157
|
: null;
|
|
4826
5158
|
try {
|
|
@@ -4831,28 +5163,32 @@ export class Session {
|
|
|
4831
5163
|
if (!final) {
|
|
4832
5164
|
throw new Error(`Agent ${agentId} disappeared while waiting`);
|
|
4833
5165
|
}
|
|
4834
|
-
let status = result.permission
|
|
5166
|
+
let status = result.permission
|
|
5167
|
+
? "permission"
|
|
5168
|
+
: result.status === "error"
|
|
5169
|
+
? "error"
|
|
5170
|
+
: "idle";
|
|
4835
5171
|
this.emit({
|
|
4836
|
-
type:
|
|
5172
|
+
type: "wait_for_finish_response",
|
|
4837
5173
|
payload: { requestId, status, final, error: null, lastMessage: result.lastMessage },
|
|
4838
5174
|
});
|
|
4839
5175
|
}
|
|
4840
5176
|
catch (error) {
|
|
4841
5177
|
const isAbort = error instanceof Error &&
|
|
4842
|
-
(error.name ===
|
|
5178
|
+
(error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
|
|
4843
5179
|
if (!isAbort) {
|
|
4844
5180
|
const message = error instanceof Error
|
|
4845
5181
|
? error.message
|
|
4846
|
-
: typeof error ===
|
|
5182
|
+
: typeof error === "string"
|
|
4847
5183
|
? error
|
|
4848
|
-
:
|
|
4849
|
-
this.sessionLogger.error({ err: error, agentId },
|
|
5184
|
+
: "Unknown error";
|
|
5185
|
+
this.sessionLogger.error({ err: error, agentId }, "wait_for_finish_request failed");
|
|
4850
5186
|
const final = await this.getAgentPayloadById(agentId);
|
|
4851
5187
|
this.emit({
|
|
4852
|
-
type:
|
|
5188
|
+
type: "wait_for_finish_response",
|
|
4853
5189
|
payload: {
|
|
4854
5190
|
requestId,
|
|
4855
|
-
status:
|
|
5191
|
+
status: "error",
|
|
4856
5192
|
final,
|
|
4857
5193
|
error: message,
|
|
4858
5194
|
lastMessage: null,
|
|
@@ -4865,8 +5201,8 @@ export class Session {
|
|
|
4865
5201
|
throw new Error(`Agent ${agentId} disappeared while waiting`);
|
|
4866
5202
|
}
|
|
4867
5203
|
this.emit({
|
|
4868
|
-
type:
|
|
4869
|
-
payload: { requestId, status:
|
|
5204
|
+
type: "wait_for_finish_response",
|
|
5205
|
+
payload: { requestId, status: "timeout", final, error: null, lastMessage: null },
|
|
4870
5206
|
});
|
|
4871
5207
|
}
|
|
4872
5208
|
finally {
|
|
@@ -4880,31 +5216,30 @@ export class Session {
|
|
|
4880
5216
|
*/
|
|
4881
5217
|
async handleAudioChunk(msg) {
|
|
4882
5218
|
if (!this.isVoiceMode) {
|
|
4883
|
-
this.sessionLogger.warn(
|
|
5219
|
+
this.sessionLogger.warn("Received voice_audio_chunk while voice mode is disabled; transcript will be emitted but voice assistant turn is skipped");
|
|
4884
5220
|
}
|
|
4885
|
-
const chunkFormat = msg.format ||
|
|
5221
|
+
const chunkFormat = msg.format || "audio/wav";
|
|
4886
5222
|
if (this.isVoiceMode) {
|
|
4887
5223
|
if (!this.voiceTurnController) {
|
|
4888
|
-
throw new Error(
|
|
5224
|
+
throw new Error("Voice mode is enabled but the voice turn controller is not running");
|
|
4889
5225
|
}
|
|
4890
|
-
const chunkBytes = Buffer.byteLength(msg.audio,
|
|
5226
|
+
const chunkBytes = Buffer.byteLength(msg.audio, "base64");
|
|
4891
5227
|
this.voiceInputChunkCount += 1;
|
|
4892
5228
|
this.voiceInputBytes += chunkBytes;
|
|
4893
5229
|
if (this.voiceInputChunkCount === 1) {
|
|
4894
5230
|
this.sessionLogger.info({
|
|
4895
5231
|
format: chunkFormat,
|
|
4896
5232
|
audioBytes: chunkBytes,
|
|
4897
|
-
},
|
|
5233
|
+
}, "Received first voice_audio_chunk for active voice mode");
|
|
4898
5234
|
}
|
|
4899
5235
|
const now = Date.now();
|
|
4900
|
-
if (this.voiceInputChunkCount % 50 === 0 ||
|
|
4901
|
-
now - this.voiceInputWindowStartedAt >= 1000) {
|
|
5236
|
+
if (this.voiceInputChunkCount % 50 === 0 || now - this.voiceInputWindowStartedAt >= 1000) {
|
|
4902
5237
|
this.sessionLogger.info({
|
|
4903
5238
|
chunkCount: this.voiceInputChunkCount,
|
|
4904
5239
|
audioBytes: this.voiceInputBytes,
|
|
4905
5240
|
windowMs: now - this.voiceInputWindowStartedAt,
|
|
4906
5241
|
format: chunkFormat,
|
|
4907
|
-
},
|
|
5242
|
+
}, "Voice input chunk summary");
|
|
4908
5243
|
this.voiceInputWindowStartedAt = now;
|
|
4909
5244
|
this.voiceInputChunkCount = 0;
|
|
4910
5245
|
this.voiceInputBytes = 0;
|
|
@@ -4915,8 +5250,8 @@ export class Session {
|
|
|
4915
5250
|
});
|
|
4916
5251
|
return;
|
|
4917
5252
|
}
|
|
4918
|
-
const chunkBuffer = Buffer.from(msg.audio,
|
|
4919
|
-
const isPCMChunk = chunkFormat.toLowerCase().includes(
|
|
5253
|
+
const chunkBuffer = Buffer.from(msg.audio, "base64");
|
|
5254
|
+
const isPCMChunk = chunkFormat.toLowerCase().includes("pcm");
|
|
4920
5255
|
if (!this.audioBuffer) {
|
|
4921
5256
|
this.audioBuffer = {
|
|
4922
5257
|
chunks: [],
|
|
@@ -4928,7 +5263,7 @@ export class Session {
|
|
|
4928
5263
|
// If the format changes mid-stream, flush what we have first
|
|
4929
5264
|
if (this.audioBuffer.isPCM !== isPCMChunk) {
|
|
4930
5265
|
this.sessionLogger.debug({
|
|
4931
|
-
oldFormat: this.audioBuffer.isPCM ?
|
|
5266
|
+
oldFormat: this.audioBuffer.isPCM ? "pcm" : this.audioBuffer.format,
|
|
4932
5267
|
newFormat: chunkFormat,
|
|
4933
5268
|
}, `Audio format changed mid-stream, flushing current buffer`);
|
|
4934
5269
|
const finalized = this.finalizeBufferedAudio();
|
|
@@ -4984,7 +5319,7 @@ export class Session {
|
|
|
4984
5319
|
const wavBuffer = convertPCMToWavBuffer(pcmBuffer, PCM_SAMPLE_RATE, PCM_CHANNELS, PCM_BITS_PER_SAMPLE);
|
|
4985
5320
|
return {
|
|
4986
5321
|
audio: wavBuffer,
|
|
4987
|
-
format:
|
|
5322
|
+
format: "audio/wav",
|
|
4988
5323
|
};
|
|
4989
5324
|
}
|
|
4990
5325
|
return {
|
|
@@ -4993,7 +5328,7 @@ export class Session {
|
|
|
4993
5328
|
};
|
|
4994
5329
|
}
|
|
4995
5330
|
async processCompletedAudio(audio, format) {
|
|
4996
|
-
if (this.processingPhase ===
|
|
5331
|
+
if (this.processingPhase === "transcribing") {
|
|
4997
5332
|
this.sessionLogger.debug({ phase: this.processingPhase, segmentCount: this.pendingAudioSegments.length + 1 }, `Buffering audio segment (phase: ${this.processingPhase})`);
|
|
4998
5333
|
this.pendingAudioSegments.push({
|
|
4999
5334
|
audio,
|
|
@@ -5019,7 +5354,7 @@ export class Session {
|
|
|
5019
5354
|
await this.processAudio(audio, format);
|
|
5020
5355
|
}
|
|
5021
5356
|
async flushPendingAudioSegments(reason) {
|
|
5022
|
-
if (this.processingPhase ===
|
|
5357
|
+
if (this.processingPhase === "transcribing" || this.pendingAudioSegments.length === 0) {
|
|
5023
5358
|
return;
|
|
5024
5359
|
}
|
|
5025
5360
|
const pendingSegments = [...this.pendingAudioSegments];
|
|
@@ -5034,21 +5369,21 @@ export class Session {
|
|
|
5034
5369
|
* Process audio through STT and then LLM
|
|
5035
5370
|
*/
|
|
5036
5371
|
async processAudio(audio, format) {
|
|
5037
|
-
this.setPhase(
|
|
5372
|
+
this.setPhase("transcribing");
|
|
5038
5373
|
this.emit({
|
|
5039
|
-
type:
|
|
5374
|
+
type: "activity_log",
|
|
5040
5375
|
payload: {
|
|
5041
5376
|
id: uuidv4(),
|
|
5042
5377
|
timestamp: new Date(),
|
|
5043
|
-
type:
|
|
5044
|
-
content:
|
|
5378
|
+
type: "system",
|
|
5379
|
+
content: "Transcribing audio...",
|
|
5045
5380
|
},
|
|
5046
5381
|
});
|
|
5047
5382
|
try {
|
|
5048
5383
|
const requestId = uuidv4();
|
|
5049
5384
|
const result = await this.sttManager.transcribe(audio, format, {
|
|
5050
5385
|
requestId,
|
|
5051
|
-
label: this.isVoiceMode ?
|
|
5386
|
+
label: this.isVoiceMode ? "voice" : "buffered",
|
|
5052
5387
|
});
|
|
5053
5388
|
const transcriptText = result.text.trim();
|
|
5054
5389
|
this.sessionLogger.info({
|
|
@@ -5056,7 +5391,7 @@ export class Session {
|
|
|
5056
5391
|
isVoiceMode: this.isVoiceMode,
|
|
5057
5392
|
transcriptLength: transcriptText.length,
|
|
5058
5393
|
transcript: transcriptText,
|
|
5059
|
-
},
|
|
5394
|
+
}, "Transcription result");
|
|
5060
5395
|
await this.handleTranscriptionResultPayload({
|
|
5061
5396
|
text: result.text,
|
|
5062
5397
|
language: result.language,
|
|
@@ -5070,15 +5405,15 @@ export class Session {
|
|
|
5070
5405
|
});
|
|
5071
5406
|
}
|
|
5072
5407
|
catch (error) {
|
|
5073
|
-
this.setPhase(
|
|
5074
|
-
this.clearSpeechInProgress(
|
|
5075
|
-
await this.flushPendingAudioSegments(
|
|
5408
|
+
this.setPhase("idle");
|
|
5409
|
+
this.clearSpeechInProgress("transcription error");
|
|
5410
|
+
await this.flushPendingAudioSegments("transcription error");
|
|
5076
5411
|
this.emit({
|
|
5077
|
-
type:
|
|
5412
|
+
type: "activity_log",
|
|
5078
5413
|
payload: {
|
|
5079
5414
|
id: uuidv4(),
|
|
5080
5415
|
timestamp: new Date(),
|
|
5081
|
-
type:
|
|
5416
|
+
type: "error",
|
|
5082
5417
|
content: `Transcription error: ${error.message}`,
|
|
5083
5418
|
},
|
|
5084
5419
|
});
|
|
@@ -5088,7 +5423,7 @@ export class Session {
|
|
|
5088
5423
|
async handleTranscriptionResultPayload(result) {
|
|
5089
5424
|
const transcriptText = result.text.trim();
|
|
5090
5425
|
this.emit({
|
|
5091
|
-
type:
|
|
5426
|
+
type: "transcription_result",
|
|
5092
5427
|
payload: {
|
|
5093
5428
|
text: result.text,
|
|
5094
5429
|
...(result.language ? { language: result.language } : {}),
|
|
@@ -5104,21 +5439,21 @@ export class Session {
|
|
|
5104
5439
|
},
|
|
5105
5440
|
});
|
|
5106
5441
|
if (!transcriptText) {
|
|
5107
|
-
this.sessionLogger.debug(
|
|
5108
|
-
this.setPhase(
|
|
5109
|
-
this.clearSpeechInProgress(
|
|
5110
|
-
await this.flushPendingAudioSegments(
|
|
5442
|
+
this.sessionLogger.debug("Empty transcription (false positive), not aborting");
|
|
5443
|
+
this.setPhase("idle");
|
|
5444
|
+
this.clearSpeechInProgress("empty transcription");
|
|
5445
|
+
await this.flushPendingAudioSegments("empty transcription");
|
|
5111
5446
|
return;
|
|
5112
5447
|
}
|
|
5113
5448
|
// Has content - abort any in-progress stream now
|
|
5114
5449
|
this.createAbortController();
|
|
5115
5450
|
if (result.debugRecordingPath) {
|
|
5116
5451
|
this.emit({
|
|
5117
|
-
type:
|
|
5452
|
+
type: "activity_log",
|
|
5118
5453
|
payload: {
|
|
5119
5454
|
id: uuidv4(),
|
|
5120
5455
|
timestamp: new Date(),
|
|
5121
|
-
type:
|
|
5456
|
+
type: "system",
|
|
5122
5457
|
content: `Saved input audio: ${result.debugRecordingPath}`,
|
|
5123
5458
|
metadata: {
|
|
5124
5459
|
recordingPath: result.debugRecordingPath,
|
|
@@ -5129,11 +5464,11 @@ export class Session {
|
|
|
5129
5464
|
});
|
|
5130
5465
|
}
|
|
5131
5466
|
this.emit({
|
|
5132
|
-
type:
|
|
5467
|
+
type: "activity_log",
|
|
5133
5468
|
payload: {
|
|
5134
5469
|
id: uuidv4(),
|
|
5135
5470
|
timestamp: new Date(),
|
|
5136
|
-
type:
|
|
5471
|
+
type: "transcript",
|
|
5137
5472
|
content: result.text,
|
|
5138
5473
|
metadata: {
|
|
5139
5474
|
...(result.language ? { language: result.language } : {}),
|
|
@@ -5141,23 +5476,23 @@ export class Session {
|
|
|
5141
5476
|
},
|
|
5142
5477
|
},
|
|
5143
5478
|
});
|
|
5144
|
-
this.clearSpeechInProgress(
|
|
5145
|
-
this.setPhase(
|
|
5479
|
+
this.clearSpeechInProgress("transcription complete");
|
|
5480
|
+
this.setPhase("idle");
|
|
5146
5481
|
if (!this.isVoiceMode) {
|
|
5147
|
-
this.sessionLogger.debug({ requestId: result.requestId },
|
|
5148
|
-
await this.flushPendingAudioSegments(
|
|
5482
|
+
this.sessionLogger.debug({ requestId: result.requestId }, "Skipping voice agent processing because voice mode is disabled");
|
|
5483
|
+
await this.flushPendingAudioSegments("voice mode disabled");
|
|
5149
5484
|
return;
|
|
5150
5485
|
}
|
|
5151
5486
|
const agentId = this.voiceModeAgentId;
|
|
5152
5487
|
if (!agentId) {
|
|
5153
|
-
this.sessionLogger.warn({ requestId: result.requestId },
|
|
5154
|
-
await this.flushPendingAudioSegments(
|
|
5488
|
+
this.sessionLogger.warn({ requestId: result.requestId }, "Skipping voice agent processing because no agent is currently voice-enabled");
|
|
5489
|
+
await this.flushPendingAudioSegments("no active voice agent");
|
|
5155
5490
|
return;
|
|
5156
5491
|
}
|
|
5157
5492
|
// Route voice utterances through the same send path as regular text input:
|
|
5158
5493
|
// interrupt-if-running, record message, then start a new stream.
|
|
5159
5494
|
await this.handleSendAgentMessage(agentId, result.text);
|
|
5160
|
-
await this.flushPendingAudioSegments(
|
|
5495
|
+
await this.flushPendingAudioSegments("transcription complete");
|
|
5161
5496
|
}
|
|
5162
5497
|
registerVoiceBridgeForAgent(agentId) {
|
|
5163
5498
|
this.registerVoiceSpeakHandler?.(agentId, async ({ text, signal }) => {
|
|
@@ -5165,16 +5500,16 @@ export class Session {
|
|
|
5165
5500
|
agentId,
|
|
5166
5501
|
textLength: text.length,
|
|
5167
5502
|
preview: text.slice(0, 160),
|
|
5168
|
-
},
|
|
5503
|
+
}, "Voice speak tool call received by session handler");
|
|
5169
5504
|
const abortSignal = signal ?? this.abortController.signal;
|
|
5170
5505
|
await this.ttsManager.generateAndWaitForPlayback(text, (msg) => this.emit(msg), abortSignal, true);
|
|
5171
|
-
this.sessionLogger.info({ agentId, textLength: text.length },
|
|
5506
|
+
this.sessionLogger.info({ agentId, textLength: text.length }, "Voice speak tool call finished playback");
|
|
5172
5507
|
this.emit({
|
|
5173
|
-
type:
|
|
5508
|
+
type: "activity_log",
|
|
5174
5509
|
payload: {
|
|
5175
5510
|
id: uuidv4(),
|
|
5176
5511
|
timestamp: new Date(),
|
|
5177
|
-
type:
|
|
5512
|
+
type: "assistant",
|
|
5178
5513
|
content: text,
|
|
5179
5514
|
},
|
|
5180
5515
|
});
|
|
@@ -5191,24 +5526,24 @@ export class Session {
|
|
|
5191
5526
|
async handleAbort() {
|
|
5192
5527
|
this.sessionLogger.info({ phase: this.processingPhase }, `Abort request, phase: ${this.processingPhase}`);
|
|
5193
5528
|
this.abortController.abort();
|
|
5194
|
-
this.ttsManager.cancelPendingPlaybacks(
|
|
5529
|
+
this.ttsManager.cancelPendingPlaybacks("abort request");
|
|
5195
5530
|
// Voice abort should always interrupt active agent output immediately.
|
|
5196
5531
|
if (this.isVoiceMode && this.voiceModeAgentId) {
|
|
5197
5532
|
try {
|
|
5198
5533
|
await this.interruptAgentIfRunning(this.voiceModeAgentId);
|
|
5199
5534
|
}
|
|
5200
5535
|
catch (error) {
|
|
5201
|
-
this.sessionLogger.warn({ err: error, agentId: this.voiceModeAgentId },
|
|
5536
|
+
this.sessionLogger.warn({ err: error, agentId: this.voiceModeAgentId }, "Failed to interrupt active voice-mode agent on abort");
|
|
5202
5537
|
}
|
|
5203
5538
|
}
|
|
5204
|
-
if (this.processingPhase ===
|
|
5539
|
+
if (this.processingPhase === "transcribing") {
|
|
5205
5540
|
// Still in STT phase - we'll buffer the next audio
|
|
5206
|
-
this.sessionLogger.debug(
|
|
5541
|
+
this.sessionLogger.debug("Will buffer next audio (currently transcribing)");
|
|
5207
5542
|
// Phase stays as 'transcribing', handleAudioChunk will handle buffering
|
|
5208
5543
|
return;
|
|
5209
5544
|
}
|
|
5210
5545
|
// Reset phase to idle and clear pending non-voice buffers.
|
|
5211
|
-
this.setPhase(
|
|
5546
|
+
this.setPhase("idle");
|
|
5212
5547
|
this.pendingAudioSegments = [];
|
|
5213
5548
|
this.clearBufferTimeout();
|
|
5214
5549
|
}
|
|
@@ -5229,20 +5564,20 @@ export class Session {
|
|
|
5229
5564
|
const phaseBeforeAbort = this.processingPhase;
|
|
5230
5565
|
const hadActiveStream = this.hasActiveAgentRun(this.voiceModeAgentId);
|
|
5231
5566
|
this.speechInProgress = true;
|
|
5232
|
-
this.sessionLogger.debug(
|
|
5567
|
+
this.sessionLogger.debug("Voice speech detected – aborting playback and active agent run");
|
|
5233
5568
|
if (this.pendingAudioSegments.length > 0) {
|
|
5234
5569
|
this.sessionLogger.debug({ segmentCount: this.pendingAudioSegments.length }, `Dropping ${this.pendingAudioSegments.length} buffered audio segment(s) due to voice speech`);
|
|
5235
5570
|
this.pendingAudioSegments = [];
|
|
5236
5571
|
}
|
|
5237
5572
|
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` :
|
|
5573
|
+
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
5574
|
this.audioBuffer = null;
|
|
5240
5575
|
}
|
|
5241
5576
|
this.clearBufferTimeout();
|
|
5242
5577
|
this.abortController.abort();
|
|
5243
5578
|
await this.handleAbort();
|
|
5244
5579
|
const latencyMs = Date.now() - chunkReceivedAt;
|
|
5245
|
-
this.sessionLogger.debug({ latencyMs, phaseBeforeAbort, hadActiveStream },
|
|
5580
|
+
this.sessionLogger.debug({ latencyMs, phaseBeforeAbort, hadActiveStream }, "[Telemetry] barge_in.llm_abort_latency");
|
|
5246
5581
|
}
|
|
5247
5582
|
/**
|
|
5248
5583
|
* Clear speech-in-progress flag once the user turn has completed
|
|
@@ -5277,9 +5612,9 @@ export class Session {
|
|
|
5277
5612
|
setBufferTimeout() {
|
|
5278
5613
|
this.clearBufferTimeout();
|
|
5279
5614
|
this.bufferTimeout = setTimeout(async () => {
|
|
5280
|
-
this.sessionLogger.debug(
|
|
5281
|
-
if (this.processingPhase ===
|
|
5282
|
-
this.sessionLogger.debug({ segmentCount: this.pendingAudioSegments.length },
|
|
5615
|
+
this.sessionLogger.debug("Buffer timeout reached, processing pending segments");
|
|
5616
|
+
if (this.processingPhase === "transcribing") {
|
|
5617
|
+
this.sessionLogger.debug({ segmentCount: this.pendingAudioSegments.length }, "Buffer timeout deferred because transcription is still in progress");
|
|
5283
5618
|
this.setBufferTimeout();
|
|
5284
5619
|
return;
|
|
5285
5620
|
}
|
|
@@ -5305,15 +5640,15 @@ export class Session {
|
|
|
5305
5640
|
* Emit a message to the client
|
|
5306
5641
|
*/
|
|
5307
5642
|
emit(msg) {
|
|
5308
|
-
if (msg.type ===
|
|
5643
|
+
if (msg.type === "audio_output" &&
|
|
5309
5644
|
(process.env.TTS_DEBUG_AUDIO_DIR || isPaseoDictationDebugEnabled()) &&
|
|
5310
5645
|
msg.payload.groupId &&
|
|
5311
|
-
typeof msg.payload.audio ===
|
|
5646
|
+
typeof msg.payload.audio === "string") {
|
|
5312
5647
|
const groupId = msg.payload.groupId;
|
|
5313
5648
|
const existing = this.ttsDebugStreams.get(groupId) ??
|
|
5314
5649
|
{ format: msg.payload.format, chunks: [] };
|
|
5315
5650
|
try {
|
|
5316
|
-
existing.chunks.push(Buffer.from(msg.payload.audio,
|
|
5651
|
+
existing.chunks.push(Buffer.from(msg.payload.audio, "base64"));
|
|
5317
5652
|
existing.format = msg.payload.format;
|
|
5318
5653
|
this.ttsDebugStreams.set(groupId, existing);
|
|
5319
5654
|
}
|
|
@@ -5328,11 +5663,11 @@ export class Session {
|
|
|
5328
5663
|
const recordingPath = await maybePersistTtsDebugAudio(Buffer.concat(final.chunks), { sessionId: this.sessionId, groupId, format: final.format }, this.sessionLogger);
|
|
5329
5664
|
if (recordingPath) {
|
|
5330
5665
|
this.onMessage({
|
|
5331
|
-
type:
|
|
5666
|
+
type: "activity_log",
|
|
5332
5667
|
payload: {
|
|
5333
5668
|
id: uuidv4(),
|
|
5334
5669
|
timestamp: new Date(),
|
|
5335
|
-
type:
|
|
5670
|
+
type: "system",
|
|
5336
5671
|
content: `Saved TTS audio: ${recordingPath}`,
|
|
5337
5672
|
metadata: { recordingPath, format: final.format, groupId },
|
|
5338
5673
|
},
|
|
@@ -5352,14 +5687,14 @@ export class Session {
|
|
|
5352
5687
|
this.onBinaryMessage(frame);
|
|
5353
5688
|
}
|
|
5354
5689
|
catch (error) {
|
|
5355
|
-
this.sessionLogger.error({ err: error },
|
|
5690
|
+
this.sessionLogger.error({ err: error }, "Failed to emit binary frame");
|
|
5356
5691
|
}
|
|
5357
5692
|
}
|
|
5358
5693
|
/**
|
|
5359
5694
|
* Clean up session resources
|
|
5360
5695
|
*/
|
|
5361
5696
|
async cleanup() {
|
|
5362
|
-
this.sessionLogger.trace(
|
|
5697
|
+
this.sessionLogger.trace("Cleaning up");
|
|
5363
5698
|
if (this.unsubscribeAgentEvents) {
|
|
5364
5699
|
this.unsubscribeAgentEvents();
|
|
5365
5700
|
this.unsubscribeAgentEvents = null;
|
|
@@ -5382,7 +5717,7 @@ export class Session {
|
|
|
5382
5717
|
await this.agentMcpClient.close();
|
|
5383
5718
|
}
|
|
5384
5719
|
catch (error) {
|
|
5385
|
-
this.sessionLogger.error({ err: error },
|
|
5720
|
+
this.sessionLogger.error({ err: error }, "Failed to close Agent MCP client");
|
|
5386
5721
|
}
|
|
5387
5722
|
this.agentMcpClient = null;
|
|
5388
5723
|
this.agentTools = null;
|
|
@@ -5395,20 +5730,20 @@ export class Session {
|
|
|
5395
5730
|
this.unsubscribeTerminalsChanged = null;
|
|
5396
5731
|
}
|
|
5397
5732
|
this.subscribedTerminalDirectories.clear();
|
|
5398
|
-
for (const unsubscribe of this.terminalSubscriptions.values()) {
|
|
5399
|
-
unsubscribe();
|
|
5400
|
-
}
|
|
5401
|
-
this.terminalSubscriptions.clear();
|
|
5402
5733
|
for (const unsubscribeExit of this.terminalExitSubscriptions.values()) {
|
|
5403
5734
|
unsubscribeExit();
|
|
5404
5735
|
}
|
|
5405
5736
|
this.terminalExitSubscriptions.clear();
|
|
5406
|
-
this.
|
|
5737
|
+
this.disposeTerminalSubscriptions();
|
|
5407
5738
|
for (const target of this.checkoutDiffTargets.values()) {
|
|
5408
5739
|
this.closeCheckoutDiffWatchTarget(target);
|
|
5409
5740
|
}
|
|
5410
5741
|
this.checkoutDiffTargets.clear();
|
|
5411
5742
|
this.checkoutDiffSubscriptions.clear();
|
|
5743
|
+
for (const target of this.workspaceGitWatchTargets.values()) {
|
|
5744
|
+
this.closeWorkspaceGitWatchTarget(target);
|
|
5745
|
+
}
|
|
5746
|
+
this.workspaceGitWatchTargets.clear();
|
|
5412
5747
|
}
|
|
5413
5748
|
// ============================================================================
|
|
5414
5749
|
// Terminal Handlers
|
|
@@ -5428,24 +5763,11 @@ export class Session {
|
|
|
5428
5763
|
unsubscribeExit();
|
|
5429
5764
|
this.terminalExitSubscriptions.delete(terminalId);
|
|
5430
5765
|
}
|
|
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
|
-
}
|
|
5766
|
+
this.detachTerminalStream(terminalId, { emitExit: true });
|
|
5445
5767
|
}
|
|
5446
5768
|
emitTerminalsChangedSnapshot(input) {
|
|
5447
5769
|
this.emit({
|
|
5448
|
-
type:
|
|
5770
|
+
type: "terminals_changed",
|
|
5449
5771
|
payload: {
|
|
5450
5772
|
cwd: input.cwd,
|
|
5451
5773
|
terminals: input.terminals,
|
|
@@ -5492,13 +5814,13 @@ export class Session {
|
|
|
5492
5814
|
});
|
|
5493
5815
|
}
|
|
5494
5816
|
catch (error) {
|
|
5495
|
-
this.sessionLogger.warn({ err: error, cwd },
|
|
5817
|
+
this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
|
|
5496
5818
|
}
|
|
5497
5819
|
}
|
|
5498
5820
|
async handleListTerminalsRequest(msg) {
|
|
5499
5821
|
if (!this.terminalManager) {
|
|
5500
5822
|
this.emit({
|
|
5501
|
-
type:
|
|
5823
|
+
type: "list_terminals_response",
|
|
5502
5824
|
payload: {
|
|
5503
5825
|
cwd: msg.cwd,
|
|
5504
5826
|
terminals: [],
|
|
@@ -5513,7 +5835,7 @@ export class Session {
|
|
|
5513
5835
|
this.ensureTerminalExitSubscription(terminal);
|
|
5514
5836
|
}
|
|
5515
5837
|
this.emit({
|
|
5516
|
-
type:
|
|
5838
|
+
type: "list_terminals_response",
|
|
5517
5839
|
payload: {
|
|
5518
5840
|
cwd: msg.cwd,
|
|
5519
5841
|
terminals: terminals.map((t) => ({ id: t.id, name: t.name })),
|
|
@@ -5522,9 +5844,9 @@ export class Session {
|
|
|
5522
5844
|
});
|
|
5523
5845
|
}
|
|
5524
5846
|
catch (error) {
|
|
5525
|
-
this.sessionLogger.error({ err: error, cwd: msg.cwd },
|
|
5847
|
+
this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to list terminals");
|
|
5526
5848
|
this.emit({
|
|
5527
|
-
type:
|
|
5849
|
+
type: "list_terminals_response",
|
|
5528
5850
|
payload: {
|
|
5529
5851
|
cwd: msg.cwd,
|
|
5530
5852
|
terminals: [],
|
|
@@ -5536,10 +5858,10 @@ export class Session {
|
|
|
5536
5858
|
async handleCreateTerminalRequest(msg) {
|
|
5537
5859
|
if (!this.terminalManager) {
|
|
5538
5860
|
this.emit({
|
|
5539
|
-
type:
|
|
5861
|
+
type: "create_terminal_response",
|
|
5540
5862
|
payload: {
|
|
5541
5863
|
terminal: null,
|
|
5542
|
-
error:
|
|
5864
|
+
error: "Terminal manager not available",
|
|
5543
5865
|
requestId: msg.requestId,
|
|
5544
5866
|
},
|
|
5545
5867
|
});
|
|
@@ -5552,7 +5874,7 @@ export class Session {
|
|
|
5552
5874
|
});
|
|
5553
5875
|
this.ensureTerminalExitSubscription(session);
|
|
5554
5876
|
this.emit({
|
|
5555
|
-
type:
|
|
5877
|
+
type: "create_terminal_response",
|
|
5556
5878
|
payload: {
|
|
5557
5879
|
terminal: { id: session.id, name: session.name, cwd: session.cwd },
|
|
5558
5880
|
error: null,
|
|
@@ -5561,9 +5883,9 @@ export class Session {
|
|
|
5561
5883
|
});
|
|
5562
5884
|
}
|
|
5563
5885
|
catch (error) {
|
|
5564
|
-
this.sessionLogger.error({ err: error, cwd: msg.cwd },
|
|
5886
|
+
this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to create terminal");
|
|
5565
5887
|
this.emit({
|
|
5566
|
-
type:
|
|
5888
|
+
type: "create_terminal_response",
|
|
5567
5889
|
payload: {
|
|
5568
5890
|
terminal: null,
|
|
5569
5891
|
error: error.message,
|
|
@@ -5575,11 +5897,10 @@ export class Session {
|
|
|
5575
5897
|
async handleSubscribeTerminalRequest(msg) {
|
|
5576
5898
|
if (!this.terminalManager) {
|
|
5577
5899
|
this.emit({
|
|
5578
|
-
type:
|
|
5900
|
+
type: "subscribe_terminal_response",
|
|
5579
5901
|
payload: {
|
|
5580
5902
|
terminalId: msg.terminalId,
|
|
5581
|
-
|
|
5582
|
-
error: 'Terminal manager not available',
|
|
5903
|
+
error: "Terminal manager not available",
|
|
5583
5904
|
requestId: msg.requestId,
|
|
5584
5905
|
},
|
|
5585
5906
|
});
|
|
@@ -5588,52 +5909,44 @@ export class Session {
|
|
|
5588
5909
|
const session = this.terminalManager.getTerminal(msg.terminalId);
|
|
5589
5910
|
if (!session) {
|
|
5590
5911
|
this.emit({
|
|
5591
|
-
type:
|
|
5912
|
+
type: "subscribe_terminal_response",
|
|
5592
5913
|
payload: {
|
|
5593
5914
|
terminalId: msg.terminalId,
|
|
5594
|
-
|
|
5595
|
-
error: 'Terminal not found',
|
|
5915
|
+
error: "Terminal not found",
|
|
5596
5916
|
requestId: msg.requestId,
|
|
5597
5917
|
},
|
|
5598
5918
|
});
|
|
5599
5919
|
return;
|
|
5600
5920
|
}
|
|
5601
5921
|
this.ensureTerminalExitSubscription(session);
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5922
|
+
const slot = this.bindActiveTerminalStream(session);
|
|
5923
|
+
if (slot === null) {
|
|
5924
|
+
this.sessionLogger.warn({
|
|
5925
|
+
terminalId: msg.terminalId,
|
|
5926
|
+
activeTerminalStreamCount: this.activeTerminalStreams.size,
|
|
5927
|
+
}, "Terminal stream slot exhaustion");
|
|
5928
|
+
this.emit({
|
|
5929
|
+
type: "subscribe_terminal_response",
|
|
5930
|
+
payload: {
|
|
5931
|
+
terminalId: msg.terminalId,
|
|
5932
|
+
error: "No terminal stream slots available",
|
|
5933
|
+
requestId: msg.requestId,
|
|
5934
|
+
},
|
|
5935
|
+
});
|
|
5936
|
+
return;
|
|
5606
5937
|
}
|
|
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
5938
|
this.emit({
|
|
5622
|
-
type:
|
|
5939
|
+
type: "subscribe_terminal_response",
|
|
5623
5940
|
payload: {
|
|
5624
5941
|
terminalId: msg.terminalId,
|
|
5625
|
-
|
|
5942
|
+
slot,
|
|
5626
5943
|
error: null,
|
|
5627
5944
|
requestId: msg.requestId,
|
|
5628
5945
|
},
|
|
5629
5946
|
});
|
|
5630
5947
|
}
|
|
5631
5948
|
handleUnsubscribeTerminalRequest(msg) {
|
|
5632
|
-
|
|
5633
|
-
if (unsubscribe) {
|
|
5634
|
-
unsubscribe();
|
|
5635
|
-
this.terminalSubscriptions.delete(msg.terminalId);
|
|
5636
|
-
}
|
|
5949
|
+
this.detachTerminalStream(msg.terminalId, { emitExit: false });
|
|
5637
5950
|
}
|
|
5638
5951
|
handleTerminalInput(msg) {
|
|
5639
5952
|
if (!this.terminalManager) {
|
|
@@ -5641,22 +5954,20 @@ export class Session {
|
|
|
5641
5954
|
}
|
|
5642
5955
|
const session = this.terminalManager.getTerminal(msg.terminalId);
|
|
5643
5956
|
if (!session) {
|
|
5644
|
-
this.sessionLogger.warn({ terminalId: msg.terminalId },
|
|
5957
|
+
this.sessionLogger.warn({ terminalId: msg.terminalId }, "Terminal not found for input");
|
|
5645
5958
|
return;
|
|
5646
5959
|
}
|
|
5647
5960
|
this.ensureTerminalExitSubscription(session);
|
|
5961
|
+
if (msg.message.type === "resize") {
|
|
5962
|
+
const currentSize = session.getSize();
|
|
5963
|
+
if (currentSize.rows === msg.message.rows && currentSize.cols === msg.message.cols) {
|
|
5964
|
+
return;
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5648
5967
|
session.send(msg.message);
|
|
5649
5968
|
}
|
|
5650
5969
|
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
|
-
}
|
|
5970
|
+
this.detachTerminalStream(terminalId, { emitExit: options?.emitExit ?? true });
|
|
5660
5971
|
this.terminalManager?.killTerminal(terminalId);
|
|
5661
5972
|
}
|
|
5662
5973
|
async killTerminalsUnderPath(rootPath) {
|
|
@@ -5678,18 +5989,18 @@ export class Session {
|
|
|
5678
5989
|
catch (error) {
|
|
5679
5990
|
const message = error instanceof Error ? error.message : String(error);
|
|
5680
5991
|
cleanupErrors.push({ cwd: terminalCwd, message });
|
|
5681
|
-
this.sessionLogger.warn({ err: error, cwd: terminalCwd },
|
|
5992
|
+
this.sessionLogger.warn({ err: error, cwd: terminalCwd }, "Failed to clean up worktree terminals during archive");
|
|
5682
5993
|
}
|
|
5683
5994
|
}
|
|
5684
5995
|
if (cleanupErrors.length > 0) {
|
|
5685
|
-
const details = cleanupErrors.map((entry) => `${entry.cwd}: ${entry.message}`).join(
|
|
5996
|
+
const details = cleanupErrors.map((entry) => `${entry.cwd}: ${entry.message}`).join("; ");
|
|
5686
5997
|
throw new Error(`Failed to clean up worktree terminals during archive (${details})`);
|
|
5687
5998
|
}
|
|
5688
5999
|
}
|
|
5689
6000
|
async handleKillTerminalRequest(msg) {
|
|
5690
6001
|
if (!this.terminalManager) {
|
|
5691
6002
|
this.emit({
|
|
5692
|
-
type:
|
|
6003
|
+
type: "kill_terminal_response",
|
|
5693
6004
|
payload: {
|
|
5694
6005
|
terminalId: msg.terminalId,
|
|
5695
6006
|
success: false,
|
|
@@ -5700,7 +6011,7 @@ export class Session {
|
|
|
5700
6011
|
}
|
|
5701
6012
|
this.killTrackedTerminal(msg.terminalId, { emitExit: true });
|
|
5702
6013
|
this.emit({
|
|
5703
|
-
type:
|
|
6014
|
+
type: "kill_terminal_response",
|
|
5704
6015
|
payload: {
|
|
5705
6016
|
terminalId: msg.terminalId,
|
|
5706
6017
|
success: true,
|
|
@@ -5708,219 +6019,150 @@ export class Session {
|
|
|
5708
6019
|
},
|
|
5709
6020
|
});
|
|
5710
6021
|
}
|
|
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;
|
|
6022
|
+
bindActiveTerminalStream(terminal) {
|
|
6023
|
+
if (!this.onBinaryMessage) {
|
|
6024
|
+
return null;
|
|
5727
6025
|
}
|
|
5728
|
-
const
|
|
5729
|
-
if (
|
|
5730
|
-
this.
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
},
|
|
5742
|
-
});
|
|
5743
|
-
return;
|
|
6026
|
+
const existingSlot = this.terminalIdToSlot.get(terminal.id);
|
|
6027
|
+
if (typeof existingSlot === "number") {
|
|
6028
|
+
const existingStream = this.activeTerminalStreams.get(existingSlot);
|
|
6029
|
+
if (existingStream) {
|
|
6030
|
+
existingStream.needsSnapshot = true;
|
|
6031
|
+
this.trySendTerminalSnapshot(existingStream);
|
|
6032
|
+
return existingSlot;
|
|
6033
|
+
}
|
|
6034
|
+
this.terminalIdToSlot.delete(terminal.id);
|
|
6035
|
+
}
|
|
6036
|
+
const slot = this.allocateTerminalSlot();
|
|
6037
|
+
if (slot === null) {
|
|
6038
|
+
return null;
|
|
5744
6039
|
}
|
|
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,
|
|
6040
|
+
const activeStream = {
|
|
6041
|
+
terminalId: terminal.id,
|
|
6042
|
+
slot,
|
|
5767
6043
|
unsubscribe: () => { },
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
pendingChunks: [],
|
|
5771
|
-
pendingBytes: 0,
|
|
6044
|
+
needsSnapshot: true,
|
|
6045
|
+
snapshotRetryTimer: null,
|
|
5772
6046
|
};
|
|
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 });
|
|
6047
|
+
this.activeTerminalStreams.set(slot, activeStream);
|
|
6048
|
+
this.terminalIdToSlot.set(terminal.id, slot);
|
|
6049
|
+
activeStream.unsubscribe = terminal.subscribe((message) => {
|
|
6050
|
+
if (this.activeTerminalStreams.get(slot) !== activeStream) {
|
|
6051
|
+
return;
|
|
6052
|
+
}
|
|
6053
|
+
if (message.type === "snapshot") {
|
|
6054
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
if (activeStream.needsSnapshot || message.data.length === 0) {
|
|
6058
|
+
return;
|
|
6059
|
+
}
|
|
6060
|
+
if (this.getCurrentBinaryBufferedAmount() >= TERMINAL_STREAM_HIGH_WATER_BYTES) {
|
|
6061
|
+
this.markAllActiveTerminalStreamsForSnapshot();
|
|
5845
6062
|
return;
|
|
5846
6063
|
}
|
|
5847
|
-
|
|
5848
|
-
|
|
6064
|
+
this.emitBinary(encodeTerminalStreamFrame({
|
|
6065
|
+
opcode: TerminalStreamOpcode.Output,
|
|
6066
|
+
slot,
|
|
6067
|
+
payload: new Uint8Array(Buffer.from(message.data, "utf8")),
|
|
6068
|
+
}));
|
|
6069
|
+
if (this.getCurrentBinaryBufferedAmount() >= TERMINAL_STREAM_HIGH_WATER_BYTES) {
|
|
6070
|
+
this.markAllActiveTerminalStreamsForSnapshot();
|
|
6071
|
+
}
|
|
6072
|
+
});
|
|
6073
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6074
|
+
return slot;
|
|
6075
|
+
}
|
|
6076
|
+
trySendTerminalSnapshot(activeStream) {
|
|
6077
|
+
if (this.activeTerminalStreams.get(activeStream.slot) !== activeStream ||
|
|
6078
|
+
!activeStream.needsSnapshot) {
|
|
5849
6079
|
return;
|
|
5850
6080
|
}
|
|
5851
|
-
this.
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
break;
|
|
6081
|
+
if (this.getCurrentBinaryBufferedAmount() > TERMINAL_STREAM_LOW_WATER_BYTES) {
|
|
6082
|
+
if (!activeStream.snapshotRetryTimer) {
|
|
6083
|
+
activeStream.snapshotRetryTimer = setTimeout(() => {
|
|
6084
|
+
activeStream.snapshotRetryTimer = null;
|
|
6085
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6086
|
+
}, 33);
|
|
5858
6087
|
}
|
|
5859
|
-
|
|
5860
|
-
binding.pendingBytes -= this.getTerminalStreamChunkByteLength(next);
|
|
5861
|
-
if (binding.pendingBytes < 0) {
|
|
5862
|
-
binding.pendingBytes = 0;
|
|
5863
|
-
}
|
|
5864
|
-
this.emitTerminalStreamChunk(streamId, binding, next);
|
|
6088
|
+
return;
|
|
5865
6089
|
}
|
|
6090
|
+
if (activeStream.snapshotRetryTimer) {
|
|
6091
|
+
clearTimeout(activeStream.snapshotRetryTimer);
|
|
6092
|
+
activeStream.snapshotRetryTimer = null;
|
|
6093
|
+
}
|
|
6094
|
+
const terminal = this.terminalManager?.getTerminal(activeStream.terminalId);
|
|
6095
|
+
if (!terminal) {
|
|
6096
|
+
this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
6099
|
+
activeStream.needsSnapshot = false;
|
|
6100
|
+
this.emitBinary(encodeTerminalStreamFrame({
|
|
6101
|
+
opcode: TerminalStreamOpcode.Snapshot,
|
|
6102
|
+
slot: activeStream.slot,
|
|
6103
|
+
payload: encodeTerminalSnapshotPayload(terminal.getState()),
|
|
6104
|
+
}));
|
|
5866
6105
|
}
|
|
5867
|
-
|
|
5868
|
-
const
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
streamId: msg.streamId,
|
|
5873
|
-
success,
|
|
5874
|
-
requestId: msg.requestId,
|
|
5875
|
-
},
|
|
5876
|
-
});
|
|
6106
|
+
markAllActiveTerminalStreamsForSnapshot() {
|
|
6107
|
+
for (const activeStream of this.activeTerminalStreams.values()) {
|
|
6108
|
+
activeStream.needsSnapshot = true;
|
|
6109
|
+
this.trySendTerminalSnapshot(activeStream);
|
|
6110
|
+
}
|
|
5877
6111
|
}
|
|
5878
|
-
|
|
5879
|
-
for (
|
|
5880
|
-
this.
|
|
6112
|
+
allocateTerminalSlot() {
|
|
6113
|
+
for (let attempt = 0; attempt < MAX_TERMINAL_STREAM_SLOTS; attempt += 1) {
|
|
6114
|
+
const slot = (this.nextTerminalSlot + attempt) % MAX_TERMINAL_STREAM_SLOTS;
|
|
6115
|
+
if (this.activeTerminalStreams.has(slot)) {
|
|
6116
|
+
continue;
|
|
6117
|
+
}
|
|
6118
|
+
this.nextTerminalSlot = (slot + 1) % MAX_TERMINAL_STREAM_SLOTS;
|
|
6119
|
+
return slot;
|
|
5881
6120
|
}
|
|
6121
|
+
return null;
|
|
5882
6122
|
}
|
|
5883
|
-
detachTerminalStream(
|
|
5884
|
-
const
|
|
5885
|
-
if (
|
|
6123
|
+
detachTerminalStream(terminalId, options) {
|
|
6124
|
+
const slot = this.terminalIdToSlot.get(terminalId);
|
|
6125
|
+
if (typeof slot !== "number") {
|
|
5886
6126
|
return false;
|
|
5887
6127
|
}
|
|
6128
|
+
const activeStream = this.activeTerminalStreams.get(slot);
|
|
6129
|
+
if (!activeStream) {
|
|
6130
|
+
this.terminalIdToSlot.delete(terminalId);
|
|
6131
|
+
return false;
|
|
6132
|
+
}
|
|
6133
|
+
this.activeTerminalStreams.delete(slot);
|
|
6134
|
+
this.terminalIdToSlot.delete(terminalId);
|
|
6135
|
+
if (activeStream.snapshotRetryTimer) {
|
|
6136
|
+
clearTimeout(activeStream.snapshotRetryTimer);
|
|
6137
|
+
activeStream.snapshotRetryTimer = null;
|
|
6138
|
+
}
|
|
5888
6139
|
try {
|
|
5889
|
-
|
|
6140
|
+
activeStream.unsubscribe();
|
|
5890
6141
|
}
|
|
5891
6142
|
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);
|
|
6143
|
+
this.sessionLogger.warn({ err: error }, "Failed to unsubscribe terminal stream");
|
|
5897
6144
|
}
|
|
5898
6145
|
if (options?.emitExit) {
|
|
5899
6146
|
this.emit({
|
|
5900
|
-
type:
|
|
6147
|
+
type: "terminal_stream_exit",
|
|
5901
6148
|
payload: {
|
|
5902
|
-
|
|
5903
|
-
terminalId: binding.terminalId,
|
|
6149
|
+
terminalId: activeStream.terminalId,
|
|
5904
6150
|
},
|
|
5905
6151
|
});
|
|
5906
6152
|
}
|
|
5907
6153
|
return true;
|
|
5908
6154
|
}
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
if (!this.terminalStreams.has(candidate)) {
|
|
5919
|
-
return candidate;
|
|
5920
|
-
}
|
|
5921
|
-
attempts += 1;
|
|
6155
|
+
disposeTerminalSubscriptions() {
|
|
6156
|
+
for (const terminalId of [...this.terminalIdToSlot.keys()]) {
|
|
6157
|
+
this.detachTerminalStream(terminalId, { emitExit: false });
|
|
6158
|
+
}
|
|
6159
|
+
}
|
|
6160
|
+
getCurrentBinaryBufferedAmount() {
|
|
6161
|
+
const bufferedAmount = this.getBinaryBufferedAmount?.() ?? 0;
|
|
6162
|
+
if (!Number.isFinite(bufferedAmount) || bufferedAmount < 0) {
|
|
6163
|
+
return 0;
|
|
5922
6164
|
}
|
|
5923
|
-
|
|
6165
|
+
return Math.floor(bufferedAmount);
|
|
5924
6166
|
}
|
|
5925
6167
|
}
|
|
5926
6168
|
//# sourceMappingURL=session.js.map
|