@seawork/server 1.0.22-rc.3 → 2.0.2-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/supervisor-entrypoint.js +48 -8
- package/dist/scripts/supervisor-entrypoint.js.map +1 -1
- package/dist/scripts/supervisor-native-classifier.js +77 -5
- package/dist/scripts/supervisor-native-classifier.js.map +1 -1
- package/dist/scripts/supervisor-stdio-tail.js +27 -0
- package/dist/scripts/supervisor-stdio-tail.js.map +1 -0
- package/dist/scripts/supervisor.js +12 -0
- package/dist/scripts/supervisor.js.map +1 -1
- package/dist/server/client/daemon-client.d.ts +142 -2
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +384 -3
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +55 -3
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +324 -45
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts +1 -0
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +8 -0
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.js +7 -2
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-response-loop.d.ts +3 -1
- package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
- package/dist/server/server/agent/agent-response-loop.js +33 -6
- package/dist/server/server/agent/agent-response-loop.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +43 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/claude-memory.d.ts +4 -0
- package/dist/server/server/agent/claude-memory.d.ts.map +1 -0
- package/dist/server/server/agent/claude-memory.js +97 -0
- package/dist/server/server/agent/claude-memory.js.map +1 -0
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +247 -0
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/mcp-shared.d.ts +2 -0
- package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.d.ts +6 -139
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +65 -33
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts +1 -0
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +36 -0
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +4 -0
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +13 -0
- package/dist/server/server/agent/provider-snapshot-manager.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 +141 -27
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-mapper.js +14 -1
- 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 +132 -4
- 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 +2233 -163
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-binary-resolver.d.ts +9 -0
- package/dist/server/server/agent/providers/codex-binary-resolver.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-binary-resolver.js +35 -14
- package/dist/server/server/agent/providers/codex-binary-resolver.js.map +1 -1
- package/dist/server/server/agent/providers/codex-health-probe.js +1 -1
- package/dist/server/server/agent/providers/codex-health-probe.js.map +1 -1
- package/dist/server/server/agent/providers/deepseek/constants.d.ts +4 -0
- package/dist/server/server/agent/providers/deepseek/constants.d.ts.map +1 -0
- package/dist/server/server/agent/providers/deepseek/constants.js +11 -0
- package/dist/server/server/agent/providers/deepseek/constants.js.map +1 -0
- package/dist/server/server/agent/providers/deepseek/event-mapper.d.ts +21 -0
- package/dist/server/server/agent/providers/deepseek/event-mapper.d.ts.map +1 -0
- package/dist/server/server/agent/providers/deepseek/event-mapper.js +286 -0
- package/dist/server/server/agent/providers/deepseek/event-mapper.js.map +1 -0
- package/dist/server/server/agent/providers/deepseek/serve-client.d.ts +94 -0
- package/dist/server/server/agent/providers/deepseek/serve-client.d.ts.map +1 -0
- package/dist/server/server/agent/providers/deepseek/serve-client.js +142 -0
- package/dist/server/server/agent/providers/deepseek/serve-client.js.map +1 -0
- package/dist/server/server/agent/providers/deepseek/serve-process.d.ts +18 -0
- package/dist/server/server/agent/providers/deepseek/serve-process.d.ts.map +1 -0
- package/dist/server/server/agent/providers/deepseek/serve-process.js +93 -0
- package/dist/server/server/agent/providers/deepseek/serve-process.js.map +1 -0
- package/dist/server/server/agent/providers/deepseek-agent.d.ts +94 -0
- package/dist/server/server/agent/providers/deepseek-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/deepseek-agent.js +811 -0
- package/dist/server/server/agent/providers/deepseek-agent.js.map +1 -0
- package/dist/server/server/agent/providers/gateway-telemetry.d.ts +9 -0
- package/dist/server/server/agent/providers/gateway-telemetry.d.ts.map +1 -0
- package/dist/server/server/agent/providers/gateway-telemetry.js +36 -0
- package/dist/server/server/agent/providers/gateway-telemetry.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent/constants.d.ts +3 -0
- package/dist/server/server/agent/providers/seaagent/constants.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent/constants.js +3 -0
- package/dist/server/server/agent/providers/seaagent/constants.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent/event-mapper.d.ts +3 -0
- package/dist/server/server/agent/providers/seaagent/event-mapper.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent/event-mapper.js +69 -0
- package/dist/server/server/agent/providers/seaagent/event-mapper.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent/rpc-client.d.ts +23 -0
- package/dist/server/server/agent/providers/seaagent/rpc-client.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent/rpc-client.js +139 -0
- package/dist/server/server/agent/providers/seaagent/rpc-client.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent/tool-call-mapper.d.ts +3 -0
- package/dist/server/server/agent/providers/seaagent/tool-call-mapper.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent/tool-call-mapper.js +38 -0
- package/dist/server/server/agent/providers/seaagent/tool-call-mapper.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent-agent.d.ts +81 -0
- package/dist/server/server/agent/providers/seaagent-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent-agent.js +502 -0
- package/dist/server/server/agent/providers/seaagent-agent.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent-binary-resolver.d.ts +18 -0
- package/dist/server/server/agent/providers/seaagent-binary-resolver.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent-binary-resolver.js +46 -0
- package/dist/server/server/agent/providers/seaagent-binary-resolver.js.map +1 -0
- package/dist/server/server/agent/providers/seaagent-health-probe.d.ts +11 -0
- package/dist/server/server/agent/providers/seaagent-health-probe.d.ts.map +1 -0
- package/dist/server/server/agent/providers/seaagent-health-probe.js +49 -0
- package/dist/server/server/agent/providers/seaagent-health-probe.js.map +1 -0
- package/dist/server/server/agent/providers/seawork-models.d.ts +8 -0
- package/dist/server/server/agent/providers/seawork-models.d.ts.map +1 -1
- package/dist/server/server/agent/providers/seawork-models.js +118 -74
- package/dist/server/server/agent/providers/seawork-models.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +2 -2
- package/dist/server/server/agent/timeline-projection.d.ts +5 -1
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
- package/dist/server/server/agent/timeline-projection.js +20 -4
- package/dist/server/server/agent/timeline-projection.js.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts +1 -0
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.js +6 -0
- package/dist/server/server/agent-attention-policy.js.map +1 -1
- package/dist/server/server/allowed-hosts.d.ts +13 -0
- package/dist/server/server/allowed-hosts.d.ts.map +1 -1
- package/dist/server/server/allowed-hosts.js +33 -0
- package/dist/server/server/allowed-hosts.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +200 -14
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/browser-extension-token.d.ts +23 -0
- package/dist/server/server/browser-extension-token.d.ts.map +1 -0
- package/dist/server/server/browser-extension-token.js +114 -0
- package/dist/server/server/browser-extension-token.js.map +1 -0
- package/dist/server/server/bug-report-handler.d.ts +7 -1
- package/dist/server/server/bug-report-handler.d.ts.map +1 -1
- package/dist/server/server/bug-report-handler.js +73 -5
- package/dist/server/server/bug-report-handler.js.map +1 -1
- package/dist/server/server/bug-report-redact.d.ts +25 -1
- package/dist/server/server/bug-report-redact.d.ts.map +1 -1
- package/dist/server/server/bug-report-redact.js +42 -5
- package/dist/server/server/bug-report-redact.js.map +1 -1
- package/dist/server/server/config.d.ts +1 -0
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +51 -1
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/crash-report.d.ts.map +1 -1
- package/dist/server/server/crash-report.js +18 -0
- package/dist/server/server/crash-report.js.map +1 -1
- package/dist/server/server/daemon-config-store.d.ts.map +1 -1
- package/dist/server/server/daemon-config-store.js +94 -3
- package/dist/server/server/daemon-config-store.js.map +1 -1
- package/dist/server/server/disk-full.d.ts +4 -0
- package/dist/server/server/disk-full.d.ts.map +1 -0
- package/dist/server/server/disk-full.js +46 -0
- package/dist/server/server/disk-full.js.map +1 -0
- package/dist/server/server/exports.d.ts +3 -2
- package/dist/server/server/exports.d.ts.map +1 -1
- package/dist/server/server/exports.js +2 -1
- package/dist/server/server/exports.js.map +1 -1
- package/dist/server/server/git-forge/github-client.d.ts +18 -0
- package/dist/server/server/git-forge/github-client.d.ts.map +1 -1
- package/dist/server/server/git-forge/github-client.js +88 -0
- package/dist/server/server/git-forge/github-client.js.map +1 -1
- package/dist/server/server/git-forge/parse-remote.d.ts +2 -0
- package/dist/server/server/git-forge/parse-remote.d.ts.map +1 -1
- package/dist/server/server/git-forge/parse-remote.js +71 -6
- package/dist/server/server/git-forge/parse-remote.js.map +1 -1
- package/dist/server/server/git-forge/service.d.ts +87 -0
- package/dist/server/server/git-forge/service.d.ts.map +1 -1
- package/dist/server/server/git-forge/service.js +198 -4
- package/dist/server/server/git-forge/service.js.map +1 -1
- package/dist/server/server/index.js +72 -0
- package/dist/server/server/index.js.map +1 -1
- package/dist/server/server/integrations/wecom-openclaw/bridge.d.ts +88 -0
- package/dist/server/server/integrations/wecom-openclaw/bridge.d.ts.map +1 -0
- package/dist/server/server/integrations/wecom-openclaw/bridge.js +1229 -0
- package/dist/server/server/integrations/wecom-openclaw/bridge.js.map +1 -0
- package/dist/server/server/integrations/wecom-openclaw/qr.d.ts +38 -0
- package/dist/server/server/integrations/wecom-openclaw/qr.d.ts.map +1 -0
- package/dist/server/server/integrations/wecom-openclaw/qr.js +101 -0
- package/dist/server/server/integrations/wecom-openclaw/qr.js.map +1 -0
- package/dist/server/server/integrations/wecom-openclaw/workspace.d.ts +5 -0
- package/dist/server/server/integrations/wecom-openclaw/workspace.d.ts.map +1 -0
- package/dist/server/server/integrations/wecom-openclaw/workspace.js +40 -0
- package/dist/server/server/integrations/wecom-openclaw/workspace.js.map +1 -0
- package/dist/server/server/latency-proxy.d.ts.map +1 -1
- package/dist/server/server/latency-proxy.js +45 -5
- package/dist/server/server/latency-proxy.js.map +1 -1
- package/dist/server/server/library/codex-skill-discovery.d.ts +9 -0
- package/dist/server/server/library/codex-skill-discovery.d.ts.map +1 -0
- package/dist/server/server/library/codex-skill-discovery.js +49 -0
- package/dist/server/server/library/codex-skill-discovery.js.map +1 -0
- package/dist/server/server/library/hub-install.d.ts +79 -0
- package/dist/server/server/library/hub-install.d.ts.map +1 -0
- package/dist/server/server/library/hub-install.js +263 -0
- package/dist/server/server/library/hub-install.js.map +1 -0
- package/dist/server/server/library/hub-test-run.d.ts +81 -0
- package/dist/server/server/library/hub-test-run.d.ts.map +1 -0
- package/dist/server/server/library/hub-test-run.js +237 -0
- package/dist/server/server/library/hub-test-run.js.map +1 -0
- package/dist/server/server/library/library-import.d.ts +27 -0
- package/dist/server/server/library/library-import.d.ts.map +1 -0
- package/dist/server/server/library/library-import.js +227 -0
- package/dist/server/server/library/library-import.js.map +1 -0
- package/dist/server/server/library/library-injection.d.ts +16 -0
- package/dist/server/server/library/library-injection.d.ts.map +1 -0
- package/dist/server/server/library/library-injection.js +49 -0
- package/dist/server/server/library/library-injection.js.map +1 -0
- package/dist/server/server/library/library-rpc.d.ts +73 -0
- package/dist/server/server/library/library-rpc.d.ts.map +1 -0
- package/dist/server/server/library/library-rpc.js +239 -0
- package/dist/server/server/library/library-rpc.js.map +1 -0
- package/dist/server/server/library/library-store.d.ts +35 -0
- package/dist/server/server/library/library-store.d.ts.map +1 -0
- package/dist/server/server/library/library-store.js +169 -0
- package/dist/server/server/library/library-store.js.map +1 -0
- package/dist/server/server/library/library-sync.d.ts +46 -0
- package/dist/server/server/library/library-sync.d.ts.map +1 -0
- package/dist/server/server/library/library-sync.js +235 -0
- package/dist/server/server/library/library-sync.js.map +1 -0
- package/dist/server/server/library/library-types.d.ts +756 -0
- package/dist/server/server/library/library-types.d.ts.map +1 -0
- package/dist/server/server/library/library-types.js +99 -0
- package/dist/server/server/library/library-types.js.map +1 -0
- package/dist/server/server/library/worktree-dev.d.ts +14 -0
- package/dist/server/server/library/worktree-dev.d.ts.map +1 -0
- package/dist/server/server/library/worktree-dev.js +24 -0
- package/dist/server/server/library/worktree-dev.js.map +1 -0
- package/dist/server/server/log-stream-error.d.ts +2 -0
- package/dist/server/server/log-stream-error.d.ts.map +1 -0
- package/dist/server/server/log-stream-error.js +33 -0
- package/dist/server/server/log-stream-error.js.map +1 -0
- package/dist/server/server/logger.d.ts +1 -0
- package/dist/server/server/logger.d.ts.map +1 -1
- package/dist/server/server/logger.js +32 -0
- package/dist/server/server/logger.js.map +1 -1
- package/dist/server/server/loop/rpc-schemas.d.ts +96 -96
- package/dist/server/server/loop-service.d.ts +18 -18
- package/dist/server/server/messages.d.ts +4 -1
- package/dist/server/server/messages.d.ts.map +1 -1
- package/dist/server/server/messages.js +40 -2
- package/dist/server/server/messages.js.map +1 -1
- package/dist/server/server/node-pty-error.d.ts +2 -0
- package/dist/server/server/node-pty-error.d.ts.map +1 -0
- package/dist/server/server/node-pty-error.js +19 -0
- package/dist/server/server/node-pty-error.js.map +1 -0
- package/dist/server/server/persisted-config.d.ts +219 -135
- package/dist/server/server/persisted-config.d.ts.map +1 -1
- package/dist/server/server/persisted-config.js +35 -1
- package/dist/server/server/persisted-config.js.map +1 -1
- package/dist/server/server/port-in-use.d.ts +4 -0
- package/dist/server/server/port-in-use.d.ts.map +1 -0
- package/dist/server/server/port-in-use.js +35 -0
- package/dist/server/server/port-in-use.js.map +1 -0
- package/dist/server/server/provider-runtime-settings-mask.d.ts +7 -0
- package/dist/server/server/provider-runtime-settings-mask.d.ts.map +1 -0
- package/dist/server/server/provider-runtime-settings-mask.js +65 -0
- package/dist/server/server/provider-runtime-settings-mask.js.map +1 -0
- package/dist/server/server/sac/auth.d.ts +12 -0
- package/dist/server/server/sac/auth.d.ts.map +1 -1
- package/dist/server/server/sac/auth.js +19 -1
- package/dist/server/server/sac/auth.js.map +1 -1
- package/dist/server/server/sac/index.d.ts +2 -2
- package/dist/server/server/sac/index.d.ts.map +1 -1
- package/dist/server/server/sac/index.js +2 -2
- package/dist/server/server/sac/index.js.map +1 -1
- package/dist/server/server/sac/poll.d.ts +2 -0
- package/dist/server/server/sac/poll.d.ts.map +1 -1
- package/dist/server/server/sac/poll.js +7 -2
- package/dist/server/server/sac/poll.js.map +1 -1
- package/dist/server/server/schedule/cron.d.ts.map +1 -1
- package/dist/server/server/schedule/cron.js +6 -6
- package/dist/server/server/schedule/cron.js.map +1 -1
- package/dist/server/server/schedule/rpc-schemas.d.ts +895 -0
- package/dist/server/server/schedule/rpc-schemas.d.ts.map +1 -1
- package/dist/server/server/schedule/rpc-schemas.js +34 -0
- package/dist/server/server/schedule/rpc-schemas.js.map +1 -1
- package/dist/server/server/schedule/service.d.ts +5 -1
- package/dist/server/server/schedule/service.d.ts.map +1 -1
- package/dist/server/server/schedule/service.js +97 -14
- package/dist/server/server/schedule/service.js.map +1 -1
- package/dist/server/server/schedule/types.d.ts +19 -0
- package/dist/server/server/schedule/types.d.ts.map +1 -1
- package/dist/server/server/schedule/types.js +1 -0
- package/dist/server/server/schedule/types.js.map +1 -1
- package/dist/server/server/session.d.ts +83 -2
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +895 -82
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/speech/native-runtime-guard.d.ts +1 -0
- package/dist/server/server/speech/native-runtime-guard.d.ts.map +1 -1
- package/dist/server/server/speech/native-runtime-guard.js +10 -4
- package/dist/server/server/speech/native-runtime-guard.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +6 -1
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +79 -7
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-git-service.d.ts +2 -1
- package/dist/server/server/workspace-git-service.d.ts.map +1 -1
- package/dist/server/server/workspace-git-service.js +7 -3
- package/dist/server/server/workspace-git-service.js.map +1 -1
- package/dist/server/server/workspace-registry-model.d.ts +1 -0
- package/dist/server/server/workspace-registry-model.d.ts.map +1 -1
- package/dist/server/server/workspace-registry-model.js +18 -0
- package/dist/server/server/workspace-registry-model.js.map +1 -1
- package/dist/server/server/worktree-session.d.ts +3 -3
- package/dist/server/server/worktree-session.d.ts.map +1 -1
- package/dist/server/server/worktree-session.js +1 -3
- package/dist/server/server/worktree-session.js.map +1 -1
- package/dist/server/shared/messages.d.ts +59658 -21927
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +531 -3
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/provider-runtime-settings.d.ts +87 -0
- package/dist/server/shared/provider-runtime-settings.d.ts.map +1 -0
- package/dist/server/shared/provider-runtime-settings.js +33 -0
- package/dist/server/shared/provider-runtime-settings.js.map +1 -0
- package/dist/server/terminal/terminal.d.ts +9 -0
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +100 -3
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +23 -1
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +182 -21
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
- package/dist/server/utils/directory-suggestions.js +57 -9
- package/dist/server/utils/directory-suggestions.js.map +1 -1
- package/dist/src/server/bug-report-redact.js +42 -5
- package/dist/src/server/bug-report-redact.js.map +1 -1
- package/dist/src/server/crash-report.js +18 -0
- package/dist/src/server/crash-report.js.map +1 -1
- package/dist/src/server/speech/native-runtime-guard.js +177 -0
- package/dist/src/server/speech/native-runtime-guard.js.map +1 -0
- package/dist/src/server/speech/speech-types.js +8 -0
- package/dist/src/server/speech/speech-types.js.map +1 -0
- package/package.json +16 -4
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
import { applyProviderEnv, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
|
|
2
|
+
import { selectCodexBinary, selectEffectiveCodexBinary, verifyCommandAvailable, } from "./codex-binary-resolver.js";
|
|
3
|
+
import { getSeaworkModels } from "./seawork-models.js";
|
|
4
|
+
import { DEEPSEEK_BINARY, DEEPSEEK_PROVIDER } from "./deepseek/constants.js";
|
|
5
|
+
import { mapHistoryItems, mapServeEvent, newAccumulator } from "./deepseek/event-mapper.js";
|
|
6
|
+
import { DeepseekServeClient } from "./deepseek/serve-client.js";
|
|
7
|
+
import { startDeepseekServe } from "./deepseek/serve-process.js";
|
|
8
|
+
import { buildSeaworkGatewayHeaders, formatDeepseekHttpHeaders } from "./gateway-telemetry.js";
|
|
9
|
+
export { DEEPSEEK_PROVIDER };
|
|
10
|
+
const AVAILABILITY_TTL_MS = 30000;
|
|
11
|
+
const DEEPSEEK_CAPABILITIES = {
|
|
12
|
+
supportsStreaming: true,
|
|
13
|
+
supportsSessionPersistence: true,
|
|
14
|
+
supportsDynamicModes: false,
|
|
15
|
+
supportsMcpServers: false,
|
|
16
|
+
supportsReasoningStream: true,
|
|
17
|
+
supportsToolInvocations: true,
|
|
18
|
+
};
|
|
19
|
+
// Modes mirror the codex auto/full-access split. CodeWhale's serve runtime
|
|
20
|
+
// gates tool execution on `auto_approve`: false => emit approval.required and
|
|
21
|
+
// block (Ask); true => run without prompting (Full Access).
|
|
22
|
+
const DEEPSEEK_MODES = [
|
|
23
|
+
{
|
|
24
|
+
id: "auto",
|
|
25
|
+
label: "Ask First",
|
|
26
|
+
description: "Edit files and run commands after you approve each action.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "full-access",
|
|
30
|
+
label: "Full Access",
|
|
31
|
+
description: "Edit files and run commands without per-action approval.",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const DEFAULT_DEEPSEEK_MODE = "auto";
|
|
35
|
+
function readEnvString(env, key) {
|
|
36
|
+
const value = env[key];
|
|
37
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the LLM gateway the CodeWhale serve subprocess should hit.
|
|
41
|
+
*
|
|
42
|
+
* serve runs in `DEEPSEEK_PROVIDER=openai` mode and reads `OPENAI_BASE_URL` /
|
|
43
|
+
* `OPENAI_API_KEY`, then appends `/v1/chat/completions` itself. The LLM gateway
|
|
44
|
+
* lives in `SEAWORK_LLM_BASE_URL` (auth.json base_url, e.g.
|
|
45
|
+
* `https://api.seawork.ai/llm`); the desktop sets `OPENAI_BASE_URL` to the
|
|
46
|
+
* *image* gateway, which has no chat-completions route. Reading OPENAI_BASE_URL
|
|
47
|
+
* verbatim is what produced `404 Cannot POST /v1/chat/completions`. So we take
|
|
48
|
+
* the gateway ONLY from SEAWORK_LLM_BASE_URL and pass it as the gateway root
|
|
49
|
+
* (do NOT append `/v1` — serve does that).
|
|
50
|
+
*/
|
|
51
|
+
export function resolveDeepseekGatewayEnv(env) {
|
|
52
|
+
return {
|
|
53
|
+
baseURL: readEnvString(env, "SEAWORK_LLM_BASE_URL"),
|
|
54
|
+
apiKey: readEnvString(env, "SEAWORK_API_KEY") ?? readEnvString(env, "OPENAI_API_KEY"),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function modeToApproval(modeId) {
|
|
58
|
+
// Full access => auto-approve everything; otherwise require approvals.
|
|
59
|
+
const full = modeId === "full-access";
|
|
60
|
+
return { auto_approve: full, allow_shell: true };
|
|
61
|
+
}
|
|
62
|
+
function promptToText(prompt) {
|
|
63
|
+
if (typeof prompt === "string")
|
|
64
|
+
return prompt;
|
|
65
|
+
const parts = [];
|
|
66
|
+
for (const block of prompt) {
|
|
67
|
+
if (block.type === "text")
|
|
68
|
+
parts.push(block.text);
|
|
69
|
+
else if (block.type === "image")
|
|
70
|
+
throw new Error("deepseek provider does not support image inputs");
|
|
71
|
+
else
|
|
72
|
+
throw new Error(`deepseek provider does not support content block "${block.type}"`);
|
|
73
|
+
}
|
|
74
|
+
return parts.join("\n");
|
|
75
|
+
}
|
|
76
|
+
function hasInvalidPersistedChatMessages(items) {
|
|
77
|
+
return items.some((item) => {
|
|
78
|
+
if (item.kind !== "user_message" && item.kind !== "agent_message") {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const text = item.detail ?? item.summary ?? "";
|
|
82
|
+
return text.trim().length === 0;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function isActiveServeTurnStatus(status) {
|
|
86
|
+
if (!status)
|
|
87
|
+
return false;
|
|
88
|
+
const normalized = status.trim().toLowerCase();
|
|
89
|
+
return (normalized === "in_progress" ||
|
|
90
|
+
normalized === "in-progress" ||
|
|
91
|
+
normalized === "running" ||
|
|
92
|
+
normalized === "active" ||
|
|
93
|
+
normalized === "pending");
|
|
94
|
+
}
|
|
95
|
+
export class DeepseekAgentClient {
|
|
96
|
+
constructor(logger, runtimeSettings) {
|
|
97
|
+
this.logger = logger;
|
|
98
|
+
this.runtimeSettings = runtimeSettings;
|
|
99
|
+
this.provider = DEEPSEEK_PROVIDER;
|
|
100
|
+
this.capabilities = DEEPSEEK_CAPABILITIES;
|
|
101
|
+
this.availabilityCache = null;
|
|
102
|
+
}
|
|
103
|
+
async createSession(config, launchContext) {
|
|
104
|
+
return this.openSession(config, null, launchContext);
|
|
105
|
+
}
|
|
106
|
+
async resumeSession(handle, overrides, launchContext) {
|
|
107
|
+
const cwd = overrides?.cwd ??
|
|
108
|
+
(typeof handle.metadata?.cwd === "string" ? handle.metadata.cwd : process.cwd());
|
|
109
|
+
const config = {
|
|
110
|
+
provider: DEEPSEEK_PROVIDER,
|
|
111
|
+
cwd,
|
|
112
|
+
model: overrides?.model ?? handle.metadata?.model,
|
|
113
|
+
modeId: overrides?.modeId ?? handle.metadata?.modeId,
|
|
114
|
+
...overrides,
|
|
115
|
+
};
|
|
116
|
+
return this.openSession(config, handle, launchContext);
|
|
117
|
+
}
|
|
118
|
+
async listModels(_options) {
|
|
119
|
+
return getSeaworkModels(DEEPSEEK_PROVIDER);
|
|
120
|
+
}
|
|
121
|
+
async listModes() {
|
|
122
|
+
return DEEPSEEK_MODES;
|
|
123
|
+
}
|
|
124
|
+
async isAvailable() {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (this.availabilityCache && now - this.availabilityCache.at < AVAILABILITY_TTL_MS) {
|
|
127
|
+
return this.availabilityCache.value;
|
|
128
|
+
}
|
|
129
|
+
let value = false;
|
|
130
|
+
try {
|
|
131
|
+
const selection = await selectEffectiveCodexBinary(this.runtimeSettings, {
|
|
132
|
+
spawnEnv: this.spawnEnv(),
|
|
133
|
+
binary: DEEPSEEK_BINARY,
|
|
134
|
+
});
|
|
135
|
+
value = await verifyCommandAvailable(selection.binary, { spawnEnv: this.spawnEnv() });
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
value = false;
|
|
139
|
+
}
|
|
140
|
+
this.availabilityCache = { value, at: now };
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
async getDiagnostic() {
|
|
144
|
+
try {
|
|
145
|
+
const spawnEnv = this.spawnEnv();
|
|
146
|
+
const selection = await selectEffectiveCodexBinary(this.runtimeSettings, {
|
|
147
|
+
spawnEnv,
|
|
148
|
+
binary: DEEPSEEK_BINARY,
|
|
149
|
+
});
|
|
150
|
+
// Report the LLM gateway the session actually spawns with — resolved the
|
|
151
|
+
// same way openSession() does (SEAWORK_LLM_BASE_URL only), so UI
|
|
152
|
+
// availability and the real serve spawn agree on the gateway.
|
|
153
|
+
const baseURL = resolveDeepseekGatewayEnv(spawnEnv).baseURL ?? "(unset)";
|
|
154
|
+
return {
|
|
155
|
+
diagnostic: [
|
|
156
|
+
`Binary: ${selection.binary} (${selection.source})`,
|
|
157
|
+
`Gateway: ${baseURL}`,
|
|
158
|
+
`Transport: codewhale serve --http`,
|
|
159
|
+
].join("\n"),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return {
|
|
164
|
+
diagnostic: `DeepSeek binary not found — ${error instanceof Error ? error.message : String(error)}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
spawnEnv() {
|
|
169
|
+
return applyProviderEnv(process.env, this.runtimeSettings);
|
|
170
|
+
}
|
|
171
|
+
async openSession(config, resumeHandle, launchContext) {
|
|
172
|
+
// Resolve the launch command through the provider runtime command config
|
|
173
|
+
// (command.replace / command.append), exactly like isAvailable() and
|
|
174
|
+
// getDiagnostic() do — otherwise a user's custom wrapper would show as
|
|
175
|
+
// "available" in the UI but be ignored when actually spawning a session.
|
|
176
|
+
// The default command is the resolved codewhale/deepseek binary; any
|
|
177
|
+
// command.replace argv or command.append args ride along as prefix args.
|
|
178
|
+
const prefix = await resolveProviderCommandPrefix(this.runtimeSettings?.command, async () => {
|
|
179
|
+
const selection = await selectCodexBinary({
|
|
180
|
+
logger: this.logger,
|
|
181
|
+
spawnEnv: this.spawnEnv(),
|
|
182
|
+
binary: DEEPSEEK_BINARY,
|
|
183
|
+
});
|
|
184
|
+
return selection.binary;
|
|
185
|
+
});
|
|
186
|
+
// Route CodeWhale at the Seawork LLM gateway via its OpenAI-compatible
|
|
187
|
+
// provider. serve reads OPENAI_BASE_URL / OPENAI_API_KEY, so we OVERRIDE
|
|
188
|
+
// them with the LLM gateway from SEAWORK_LLM_BASE_URL (auth.json base_url):
|
|
189
|
+
// spawnEnv()'s OPENAI_BASE_URL is the desktop image gateway and would 404.
|
|
190
|
+
//
|
|
191
|
+
// Merge launchContext.env FIRST (into mergedEnv), then resolve the gateway
|
|
192
|
+
// from that merged view and apply the OPENAI_* override LAST — otherwise a
|
|
193
|
+
// launchContext-supplied OPENAI_BASE_URL (image gateway) would spread in
|
|
194
|
+
// after the override and re-introduce the 404, and a launchContext-supplied
|
|
195
|
+
// SEAWORK_LLM_BASE_URL would be missed. spawnEnv() already merges provider
|
|
196
|
+
// runtimeSettings.env over process.env (applyProviderEnv), so a per-provider
|
|
197
|
+
// override survives. Same env basis isAvailable()/getDiagnostic() use.
|
|
198
|
+
const mergedEnv = {
|
|
199
|
+
...this.spawnEnv(),
|
|
200
|
+
...(launchContext?.env ?? {}),
|
|
201
|
+
DEEPSEEK_PROVIDER: "openai",
|
|
202
|
+
...(config.model ? { DEEPSEEK_MODEL: config.model } : {}),
|
|
203
|
+
DEEPSEEK_HTTP_HEADERS: formatDeepseekHttpHeaders(buildSeaworkGatewayHeaders(DEEPSEEK_PROVIDER)),
|
|
204
|
+
};
|
|
205
|
+
const gateway = resolveDeepseekGatewayEnv(mergedEnv);
|
|
206
|
+
const env = { ...mergedEnv };
|
|
207
|
+
// When SEAWORK_LLM_BASE_URL is missing there is NO LLM gateway. Don't leave
|
|
208
|
+
// the inherited OPENAI_BASE_URL (desktop's image gateway) in place — serve
|
|
209
|
+
// would silently POST to the wrong host and 404. Strip it so DeepSeek fails
|
|
210
|
+
// as "not logged in" instead. When present, OVERRIDE serve's OPENAI_* with
|
|
211
|
+
// the LLM gateway so the image gateway can never win.
|
|
212
|
+
if (gateway.baseURL) {
|
|
213
|
+
env.OPENAI_BASE_URL = gateway.baseURL;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
delete env.OPENAI_BASE_URL;
|
|
217
|
+
}
|
|
218
|
+
if (gateway.apiKey) {
|
|
219
|
+
env.OPENAI_API_KEY = gateway.apiKey;
|
|
220
|
+
}
|
|
221
|
+
const serve = await startDeepseekServe({
|
|
222
|
+
binary: prefix.command,
|
|
223
|
+
prefixArgs: prefix.args,
|
|
224
|
+
env,
|
|
225
|
+
logger: this.logger,
|
|
226
|
+
});
|
|
227
|
+
// If the handshake (createThread / SSE) fails, tear the serve subprocess
|
|
228
|
+
// down before propagating — otherwise the orphaned process leaks.
|
|
229
|
+
try {
|
|
230
|
+
const session = new DeepseekSession(config, resumeHandle, this.logger, serve);
|
|
231
|
+
await session.connect();
|
|
232
|
+
return session;
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
serve.close();
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export class DeepseekSession {
|
|
241
|
+
constructor(config, resumeHandle, logger, serve) {
|
|
242
|
+
this.config = config;
|
|
243
|
+
this.resumeHandle = resumeHandle;
|
|
244
|
+
this.logger = logger;
|
|
245
|
+
this.serve = serve;
|
|
246
|
+
this.provider = DEEPSEEK_PROVIDER;
|
|
247
|
+
this.capabilities = DEEPSEEK_CAPABILITIES;
|
|
248
|
+
this.subscribers = new Set();
|
|
249
|
+
this.history = [];
|
|
250
|
+
// Timeline items rebuilt from the persisted thread on resume; streamHistory()
|
|
251
|
+
// replays these once so a restarted daemon shows past turns.
|
|
252
|
+
this.persistedHistory = [];
|
|
253
|
+
this.pendingPermissions = new Map();
|
|
254
|
+
// approvalId -> turnId, so terminal events can drop a turn's stale approvals.
|
|
255
|
+
this.pendingPermissionTurn = new Map();
|
|
256
|
+
this.threadId = null;
|
|
257
|
+
this.sseAbort = null;
|
|
258
|
+
this.eventStreamReady = null;
|
|
259
|
+
this.closed = false;
|
|
260
|
+
// SSE turn id from the most recent turn.started frame — the SAME id run()
|
|
261
|
+
// pins as ourTurnId — and the id of the last turn that reached a terminal
|
|
262
|
+
// event. If the stream dies after lastStartedTurnId but before that turn is
|
|
263
|
+
// terminated, we synthesize turn_failed with this exact id so run()'s turnId
|
|
264
|
+
// filter doesn't drop it.
|
|
265
|
+
this.lastStartedTurnId = null;
|
|
266
|
+
this.lastTerminatedTurnId = null;
|
|
267
|
+
// True from just before the POST /turns until that turn reaches a terminal
|
|
268
|
+
// event. Lets the SSE finally{} emit a last-resort turn_failed if the stream
|
|
269
|
+
// dies in the narrow window after connect but before turn.started AND before
|
|
270
|
+
// the POST response set activeTurnId — otherwise run() would hang forever.
|
|
271
|
+
this.turnStartInFlight = false;
|
|
272
|
+
// turnId -> ordered authoritative assistant text per completed item
|
|
273
|
+
// (itemId -> full text), populated from every completed agent_message item.
|
|
274
|
+
// run() joins these in insertion order for a correct finalText across
|
|
275
|
+
// multi-segment turns (tool rounds), independent of any lossy delta stream.
|
|
276
|
+
this.assistantTextByTurn = new Map();
|
|
277
|
+
this.activeTurnId = null;
|
|
278
|
+
this.recoveringConflict = false;
|
|
279
|
+
this.client = new DeepseekServeClient(serve.baseUrl, serve.token, logger);
|
|
280
|
+
this.model = config.model ?? null;
|
|
281
|
+
this.currentMode = config.modeId ?? DEFAULT_DEEPSEEK_MODE;
|
|
282
|
+
}
|
|
283
|
+
get id() {
|
|
284
|
+
return this.threadId;
|
|
285
|
+
}
|
|
286
|
+
async connect() {
|
|
287
|
+
const approval = modeToApproval(this.currentMode);
|
|
288
|
+
if (this.resumeHandle?.sessionId) {
|
|
289
|
+
const resumeThreadId = this.resumeHandle.sessionId;
|
|
290
|
+
let persistedItems = [];
|
|
291
|
+
let inspectedPersistedThread = false;
|
|
292
|
+
try {
|
|
293
|
+
const detail = await this.client.getThreadDetail(resumeThreadId);
|
|
294
|
+
persistedItems = detail.items ?? [];
|
|
295
|
+
inspectedPersistedThread = true;
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
this.logger.warn({ err }, "deepseek: failed to inspect persisted thread before resume");
|
|
299
|
+
}
|
|
300
|
+
this.persistedHistory = mapHistoryItems(persistedItems);
|
|
301
|
+
if (hasInvalidPersistedChatMessages(persistedItems)) {
|
|
302
|
+
this.logger.warn({ threadId: resumeThreadId }, "deepseek: persisted thread contains empty chat messages; starting a fresh thread to avoid gateway 400s");
|
|
303
|
+
const thread = await this.client.createThread({
|
|
304
|
+
model: this.model ?? undefined,
|
|
305
|
+
workspace: this.config.cwd,
|
|
306
|
+
mode: "agent",
|
|
307
|
+
system_prompt: this.config.systemPrompt,
|
|
308
|
+
...approval,
|
|
309
|
+
});
|
|
310
|
+
this.threadId = thread.id;
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
this.threadId = resumeThreadId;
|
|
314
|
+
await this.client.resumeThread(this.threadId).catch((err) => {
|
|
315
|
+
this.logger.warn({ err }, "deepseek: resume thread failed; continuing");
|
|
316
|
+
});
|
|
317
|
+
if (!inspectedPersistedThread) {
|
|
318
|
+
await this.loadPersistedHistory();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const thread = await this.client.createThread({
|
|
324
|
+
model: this.model ?? undefined,
|
|
325
|
+
workspace: this.config.cwd,
|
|
326
|
+
mode: "agent",
|
|
327
|
+
system_prompt: this.config.systemPrompt,
|
|
328
|
+
...approval,
|
|
329
|
+
});
|
|
330
|
+
this.threadId = thread.id;
|
|
331
|
+
}
|
|
332
|
+
this.emit({ type: "thread_started", sessionId: this.threadId, provider: DEEPSEEK_PROVIDER });
|
|
333
|
+
}
|
|
334
|
+
async run(prompt, options) {
|
|
335
|
+
const finalTexts = [];
|
|
336
|
+
const timeline = [];
|
|
337
|
+
return new Promise((resolve, reject) => {
|
|
338
|
+
let ourTurnId = null;
|
|
339
|
+
let settled = false;
|
|
340
|
+
const finalize = (o) => {
|
|
341
|
+
if (settled)
|
|
342
|
+
return;
|
|
343
|
+
settled = true;
|
|
344
|
+
unsubscribe();
|
|
345
|
+
if (o.ok) {
|
|
346
|
+
// Prefer the authoritative per-item assistant text (joined across all
|
|
347
|
+
// completed agent_message segments of the turn) — correct even with
|
|
348
|
+
// tool rounds or a lossy delta in any one segment. Fall back to the
|
|
349
|
+
// concatenated streamed chunks if we somehow recorded none.
|
|
350
|
+
const byItem = ourTurnId ? this.assistantTextByTurn.get(ourTurnId) : undefined;
|
|
351
|
+
if (ourTurnId)
|
|
352
|
+
this.assistantTextByTurn.delete(ourTurnId);
|
|
353
|
+
const authoritative = byItem && byItem.size > 0 ? [...byItem.values()].join("") : undefined;
|
|
354
|
+
resolve({
|
|
355
|
+
sessionId: this.threadId ?? "",
|
|
356
|
+
finalText: authoritative ?? finalTexts.join(""),
|
|
357
|
+
timeline,
|
|
358
|
+
canceled: o.canceled,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else
|
|
362
|
+
reject(o.error);
|
|
363
|
+
};
|
|
364
|
+
const unsubscribe = this.subscribe((event) => {
|
|
365
|
+
if (settled)
|
|
366
|
+
return;
|
|
367
|
+
const evTurn = event.turnId;
|
|
368
|
+
if (event.type === "turn_started" && ourTurnId === null) {
|
|
369
|
+
ourTurnId = evTurn ?? null;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (ourTurnId !== null && evTurn && evTurn !== ourTurnId)
|
|
373
|
+
return;
|
|
374
|
+
if (event.type === "timeline") {
|
|
375
|
+
timeline.push(event.item);
|
|
376
|
+
if (event.item.type === "assistant_message")
|
|
377
|
+
finalTexts.push(event.item.text);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (event.type === "turn_completed")
|
|
381
|
+
finalize({ ok: true, canceled: false });
|
|
382
|
+
else if (event.type === "turn_canceled")
|
|
383
|
+
finalize({ ok: true, canceled: true });
|
|
384
|
+
else if (event.type === "turn_failed")
|
|
385
|
+
finalize({ ok: false, error: new Error(event.error || "deepseek turn failed") });
|
|
386
|
+
});
|
|
387
|
+
this.startTurn(prompt, options).catch((err) => finalize({ ok: false, error: err instanceof Error ? err : new Error(String(err)) }));
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async startTurn(prompt, _options) {
|
|
391
|
+
if (!this.threadId)
|
|
392
|
+
throw new Error("deepseek session is not connected");
|
|
393
|
+
const text = promptToText(prompt);
|
|
394
|
+
const approval = modeToApproval(this.currentMode);
|
|
395
|
+
// Reset the per-turn SSE-start marker BEFORE anything for this turn can
|
|
396
|
+
// arrive on the stream. CodeWhale can emit turn.started within milliseconds
|
|
397
|
+
// — possibly before the POST /turns response returns — and the detached SSE
|
|
398
|
+
// consumer sets lastStartedTurnId from that frame. If we cleared it AFTER
|
|
399
|
+
// the POST, we'd wipe the real SSE turn id the consumer just recorded, so a
|
|
400
|
+
// later mid-turn stream death would fall back to the HTTP turn id (which can
|
|
401
|
+
// differ from the SSE id run() pinned) and run()'s turnId filter would drop
|
|
402
|
+
// the synthetic turn_failed, hanging the call. Clear first; the consumer
|
|
403
|
+
// then repopulates it from this turn's turn.started.
|
|
404
|
+
this.lastStartedTurnId = null;
|
|
405
|
+
// Await the SSE subscription BEFORE starting the turn. CodeWhale can emit
|
|
406
|
+
// turn.started/item.delta/turn.completed within milliseconds for a short
|
|
407
|
+
// reply; if the /events connection weren't established first, run() could
|
|
408
|
+
// miss turn_completed and hang. ensureEventStream resolves only after the
|
|
409
|
+
// HTTP connection is open.
|
|
410
|
+
await this.ensureEventStream();
|
|
411
|
+
// Mark a turn-start as in flight BEFORE the POST. If the SSE stream dies in
|
|
412
|
+
// the window between here and the POST returning — with no turn.started and
|
|
413
|
+
// no activeTurnId yet — the stream finally{} uses this flag to emit a
|
|
414
|
+
// last-resort turn_failed so a waiting run() settles instead of hanging.
|
|
415
|
+
this.turnStartInFlight = true;
|
|
416
|
+
let res;
|
|
417
|
+
try {
|
|
418
|
+
res = await this.client.startTurn(this.threadId, {
|
|
419
|
+
prompt: text,
|
|
420
|
+
model: this.model ?? undefined,
|
|
421
|
+
mode: "agent",
|
|
422
|
+
...approval,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
const recovered = await this.tryRecoverActiveTurnConflict(err);
|
|
427
|
+
if (recovered) {
|
|
428
|
+
try {
|
|
429
|
+
// Recovery reopened the SSE stream and cleared the old turn state.
|
|
430
|
+
// Re-arm the start window before the retry POST so a new stream EOF
|
|
431
|
+
// still triggers the keyless turn_failed protection until this turn
|
|
432
|
+
// either emits turn.started or the POST returns an HTTP turn id.
|
|
433
|
+
this.turnStartInFlight = true;
|
|
434
|
+
res = await this.client.startTurn(this.threadId, {
|
|
435
|
+
prompt: text,
|
|
436
|
+
model: this.model ?? undefined,
|
|
437
|
+
mode: "agent",
|
|
438
|
+
...approval,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch (retryErr) {
|
|
442
|
+
this.turnStartInFlight = false;
|
|
443
|
+
throw retryErr;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
// The POST itself failed. run() settles via its own startTurn().catch(),
|
|
448
|
+
// so clear the in-flight flag here — otherwise a later unrelated stream
|
|
449
|
+
// EOF would trip the keyless synthetic-failure branch and emit a spurious
|
|
450
|
+
// turn_failed for a turn that never started.
|
|
451
|
+
this.turnStartInFlight = false;
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const turnId = res.turn?.id ?? `turn-${Date.now()}`;
|
|
456
|
+
this.activeTurnId = turnId;
|
|
457
|
+
return { turnId };
|
|
458
|
+
}
|
|
459
|
+
async tryRecoverActiveTurnConflict(error) {
|
|
460
|
+
if (!this.threadId)
|
|
461
|
+
return false;
|
|
462
|
+
if (this.recoveringConflict)
|
|
463
|
+
return false;
|
|
464
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
465
|
+
if (!/\/turns -> 409\b/.test(message) || !/active turn/i.test(message)) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
this.recoveringConflict = true;
|
|
469
|
+
try {
|
|
470
|
+
const conflictTurnId = await this.resolveConflictingTurnId();
|
|
471
|
+
if (!conflictTurnId)
|
|
472
|
+
return false;
|
|
473
|
+
this.logger.warn({ threadId: this.threadId, conflictTurnId }, "deepseek: canceling conflicting active turn before retrying");
|
|
474
|
+
await this.client.cancelTurn(this.threadId, conflictTurnId);
|
|
475
|
+
this.lastStartedTurnId = null;
|
|
476
|
+
this.lastTerminatedTurnId = conflictTurnId;
|
|
477
|
+
this.clearPendingPermissionsForTurn(conflictTurnId);
|
|
478
|
+
this.turnStartInFlight = false;
|
|
479
|
+
this.sseAbort?.abort();
|
|
480
|
+
this.eventStreamReady = null;
|
|
481
|
+
await this.ensureEventStream();
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
catch (recoverErr) {
|
|
485
|
+
this.logger.warn({ err: recoverErr }, "deepseek: failed to recover from active turn conflict");
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
this.recoveringConflict = false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
async resolveConflictingTurnId() {
|
|
493
|
+
try {
|
|
494
|
+
const detail = await this.client.getThreadDetail(this.threadId);
|
|
495
|
+
const detailTurn = detail.turn;
|
|
496
|
+
return (detail.active_turn_id ??
|
|
497
|
+
detail.activeTurnId ??
|
|
498
|
+
detail.active_turn?.id ??
|
|
499
|
+
detail.activeTurn?.id ??
|
|
500
|
+
detail.current_turn?.id ??
|
|
501
|
+
detail.currentTurn?.id ??
|
|
502
|
+
(isActiveServeTurnStatus(detailTurn?.status) ? (detailTurn?.id ?? null) : null) ??
|
|
503
|
+
detail.turns?.find((turn) => isActiveServeTurnStatus(turn?.status))?.id ??
|
|
504
|
+
this.getRecoverableLocalActiveTurnId());
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
const fallbackTurnId = this.getRecoverableLocalActiveTurnId();
|
|
508
|
+
if (fallbackTurnId) {
|
|
509
|
+
this.logger.warn({ err: error, threadId: this.threadId, conflictTurnId: fallbackTurnId }, "deepseek: getThreadDetail failed during conflict recovery; falling back to local activeTurnId");
|
|
510
|
+
return fallbackTurnId;
|
|
511
|
+
}
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
getRecoverableLocalActiveTurnId() {
|
|
516
|
+
if (!this.activeTurnId)
|
|
517
|
+
return null;
|
|
518
|
+
if (this.activeTurnId === this.lastTerminatedTurnId)
|
|
519
|
+
return null;
|
|
520
|
+
return this.activeTurnId;
|
|
521
|
+
}
|
|
522
|
+
// Establish the per-thread SSE subscription and resolve only after the
|
|
523
|
+
// connection is open. Idempotent while a healthy stream is live: concurrent
|
|
524
|
+
// and later callers await the same promise. RECOVERABLE: if the connection
|
|
525
|
+
// fails to open, or the stream later ends/errors, `eventStreamReady` is
|
|
526
|
+
// cleared so the next startTurn reopens `/events` instead of reusing a stale
|
|
527
|
+
// (rejected or dead) promise — which would otherwise hang the next turn.
|
|
528
|
+
ensureEventStream() {
|
|
529
|
+
if (this.eventStreamReady)
|
|
530
|
+
return this.eventStreamReady;
|
|
531
|
+
if (this.closed)
|
|
532
|
+
return Promise.reject(new Error("deepseek session is closed"));
|
|
533
|
+
const abort = new AbortController();
|
|
534
|
+
this.sseAbort = abort;
|
|
535
|
+
const acc = newAccumulator();
|
|
536
|
+
const ready = (async () => {
|
|
537
|
+
const { frames } = await this.client.openEventStream(this.threadId, abort.signal);
|
|
538
|
+
// Connection is open here; consume frames detached so the caller can
|
|
539
|
+
// proceed to POST /turns without blocking on the (endless) stream.
|
|
540
|
+
(async () => {
|
|
541
|
+
try {
|
|
542
|
+
for await (const frame of frames) {
|
|
543
|
+
const { events, pendingApproval, divergentFinalText, completedAssistantItem } = mapServeEvent(frame, acc);
|
|
544
|
+
// Track the SSE turn id (what run() pins) so a mid-turn stream death
|
|
545
|
+
// can synthesize turn_failed with the matching id.
|
|
546
|
+
if (frame.event === "turn.started" && frame.data?.turn_id) {
|
|
547
|
+
this.lastStartedTurnId = frame.data.turn_id;
|
|
548
|
+
}
|
|
549
|
+
if (pendingApproval) {
|
|
550
|
+
this.pendingPermissions.set(pendingApproval.id, pendingApproval);
|
|
551
|
+
const apprTurn = frame.data?.turn_id ?? this.activeTurnId;
|
|
552
|
+
if (apprTurn)
|
|
553
|
+
this.pendingPermissionTurn.set(pendingApproval.id, apprTurn);
|
|
554
|
+
}
|
|
555
|
+
if (divergentFinalText) {
|
|
556
|
+
this.logger.warn("deepseek: completed item text diverged from streamed deltas; live timeline keeps the streamed text");
|
|
557
|
+
}
|
|
558
|
+
// Record the authoritative per-item assistant text so run() can
|
|
559
|
+
// rebuild finalText from ALL segments of the turn. Key by the SSE
|
|
560
|
+
// frame's turn id — the SAME id run() pins from turn.started — so
|
|
561
|
+
// write/read keys can't drift from activeTurnId (the HTTP response).
|
|
562
|
+
if (completedAssistantItem) {
|
|
563
|
+
const frameTurnId = frame.data?.turn_id ?? this.activeTurnId;
|
|
564
|
+
if (frameTurnId) {
|
|
565
|
+
let byItem = this.assistantTextByTurn.get(frameTurnId);
|
|
566
|
+
if (!byItem) {
|
|
567
|
+
byItem = new Map();
|
|
568
|
+
this.assistantTextByTurn.set(frameTurnId, byItem);
|
|
569
|
+
}
|
|
570
|
+
byItem.set(completedAssistantItem.itemId, completedAssistantItem.text);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
for (const ev of events)
|
|
574
|
+
this.emit(ev);
|
|
575
|
+
// Terminal-event cleanup. The accumulator and per-turn maps only
|
|
576
|
+
// matter WITHIN a turn (delta coalescing + finalText rebuild), so
|
|
577
|
+
// reset them when the turn ends. This bounds memory on a long-lived
|
|
578
|
+
// session and prevents a non-unique server item id from a later
|
|
579
|
+
// turn colliding with a stale entry from an earlier one.
|
|
580
|
+
if (frame.event === "turn.completed" ||
|
|
581
|
+
frame.event === "turn.failed" ||
|
|
582
|
+
frame.event === "turn.canceled") {
|
|
583
|
+
acc.items.clear();
|
|
584
|
+
const tid = frame.data?.turn_id;
|
|
585
|
+
// Record that this turn reached a terminal event, so the stream's
|
|
586
|
+
// finally{} doesn't synthesize a duplicate turn_failed for it.
|
|
587
|
+
this.lastTerminatedTurnId = tid ?? this.activeTurnId;
|
|
588
|
+
this.activeTurnId = null;
|
|
589
|
+
this.turnStartInFlight = false;
|
|
590
|
+
if (tid) {
|
|
591
|
+
// emit() ran run()'s finalize synchronously above, so it has
|
|
592
|
+
// already read+deleted this entry on the normal path; deleting
|
|
593
|
+
// again reclaims it if no run() was waiting (e.g. a turn that
|
|
594
|
+
// failed before turn_started was observed).
|
|
595
|
+
this.assistantTextByTurn.delete(tid);
|
|
596
|
+
// Drop any approvals still pending for this turn — the server
|
|
597
|
+
// won't act on them once the turn is terminal, and the UI
|
|
598
|
+
// shouldn't keep showing a stale prompt.
|
|
599
|
+
this.clearPendingPermissionsForTurn(tid);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch (err) {
|
|
605
|
+
if (!abort.signal.aborted) {
|
|
606
|
+
this.logger.warn({ err }, "deepseek: event stream ended unexpectedly");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
// If the stream died (EOF/error, not a deliberate abort) while a turn
|
|
611
|
+
// was in flight, synthesize turn_failed so a waiting run() settles and
|
|
612
|
+
// the UI leaves the running state instead of hanging forever. Use the
|
|
613
|
+
// SSE turn id run() pinned (lastStartedTurnId) so its turnId filter
|
|
614
|
+
// accepts the event; fall back to activeTurnId when turn.started was
|
|
615
|
+
// never observed (then run()'s ourTurnId is still null and accepts any
|
|
616
|
+
// turn_failed).
|
|
617
|
+
const inFlightTurnId = this.lastStartedTurnId ?? this.activeTurnId;
|
|
618
|
+
if (!abort.signal.aborted &&
|
|
619
|
+
inFlightTurnId &&
|
|
620
|
+
inFlightTurnId !== this.lastTerminatedTurnId) {
|
|
621
|
+
this.emit({
|
|
622
|
+
type: "turn_failed",
|
|
623
|
+
provider: DEEPSEEK_PROVIDER,
|
|
624
|
+
error: "deepseek event stream ended before the turn completed",
|
|
625
|
+
turnId: inFlightTurnId,
|
|
626
|
+
});
|
|
627
|
+
this.activeTurnId = null;
|
|
628
|
+
this.lastTerminatedTurnId = inFlightTurnId;
|
|
629
|
+
this.turnStartInFlight = false;
|
|
630
|
+
acc.items.clear();
|
|
631
|
+
this.assistantTextByTurn.delete(inFlightTurnId);
|
|
632
|
+
this.clearPendingPermissionsForTurn(inFlightTurnId);
|
|
633
|
+
}
|
|
634
|
+
else if (!abort.signal.aborted && this.turnStartInFlight) {
|
|
635
|
+
// Edge race: the stream died after connect but BEFORE turn.started
|
|
636
|
+
// and before the POST set activeTurnId, so we have no turn id to key
|
|
637
|
+
// on. Emit a turnId-less turn_failed — run() accepts it while its
|
|
638
|
+
// ourTurnId is still null — so a turn whose start is in flight still
|
|
639
|
+
// settles instead of hanging once the POST later resolves.
|
|
640
|
+
this.turnStartInFlight = false;
|
|
641
|
+
this.emit({
|
|
642
|
+
type: "turn_failed",
|
|
643
|
+
provider: DEEPSEEK_PROVIDER,
|
|
644
|
+
error: "deepseek event stream ended before the turn started",
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
// The reader is exhausted — allow a future turn to reopen the stream
|
|
648
|
+
// (unless we resumed/closed). Only clear if this is still the active
|
|
649
|
+
// stream (guard against a newer ensureEventStream having replaced it).
|
|
650
|
+
if (this.sseAbort === abort && !this.closed) {
|
|
651
|
+
this.eventStreamReady = null;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
})();
|
|
655
|
+
})();
|
|
656
|
+
// On connect failure, clear so the next call retries rather than reusing a
|
|
657
|
+
// permanently-rejected promise.
|
|
658
|
+
this.eventStreamReady = ready.catch((err) => {
|
|
659
|
+
if (this.sseAbort === abort)
|
|
660
|
+
this.eventStreamReady = null;
|
|
661
|
+
throw err;
|
|
662
|
+
});
|
|
663
|
+
return this.eventStreamReady;
|
|
664
|
+
}
|
|
665
|
+
subscribe(callback) {
|
|
666
|
+
this.subscribers.add(callback);
|
|
667
|
+
return () => this.subscribers.delete(callback);
|
|
668
|
+
}
|
|
669
|
+
async *streamHistory() {
|
|
670
|
+
// Replay persisted turns first (oldest → newest), then live events from
|
|
671
|
+
// this process. Drained so a second call doesn't double up.
|
|
672
|
+
if (this.persistedHistory.length > 0) {
|
|
673
|
+
const items = this.persistedHistory;
|
|
674
|
+
this.persistedHistory = [];
|
|
675
|
+
for (const item of items) {
|
|
676
|
+
yield { type: "timeline", provider: DEEPSEEK_PROVIDER, item };
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
for (const event of this.history)
|
|
680
|
+
yield event;
|
|
681
|
+
}
|
|
682
|
+
// Fetch the persisted thread items and rebuild them as timeline items so the
|
|
683
|
+
// UI shows prior turns after a resume. Best-effort: a failure here must not
|
|
684
|
+
// block the session from connecting.
|
|
685
|
+
async loadPersistedHistory() {
|
|
686
|
+
if (!this.threadId)
|
|
687
|
+
return;
|
|
688
|
+
try {
|
|
689
|
+
const detail = await this.client.getThreadDetail(this.threadId);
|
|
690
|
+
this.persistedHistory = mapHistoryItems(detail.items ?? []);
|
|
691
|
+
}
|
|
692
|
+
catch (err) {
|
|
693
|
+
this.logger.warn({ err }, "deepseek: failed to load persisted history");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async getRuntimeInfo() {
|
|
697
|
+
return { provider: DEEPSEEK_PROVIDER, sessionId: this.threadId, model: this.model };
|
|
698
|
+
}
|
|
699
|
+
async getAvailableModes() {
|
|
700
|
+
return DEEPSEEK_MODES;
|
|
701
|
+
}
|
|
702
|
+
async getCurrentMode() {
|
|
703
|
+
return this.currentMode;
|
|
704
|
+
}
|
|
705
|
+
async setMode(modeId) {
|
|
706
|
+
this.currentMode = modeId;
|
|
707
|
+
}
|
|
708
|
+
getPendingPermissions() {
|
|
709
|
+
return [...this.pendingPermissions.values()];
|
|
710
|
+
}
|
|
711
|
+
async respondToPermission(requestId, response) {
|
|
712
|
+
const pending = this.pendingPermissions.get(requestId);
|
|
713
|
+
if (!pending)
|
|
714
|
+
return;
|
|
715
|
+
this.pendingPermissions.delete(requestId);
|
|
716
|
+
this.pendingPermissionTurn.delete(requestId);
|
|
717
|
+
await this.client.decideApproval(requestId, response.behavior === "allow" ? "allow" : "deny");
|
|
718
|
+
this.emit({
|
|
719
|
+
type: "permission_resolved",
|
|
720
|
+
provider: DEEPSEEK_PROVIDER,
|
|
721
|
+
requestId,
|
|
722
|
+
resolution: response,
|
|
723
|
+
turnId: this.activeTurnId ?? undefined,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
// Drop approvals still pending for a turn that just ended (completed/failed/
|
|
727
|
+
// canceled). The server won't act on them anymore, so emit a deny resolution
|
|
728
|
+
// for each so the UI stops showing the stale prompt.
|
|
729
|
+
clearPendingPermissionsForTurn(turnId) {
|
|
730
|
+
for (const [approvalId, ownerTurn] of [...this.pendingPermissionTurn]) {
|
|
731
|
+
if (ownerTurn !== turnId)
|
|
732
|
+
continue;
|
|
733
|
+
this.pendingPermissionTurn.delete(approvalId);
|
|
734
|
+
if (this.pendingPermissions.delete(approvalId)) {
|
|
735
|
+
this.emit({
|
|
736
|
+
type: "permission_resolved",
|
|
737
|
+
provider: DEEPSEEK_PROVIDER,
|
|
738
|
+
requestId: approvalId,
|
|
739
|
+
resolution: { behavior: "deny", message: "turn ended before approval" },
|
|
740
|
+
turnId,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
describePersistence() {
|
|
746
|
+
if (!this.threadId)
|
|
747
|
+
return null;
|
|
748
|
+
return {
|
|
749
|
+
provider: DEEPSEEK_PROVIDER,
|
|
750
|
+
sessionId: this.threadId,
|
|
751
|
+
nativeHandle: this.threadId,
|
|
752
|
+
metadata: {
|
|
753
|
+
cwd: this.config.cwd,
|
|
754
|
+
modeId: this.currentMode,
|
|
755
|
+
...(this.model ? { model: this.model } : {}),
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
async interrupt() {
|
|
760
|
+
// The server-side cancel addresses the turn by its HTTP id...
|
|
761
|
+
if (this.threadId && this.activeTurnId) {
|
|
762
|
+
await this.client.cancelTurn(this.threadId, this.activeTurnId, { suppressErrors: true });
|
|
763
|
+
}
|
|
764
|
+
// ...but the locally-emitted terminal event must use the id run() pinned
|
|
765
|
+
// from turn.started (lastStartedTurnId), since run()'s turnId filter drops
|
|
766
|
+
// events whose turnId != ourTurnId. The SSE turn id can differ from the
|
|
767
|
+
// HTTP startTurn() id; using activeTurnId here would let an interrupt fail
|
|
768
|
+
// to settle a waiting run(), leaving the UI stuck if the server's terminal
|
|
769
|
+
// SSE frame is then lost/delayed. Fall back to activeTurnId only when
|
|
770
|
+
// turn.started hasn't been seen (then ourTurnId is null and accepts any id).
|
|
771
|
+
const runTurnId = this.lastStartedTurnId ?? this.activeTurnId;
|
|
772
|
+
// Mark the turn terminated so a subsequent mid-turn stream death does NOT
|
|
773
|
+
// synthesize a duplicate turn_failed for it.
|
|
774
|
+
this.lastTerminatedTurnId = runTurnId;
|
|
775
|
+
this.activeTurnId = null;
|
|
776
|
+
this.turnStartInFlight = false;
|
|
777
|
+
this.emit({
|
|
778
|
+
type: "turn_canceled",
|
|
779
|
+
provider: DEEPSEEK_PROVIDER,
|
|
780
|
+
reason: "interrupted",
|
|
781
|
+
turnId: runTurnId ?? undefined,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async close() {
|
|
785
|
+
this.closed = true;
|
|
786
|
+
this.sseAbort?.abort();
|
|
787
|
+
this.eventStreamReady = null;
|
|
788
|
+
this.assistantTextByTurn.clear();
|
|
789
|
+
this.pendingPermissions.clear();
|
|
790
|
+
this.pendingPermissionTurn.clear();
|
|
791
|
+
this.serve.close();
|
|
792
|
+
}
|
|
793
|
+
getRecentStderrTail() {
|
|
794
|
+
return this.serve.stderrTail() || undefined;
|
|
795
|
+
}
|
|
796
|
+
async setModel(modelId) {
|
|
797
|
+
this.model = modelId;
|
|
798
|
+
}
|
|
799
|
+
emit(event) {
|
|
800
|
+
this.history.push(event);
|
|
801
|
+
for (const subscriber of this.subscribers) {
|
|
802
|
+
try {
|
|
803
|
+
subscriber(event);
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
this.logger.warn({ error }, "deepseek subscriber threw");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
//# sourceMappingURL=deepseek-agent.js.map
|