create-walle 0.10.2 → 0.10.3
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/README.md +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
- package/template/coding-task-manager/db.js +156 -32
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +84 -1
- package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +77 -51
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +36 -10
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- package/template/coding-task-manager/lib/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
- package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
- package/template/coding-task-manager/lib/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- package/template/coding-task-manager/lib/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- package/template/coding-task-manager/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
- package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
- package/template/coding-task-manager/server.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/shared/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +5 -0
- package/template/shared/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- package/template/shared/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- package/template/wall-e/http/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +174 -1
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- package/template/wall-e/lib/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-2 (docs/deepseek-harness-acp-agent-2026-10-28.html §5 D-1/D-2/D-17, §9 S-11; §6.3).
|
|
4
|
+
//
|
|
5
|
+
// The create strategy for a `dsh` (DeepSeek Harness) session: no PTY, no terminal — one ACP child
|
|
6
|
+
// driven by lib/acp/dsh-session-runtime.js (see lib/session-create/structured-session.js for the
|
|
7
|
+
// dispatcher that reaches this after the Wall-E strategy declines the request).
|
|
8
|
+
//
|
|
9
|
+
// WP-3 adds the CTM-owned transcript: one writer per session (lib/acp/dsh-transcript.js) whose path is
|
|
10
|
+
// the session's `jsonlPath`, registered with the session stream through `linkSession` (D-8).
|
|
11
|
+
//
|
|
12
|
+
// WP-4 adds prompts/approvals/status (lib/acp/dsh-prompt-lane.js). WP-5 adds the lifecycle: a `dsh`
|
|
13
|
+
// startup task is written at create (D-B), the restore classifies 'defer' so boot spawns nothing and
|
|
14
|
+
// builds a childless placeholder instead, and `restoreDshSession` (below) is the ONE activation path
|
|
15
|
+
// that resumes the same ACP session on attach/input (D-9/S-40). A child that dies on its own leaves
|
|
16
|
+
// the session in the map restartable (D-5); kill retires the startup task and unlinks nothing (D-10).
|
|
17
|
+
//
|
|
18
|
+
// WP-6 (docs/deepseek-harness-acp-agent-2026-10-28.html §6.6): routed model access. Before the child
|
|
19
|
+
// starts, the create payload's `{routeAlias, model}` (or the OQ-4 default route) is turned into a
|
|
20
|
+
// 0600 patch plus a session token by lib/acp/dsh-route-patch.js; the token is released exactly once,
|
|
21
|
+
// on the child's exit or on a failed start. A create with no usable route is REFUSED with
|
|
22
|
+
// `dsh_route_unavailable` — never a silent fall back to dsh's own key (D-12).
|
|
23
|
+
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
const fsp = require('node:fs/promises');
|
|
26
|
+
const { createDshTranscriptWriter, dshTranscriptDir } = require('../acp/dsh-transcript');
|
|
27
|
+
const { prepareDshRoute, releaseDshRoute } = require('../acp/dsh-route-patch');
|
|
28
|
+
const { toAcpMcpServers, withWalleCaller } = require('../acp/dsh-mcp-servers');
|
|
29
|
+
const { parseJsonMcpServers } = require('../mcp-annotations');
|
|
30
|
+
const { getSessionAgentCapabilities } = require('../agent-capabilities');
|
|
31
|
+
const { strFlag } = require('../env-flags');
|
|
32
|
+
const {
|
|
33
|
+
resolveMcpProfile, normalizeMcpProfile, selectClaudeMcpServers, CORE_MCP_SERVERS, MCP_PROFILE_FULL, MCP_PROFILE_NONE,
|
|
34
|
+
} = require('../lifecycle-wake-experiment');
|
|
35
|
+
|
|
36
|
+
let _getDeps = null;
|
|
37
|
+
|
|
38
|
+
function configure(getDeps) {
|
|
39
|
+
if (typeof getDeps !== 'function') throw new TypeError('session-create dsh requires a dependency getter');
|
|
40
|
+
_getDeps = getDeps;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================================
|
|
44
|
+
// WP-10 step A (C-6/D-16, OQ-12) — the session's MCP servers reach the ACP child
|
|
45
|
+
// ============================================================================================
|
|
46
|
+
//
|
|
47
|
+
// A dsh session starts with the SAME MCP profile a Claude session in that CTM session would get:
|
|
48
|
+
// the per-session setting (`mcp_profile` on the create frame, persisted onto the durable
|
|
49
|
+
// `startup_tasks` row), else the host default (`CTM_MCP_PROFILE`), else 'core' — `resolveMcpProfile`'s
|
|
50
|
+
// own order, reused rather than re-derived. The Claude path turns that profile into
|
|
51
|
+
// `--strict-mcp-config --mcp-config {mcpServers}` with the map read out of the USER's own
|
|
52
|
+
// ~/.claude.json (lib/lifecycle-wake-experiment.js `resolveCoreClaudeMcpServers`); dsh has no config
|
|
53
|
+
// file of its own, so the same map is materialized here and converted to ACP entries by
|
|
54
|
+
// lib/acp/dsh-mcp-servers.js.
|
|
55
|
+
//
|
|
56
|
+
// The Wall-E server authenticates the CTM session it serves with CTM_SESSION_ID + WALL_E_API_TOKEN
|
|
57
|
+
// from the SAME env seam every other CTM child reads (lib/env-flags.js; never a raw process.env read).
|
|
58
|
+
// Both values ride the ACP JSON-RPC request on the child's stdin ONLY — never the child's process
|
|
59
|
+
// env, argv, a file or a log line (never logged here, and toAcpMcpServers logs reasons only).
|
|
60
|
+
|
|
61
|
+
// `<homeDir>/.claude.json` parsed, or null. A missing, unreadable or invalid file is "the user
|
|
62
|
+
// declared no MCP servers", never an error worth surfacing. The parsed text is deliberately not held
|
|
63
|
+
// in a variable named `content`/`blob`/`transcript` (main-thread lint's JSON.parse hot-path pattern).
|
|
64
|
+
async function readClaudeJson(homeDir) {
|
|
65
|
+
if (!homeDir) return null;
|
|
66
|
+
let raw = '';
|
|
67
|
+
try {
|
|
68
|
+
raw = await fsp.readFile(path.join(homeDir, '.claude.json'), 'utf8');
|
|
69
|
+
} catch { /* expected: no ~/.claude.json (a host where Claude Code never ran) -- no MCP servers to forward */
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
75
|
+
} catch { /* expected: a torn or hand-broken ~/.claude.json -- Claude itself would refuse it too; forward none */
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The normalized MCP server map for `profile`, read the way the Claude path reads it: the USER's own
|
|
82
|
+
* `<homeDir>/.claude.json`, user scope (`mcpServers`) then project scope (`projects[cwd].mcpServers`,
|
|
83
|
+
* which wins). `<cwd>/.mcp.json` is NEVER consulted — it is repo-controlled, so a checked-out branch
|
|
84
|
+
* could point `command` at anything (the same two scopes, and the same reasoning, as the security
|
|
85
|
+
* note on `resolveCoreClaudeMcpServers`).
|
|
86
|
+
*
|
|
87
|
+
* 'none' -> {} (dsh's own key, no MCP server at all)
|
|
88
|
+
* 'core' -> CORE_MCP_SERVERS only (the WP-1 cheap default: wall-e, codebase-memory-mcp)
|
|
89
|
+
* 'full' -> everything the user's config declares (dsh has no config file to inherit, so 'full'
|
|
90
|
+
* must be materialized here instead of being left to the CLI as it is for Claude)
|
|
91
|
+
*
|
|
92
|
+
* Async fs only: this runs on the interactive create/restore path (main-thread lint).
|
|
93
|
+
*/
|
|
94
|
+
async function resolveProfileMcpServers({ profile, homeDir = '', cwd = '' } = {}) {
|
|
95
|
+
const normalized = normalizeMcpProfile(profile) || 'core';
|
|
96
|
+
if (normalized === MCP_PROFILE_NONE) return {};
|
|
97
|
+
const claudeJson = await readClaudeJson(homeDir);
|
|
98
|
+
if (!claudeJson) return {};
|
|
99
|
+
const selected = selectClaudeMcpServers(claudeJson, normalized === MCP_PROFILE_FULL ? null : CORE_MCP_SERVERS, { cwd });
|
|
100
|
+
return parseJsonMcpServers({ mcpServers: selected });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The ACP `mcpServers` list (and the profile it came from) for ONE dsh start. `profileSource.mcpProfile`
|
|
105
|
+
* is the raw session setting — `mcp_profile ?? mcpProfile`, exactly what server.js's
|
|
106
|
+
* `_sessionMcpProfileSetting` reads on the Claude path.
|
|
107
|
+
*
|
|
108
|
+
* Never throws: a resolver failure leaves the child with no forwarded servers (its own key) rather
|
|
109
|
+
* than costing the session its start.
|
|
110
|
+
*
|
|
111
|
+
* @returns {Promise<{profile: string, mcpServers: Array<object>}>}
|
|
112
|
+
*/
|
|
113
|
+
async function buildDshMcpServers(deps, { profileSource = {}, env = {}, cwd = '', ctmSessionId = '' } = {}) {
|
|
114
|
+
const resolve = typeof deps.resolveMcpServers === 'function' ? deps.resolveMcpServers : resolveProfileMcpServers;
|
|
115
|
+
const { profile } = resolveMcpProfile({ mcpProfile: profileSource.mcpProfile, env });
|
|
116
|
+
let servers = {};
|
|
117
|
+
try {
|
|
118
|
+
servers = await resolve({ profile, homeDir: String(env.HOME || ''), cwd, ctmSessionId });
|
|
119
|
+
} catch (error) {
|
|
120
|
+
/* REPORT: this start forwards no MCP servers; the session itself must still come up. */
|
|
121
|
+
deps.logError(`[dsh-session] MCP server resolution failed (${(error && error.message) || 'error'}); starting with no forwarded MCP servers`);
|
|
122
|
+
servers = {};
|
|
123
|
+
}
|
|
124
|
+
const entries = toAcpMcpServers(servers, {
|
|
125
|
+
// A dropped declaration is a fact about the user's config, reported once per start, by NAME and
|
|
126
|
+
// reason — toAcpMcpServers never puts an env/header VALUE in a line.
|
|
127
|
+
log: (message) => deps.logError(`[dsh-session] ${message}`),
|
|
128
|
+
});
|
|
129
|
+
return { profile, mcpServers: withWalleCaller(entries, { ctmSessionId, walleToken: strFlag('WALL_E_API_TOKEN', '') }) };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Persist an EXPLICIT per-session profile onto the startup_tasks row `addStartupTaskWithRetry` just
|
|
133
|
+
// wrote, so a deferred restore resolves the SAME servers (the Claude path's own contract:
|
|
134
|
+
// lib/session-create/provider-process.js writes this column post-create). '' ("no opinion", or a
|
|
135
|
+
// value normalizeMcpProfile collapsed) is deliberately NOT written — the column's DEFAULT '' already
|
|
136
|
+
// means "no opinion" and old rows must keep reading that way. Best-effort: never fails a create.
|
|
137
|
+
function persistDshMcpProfile(deps, sessionId, profile) {
|
|
138
|
+
if (!profile || typeof deps.dbModule?.updateStartupTaskMcpProfile !== 'function') return undefined;
|
|
139
|
+
// The owner write may be sync or async (owner client); the chain catches both.
|
|
140
|
+
return Promise.resolve().then(() => deps.dbModule.updateStartupTaskMcpProfile(sessionId, profile)).catch((error) => {
|
|
141
|
+
/* REPORT: only the next restore's profile is affected; it falls back to the default. */
|
|
142
|
+
deps.logError(`[dsh-session] mcp_profile persist failed (${(error && error.message) || 'error'})`);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// The model the bridge reports as current (`configOptions` category/id `model`) — the ACP session's
|
|
147
|
+
// own choice, surfaced on the session row and the index. Anything unexpected reads as null.
|
|
148
|
+
//
|
|
149
|
+
// WP-8 (§9b M-12): the ACP `model` option's `currentValue` is a JSON PAIR STRING — `["<group>","<model>"]`
|
|
150
|
+
// (see tests/fixtures/acp/dsh-0.2.1-rc.2-turn.jsonl) — so the plain model id is the pair's SECOND
|
|
151
|
+
// element, never the raw JSON text (the rev-5 cosmetic bug: an own-key route stored the JSON pair as
|
|
152
|
+
// `model_id`). A plain (non-JSON) value is returned unchanged.
|
|
153
|
+
function plainModelIdFromConfigValue(value) {
|
|
154
|
+
if (typeof value !== 'string' || !value) return null;
|
|
155
|
+
if (value[0] === '[') {
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(value);
|
|
158
|
+
if (Array.isArray(parsed) && parsed.length >= 2 && typeof parsed[1] === 'string' && parsed[1]) return parsed[1];
|
|
159
|
+
} catch { /* expected: a non-JSON currentValue is just the plain model id */ }
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The ACP `model` config option, or null. ONE finder: the owner bind, the runtime's `setModel` and the
|
|
165
|
+
// lead-model controller all ask here instead of re-deriving the match.
|
|
166
|
+
function findModelOption(configOptions) {
|
|
167
|
+
if (!Array.isArray(configOptions)) return null;
|
|
168
|
+
return configOptions.find((option) => option
|
|
169
|
+
&& (option.category === 'model' || option.id === 'model')) || null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function modelIdFromConfigOptions(configOptions) {
|
|
173
|
+
const entry = findModelOption(configOptions);
|
|
174
|
+
return entry ? plainModelIdFromConfigValue(entry.currentValue) : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Every selectable entry of an ACP config option, flattened across the bridge's NESTED group shape
|
|
178
|
+
// (`options: [{group, name, options: [{value, name}]}]`, see tests/fixtures/acp/dsh-0.2.1-rc.2-turn.jsonl)
|
|
179
|
+
// — the ONE place that nesting is read. `value` is the OPAQUE value the wire expects (for the model
|
|
180
|
+
// option it is the JSON pair string); `name` is the display label the child also accepts by identity.
|
|
181
|
+
function configOptionEntries(option) {
|
|
182
|
+
const out = [];
|
|
183
|
+
const push = (item) => {
|
|
184
|
+
if (!item || typeof item !== 'object') return;
|
|
185
|
+
if (typeof item.value === 'string' && item.value) {
|
|
186
|
+
out.push({ value: item.value, name: typeof item.name === 'string' ? item.name : '' });
|
|
187
|
+
}
|
|
188
|
+
if (Array.isArray(item.options)) for (const child of item.options) push(child);
|
|
189
|
+
};
|
|
190
|
+
if (option && Array.isArray(option.options)) for (const item of option.options) push(item);
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The OPAQUE ACP option value for a plain model id: the entry whose JSON pair's second element (or
|
|
196
|
+
* whose display name) IS the pick. Null when the bridge does not offer it — the caller refuses
|
|
197
|
+
* `dsh_option_unavailable` rather than sending a hand-built string (D-12).
|
|
198
|
+
*/
|
|
199
|
+
function configValueForModelId(option, modelId) {
|
|
200
|
+
const wanted = String(modelId == null ? '' : modelId).trim();
|
|
201
|
+
if (!wanted) return null;
|
|
202
|
+
for (const entry of configOptionEntries(option)) {
|
|
203
|
+
if (plainModelIdFromConfigValue(entry.value) === wanted || (entry.name && entry.name === wanted)) return entry.value;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The OPAQUE ACP option value for an effort LEVEL (`'' | 'off' | 'low' | 'high' | 'max'`). A plain
|
|
210
|
+
* (non-JSON) value is matched exactly; the display name is only consulted for a NON-empty level, so an
|
|
211
|
+
* unnamed entry can never be matched by the empty default level.
|
|
212
|
+
*/
|
|
213
|
+
function configValueForOptionLevel(option, level) {
|
|
214
|
+
const wanted = String(level == null ? '' : level);
|
|
215
|
+
for (const entry of configOptionEntries(option)) {
|
|
216
|
+
if (entry.value === wanted) return entry.value;
|
|
217
|
+
if (wanted && (plainModelIdFromConfigValue(entry.value) === wanted || entry.name === wanted)) return entry.value;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ============================================================================================
|
|
223
|
+
// WP-8 (§9b M-7/M-16, D-12/D-13) — the dsh session joins the durable session-model owner store
|
|
224
|
+
// ============================================================================================
|
|
225
|
+
//
|
|
226
|
+
// dsh does NOT defer (M-7): there is no CLI hook and no native transcript to scan, and the child is
|
|
227
|
+
// an ACP process, never a PTY — so the owner binds from the bridge's OWN `configOptions` at session
|
|
228
|
+
// start (session/new and session/resume both return it) and again on every `config_option_update`.
|
|
229
|
+
// Everything here is a no-op when the runtime is not wired, so a unit harness that passes no
|
|
230
|
+
// `modelStateRuntime` still exercises the create/restore lifecycle unchanged.
|
|
231
|
+
|
|
232
|
+
// The model-owner SOURCE for this harness (lib/coding-session-model-state.js SOURCE_GROUPS.dsh).
|
|
233
|
+
const DSH_CONFIG_OPTION_SOURCE = 'dsh:config-option';
|
|
234
|
+
// WP-8 step 3 (D-13): the SOURCE of the ONE `completed-turn` observation a settled turn writes.
|
|
235
|
+
const DSH_TURN_SOURCE = 'dsh:turn';
|
|
236
|
+
|
|
237
|
+
// The ACP effort config option, or null. dsh's own effort option id is the ACP `reasoning_effort`; ONE
|
|
238
|
+
// finder so the picker read, the runtime's `setEffort` and the lead-model controller agree on the id
|
|
239
|
+
// they write (WP-8 §6.7).
|
|
240
|
+
function findEffortOption(configOptions) {
|
|
241
|
+
if (!Array.isArray(configOptions)) return null;
|
|
242
|
+
return configOptions.find((entry) => entry && typeof entry === 'object'
|
|
243
|
+
&& (entry.id === 'reasoning_effort' || entry.id === 'reasoning-effort' || entry.category === 'reasoning_effort')) || null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// The effort the picker must show, when the bridge declares one. Anything else (or a non-string value)
|
|
247
|
+
// reads as "not reported".
|
|
248
|
+
function effortFromConfigOptions(configOptions) {
|
|
249
|
+
const option = findEffortOption(configOptions);
|
|
250
|
+
const value = option && typeof option.currentValue === 'string' ? option.currentValue.trim() : '';
|
|
251
|
+
return value && value.length <= 64 ? value : null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
* The durable per-lifetime process generation. This is the SAME allocator the PTY launch plan and the
|
|
256
|
+
* API-attach path use (lib/coding-session-generation-runtime.js), never a process-local counter: the
|
|
257
|
+
* model-owner store fences rebinds numerically (`owner.processGeneration < current` is `stale`, and
|
|
258
|
+
* the adoption fence only admits the LATEST reservation), so a counter that restarts at 1 after a CTM
|
|
259
|
+
* restart would refuse every re-bind of a surviving ACP session. A host without the runtime, or a
|
|
260
|
+
* failed reservation, yields NO generation — the session then stays unbound (`dsh_presession`) rather
|
|
261
|
+
* than binding a made-up number, and the create/restore is never failed by this bookkeeping write.
|
|
262
|
+
*/
|
|
263
|
+
async function _allocateDshGeneration(ctmSessionId) {
|
|
264
|
+
const deps = _getDeps();
|
|
265
|
+
const runtime = deps.codingSessionGenerationRuntime;
|
|
266
|
+
if (!runtime || typeof runtime.allocateGeneration !== 'function') return null;
|
|
267
|
+
try {
|
|
268
|
+
const result = await runtime.allocateGeneration({ sessionId: ctmSessionId });
|
|
269
|
+
return Number.isSafeInteger(result?.generation) && result.generation >= 0 ? result.generation : null;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
deps.logError(`[dsh-session] generation allocation failed for ${String(ctmSessionId).slice(0, 8)}: ${(error && error.message) || error}`);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/*
|
|
277
|
+
* Bind (or re-bind) the durable owner row for a dsh session from its ACP config options.
|
|
278
|
+
*
|
|
279
|
+
* The owner tuple is the model runtime's OWN `snapshot()` (one declaration —
|
|
280
|
+
* lib/coding-session-model-owner.js) so a hand-built literal can never disagree with the collector.
|
|
281
|
+
* A session with no generation has no owner yet and is skipped explicitly. A null model still binds
|
|
282
|
+
* the OWNER (the row exists; it simply carries no fact) — that is what distinguishes "bound, nothing
|
|
283
|
+
* observed yet" from `dsh_presession`.
|
|
284
|
+
*/
|
|
285
|
+
async function bindDshModelOwner(session, configOptions) {
|
|
286
|
+
// Never fatal, never an unhandled rejection: the config-option observer calls this fire-and-forget,
|
|
287
|
+
// and a failed owner write must leave the session running (unbound = `dsh_presession`).
|
|
288
|
+
try {
|
|
289
|
+
return await _bindDshModelOwner(session, configOptions);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
_getDeps().logError(`[dsh-session] model owner bind failed for ${String(session && session.id).slice(0, 8)}: ${(error && error.message) || error}`);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function _bindDshModelOwner(session, configOptions) {
|
|
297
|
+
const runtime = _getDeps().modelStateRuntime;
|
|
298
|
+
if (!session || !runtime || typeof runtime.applyResult !== 'function') return false;
|
|
299
|
+
const owner = runtime.snapshot(session, 'dsh');
|
|
300
|
+
if (!owner) return false;
|
|
301
|
+
const observations = [];
|
|
302
|
+
const modelId = modelIdFromConfigOptions(configOptions);
|
|
303
|
+
if (modelId) {
|
|
304
|
+
// ONE selection + ONE resolved observation, same value and timestamp: the bridge reports what it
|
|
305
|
+
// is actually running (there is no alias stage to wait out), so both slots carry concrete
|
|
306
|
+
// evidence. `Date.now()` (not the injected create clock) is the observation time the reducer
|
|
307
|
+
// orders on, and a `config_option_update` is always NEWER than the fact it replaces.
|
|
308
|
+
const observedAtMs = Date.now();
|
|
309
|
+
const effort = effortFromConfigOptions(configOptions);
|
|
310
|
+
const base = {
|
|
311
|
+
ctmSessionId: owner.ctmSessionId,
|
|
312
|
+
agentType: owner.agentType,
|
|
313
|
+
providerSessionId: owner.providerSessionId,
|
|
314
|
+
processGeneration: owner.processGeneration,
|
|
315
|
+
};
|
|
316
|
+
for (const kind of ['selection', 'resolved']) {
|
|
317
|
+
observations.push({
|
|
318
|
+
...base, kind, value: modelId, providerId: 'dsh', selectionKind: 'concrete',
|
|
319
|
+
effort, source: DSH_CONFIG_OPTION_SOURCE, sourceRevision: null, outcome: null, observedAtMs,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const outcome = await runtime.applyResult(session, { id: String(session.id || ''), owner, observations });
|
|
324
|
+
/*
|
|
325
|
+
* A bare `applyResult` (origin 'legacy') deliberately publishes NO health claim — it is not an
|
|
326
|
+
* acknowledged collector read. Without the read below the public envelope would answer
|
|
327
|
+
* `owner_unavailable` for a session whose row IS bound, so the durable restore read publishes this
|
|
328
|
+
* owner's own state: `restored` with facts, `no_model_evidence` without. This is exactly what the
|
|
329
|
+
* periodic model-sync hydrate does for every other native session, one tick later.
|
|
330
|
+
*/
|
|
331
|
+
await runtime.hydrate(session);
|
|
332
|
+
return outcome;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function isDshRequest(state, deps) {
|
|
336
|
+
const agentType = state.agentType || state.msg?.agentType;
|
|
337
|
+
if (deps.normalizeAgentType(agentType) === 'dsh') return true;
|
|
338
|
+
const cmd = (state.launchParams && state.launchParams.cmd) || state.msg?.cmd;
|
|
339
|
+
return deps.detectAgentType(cmd) === 'dsh';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function createDshSession(state) {
|
|
343
|
+
const deps = _getDeps();
|
|
344
|
+
if (!isDshRequest(state, deps)) return false;
|
|
345
|
+
|
|
346
|
+
const { id, ws } = state;
|
|
347
|
+
const launch = state.launchParams || {};
|
|
348
|
+
const msg = state.msg || {};
|
|
349
|
+
const cwd = state.cwd || launch.cwd || '';
|
|
350
|
+
const title = launch.title || 'DeepSeek Harness';
|
|
351
|
+
// WP-14 (docs/deepseek-harness-acp-agent-2026-10-28.html §9b W-2/W-3, D-21). A DELEGATED WORKER
|
|
352
|
+
// reaches this same create path (delegate-worker.js's deps.createSession -> handleCreatePrepared ->
|
|
353
|
+
// handleCreate -> structured-session.js, routed here by `agentType:'dsh'`). Instead of an argv it
|
|
354
|
+
// carries ACP start params (resolve-launch-params.js's dsh branch): the brief as the FIRST PROMPT,
|
|
355
|
+
// the routed pick, the report path and the MCP set. Its proxy credential is the WORKER token the
|
|
356
|
+
// delegate path already minted (budgeted; leases apply) and that rides the request's own resolved
|
|
357
|
+
// launch env — so the lead token `prepareDshRoute` would otherwise mint is not wanted here (see the
|
|
358
|
+
// credential block below). `workerCreate` is derived from the create request itself; an interactive
|
|
359
|
+
// dsh card never carries it and its behaviour is byte-for-byte unchanged.
|
|
360
|
+
const acpLaunch = (msg.acp && typeof msg.acp === 'object' && !Array.isArray(msg.acp))
|
|
361
|
+
? msg.acp
|
|
362
|
+
: (launch.acp && typeof launch.acp === 'object' && !Array.isArray(launch.acp) ? launch.acp : null);
|
|
363
|
+
const workerCreate = msg.agentRole === 'worker' || !!acpLaunch;
|
|
364
|
+
const launchEnv = deps.buildLaunchEnv(state);
|
|
365
|
+
const workerCredential = workerCreate && launchEnv && typeof launchEnv.ANTHROPIC_AUTH_TOKEN === 'string'
|
|
366
|
+
? launchEnv.ANTHROPIC_AUTH_TOKEN : '';
|
|
367
|
+
|
|
368
|
+
// WP-6 (OQ-4): the card's Model select sends `{routeAlias, model}` on the create payload; absent,
|
|
369
|
+
// dsh-route-patch picks the default (first DeepSeek-model Portkey route serving anthropic-messages).
|
|
370
|
+
// `routeAlias:'dsh-own-key'` is the explicit opt-out that keeps dsh on its own provider.
|
|
371
|
+
const routeDeps = deps.dshRouteDeps || null;
|
|
372
|
+
const route = await prepareDshRoute({
|
|
373
|
+
ctmSessionId: id,
|
|
374
|
+
routeAlias: msg.routeAlias || msg.route_alias || launch.routeAlias || null,
|
|
375
|
+
model: msg.model || msg.routeModel || null,
|
|
376
|
+
deps: routeDeps,
|
|
377
|
+
});
|
|
378
|
+
if (!route || !route.ok) {
|
|
379
|
+
deps.wsSend(ws, JSON.stringify({
|
|
380
|
+
type: 'error',
|
|
381
|
+
id,
|
|
382
|
+
code: (route && route.reason) || 'dsh_route_unavailable',
|
|
383
|
+
message: (route && route.detail) || 'No model route is available for the DeepSeek Harness.',
|
|
384
|
+
}));
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
// WP-10 step A (C-6/D-16): the create frame's own profile setting (`mcp_profile`, '' = no
|
|
388
|
+
// opinion), the same field server.js's _sessionMcpProfileSetting reads on the Claude path. The
|
|
389
|
+
// RESOLVED profile (setting -> host default -> 'core') is what the servers come from; only the
|
|
390
|
+
// explicit setting is persisted onto the durable row below.
|
|
391
|
+
const mcpProfileSetting = normalizeMcpProfile(msg.mcp_profile ?? msg.mcpProfile ?? launch.mcp_profile ?? launch.mcpProfile);
|
|
392
|
+
const mcp = await buildDshMcpServers(deps, {
|
|
393
|
+
profileSource: { mcpProfile: mcpProfileSetting },
|
|
394
|
+
env: launchEnv,
|
|
395
|
+
cwd,
|
|
396
|
+
ctmSessionId: id,
|
|
397
|
+
});
|
|
398
|
+
// WP-14: a worker create keeps the credential its own launch env carries (the budgeted worker
|
|
399
|
+
// token) instead of the lead token registered a moment ago — an unused registration is an artifact
|
|
400
|
+
// CTM should not leave live. The patch is still written and kept (D-10: CTM never deletes
|
|
401
|
+
// artifacts; it declares the loopback baseURL the child must use), and the worker token is released
|
|
402
|
+
// by the worker-exit path (delegate-worker.js / server.js), never here.
|
|
403
|
+
let routeToken = route.token;
|
|
404
|
+
if (workerCredential && routeToken) {
|
|
405
|
+
await releaseDshRoute({ ctmSessionId: id, token: routeToken, deps: routeDeps });
|
|
406
|
+
routeToken = null;
|
|
407
|
+
}
|
|
408
|
+
// WP-10 step A: a worker create's own resolved MCP map (the map delegate-worker hands
|
|
409
|
+
// resolveWorkerLaunchParams, which the launch spec carries as `acp.mcpServers`) is converted here the
|
|
410
|
+
// same way the profile map is; absent one, the dsh profile resolution above stands.
|
|
411
|
+
const workerMcpMap = workerCreate && acpLaunch && acpLaunch.mcpServers && typeof acpLaunch.mcpServers === 'object'
|
|
412
|
+
&& !Array.isArray(acpLaunch.mcpServers) && Object.keys(acpLaunch.mcpServers).length ? acpLaunch.mcpServers : null;
|
|
413
|
+
// A worker never carries the CTM runtime bearer (agent-presets.js's worker strip, C-8/W-7), so its
|
|
414
|
+
// wall-e entry is stamped with its own session id only; and a worker with no MCP map gets NONE (the
|
|
415
|
+
// strict-MCP diet a claude/codex worker gets), never the lead profile's servers.
|
|
416
|
+
const acpMcpServers = workerCreate
|
|
417
|
+
? (workerMcpMap ? withWalleCaller(toAcpMcpServers(workerMcpMap, { log: (message) => deps.logError(`[dsh-session] ${message}`) }), { ctmSessionId: id }) : [])
|
|
418
|
+
: null;
|
|
419
|
+
// WP-8 (M-16): the live session object does not exist until below; an update that arrives in the
|
|
420
|
+
// meantime (the child is already talking) is bound against whatever `bindSession` holds by then.
|
|
421
|
+
let bindSession = null;
|
|
422
|
+
const result = await deps.dshRuntime.start({
|
|
423
|
+
ctmSessionId: id,
|
|
424
|
+
cwd,
|
|
425
|
+
env: { ...launchEnv, ...(route.env || {}), ...(workerCredential ? { ANTHROPIC_AUTH_TOKEN: workerCredential } : {}) },
|
|
426
|
+
mcpServers: acpMcpServers || mcp.mcpServers,
|
|
427
|
+
...(route.patchPath ? { patchPath: route.patchPath } : {}),
|
|
428
|
+
// D-12: a live pick is `session/set_config_option`; the bridge answers with a
|
|
429
|
+
// `config_option_update` carrying the new list, which is the model-owner evidence.
|
|
430
|
+
onConfigOptions: (_id, configOptions) => { if (bindSession) void bindDshModelOwner(bindSession, configOptions); },
|
|
431
|
+
// WP-6: the runtime fires this exactly once, on whichever path the child dies. A start that
|
|
432
|
+
// FAILS never fires it (the token is released below instead), so there is no double revoke.
|
|
433
|
+
// WP-14: null for a worker create (its own credential was released above).
|
|
434
|
+
...(routeToken ? { onStopped: () => { void releaseDshRoute({ ctmSessionId: id, token: routeToken, deps: routeDeps }); } } : {}),
|
|
435
|
+
});
|
|
436
|
+
if (!result || !result.ok) {
|
|
437
|
+
// A token whose child never started would otherwise stay live in the proxy until a restart.
|
|
438
|
+
if (route.token) await releaseDshRoute({ ctmSessionId: id, token: route.token, deps: routeDeps });
|
|
439
|
+
// Same frame the existing create path sends for a create that could not start: type 'error'
|
|
440
|
+
// with the machine `code` (see lib/session-create/provider-process.js's spawn-failure frame).
|
|
441
|
+
deps.wsSend(ws, JSON.stringify({
|
|
442
|
+
type: 'error',
|
|
443
|
+
id,
|
|
444
|
+
code: (result && result.code) || 'dsh_start_failed',
|
|
445
|
+
message: (result && result.message) || 'DeepSeek Harness could not be started.',
|
|
446
|
+
}));
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const now = deps.now();
|
|
451
|
+
const modelId = modelIdFromConfigOptions(result.configOptions);
|
|
452
|
+
// D-8: CTM owns the transcript — `<dataDir>/dsh-transcripts/<acpSessionId>.jsonl`, appended to by every
|
|
453
|
+
// ACP update (server.js forwards them to this writer) and by every user prompt (WP-4). The file is never
|
|
454
|
+
// read back here, never truncated and never unlinked (D-10).
|
|
455
|
+
const transcript = createDshTranscriptWriter({
|
|
456
|
+
dataDir: deps.getDataDir(),
|
|
457
|
+
acpSessionId: result.acpSessionId,
|
|
458
|
+
cwd,
|
|
459
|
+
});
|
|
460
|
+
transcript.setModel(modelId);
|
|
461
|
+
const session = {
|
|
462
|
+
id,
|
|
463
|
+
type: 'dsh',
|
|
464
|
+
transport: 'acp',
|
|
465
|
+
agentType: 'dsh',
|
|
466
|
+
cmd: 'dsh',
|
|
467
|
+
args: [],
|
|
468
|
+
// WP-11 (D-20): the LIVE capability record of this child — `input.attachments` is true exactly
|
|
469
|
+
// when the child's own `initialize` advertised image prompt support
|
|
470
|
+
// (lib/acp/dsh-session-runtime.js `get().imageInput`; the static table row stays false).
|
|
471
|
+
agentCapabilities: getSessionAgentCapabilities('dsh', { imageInput: result.imageInput === true }),
|
|
472
|
+
title,
|
|
473
|
+
label: title,
|
|
474
|
+
cwd,
|
|
475
|
+
clients: ws ? [ws] : [],
|
|
476
|
+
createdAt: now,
|
|
477
|
+
lastActivity: now,
|
|
478
|
+
lastMeaningfulActivityAt: now,
|
|
479
|
+
agentSessionId: result.acpSessionId,
|
|
480
|
+
model_id: modelId,
|
|
481
|
+
model_provider: 'dsh',
|
|
482
|
+
userRenamed: false,
|
|
483
|
+
jsonlPath: transcript.path,
|
|
484
|
+
// The delivery target's process identity (lib/remote-terminal-target.js); cleared on child exit.
|
|
485
|
+
_acpProcess: Object.freeze({ acpSessionId: result.acpSessionId }),
|
|
486
|
+
};
|
|
487
|
+
session._dshTranscript = transcript;
|
|
488
|
+
// WP-14 (§9b W-3, D-21): the live report source for a delegated dsh worker — the last assistant
|
|
489
|
+
// text of the turn this session last ran, read from the ACP runtime (lib/acp/dsh-session-runtime.js
|
|
490
|
+
// `get().finalText`). delegate-worker.js's getWorkerStatus reads exactly this property as the
|
|
491
|
+
// `final_result` source, the same way it reads a PTY worker's turn counter. NON-ENUMERABLE, like
|
|
492
|
+
// `_dshRoute` below: it is a read-through accessor, not part of the session row a client sees. A
|
|
493
|
+
// gone child reads '' (never a throw), so a status read after an exit degrades to the value the
|
|
494
|
+
// exit path stamped into the worker row instead.
|
|
495
|
+
Object.defineProperty(session, '_dshFinalText', {
|
|
496
|
+
get: () => {
|
|
497
|
+
const live = deps.dshRuntime.get(id);
|
|
498
|
+
return live && typeof live.finalText === 'string' ? live.finalText : '';
|
|
499
|
+
},
|
|
500
|
+
enumerable: false,
|
|
501
|
+
configurable: true,
|
|
502
|
+
});
|
|
503
|
+
// WP-10 step A: the RESOLVED MCP profile this child was started with, kept on the live row so the
|
|
504
|
+
// restart-through-the-map path (markDshRestartable) resumes the same servers. Not secret, and never
|
|
505
|
+
// sent to a client (the session row's own field list decides what leaves CTM).
|
|
506
|
+
session._mcpProfile = mcp.profile;
|
|
507
|
+
// WP-8 (M-7/M-16): the durable per-lifetime generation the owner store fences rebinds with — the
|
|
508
|
+
// SAME allocator the PTY launch plan and the API-attach path use. Set BEFORE the bind below:
|
|
509
|
+
// without it `snapshot()` is null and the owner stays unbound (`dsh_presession`).
|
|
510
|
+
const generation = await _allocateDshGeneration(id);
|
|
511
|
+
if (generation !== null) session._modelProcessGeneration = generation;
|
|
512
|
+
bindSession = session;
|
|
513
|
+
// WP-6: the live routed token/alias/model, held in memory ONLY. NON-ENUMERABLE on purpose: no
|
|
514
|
+
// Object.keys/for-in/spread/JSON.stringify of a session row can ever carry the token into a client
|
|
515
|
+
// frame, a broadcast or the DB — only code that names `_dshRoute` can read it.
|
|
516
|
+
if (route.token) {
|
|
517
|
+
Object.defineProperty(session, '_dshRoute', {
|
|
518
|
+
value: Object.freeze({ token: route.token, alias: route.alias, model: route.model }),
|
|
519
|
+
enumerable: false,
|
|
520
|
+
writable: true,
|
|
521
|
+
configurable: true,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
deps.setSessionAttentionFields(session, {});
|
|
525
|
+
deps.setSession(id, session);
|
|
526
|
+
deps.setupQueueForSession(id);
|
|
527
|
+
deps.telemetry.track('ctm_session', { type: 'dsh' });
|
|
528
|
+
try {
|
|
529
|
+
await deps.detachOwnerMutation('dsh-session-created', () => deps.dbModule.upsertSession(id, {
|
|
530
|
+
agentSessionId: result.acpSessionId,
|
|
531
|
+
provider: 'dsh',
|
|
532
|
+
cwd,
|
|
533
|
+
projectPath: cwd,
|
|
534
|
+
title,
|
|
535
|
+
jsonlPath: transcript.path,
|
|
536
|
+
fileSize: 0,
|
|
537
|
+
modifiedAt: '',
|
|
538
|
+
model: modelId || '',
|
|
539
|
+
hostname: deps.hostname,
|
|
540
|
+
}));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
deps.logError('[dsh-session] index create error:', error.message);
|
|
543
|
+
}
|
|
544
|
+
// WP-8 (M-16): the child is up AND its `agent_sessions` row exists (`provider:'dsh'`,
|
|
545
|
+
// `agent_session_id` = the ACP id — the row the store's bind requires), so the model owner can bind.
|
|
546
|
+
// Never fatal: an unbound owner is exactly the `dsh_presession` state the public envelope names.
|
|
547
|
+
await bindDshModelOwner(session, result.configOptions);
|
|
548
|
+
// S-26: the session stream maps CTM session -> ACP session (+ transcript path) so the live Conv tail
|
|
549
|
+
// reads the file this writer appends to.
|
|
550
|
+
deps.linkSession(id, result.acpSessionId, transcript.path);
|
|
551
|
+
// WP-5 (D-B, S-40/S-41): the durable restore record. dsh's argv is built by the runtime from
|
|
552
|
+
// `--profile acp` (+ the patch), NEVER from this array, so the array is free to carry the ROUTED
|
|
553
|
+
// PICK: `args[0]` is the route alias (`'dsh-own-key'` for the explicit own-key opt-out) and
|
|
554
|
+
// `chat_session_id` is the ACP session id to `session/resume`. The row carries model_id too.
|
|
555
|
+
// It NEVER carries the token or any provider key — nothing secret is ever written to startup_tasks.
|
|
556
|
+
if (!msg._isRestore) {
|
|
557
|
+
const taskWrite = deps.addStartupTaskWithRetry('dsh', [
|
|
558
|
+
id, title, 'dsh', [route.alias || 'dsh-own-key'], cwd, route.model || modelId || null, 'dsh', result.acpSessionId,
|
|
559
|
+
]);
|
|
560
|
+
// WP-10 step A: the profile rides the row the write above creates (never the write's own args —
|
|
561
|
+
// nothing about MCP changes the startup_tasks schema), chained so the UPDATE cannot run before
|
|
562
|
+
// the row exists. An explicit setting only: see persistDshMcpProfile.
|
|
563
|
+
if (mcpProfileSetting) {
|
|
564
|
+
void Promise.resolve(taskWrite).then(() => persistDshMcpProfile(deps, id, mcpProfileSetting));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const live = deps.dshRuntime.get(id);
|
|
568
|
+
deps.sendCreated(ws, session, { pid: live ? live.pid : undefined });
|
|
569
|
+
deps.broadcastSessionList(true, { reason: 'dsh-created' });
|
|
570
|
+
// WP-14 (§9b W-2): the delegated dsh worker's OTHER half of the launch spec — the brief as the FIRST
|
|
571
|
+
// PROMPT. There is no argv to carry it (that is the whole point of the dsh branch in
|
|
572
|
+
// resolve-launch-params.js) and no user at the keyboard, so the create delivers it itself, once, as
|
|
573
|
+
// soon as the child is up. Deliberately AFTER the created frame/list broadcast (the session exists and
|
|
574
|
+
// is visible whether or not the prompt lands) and non-fatal: a prompt that cannot be written leaves
|
|
575
|
+
// the worker with an empty report and its own failure classified at exit — never a half-created
|
|
576
|
+
// session. The runtime resets the turn's finalText on this call, so the report is THIS turn's text.
|
|
577
|
+
const firstPrompt = workerCreate && acpLaunch && typeof acpLaunch.prompt === 'string' ? acpLaunch.prompt : '';
|
|
578
|
+
if (firstPrompt) {
|
|
579
|
+
const delivery = await deps.dshRuntime.prompt(id, { text: firstPrompt });
|
|
580
|
+
if (!delivery || delivery.ok !== true) {
|
|
581
|
+
deps.logError(`[dsh-session] worker brief prompt refused (${(delivery && delivery.code) || 'dsh_prompt_failed'})`);
|
|
582
|
+
} else {
|
|
583
|
+
// The prompt is on the wire; the turn settles in the runtime. A rejection is the child's own turn
|
|
584
|
+
// failure and is reported through the transcript/status path, never as an unhandled rejection here.
|
|
585
|
+
void delivery.prompt.catch((error) => deps.logError(`[dsh-session] worker brief turn failed (${(error && error.message) || 'error'})`));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ============================================================================================
|
|
592
|
+
// WP-5 — lifecycle: restore on attach, child-exit restartability, kill
|
|
593
|
+
// ============================================================================================
|
|
594
|
+
|
|
595
|
+
/** The CTM-owned transcript path for an ACP session id (`<dataDir>/dsh-transcripts/<id>.jsonl`). */
|
|
596
|
+
function dshTranscriptPathFor(acpSessionId) {
|
|
597
|
+
const deps = _getDeps();
|
|
598
|
+
return path.join(dshTranscriptDir(deps.getDataDir()), `${String(acpSessionId || '')}.jsonl`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// D-D (AC-5c): a dsh child that died on its own leaves the session in the map, restartable. The
|
|
602
|
+
// session becomes an ordinary deferred placeholder, so the NEXT Send reaches it through the existing
|
|
603
|
+
// queued-input → _startDeferredRestoreSession path and resumes the SAME ACP session.
|
|
604
|
+
function markDshRestartable(session) {
|
|
605
|
+
if (!session || session.type !== 'dsh') return false;
|
|
606
|
+
const existing = session._restoreTask;
|
|
607
|
+
if (existing && existing.type === 'dsh') {
|
|
608
|
+
session._restoreDeferred = true;
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
session._restoreTask = {
|
|
612
|
+
type: 'dsh',
|
|
613
|
+
session_id: session.id,
|
|
614
|
+
cwd: session.cwd || '',
|
|
615
|
+
label: session.label || session.title || 'DeepSeek Harness',
|
|
616
|
+
// The routed pick the next mint must reuse: the live in-memory route alias, else the own-key opt-out.
|
|
617
|
+
args: [(session._dshRoute && session._dshRoute.alias) || 'dsh-own-key'],
|
|
618
|
+
model_id: session.model_id || null,
|
|
619
|
+
chat_session_id: session.agentSessionId || null,
|
|
620
|
+
// WP-10 step A: the profile this child was started with, so the next activation forwards the
|
|
621
|
+
// same MCP servers (the durable path reads the same field off the startup_tasks row).
|
|
622
|
+
mcp_profile: session._mcpProfile || '',
|
|
623
|
+
};
|
|
624
|
+
session._restoreDeferred = true;
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// The failure half of a restore: the session STAYS in the map (restartable) and the reason is visible
|
|
629
|
+
// to every attached client. It is deliberately NOT a silent own-key fallback (D-12).
|
|
630
|
+
function _failDshRestore(session, code, message) {
|
|
631
|
+
const deps = _getDeps();
|
|
632
|
+
session._dshExitReason = { code, message };
|
|
633
|
+
session._exited = true;
|
|
634
|
+
session._lifecycleState = 'exited';
|
|
635
|
+
session._restoreDeferred = true; // stays restartable: a later attach/Send retries the same path
|
|
636
|
+
const payload = JSON.stringify({ type: 'exit', id: session.id, exitCode: null, signal: null, reason: code, message });
|
|
637
|
+
for (const client of session.clients || []) {
|
|
638
|
+
try {
|
|
639
|
+
if (client && client.readyState === 1) deps.wsSend(client, payload);
|
|
640
|
+
} catch (error) {
|
|
641
|
+
// expected: a dead socket must never break the restore failure path.
|
|
642
|
+
void error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
try { deps.broadcastSessionList(true, { reason: 'dsh-restore-failed' }); } catch (error) { void error; }
|
|
646
|
+
deps.logError(`[dsh-session] restore failed for ${String(session.id || '').slice(0, 8)}: ${code}`);
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// The success half of a restore, split out so restoreDshSession can keep one promise identity.
|
|
651
|
+
async function _runDshRestore(session, ctx) {
|
|
652
|
+
const deps = _getDeps();
|
|
653
|
+
const { id, cwd, routeAlias, model, acpSessionId } = ctx;
|
|
654
|
+
const routeDeps = deps.dshRouteDeps || null;
|
|
655
|
+
// D-9: a FRESH token is minted from the stored pick on every restore (the old one died with the
|
|
656
|
+
// previous CTM process). A refusal surfaces as this session's exit reason — never a silent fallback.
|
|
657
|
+
const route = await prepareDshRoute({ ctmSessionId: id, routeAlias, model, deps: routeDeps });
|
|
658
|
+
if (!route || !route.ok) {
|
|
659
|
+
return _failDshRestore(session, (route && route.reason) || 'dsh_route_unavailable',
|
|
660
|
+
(route && route.detail) || 'No model route is available for the DeepSeek Harness.');
|
|
661
|
+
}
|
|
662
|
+
// WP-10 step A (C-6/D-16): the SAME profile the create resolved — the durable `startup_tasks` row
|
|
663
|
+
// the create wrote it onto (`mcp_profile`), else the live row's copy (a restart through the map).
|
|
664
|
+
const launchEnv = deps.buildLaunchEnv({});
|
|
665
|
+
const task = session._restoreTask || {};
|
|
666
|
+
const mcp = await buildDshMcpServers(deps, {
|
|
667
|
+
profileSource: { mcpProfile: task.mcp_profile ?? task.mcpProfile ?? session._mcpProfile },
|
|
668
|
+
env: launchEnv,
|
|
669
|
+
cwd,
|
|
670
|
+
ctmSessionId: id,
|
|
671
|
+
});
|
|
672
|
+
const result = await deps.dshRuntime.start({
|
|
673
|
+
ctmSessionId: id,
|
|
674
|
+
cwd,
|
|
675
|
+
env: { ...launchEnv, ...(route.env || {}) },
|
|
676
|
+
mcpServers: mcp.mcpServers,
|
|
677
|
+
...(route.patchPath ? { patchPath: route.patchPath } : {}),
|
|
678
|
+
// D-9: resume the SAME ACP session — `session/resume`, never `session/new`. History is NOT
|
|
679
|
+
// replayed; the CTM transcript already holds it (that is the whole point of D-8).
|
|
680
|
+
...(acpSessionId ? { resumeAcpSessionId: acpSessionId } : {}),
|
|
681
|
+
// WP-8 (M-16): `session/resume` returns the configOptions too, and every later
|
|
682
|
+
// `config_option_update` re-binds through the SAME observer.
|
|
683
|
+
onConfigOptions: (_id, configOptions) => { void bindDshModelOwner(session, configOptions); },
|
|
684
|
+
...(route.token ? { onStopped: () => { void releaseDshRoute({ ctmSessionId: id, token: route.token, deps: routeDeps }); } } : {}),
|
|
685
|
+
});
|
|
686
|
+
if (!result || !result.ok) {
|
|
687
|
+
if (route.token) await releaseDshRoute({ ctmSessionId: id, token: route.token, deps: routeDeps });
|
|
688
|
+
return _failDshRestore(session, (result && result.code) || 'dsh_start_failed',
|
|
689
|
+
(result && result.message) || 'DeepSeek Harness could not be restarted.');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const resolvedAcpId = result.acpSessionId || acpSessionId;
|
|
693
|
+
const modelId = modelIdFromConfigOptions(result.configOptions) || model || null;
|
|
694
|
+
// The SAME file, reopened append-only (its tail read re-seeds the parentUuid chain, so the restored
|
|
695
|
+
// rows continue the existing conversation instead of starting a second chain).
|
|
696
|
+
const transcript = createDshTranscriptWriter({ dataDir: deps.getDataDir(), acpSessionId: resolvedAcpId, cwd });
|
|
697
|
+
transcript.setModel(modelId);
|
|
698
|
+
const live = deps.dshRuntime.get(id);
|
|
699
|
+
// In-place mutation: same id, same `clients` array, so every attached tab keeps its socket.
|
|
700
|
+
session.transport = 'acp';
|
|
701
|
+
session.agentType = 'dsh';
|
|
702
|
+
// WP-11 (D-20): a resumed child is a NEW process lifetime, so the live image capability is
|
|
703
|
+
// re-stamped from THIS start's initialize answer, exactly like the create path.
|
|
704
|
+
session.agentCapabilities = getSessionAgentCapabilities('dsh', { imageInput: result.imageInput === true });
|
|
705
|
+
session.agentSessionId = resolvedAcpId;
|
|
706
|
+
session.jsonlPath = transcript.path;
|
|
707
|
+
session.model_id = modelId;
|
|
708
|
+
session.model_provider = 'dsh';
|
|
709
|
+
session._dshTranscript = transcript;
|
|
710
|
+
session._dshExitReason = null;
|
|
711
|
+
session._exited = false;
|
|
712
|
+
session._lifecycleState = 'live';
|
|
713
|
+
session._restoreDeferred = false;
|
|
714
|
+
session.pid = live ? live.pid : session.pid;
|
|
715
|
+
// Fresh per-child process identity for the delivery target (set per child, cleared on child exit).
|
|
716
|
+
session._acpProcess = Object.freeze({ acpSessionId: resolvedAcpId });
|
|
717
|
+
if (route.token) {
|
|
718
|
+
Object.defineProperty(session, '_dshRoute', {
|
|
719
|
+
value: Object.freeze({ token: route.token, alias: route.alias, model: route.model }),
|
|
720
|
+
enumerable: false,
|
|
721
|
+
writable: true,
|
|
722
|
+
configurable: true,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
deps.setSession(id, session);
|
|
726
|
+
deps.linkSession(id, resolvedAcpId, transcript.path);
|
|
727
|
+
// WP-8 (M-7/M-16): the resumed child is a NEW process lifetime, so it gets a fresh durable
|
|
728
|
+
// generation (the allocator is monotonic — the store fences rebinds numerically), and the owner is
|
|
729
|
+
// bound again for the SAME ACP session id from the configOptions `session/resume` just returned.
|
|
730
|
+
const generation = await _allocateDshGeneration(id);
|
|
731
|
+
if (generation !== null) session._modelProcessGeneration = generation;
|
|
732
|
+
await bindDshModelOwner(session, result.configOptions);
|
|
733
|
+
deps.broadcastSessionList(true, { reason: 'dsh-resumed' });
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* D-C/D-9/S-40 — the ONE activation path for a restored dsh session.
|
|
739
|
+
*
|
|
740
|
+
* The boot loop classifies a `dsh` startup task `'defer'` (lib/resume-admission.js), so boot builds a
|
|
741
|
+
* CHILDLESS placeholder and spawns nothing. A client attach or a queued Send funnels through
|
|
742
|
+
* server.js's `_startDeferredRestoreSessionNow`, which prefixes this call. The existing placeholder
|
|
743
|
+
* object is mutated in place — never replaced — so its id and its attached clients survive.
|
|
744
|
+
*
|
|
745
|
+
* @param {object} session the deferred placeholder (`type: 'dsh'`)
|
|
746
|
+
* @param {object} [opts]
|
|
747
|
+
* @returns {Promise<boolean>} true when the child is up and the session is live again
|
|
748
|
+
*/
|
|
749
|
+
async function restoreDshSession(session, opts = {}) {
|
|
750
|
+
const deps = _getDeps();
|
|
751
|
+
if (!session || session.type !== 'dsh') return false;
|
|
752
|
+
const id = String(session.id || '');
|
|
753
|
+
if (!id) return false;
|
|
754
|
+
// Already live (a racing wake won): nothing to do, and specifically no second token minted.
|
|
755
|
+
if (deps.dshRuntime.isRunning(id)) return true;
|
|
756
|
+
if (session._dshRestorePromise) return session._dshRestorePromise;
|
|
757
|
+
const task = session._restoreTask || {};
|
|
758
|
+
const cwd = session.cwd || task.cwd || '';
|
|
759
|
+
const routeAlias = Array.isArray(task.args) && task.args.length ? task.args[0] : null;
|
|
760
|
+
const model = task.model_id || session.model_id || null;
|
|
761
|
+
const acpSessionId = String(task.chat_session_id || session.agentSessionId || '');
|
|
762
|
+
session._restoreStarting = true;
|
|
763
|
+
void opts; // the activation source is recorded by the caller's own lifecycle stamps
|
|
764
|
+
const pending = _runDshRestore(session, { id, cwd, routeAlias, model, acpSessionId })
|
|
765
|
+
.then((started) => { session._restoreStarting = false; return started; })
|
|
766
|
+
.catch((error) => {
|
|
767
|
+
session._restoreStarting = false;
|
|
768
|
+
return _failDshRestore(session, 'dsh_start_failed',
|
|
769
|
+
(error && error.message) || 'DeepSeek Harness could not be restarted.');
|
|
770
|
+
});
|
|
771
|
+
session._dshRestorePromise = pending;
|
|
772
|
+
try {
|
|
773
|
+
return await pending;
|
|
774
|
+
} finally {
|
|
775
|
+
if (session._dshRestorePromise === pending) session._dshRestorePromise = null;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// ============================================================================================
|
|
780
|
+
// WP-8 step 3 (§6.7, §9b M-15/M-16; D-12/D-13) — the live pick: effort/model write, route relaunch
|
|
781
|
+
// ============================================================================================
|
|
782
|
+
|
|
783
|
+
/*
|
|
784
|
+
* ONE `completed-turn` observation when a prompt turn settles `completed` (D-13 semantics, the SAME
|
|
785
|
+
* `applyResult` path `bindDshModelOwner` uses — the store admits `dsh:turn` for that kind already).
|
|
786
|
+
*
|
|
787
|
+
* The value is the session's CURRENT model read from the live runtime entry's `configOptions` (the
|
|
788
|
+
* bridge is the only truth for what actually ran), falling back to the session row. A turn with no
|
|
789
|
+
* model evidence writes nothing: the store's own rule for this kind requires a concrete value, and
|
|
790
|
+
* inventing one would be worse than staying silent. Never fatal — a failed observation write must
|
|
791
|
+
* never turn a completed turn into a failed one.
|
|
792
|
+
*/
|
|
793
|
+
async function observeDshCompletedTurn(session, configOptions) {
|
|
794
|
+
try {
|
|
795
|
+
return await _observeDshCompletedTurn(session, configOptions);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
_getDeps().logError(`[dsh-session] completed-turn observation failed for ${String(session && session.id).slice(0, 8)}: ${(error && error.message) || error}`);
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async function _observeDshCompletedTurn(session, configOptions) {
|
|
803
|
+
const deps = _getDeps();
|
|
804
|
+
const runtime = deps.modelStateRuntime;
|
|
805
|
+
if (!session || !runtime || typeof runtime.applyResult !== 'function') return false;
|
|
806
|
+
const owner = runtime.snapshot(session, 'dsh');
|
|
807
|
+
if (!owner) return false;
|
|
808
|
+
const live = typeof deps.dshRuntime?.get === 'function' ? deps.dshRuntime.get(session.id) : null;
|
|
809
|
+
const options = Array.isArray(configOptions) ? configOptions : (live ? live.configOptions : null);
|
|
810
|
+
const value = modelIdFromConfigOptions(options) || session.model_id || null;
|
|
811
|
+
if (!value) return false;
|
|
812
|
+
const observation = {
|
|
813
|
+
ctmSessionId: owner.ctmSessionId,
|
|
814
|
+
agentType: owner.agentType,
|
|
815
|
+
providerSessionId: owner.providerSessionId,
|
|
816
|
+
processGeneration: owner.processGeneration,
|
|
817
|
+
kind: 'completed-turn',
|
|
818
|
+
value,
|
|
819
|
+
providerId: 'dsh',
|
|
820
|
+
selectionKind: 'concrete',
|
|
821
|
+
effort: effortFromConfigOptions(options),
|
|
822
|
+
source: DSH_TURN_SOURCE,
|
|
823
|
+
sourceRevision: null,
|
|
824
|
+
outcome: 'completed',
|
|
825
|
+
observedAtMs: Date.now(),
|
|
826
|
+
};
|
|
827
|
+
return runtime.applyResult(session, { id: String(session.id || ''), owner, observations: [observation] });
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* D-12: a pick that names a DIFFERENT gateway route cannot be applied in place — the ACP child holds
|
|
832
|
+
* ONE route's patch and ONE session token — so the child is STOPPED and a fresh one is started
|
|
833
|
+
* through the SAME restore path, resuming the SAME ACP session id (`session/resume`, never
|
|
834
|
+
* `session/new`), while `prepareDshRoute` mints the new route's token and the runtime's one-shot
|
|
835
|
+
* `onStopped` hook releases the old one.
|
|
836
|
+
*
|
|
837
|
+
* The stop is INTENTIONAL by construction: the runtime marks the exit `intentional`, so server.js's
|
|
838
|
+
* `_onDshChildExit` returns before `markDshRestartable` — a route switch is not a crash, and the
|
|
839
|
+
* session must NOT come out of it looking asleep.
|
|
840
|
+
*
|
|
841
|
+
* A refused/failed relaunch surfaces through `_failDshRestore` (the same reason code every restore
|
|
842
|
+
* failure publishes) and is returned here as `{ ok:false, code }`, never thrown.
|
|
843
|
+
*
|
|
844
|
+
* @returns {Promise<{ok: true}|{ok: false, code: string, message: string}>}
|
|
845
|
+
*/
|
|
846
|
+
async function relaunchDshSession(session, opts = {}) {
|
|
847
|
+
const deps = _getDeps();
|
|
848
|
+
if (!session || session.type !== 'dsh') {
|
|
849
|
+
return { ok: false, code: 'dsh_session_unavailable', message: 'This DeepSeek Harness session is not available.' };
|
|
850
|
+
}
|
|
851
|
+
const id = String(session.id || '');
|
|
852
|
+
if (!id) return { ok: false, code: 'dsh_session_unavailable', message: 'This DeepSeek Harness session is not available.' };
|
|
853
|
+
if (session._dshRestorePromise) return session._dshRestorePromise;
|
|
854
|
+
const cwd = session.cwd || (session._restoreTask && session._restoreTask.cwd) || '';
|
|
855
|
+
const acpSessionId = String(session.agentSessionId || (session._restoreTask && session._restoreTask.chat_session_id) || '');
|
|
856
|
+
const routeAlias = opts.routeAlias || null;
|
|
857
|
+
const model = opts.model || null;
|
|
858
|
+
session._restoreStarting = true;
|
|
859
|
+
const pending = (async () => {
|
|
860
|
+
// Intentional stop WITHOUT session/close (§6.7: the next child resumes this ACP session). The old
|
|
861
|
+
// route's token is released by the runtime's one-shot onStopped hook, and the exit is never
|
|
862
|
+
// reported as a crash (see _onDshChildExit's `intentional` early return).
|
|
863
|
+
await deps.dshRuntime.stop(id, { reason: 'dsh_route_switch', closeAcpSession: false });
|
|
864
|
+
const started = await _runDshRestore(session, { id, cwd, routeAlias, model, acpSessionId });
|
|
865
|
+
if (!started) {
|
|
866
|
+
const reason = session._dshExitReason || {};
|
|
867
|
+
return { ok: false, code: reason.code || 'dsh_start_failed', message: reason.message || 'DeepSeek Harness could not be restarted.' };
|
|
868
|
+
}
|
|
869
|
+
// The durable CTM-restart record follows the switch, so a later boot resumes on the NEW route:
|
|
870
|
+
// the startup-task `args[0]` is exactly what `_runDshRestore` reads back as `routeAlias`.
|
|
871
|
+
persistDshRouteAlias(session, { routeAlias, model });
|
|
872
|
+
return { ok: true };
|
|
873
|
+
})().then(
|
|
874
|
+
(result) => { session._restoreStarting = false; return result; },
|
|
875
|
+
(error) => {
|
|
876
|
+
session._restoreStarting = false;
|
|
877
|
+
_failDshRestore(session, 'dsh_start_failed', (error && error.message) || 'DeepSeek Harness could not be restarted.');
|
|
878
|
+
return { ok: false, code: 'dsh_start_failed', message: (error && error.message) || 'DeepSeek Harness could not be restarted.' };
|
|
879
|
+
},
|
|
880
|
+
);
|
|
881
|
+
session._dshRestorePromise = pending;
|
|
882
|
+
try {
|
|
883
|
+
return await pending;
|
|
884
|
+
} finally {
|
|
885
|
+
if (session._dshRestorePromise === pending) session._dshRestorePromise = null;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* The durable route record for a dsh session — the SAME `startup_tasks` row `createDshSession` writes
|
|
891
|
+
* (INSERT OR REPLACE on `ctm_session_id`, so this rewrites it in place). `args[0]` is the route alias
|
|
892
|
+
* and `chat_session_id` the ACP id to resume; NOTHING secret is ever stored (the token lives only in
|
|
893
|
+
* the non-enumerable in-memory `_dshRoute`). Never fatal: a failed write leaves the previous record.
|
|
894
|
+
*/
|
|
895
|
+
function persistDshRouteAlias(session, { routeAlias, model } = {}) {
|
|
896
|
+
const deps = _getDeps();
|
|
897
|
+
const id = session ? String(session.id || '') : '';
|
|
898
|
+
if (!id || typeof deps.addStartupTaskWithRetry !== 'function') return false;
|
|
899
|
+
try {
|
|
900
|
+
void deps.addStartupTaskWithRetry('dsh', [
|
|
901
|
+
id, session.label || session.title || 'DeepSeek Harness', 'dsh',
|
|
902
|
+
[routeAlias || 'dsh-own-key'], session.cwd || '', model || session.model_id || null, 'dsh',
|
|
903
|
+
session.agentSessionId || null,
|
|
904
|
+
]);
|
|
905
|
+
return true;
|
|
906
|
+
} catch (error) {
|
|
907
|
+
deps.logError(`[dsh-session] route persist failed for ${id.slice(0, 8)}: ${(error && error.message) || error}`);
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
module.exports = {
|
|
913
|
+
configure,
|
|
914
|
+
createDshSession,
|
|
915
|
+
markDshRestartable,
|
|
916
|
+
modelIdFromConfigOptions,
|
|
917
|
+
effortFromConfigOptions,
|
|
918
|
+
findModelOption,
|
|
919
|
+
findEffortOption,
|
|
920
|
+
configValueForModelId,
|
|
921
|
+
configValueForOptionLevel,
|
|
922
|
+
plainModelIdFromConfigValue,
|
|
923
|
+
configOptionEntries,
|
|
924
|
+
bindDshModelOwner,
|
|
925
|
+
observeDshCompletedTurn,
|
|
926
|
+
relaunchDshSession,
|
|
927
|
+
persistDshRouteAlias,
|
|
928
|
+
restoreDshSession,
|
|
929
|
+
dshTranscriptPathFor,
|
|
930
|
+
};
|