create-walle 0.10.0 → 0.10.2
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 +88 -41
- package/lib/owner-write-safety.js +135 -0
- package/lib/public-runtime.js +13 -5
- package/lib/release-schema.json +3 -2
- package/lib/runtime-closure-policy.json +7 -5
- package/package.json +10 -4
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +10 -22
- package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
- package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
- package/template/coding-task-manager/bin/claude-hook.js +7 -3
- package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
- package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
- package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
- package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
- package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
- package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
- package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +705 -286
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
- package/template/coding-task-manager/lib/agent-presets.js +15 -0
- package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
- package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/ingress.js +35 -12
- package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
- package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
- package/template/coding-task-manager/lib/api/machines.js +461 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/sessions.js +160 -8
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/worktrees.js +38 -37
- package/template/coding-task-manager/lib/approval-drift.js +16 -1
- package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
- package/template/coding-task-manager/lib/approval-scope.js +68 -4
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
- package/template/coding-task-manager/lib/auth-rules.js +186 -45
- package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
- package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
- package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-session-title.js +6 -30
- package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
- package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
- package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
- package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-local-links.js +267 -0
- package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
- package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
- package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
- package/template/coding-task-manager/lib/db-domains.js +21 -2
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -3
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
- package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
- package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
- package/template/coding-task-manager/lib/headless-term-service.js +60 -23
- package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +51 -13
- package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
- package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
- package/template/coding-task-manager/lib/host-stats.js +328 -0
- package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
- package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
- package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
- package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +13 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
- package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
- package/template/coding-task-manager/lib/launch-model-override.js +160 -1
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
- package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/login-tripwire.js +18 -0
- package/template/coding-task-manager/lib/machine-pairing.js +238 -0
- package/template/coding-task-manager/lib/machine-pool.js +409 -0
- package/template/coding-task-manager/lib/machine-registry.js +749 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
- package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
- package/template/coding-task-manager/lib/media-journal.js +84 -27
- package/template/coding-task-manager/lib/media-store-options.js +43 -15
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
- package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
- package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
- package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
- package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-history-control.js +62 -14
- package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
- package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
- package/template/coding-task-manager/lib/raw-history-block.js +3 -1
- package/template/coding-task-manager/lib/read-pool-client.js +36 -24
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
- package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
- package/template/coding-task-manager/lib/restart-preflight.js +9 -2
- package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
- package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
- package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
- package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
- package/template/coding-task-manager/lib/resume-cwd.js +13 -0
- package/template/coding-task-manager/lib/resume-from-store.js +12 -1
- package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
- package/template/coding-task-manager/lib/resume-preflight.js +21 -16
- package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
- package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
- package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
- package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
- package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
- package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
- package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
- package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
- package/template/coding-task-manager/lib/session-create/response.js +30 -12
- package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
- package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
- package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +152 -206
- package/template/coding-task-manager/lib/session-jobs.js +17 -9
- package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
- package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- package/template/coding-task-manager/lib/session-status.js +2 -2
- package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
- package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
- package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
- package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
- package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
- package/template/coding-task-manager/lib/transcript-link.js +77 -0
- package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
- package/template/coding-task-manager/lib/transcript-source.js +8 -3
- package/template/coding-task-manager/lib/transcript-store.js +40 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
- package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
- package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
- package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
- package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
- package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
- package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/ws/input.js +1 -0
- package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
- package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
- package/template/coding-task-manager/lib/ws-router.js +68 -1
- package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
- package/template/coding-task-manager/oauth-proxy.js +99 -55
- package/template/coding-task-manager/package-lock.json +24 -0
- package/template/coding-task-manager/package.json +3 -0
- package/template/coding-task-manager/protocol/message-table.json +0 -10
- package/template/coding-task-manager/protocol/messages.js +0 -10
- package/template/coding-task-manager/protocol/ws-schema.json +0 -20
- package/template/coding-task-manager/protocol/ws-schema.md +0 -2
- package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
- package/template/coding-task-manager/providers/codex-mcp.js +14 -5
- package/template/coding-task-manager/public/css/app.css +279 -2
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +297 -240
- package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
- package/template/coding-task-manager/public/js/background-work.js +13 -7
- package/template/coding-task-manager/public/js/client-health-core.js +81 -0
- package/template/coding-task-manager/public/js/client-health-main.js +61 -0
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
- package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
- package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
- package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
- package/template/coding-task-manager/public/js/history-scroller.js +187 -9
- package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
- package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
- package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
- package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/protocol.js +0 -10
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/reviews.js +7 -3
- package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
- package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
- package/template/coding-task-manager/public/js/row-join.js +18 -3
- package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
- package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
- package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
- package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
- package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
- package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
- package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
- package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
- package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
- package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
- package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/m/app.css +226 -8
- package/template/coding-task-manager/public/m/app.js +502 -9
- package/template/coding-task-manager/public/m/index.html +38 -1
- package/template/coding-task-manager/public/m/machines.js +1313 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
- package/template/coding-task-manager/public/m/sw.js +5 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/server/approvals.js +5 -6
- package/template/coding-task-manager/server/boot.js +136 -17
- package/template/coding-task-manager/server.js +964 -425
- package/template/coding-task-manager/session-integrity.js +7 -9
- package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
- package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
- package/template/coding-task-manager/workers/session-host-process.js +14 -3
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/worktree-worker.js +34 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/scripts/conversation-storage-v2-migration.js +78 -11
- package/template/shared/conversation-storage-v2-contract.js +42 -6
- package/template/shared/fixtures/worker-status-payloads.json +392 -0
- package/template/shared/git-remote-key.js +83 -0
- package/template/shared/host-identity.js +98 -2
- package/template/shared/owner-write-safety.js +135 -0
- package/template/shared/public-runtime.js +13 -5
- package/template/shared/release-schema.json +3 -2
- package/template/shared/report-citations.js +14 -0
- package/template/shared/sqlite-write-lock.js +361 -223
- package/template/shared/storage/contract.js +35 -3
- package/template/wall-e/agent.js +21 -0
- package/template/wall-e/api-walle.js +5 -0
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/migrations.js +51 -22
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/owner-call-whitelist.js +16 -0
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain.js +1 -1
- package/template/wall-e/coding/host-capabilities.js +2 -2
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/fly.toml +1 -1
- package/template/wall-e/http/auth.js +18 -1
- package/template/wall-e/http/blog-views-api.js +318 -0
- package/template/wall-e/http/helper-rules-api.js +321 -0
- package/template/wall-e/http/model-proxy-providers.js +3 -1
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/lib/blog-view-policy.js +88 -0
- package/template/wall-e/lib/blog-view-store.js +169 -0
- package/template/wall-e/lib/client-config-lock.js +2 -1
- package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
- package/template/wall-e/lib/error-kinds.js +10 -1
- package/template/wall-e/lib/feature-manifest.js +1 -1
- package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +277 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
- package/template/wall-e/lib/helper-rules/store.js +667 -0
- package/template/wall-e/lib/mcp-integration.js +5 -6
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
- package/template/wall-e/lib/telemetry-db-schema.js +37 -0
- package/template/wall-e/lib/telemetry-process-client.js +76 -1
- package/template/wall-e/llm/default-fallback.js +4 -0
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +6 -0
- package/template/wall-e/llm/portkey-http.js +150 -0
- package/template/wall-e/llm/portkey-sync.js +15 -145
- package/template/wall-e/llm/worker-mcp-routing.js +61 -0
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +45 -0
- package/template/wall-e/mcp-protocol-contract.js +41 -1
- package/template/wall-e/mcp-server.js +13 -2
- package/template/wall-e/mcp-worker-schemas.js +20 -2
- package/template/wall-e/memory/ctm-context-client.js +25 -5
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +35 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
- package/template/wall-e/tools/local-tools.js +12 -5
- package/template/wall-e/workers/brain-owner-worker.js +9 -0
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/telemetry-worker.js +8 -0
- package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-0 (remote-machine-hub-sessions-2026-10-22.html §6.1): the hub machine-registry routes.
|
|
4
|
+
//
|
|
5
|
+
// GET /api/machines -> { machines:[{instanceId, displayName, url, urls, health,
|
|
6
|
+
// lastSeenAt, favorite, hidden, stats, lastUsedAt, os, arch,
|
|
7
|
+
// hostname, peerCredential, agents, …}], hubNetworkSuspect }
|
|
8
|
+
// `peerCredential` (WP-1 D-4, WP-2a D-8 rev 2) is
|
|
9
|
+
// 'pool' | 'env' | 'missing' — a hub-local fact repeated on
|
|
10
|
+
// every row, never the token.
|
|
11
|
+
// `agents` (WP-3) is the box's last-read coding-CLI sign-in
|
|
12
|
+
// verdict: { claude, codex, checkedAt } with each agent
|
|
13
|
+
// 'signed_in' | 'signed_out' | 'unknown'. The all-'unknown'
|
|
14
|
+
// default means "never fetched" — a hub that has not probed
|
|
15
|
+
// this box yet must not imply either answer.
|
|
16
|
+
// POST /api/machines -> register { url, displayName?, walleUrl? } (one manifest
|
|
17
|
+
// probe IS the identity pin, D-3)
|
|
18
|
+
// POST /api/machines/pair -> WP-3 §6.3: join a pool by redeeming the box's pairing code
|
|
19
|
+
// POST /api/machines/:id/remove -> unregister; WP-3 §6.3 extends it to a pool member
|
|
20
|
+
//
|
|
21
|
+
// Unlike the lib/api/*.js extraction slices, this module is NEW surface (WP-0), so the handler
|
|
22
|
+
// bodies live here rather than in server.js's legacy handleApi chain — there is no legacy branch
|
|
23
|
+
// to delete, and tests-integration/api-router-parity.test.js's no-dual-dispatch rule is satisfied
|
|
24
|
+
// by construction. Handlers are thin: the policy (pin/mismatch/probe) is lib/machine-registry.js,
|
|
25
|
+
// the SQL is db.js's owner zone, and the pairing codec/verifier is lib/machine-pairing.js.
|
|
26
|
+
//
|
|
27
|
+
// Auth is NOT evaluated here: lib/auth-rules.js carries the rows (loopbackOnly + admin, modeled on
|
|
28
|
+
// the /api/setup/* rules), and a route with no rule is 403 auth_rule_missing by design — so the
|
|
29
|
+
// rule rows and these registrations must land together.
|
|
30
|
+
|
|
31
|
+
const { registerApiRoutes } = require('../api-router');
|
|
32
|
+
const machinePairing = require('../machine-pairing');
|
|
33
|
+
|
|
34
|
+
// §6.3: the identity probe is a reachability check, not a data fetch — it must not hold a request
|
|
35
|
+
// open on a box that is merely slow to answer.
|
|
36
|
+
const PAIR_PROBE_TIMEOUT_MS = 5_000;
|
|
37
|
+
// §6.3: roster pushes and rotation calls are best-effort against a possibly-dead member. Ten seconds
|
|
38
|
+
// matches lib/remote-machine-proxy.js's DEFAULT_TIMEOUT_MS, the proxy's own box budget.
|
|
39
|
+
const PAIR_PUSH_TIMEOUT_MS = 10_000;
|
|
40
|
+
|
|
41
|
+
let _deps = null;
|
|
42
|
+
|
|
43
|
+
function configure({
|
|
44
|
+
db,
|
|
45
|
+
registry,
|
|
46
|
+
pool,
|
|
47
|
+
readJsonRequestBody,
|
|
48
|
+
sendJsonResponse,
|
|
49
|
+
ready = null,
|
|
50
|
+
fetchImpl = null,
|
|
51
|
+
} = {}) {
|
|
52
|
+
_deps = { db, registry, pool, readJsonRequestBody, sendJsonResponse, ready, fetchImpl };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The boot read's promise (server.js `_machinePoolReady`) — pair/remove MUTATE the pool. Accepts
|
|
57
|
+
* either the promise itself or a thunk returning it: server.js configures this cluster (and its
|
|
58
|
+
* `machines` route group) BEFORE the pool block is evaluated, so the value can only be read lazily.
|
|
59
|
+
*/
|
|
60
|
+
function ready() {
|
|
61
|
+
const source = _deps && _deps.ready;
|
|
62
|
+
if (typeof source === 'function') return Promise.resolve(source());
|
|
63
|
+
return source || Promise.resolve();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _send(res, status, payload) {
|
|
67
|
+
if (_deps && typeof _deps.sendJsonResponse === 'function') return _deps.sendJsonResponse(res, status, payload);
|
|
68
|
+
const body = JSON.stringify(payload || {});
|
|
69
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
|
|
70
|
+
res.end(body);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** This machine's own minted id — '' rather than a throw before boot has primed identity. */
|
|
74
|
+
function _instanceId() {
|
|
75
|
+
try {
|
|
76
|
+
const identity = require('../instance-identity-access').getInstanceIdentity({ required: false });
|
|
77
|
+
return (identity && identity.instanceId) || '';
|
|
78
|
+
} catch {
|
|
79
|
+
return ''; // expected: instance identity is not ready yet (early boot / pre-initDb)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The pool store — the singleton by default, or whatever the injected thunk returns (see ready()). */
|
|
84
|
+
function _pool() {
|
|
85
|
+
const source = _deps && _deps.pool;
|
|
86
|
+
if (typeof source === 'function') return source();
|
|
87
|
+
return source || require('../machine-pool').getMachinePool();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* WP-2a D-8: the credential this member dials ONE target with (pool for a pool member, env
|
|
92
|
+
* otherwise). Used for OPERATING calls — the P-5/P-6 roster push and rotate — where the target is a
|
|
93
|
+
* machine this pool already holds. The identity PROBE and the JOIN hand-off deliberately do NOT use
|
|
94
|
+
* it: neither carries an Authorization header (see `_probeInstanceId` and step 5 below), because the
|
|
95
|
+
* box being paired has not issued this member anything to authenticate with.
|
|
96
|
+
*/
|
|
97
|
+
function _peerHeadersFor(instanceId) {
|
|
98
|
+
try {
|
|
99
|
+
return require('../remote-machine-create').buildPeerControlHeaders(process.env, { instanceId }) || {};
|
|
100
|
+
} catch {
|
|
101
|
+
return {}; // expected: no credential configured — the call goes out anonymous and is refused
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* One JSON round trip with a deadline. `AbortSignal.timeout(ms)` is the codebase's sanctioned
|
|
107
|
+
* non-timer deadline (lib/api/setup.js:168,190,209; lib/api/painted-rows.js:28; lib/oauth-proxy-
|
|
108
|
+
* supervisor.js) — this file therefore adds NO `setTimeout` site for the stabilization lint to own.
|
|
109
|
+
*/
|
|
110
|
+
function _fetchJson(url, { method = 'GET', headers = {}, body = null, timeoutMs = PAIR_PUSH_TIMEOUT_MS } = {}) {
|
|
111
|
+
const fetchFn = (_deps && _deps.fetchImpl) || global.fetch;
|
|
112
|
+
if (typeof fetchFn !== 'function') return Promise.resolve({ ok: false, status: 0, body: null });
|
|
113
|
+
const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'
|
|
114
|
+
? AbortSignal.timeout(timeoutMs)
|
|
115
|
+
: undefined;
|
|
116
|
+
return fetchFn(url, {
|
|
117
|
+
method,
|
|
118
|
+
headers: {
|
|
119
|
+
Accept: 'application/json',
|
|
120
|
+
...(body === null ? {} : { 'Content-Type': 'application/json' }),
|
|
121
|
+
...headers,
|
|
122
|
+
},
|
|
123
|
+
body: body === null ? undefined : JSON.stringify(body),
|
|
124
|
+
signal,
|
|
125
|
+
}).then(async (res) => {
|
|
126
|
+
let parsed = null;
|
|
127
|
+
try {
|
|
128
|
+
parsed = await res.json();
|
|
129
|
+
} catch {
|
|
130
|
+
parsed = null; // expected: a non-JSON body is reported by status alone, never as a transport fault
|
|
131
|
+
}
|
|
132
|
+
return { ok: !!res.ok, status: Number(res.status) || 0, body: parsed };
|
|
133
|
+
}).catch(() => ({ ok: false, status: 0, body: null }));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The identity answer's instance id — the same three shapes lib/machine-registry.js accepts. */
|
|
137
|
+
function _manifestInstanceId(body) {
|
|
138
|
+
const manifest = (body && (body.manifest || body.instance)) || body || {};
|
|
139
|
+
return String(
|
|
140
|
+
manifest.instanceId || manifest.instance_id || manifest.id
|
|
141
|
+
|| (body && (body.instanceId || body.instance_id)) || '',
|
|
142
|
+
).trim();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* §6.3 step 2 (D-9): the identity probe, aimed at the box's PRE-AUTH `GET /api/machine-pairing/
|
|
147
|
+
* identity`. Reachable-and-matching / reachable-but-different / unreachable are three DIFFERENT
|
|
148
|
+
* answers — collapsing them is what makes a mis-pasted pairing string silently pair the wrong
|
|
149
|
+
* machine — and the identity comparison itself stays with the caller.
|
|
150
|
+
*
|
|
151
|
+
* WHY NOT /api/sync/manifest. That route is `sync`-scoped (lib/auth-rules.js), and a machine that has
|
|
152
|
+
* not paired yet holds no credential of this box at all: on a real LAN the probe would 401 and the
|
|
153
|
+
* pair would die as `machine_unreachable` before it began. The pairing routes are dispatched with
|
|
154
|
+
* phase 'api-cloudflare', BEFORE the auth wall, precisely so an unauthenticated member can ask this.
|
|
155
|
+
*
|
|
156
|
+
* NO AUTHORIZATION HEADER, on purpose. The box is not a pool member yet, so D-8 would offer the
|
|
157
|
+
* shared pool secret here — a secret handed to an address that came out of a pasted pairing string or
|
|
158
|
+
* a typed field, for a call that cannot be verified by the receiver anyway. The probe asks a
|
|
159
|
+
* BOOLEAN question ("is a code showing here") and needs no credential to do it.
|
|
160
|
+
*/
|
|
161
|
+
async function _probeInstanceId(url) {
|
|
162
|
+
const res = await _fetchJson(`${url}/api/machine-pairing/identity`, {
|
|
163
|
+
headers: {},
|
|
164
|
+
timeoutMs: PAIR_PROBE_TIMEOUT_MS,
|
|
165
|
+
});
|
|
166
|
+
if (!res.ok) return { ok: false };
|
|
167
|
+
const instanceId = _manifestInstanceId(res.body);
|
|
168
|
+
if (!instanceId) return { ok: false };
|
|
169
|
+
return { ok: true, instanceId };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* P-1: this machine's roster as the wire carries it — every OTHER POOL MEMBER row plus SELF (last),
|
|
174
|
+
* each `{instanceId, urls}`.
|
|
175
|
+
*
|
|
176
|
+
* F2: ONE implementation, owned by lib/api/machine-pool.js (the module that owns the pool store) and
|
|
177
|
+
* consumed here. The local copy this file used to carry mapped EVERY machine row, so a legacy
|
|
178
|
+
* env-token row (P-8: a machine this hub dials with CTM_SYNC_PEER_TOKEN, never a pool member) was
|
|
179
|
+
* pushed to a newly paired box as a FELLOW MEMBER of a pool it had not joined — the box would then
|
|
180
|
+
* dial it with the pool bearer and be refused.
|
|
181
|
+
*/
|
|
182
|
+
function _rosterMembers() {
|
|
183
|
+
return require('./machine-pool').rosterMembers();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* P-4 dial order, for one POST: try every URL the target advertises, in order, and stop at the first
|
|
188
|
+
* one that ANSWERS. A transport failure (status 0) moves on to the next URL; an HTTP answer — even a
|
|
189
|
+
* refusal — ends the attempt, because a second URL that answers differently is a different machine.
|
|
190
|
+
*/
|
|
191
|
+
async function _postToTarget(target, pathname, body, headers) {
|
|
192
|
+
const urls = (Array.isArray(target.urls) && target.urls.length ? target.urls : [String(target.url || '')])
|
|
193
|
+
.map((url) => String(url || '').trim().replace(/\/+$/, ''))
|
|
194
|
+
.filter(Boolean);
|
|
195
|
+
for (const base of urls) {
|
|
196
|
+
const res = await _fetchJson(`${base}${pathname}`, { method: 'POST', body, headers: headers || {} });
|
|
197
|
+
if (res.ok) return true;
|
|
198
|
+
if (res.status) return false;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function handleMachinesList(req, res) {
|
|
204
|
+
const registry = _deps && _deps.registry;
|
|
205
|
+
const { machines, hubNetworkSuspect } = registry.listMachines({ db: _deps && _deps.db });
|
|
206
|
+
// WP-1 (D-4) / P-8: which credential this hub will actually dial THIS row with — computed per row
|
|
207
|
+
// from the row's instanceId, because a pool hub sends the pool secret only to pool members and the
|
|
208
|
+
// env bearer to a legacy box that never joined (see peerCredentialState). The token itself is
|
|
209
|
+
// never read into this response — only which class is in play.
|
|
210
|
+
const peerCredentialFor = require('../remote-machine-create').peerCredentialState;
|
|
211
|
+
// WP-3: the box's coding-CLI sign-in truth the probe sweep last read. Guarded by typeof so a test
|
|
212
|
+
// registry that only carries listMachines still serves rows (they just read 'unknown').
|
|
213
|
+
const agentAuthFor = registry && typeof registry.agentAuthFor === 'function' ? registry.agentAuthFor : null;
|
|
214
|
+
_send(res, 200, {
|
|
215
|
+
machines: machines.map((machine) => ({
|
|
216
|
+
instanceId: machine.instanceId,
|
|
217
|
+
displayName: machine.displayName,
|
|
218
|
+
url: machine.url,
|
|
219
|
+
walleUrl: machine.walleUrl,
|
|
220
|
+
enabled: machine.enabled,
|
|
221
|
+
health: machine.health,
|
|
222
|
+
healthDetail: machine.healthDetail,
|
|
223
|
+
lastSeenAt: machine.lastSeenAt,
|
|
224
|
+
pickerState: machine.pickerState,
|
|
225
|
+
visible: machine.visible,
|
|
226
|
+
// WP-2 (P-1/P-4): the dial list, in dial order — the roster's urls, `url` first when the roster
|
|
227
|
+
// is empty. The picker and the Machines page dial/render these; `url` stays the row's cached
|
|
228
|
+
// working address.
|
|
229
|
+
urls: Array.isArray(machine.urls) ? machine.urls : [],
|
|
230
|
+
// WP-2 (D-4 rev 2): machine-owned settings, read from the box's own manifest by the sweep.
|
|
231
|
+
// `favorite` is the picker star and is NEVER the identity pin (that word is reserved).
|
|
232
|
+
favorite: machine.favorite ? 1 : 0,
|
|
233
|
+
hidden: machine.hidden ? 1 : 0,
|
|
234
|
+
// WP-2 (D-12/D-16): the box's own host sample + the hub-clock stamp of the read (null = never
|
|
235
|
+
// read), and the newest session create on that machine (0 = none). Readers label staleness.
|
|
236
|
+
stats: machine.stats || null,
|
|
237
|
+
lastUsedAt: Number(machine.lastUsedAt) || 0,
|
|
238
|
+
os: machine.os || '',
|
|
239
|
+
arch: machine.arch || '',
|
|
240
|
+
hostname: machine.hostname || '',
|
|
241
|
+
peerCredential: peerCredentialFor(process.env, { instanceId: machine.instanceId }),
|
|
242
|
+
agents: (agentAuthFor ? agentAuthFor.call(registry, machine.instanceId) : null)
|
|
243
|
+
|| { claude: 'unknown', codex: 'unknown', checkedAt: 0 },
|
|
244
|
+
})),
|
|
245
|
+
hubNetworkSuspect,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function handleMachinesCreate(req, res) {
|
|
250
|
+
let body = {};
|
|
251
|
+
try {
|
|
252
|
+
body = await _deps.readJsonRequestBody(req);
|
|
253
|
+
} catch {
|
|
254
|
+
return _send(res, 400, { ok: false, error: 'invalid_json' });
|
|
255
|
+
}
|
|
256
|
+
const result = await _deps.registry.registerMachine({
|
|
257
|
+
url: body && (body.url || body.baseUrl || body.base_url),
|
|
258
|
+
displayName: body && (body.displayName || body.display_name || body.name),
|
|
259
|
+
walleUrl: body && (body.walleUrl || body.walle_url),
|
|
260
|
+
}, {
|
|
261
|
+
db: _deps.db,
|
|
262
|
+
// The hub's peer credential on the identity probe, so a box behind a relay or tunnel answers.
|
|
263
|
+
// P-8: the builder is PER TARGET in WP-2a, but the box's instance id is not known until the
|
|
264
|
+
// probe answers — so registration uses the no-target form, which is exactly today's behaviour
|
|
265
|
+
// (pool-if-pool, else the env bearer).
|
|
266
|
+
peerHeadersFor: () => require('../remote-machine-create').buildPeerControlHeaders(process.env) || {},
|
|
267
|
+
});
|
|
268
|
+
if (!result.ok) {
|
|
269
|
+
// machine_unreachable / machine_identity_mismatch / machine_manifest_invalid / machine_url_required
|
|
270
|
+
const status = result.code === 'machine_identity_mismatch' ? 409 : 502;
|
|
271
|
+
return _send(res, status, { ok: false, error: result.code, detail: result.error || '' });
|
|
272
|
+
}
|
|
273
|
+
return _send(res, 200, { ok: true, machine: result.machine });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* WP-3 §6.3 — the MEMBER side of pairing. Steps 1-7 in the doc's order, with the doc's verbatim
|
|
278
|
+
* error codes. The token minted here is handed to the box ONCE and never echoed, logged or stored
|
|
279
|
+
* anywhere except lib/machine-pool.js's 0600 file.
|
|
280
|
+
*/
|
|
281
|
+
async function handleMachinesPair(req, res) {
|
|
282
|
+
let body = {};
|
|
283
|
+
try {
|
|
284
|
+
body = await _deps.readJsonRequestBody(req);
|
|
285
|
+
} catch {
|
|
286
|
+
return _send(res, 400, { ok: false, error: 'invalid_json' });
|
|
287
|
+
}
|
|
288
|
+
// 1. parse — a `ctmpair1:` container, or the `code` + `url` pair the UI's manual form collects.
|
|
289
|
+
const parsed = machinePairing.decodePairingString(body && body.pairingString);
|
|
290
|
+
const code = parsed ? parsed.code : machinePairing.normalizeCode(body && body.code);
|
|
291
|
+
const claimedId = parsed ? parsed.instanceId : '';
|
|
292
|
+
const candidates = [];
|
|
293
|
+
if (parsed) candidates.push(...parsed.urls);
|
|
294
|
+
if (body && body.url) candidates.push(String(body.url).trim().replace(/\/+$/, ''));
|
|
295
|
+
const seen = new Set();
|
|
296
|
+
const dialOrder = candidates.filter((url) => {
|
|
297
|
+
if (!url || seen.has(url)) return false;
|
|
298
|
+
seen.add(url);
|
|
299
|
+
return true;
|
|
300
|
+
});
|
|
301
|
+
if (code.length !== machinePairing.CODE_LENGTH || !dialOrder.length) {
|
|
302
|
+
return _send(res, 400, { ok: false, error: 'pairing_string_invalid' });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 2. identity-probe the candidates until one names the id the pairing string claimed.
|
|
306
|
+
let boxUrl = '';
|
|
307
|
+
let boxInstanceId = '';
|
|
308
|
+
let mismatched = '';
|
|
309
|
+
for (const candidate of dialOrder) {
|
|
310
|
+
const probe = await _probeInstanceId(candidate);
|
|
311
|
+
if (!probe.ok) continue;
|
|
312
|
+
if (claimedId && probe.instanceId !== claimedId) {
|
|
313
|
+
mismatched = probe.instanceId;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
boxUrl = candidate;
|
|
317
|
+
boxInstanceId = probe.instanceId;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
if (!boxUrl) {
|
|
321
|
+
return mismatched
|
|
322
|
+
? _send(res, 409, { ok: false, error: 'machine_identity_mismatch', detail: `manifest ${mismatched} != ${claimedId}` })
|
|
323
|
+
: _send(res, 502, { ok: false, error: 'machine_unreachable' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
await ready();
|
|
327
|
+
const pool = _pool();
|
|
328
|
+
const selfId = _instanceId();
|
|
329
|
+
// 3. mint the pool if this member has none yet (§6.3 — the first pairing creates the shared secret).
|
|
330
|
+
const token = await pool.ensurePool();
|
|
331
|
+
// 4. bump the roster, then build the membership set the box will store: every POOL MEMBER we
|
|
332
|
+
// already hold, plus the box itself (P-1's roster already carries SELF last). The box stores
|
|
333
|
+
// this MINUS itself (P-8). F2: the set comes from lib/api/machine-pool.js's `rosterMembers()`,
|
|
334
|
+
// so a legacy env-token row — a registry row that is NOT a pool member — is never advertised to
|
|
335
|
+
// the new box as one of its peers.
|
|
336
|
+
const version = await pool.bumpRoster(selfId);
|
|
337
|
+
const members = [
|
|
338
|
+
..._rosterMembers().filter((member) => member.instanceId !== boxInstanceId),
|
|
339
|
+
{ instanceId: boxInstanceId, urls: [boxUrl] },
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
// 5. hand the token to the box. The response is the box's own identity + dial list; its error
|
|
343
|
+
// codes are relayed VERBATIM (§6.3) so the UI can say exactly what happened.
|
|
344
|
+
//
|
|
345
|
+
// F1: NO Authorization header. The join is the PRE-AUTH act that establishes this member's
|
|
346
|
+
// credential on that box, so there is nothing meaningful to present — and everything D-8 could
|
|
347
|
+
// pick here (the pool secret, or a legacy env bearer) is a secret sent to an unverified address
|
|
348
|
+
// for a call the box cannot authenticate either way. The pool secret is proven by the CODE and
|
|
349
|
+
// by the body's `token`, which is what the box adopts.
|
|
350
|
+
const join = await _fetchJson(`${boxUrl}/api/machine-pairing/join`, {
|
|
351
|
+
method: 'POST',
|
|
352
|
+
headers: {},
|
|
353
|
+
body: { code: machinePairing.formatCode(code), token, version, members, primary: { instanceId: selfId } },
|
|
354
|
+
});
|
|
355
|
+
if (!join.ok) {
|
|
356
|
+
const relayed = String((join.body && join.body.error) || '');
|
|
357
|
+
if (relayed === 'pairing_code_invalid') return _send(res, 410, { ok: false, error: 'pairing_code_invalid' });
|
|
358
|
+
if (relayed === 'rate_limited') return _send(res, 429, { ok: false, error: 'rate_limited' });
|
|
359
|
+
if (relayed === 'pool_member_exists') return _send(res, 409, { ok: false, error: 'pool_member_exists' });
|
|
360
|
+
if (relayed === 'pool_token_invalid') return _send(res, 400, { ok: false, error: 'pool_token_invalid' });
|
|
361
|
+
return _send(res, 502, { ok: false, error: 'machine_unreachable', detail: relayed || `http_${join.status}` });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 6. record the box locally. `url` is the address we just PROVED reachable (the probe's winner) and
|
|
365
|
+
// it leads the dial list; the box's own advertised URLs follow as fallbacks (P-4). A row whose
|
|
366
|
+
// first URL is a LAN address this member cannot reach would leave the new member unusable.
|
|
367
|
+
const advertised = Array.isArray(join.body && join.body.urls) ? join.body.urls.map((url) => String(url || '')) : [];
|
|
368
|
+
const boxUrls = [boxUrl, ...advertised.filter((url) => url && url !== boxUrl)];
|
|
369
|
+
await _deps.db.upsertMachineRow({
|
|
370
|
+
instanceId: boxInstanceId,
|
|
371
|
+
displayName: String((join.body && join.body.displayName) || '').slice(0, 200),
|
|
372
|
+
url: boxUrl,
|
|
373
|
+
urls: boxUrls,
|
|
374
|
+
});
|
|
375
|
+
await pool.addMember(boxInstanceId);
|
|
376
|
+
if (_deps.registry && typeof _deps.registry.noteRegistryChanged === 'function') _deps.registry.noteRegistryChanged();
|
|
377
|
+
|
|
378
|
+
// 7. P-5: push the roster to every OTHER member, best-effort. The box is skipped — it was just
|
|
379
|
+
// handed the same set (and the same version) inside the join request. A failure is not an error
|
|
380
|
+
// here: the next sweep pull (lib/machine-registry.js) converges the same state.
|
|
381
|
+
const roster = _rosterMembers();
|
|
382
|
+
const targets = roster.filter((member) => member.instanceId !== selfId && member.instanceId !== boxInstanceId);
|
|
383
|
+
const pushed = [];
|
|
384
|
+
const unreachable = [];
|
|
385
|
+
for (const target of targets) {
|
|
386
|
+
const ok = await _postToTarget(target, '/api/pool/roster', { version, members: roster }, _peerHeadersFor(target.instanceId));
|
|
387
|
+
if (ok) pushed.push(target.instanceId);
|
|
388
|
+
else unreachable.push(target.instanceId);
|
|
389
|
+
}
|
|
390
|
+
return _send(res, 201, { ok: true, machine: { instanceId: boxInstanceId, url: boxUrl, urls: boxUrls }, pushed, unreachable });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* §6.3: unregister a machine. A POOL member also rotates: the removed machine holds the shared
|
|
395
|
+
* secret, so nothing but a new one keeps it out. A legacy env-token row (P-8) holds no pool secret
|
|
396
|
+
* and keeps the plain delete.
|
|
397
|
+
*/
|
|
398
|
+
async function handleMachineRemove(req, res, url, params) {
|
|
399
|
+
const instanceId = decodeURIComponent((params && (params['1'] || params.id)) || '');
|
|
400
|
+
if (!instanceId) return _send(res, 400, { ok: false, error: 'machine_id_required' });
|
|
401
|
+
const pool = _pool();
|
|
402
|
+
const rows = _deps.db.listMachineRows() || [];
|
|
403
|
+
if (!pool.hasPool() || !pool.isMember(instanceId)) {
|
|
404
|
+
// WP-2: the machine writes are DB-owner mutations (db.js's DB_OWNER_MUTATION_ACTIONS), so the
|
|
405
|
+
// handle here is the owner-client Proxy and this call is a PROMISE. Await it before computing
|
|
406
|
+
// `removed` — a synchronous read of the result would report "0 removed" for every real delete.
|
|
407
|
+
const removed = await _deps.db.deleteMachineRow(instanceId);
|
|
408
|
+
if (_deps.registry && typeof _deps.registry.noteRegistryChanged === 'function') _deps.registry.noteRegistryChanged();
|
|
409
|
+
return _send(res, 200, { ok: true, removed: Number(removed) || 0 });
|
|
410
|
+
}
|
|
411
|
+
await ready();
|
|
412
|
+
const selfId = _instanceId();
|
|
413
|
+
// P-6: capture the OLD credential headers BEFORE rotate(). Every remaining member still holds the
|
|
414
|
+
// old secret, so it is the only credential that can authenticate the rotation itself.
|
|
415
|
+
const oldHeaders = new Map();
|
|
416
|
+
for (const row of rows) {
|
|
417
|
+
if (row.instanceId && row.instanceId !== instanceId) oldHeaders.set(row.instanceId, _peerHeadersFor(row.instanceId));
|
|
418
|
+
}
|
|
419
|
+
const removed = await _deps.db.deleteMachineRow(instanceId);
|
|
420
|
+
await pool.removeMember(instanceId);
|
|
421
|
+
const version = await pool.bumpRoster(selfId);
|
|
422
|
+
const roster = _rosterMembers();
|
|
423
|
+
const targets = roster.filter((member) => member.instanceId !== selfId);
|
|
424
|
+
// P-5 first: the remaining members learn the new membership set while the old secret still works.
|
|
425
|
+
for (const target of targets) {
|
|
426
|
+
await _postToTarget(target, '/api/pool/roster', { version, members: roster }, oldHeaders.get(target.instanceId));
|
|
427
|
+
}
|
|
428
|
+
// P-6: then the secret itself changes, and each reachable member is told (with the OLD credential).
|
|
429
|
+
const newToken = await pool.rotate();
|
|
430
|
+
const rotated = [];
|
|
431
|
+
const unreachable = [];
|
|
432
|
+
for (const target of targets) {
|
|
433
|
+
const ok = await _postToTarget(target, '/api/pool/rotate', { token: newToken }, oldHeaders.get(target.instanceId));
|
|
434
|
+
if (ok) rotated.push(target.instanceId);
|
|
435
|
+
else unreachable.push(target.instanceId);
|
|
436
|
+
}
|
|
437
|
+
if (_deps.registry && typeof _deps.registry.noteRegistryChanged === 'function') _deps.registry.noteRegistryChanged();
|
|
438
|
+
// §6.3/AC-3d: `unreachable` members are the ones the UI warns about — they still hold the OLD
|
|
439
|
+
// secret and must re-pair (the old one stops being accepted after lib/machine-pool.js's 10 min
|
|
440
|
+
// rotation grace, ROTATION_GRACE_MS).
|
|
441
|
+
return _send(res, 200, { ok: true, removed: Number(removed) || 0, rotated, unreachable });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function register() {
|
|
445
|
+
registerApiRoutes('machines', [
|
|
446
|
+
{ method: 'GET', path: '/api/machines', handler: (req, res) => handleMachinesList(req, res) },
|
|
447
|
+
{ method: 'POST', path: '/api/machines', handler: (req, res) => handleMachinesCreate(req, res) },
|
|
448
|
+
{ method: 'POST', path: '/api/machines/pair', handler: (req, res) => handleMachinesPair(req, res) },
|
|
449
|
+
{ method: 'POST', regex: /^\/api\/machines\/([^/]+)\/remove$/, handler: (req, res, url, params) => handleMachineRemove(req, res, url, params) },
|
|
450
|
+
]);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
module.exports = {
|
|
454
|
+
configure,
|
|
455
|
+
register,
|
|
456
|
+
ready,
|
|
457
|
+
handleMachinesList,
|
|
458
|
+
handleMachinesCreate,
|
|
459
|
+
handleMachinesPair,
|
|
460
|
+
handleMachineRemove,
|
|
461
|
+
};
|
|
@@ -32,9 +32,19 @@
|
|
|
32
32
|
// rounds out the DISPATCH-ONLY additions.
|
|
33
33
|
|
|
34
34
|
const { registerApiRoutes } = require('../api-router');
|
|
35
|
+
const remoteMachineProxy = require('../remote-machine-proxy');
|
|
35
36
|
|
|
36
37
|
let _deps = null;
|
|
37
38
|
|
|
39
|
+
// Remote machine hub WP-4 (§6.5): a picker request naming another machine (?machine= or
|
|
40
|
+
// X-CTM-Machine) is answered from that box; every other request is the unchanged local handler.
|
|
41
|
+
function _machineRouted(route, local) {
|
|
42
|
+
return (req, res, url) => (remoteMachineProxy.isMachineRouted(req, url)
|
|
43
|
+
? remoteMachineProxy.maybeProxyMachineRequest(req, res, url, { route })
|
|
44
|
+
.then((handled) => (handled ? undefined : local(req, res, url)))
|
|
45
|
+
: local(req, res, url));
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
function configure({
|
|
39
49
|
apiCtmCoopJobsStatus,
|
|
40
50
|
apiCtmSessionRowsStatus,
|
|
@@ -59,6 +69,7 @@ function configure({
|
|
|
59
69
|
apiDeleteHook,
|
|
60
70
|
apiRestartCtm,
|
|
61
71
|
restartGuardSnapshot,
|
|
72
|
+
proxyPreflight,
|
|
62
73
|
wss,
|
|
63
74
|
clientBuildDiagnosticDetails,
|
|
64
75
|
attachedSessionIds,
|
|
@@ -93,6 +104,7 @@ function configure({
|
|
|
93
104
|
apiDeleteHook,
|
|
94
105
|
apiRestartCtm,
|
|
95
106
|
restartGuardSnapshot,
|
|
107
|
+
proxyPreflight,
|
|
96
108
|
wss,
|
|
97
109
|
clientBuildDiagnosticDetails,
|
|
98
110
|
attachedSessionIds,
|
|
@@ -213,9 +225,12 @@ async function handleConversationDebtRedrive(req, res, url) {
|
|
|
213
225
|
|
|
214
226
|
function register() {
|
|
215
227
|
registerApiRoutes('restart', [
|
|
216
|
-
{ method: 'GET', path: '/api/restart/ctm/preflight', handler: (req, res) => {
|
|
228
|
+
{ method: 'GET', path: '/api/restart/ctm/preflight', handler: async (req, res) => {
|
|
229
|
+
// WP-3 D-10 (model-proxy-survives-ctm-restart): the model-proxy verdict is a live probe of the
|
|
230
|
+
// proxy's record and /_health, so it is awaited here and injected into the otherwise-sync snapshot.
|
|
231
|
+
const proxy = await _deps.proxyPreflight();
|
|
217
232
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
218
|
-
res.end(JSON.stringify({ ok: true, preflight: _deps.restartGuardSnapshot().preflight }));
|
|
233
|
+
res.end(JSON.stringify({ ok: true, preflight: _deps.restartGuardSnapshot(Date.now(), { proxy }).preflight }));
|
|
219
234
|
} },
|
|
220
235
|
]);
|
|
221
236
|
registerApiRoutes('misc-ops', [
|
|
@@ -248,9 +263,9 @@ function register() {
|
|
|
248
263
|
handler: (req, res, url) => handleWorkspaceSessionsDelete(req, res, url),
|
|
249
264
|
},
|
|
250
265
|
{ method: 'DELETE', prefix: '/api/workspaces/', handler: (req, res, url) => handleWorkspaceDelete(req, res, url) },
|
|
251
|
-
{ method: 'GET', path: '/api/projects', handler: (req, res) => _deps.apiGetProjects(req, res) },
|
|
252
|
-
{ method: 'GET', path: '/api/path-suggest', handler: (req, res, url) => _deps.apiPathSuggest(req, res, url) },
|
|
253
|
-
{ method: 'POST', path: '/api/path-status', handler: (req, res) => _deps.apiPathStatus(req, res) },
|
|
266
|
+
{ method: 'GET', path: '/api/projects', handler: _machineRouted('GET /api/projects', (req, res) => _deps.apiGetProjects(req, res)) },
|
|
267
|
+
{ method: 'GET', path: '/api/path-suggest', handler: _machineRouted('GET /api/path-suggest', (req, res, url) => _deps.apiPathSuggest(req, res, url)) },
|
|
268
|
+
{ method: 'POST', path: '/api/path-status', handler: _machineRouted('POST /api/path-status', (req, res) => _deps.apiPathStatus(req, res)) },
|
|
254
269
|
{ method: 'GET', path: '/api/rules', handler: (req, res, url) => _deps.apiGetRules(req, res, url) },
|
|
255
270
|
{ method: 'PUT', path: '/api/rules', handler: (req, res) => _deps.apiPutRules(req, res) },
|
|
256
271
|
{ method: 'GET', path: '/api/rules/list', handler: (req, res) => _deps.apiListRules(req, res) },
|
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
// Round-3 WP-5 T3 group 5 (design doc §5 D-1/D-3/D-8, §8 WP-5, §9.5 S-42):
|
|
4
4
|
// small NEW module for the coding-CLI provider OAuth login broker cluster --
|
|
5
|
-
// GET /api/providers/login-state
|
|
5
|
+
// GET /api/providers/login-state, POST /api/providers/:id/login and
|
|
6
|
+
// POST /api/providers/:id/login/code. This is
|
|
6
7
|
// a distinct "providers" concept from lib/api/setup.js's LLM-provider
|
|
7
8
|
// API-key setup (setup/providers, setup/save-provider, etc.): providerLoginBroker
|
|
8
9
|
// tracks OAuth login flows for coding-agent CLIs (claude/codex/gemini), not
|
|
9
10
|
// API-key-based model providers, so it gets its own module rather than
|
|
10
|
-
// extending setup.js. DISPATCH-ONLY:
|
|
11
|
-
//
|
|
11
|
+
// extending setup.js. DISPATCH-ONLY: every route calls a providerLoginBroker
|
|
12
|
+
// method directly, no server.js function bodies to move.
|
|
12
13
|
//
|
|
13
|
-
// The POST
|
|
14
|
+
// The POST routes carry a regex (`/^\/api\/providers\/([^/]+)\/login$/` and
|
|
15
|
+
// `/^\/api\/providers\/([^/]+)\/login\/code$/`)
|
|
14
16
|
// the legacy chain's own scanner never counted (the `[^/]` character class
|
|
15
17
|
// breaks bin/generate-route-golden.js's REGEX_RE, so this spec never showed
|
|
16
18
|
// up in lint/route-golden.json's chain -- see lib/api/sessions.js's group-5
|
|
17
19
|
// header for the same gap on /api/sessions/:id/interrupt etc.).
|
|
18
20
|
|
|
19
21
|
const { registerApiRoutes } = require('../api-router');
|
|
22
|
+
const remoteMachineProxy = require('../remote-machine-proxy');
|
|
20
23
|
|
|
21
24
|
let _deps = null;
|
|
22
25
|
|
|
@@ -24,6 +27,24 @@ function configure({ providerLoginBroker, readJsonRequestBody } = {}) {
|
|
|
24
27
|
_deps = { providerLoginBroker, readJsonRequestBody };
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
// WP-3 (remote-agent-sign-in-from-hub-browser-2026-10-25.html §5 D-7/D-9, §6): a sign-in request
|
|
31
|
+
// that names ANOTHER machine (`?machine=` / `X-CTM-Machine`) is answered from that box over the
|
|
32
|
+
// peer-bearer leg — the wrapper shape lib/api/misc-ops.js:41 uses. A request naming no machine
|
|
33
|
+
// (or THIS instance) is the unchanged local handler, byte for byte.
|
|
34
|
+
//
|
|
35
|
+
// Unlike misc-ops' routes (all exact paths), two of these are regex routes, so the wrapper
|
|
36
|
+
// forwards the router's `params` (api-router.js:136) as well: dropping it would read `params['1']`
|
|
37
|
+
// off undefined. `...args` keeps the local call identical to a directly-registered handler.
|
|
38
|
+
function _machineRouted(route, local) {
|
|
39
|
+
return (...args) => {
|
|
40
|
+
const [req, res, url] = args;
|
|
41
|
+
return remoteMachineProxy.isMachineRouted(req, url)
|
|
42
|
+
? remoteMachineProxy.maybeProxyMachineRequest(req, res, url, { route })
|
|
43
|
+
.then((handled) => (handled ? undefined : local(...args)))
|
|
44
|
+
: local(...args);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
// WP-0: getState() now awaits the CLI account probe (lib/coding-agent-auth), so both
|
|
28
49
|
// handlers are async — the api-router awaits handler promises before falling through.
|
|
29
50
|
async function handleProvidersLoginState(req, res) {
|
|
@@ -52,13 +73,39 @@ async function handleProviderLogin(req, res, providerIdRaw) {
|
|
|
52
73
|
res.end(JSON.stringify(result));
|
|
53
74
|
}
|
|
54
75
|
|
|
76
|
+
// WP-1 (D-4/D-5/D-6): the code-return route. Body shaping only — the broker owns every
|
|
77
|
+
// validation (open flow, live tab, `codeEntry === 'paste'`, normalisation, state match) and
|
|
78
|
+
// the single PTY write, so this handler never decides anything about a code. An unreadable or
|
|
79
|
+
// oversized body simply arrives as `code: ''`, which the broker refuses as `code_format_invalid`.
|
|
80
|
+
async function handleProviderLoginCode(req, res, providerIdRaw) {
|
|
81
|
+
const providerId = decodeURIComponent(providerIdRaw || '');
|
|
82
|
+
let body = {};
|
|
83
|
+
if (_deps.readJsonRequestBody) {
|
|
84
|
+
try {
|
|
85
|
+
body = (await _deps.readJsonRequestBody(req, { limitBytes: 16 * 1024 })) || {};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// HANDLE: an unreadable/oversized body is not a 500 — the missing code is the refusal.
|
|
88
|
+
body = {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const code = body && typeof body === 'object' && typeof body.code === 'string' ? body.code : '';
|
|
92
|
+
const result = await _deps.providerLoginBroker.submitCode(providerId, { code });
|
|
93
|
+
res.writeHead(result.status || 500, { 'Content-Type': 'application/json' });
|
|
94
|
+
res.end(JSON.stringify(result));
|
|
95
|
+
}
|
|
96
|
+
|
|
55
97
|
function register() {
|
|
56
98
|
registerApiRoutes('providers', [
|
|
57
|
-
{ method: 'GET', path: '/api/providers/login-state', handler: (req, res) => handleProvidersLoginState(req, res) },
|
|
99
|
+
{ method: 'GET', path: '/api/providers/login-state', handler: _machineRouted('GET /api/providers/login-state', (req, res) => handleProvidersLoginState(req, res)) },
|
|
58
100
|
{
|
|
59
101
|
method: 'POST',
|
|
60
102
|
regex: /^\/api\/providers\/([^/]+)\/login$/,
|
|
61
|
-
handler: (req, res, url, params) => handleProviderLogin(req, res, params['1']),
|
|
103
|
+
handler: _machineRouted('POST /api/providers/:id/login', (req, res, url, params) => handleProviderLogin(req, res, params['1'])),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
method: 'POST',
|
|
107
|
+
regex: /^\/api\/providers\/([^/]+)\/login\/code$/,
|
|
108
|
+
handler: _machineRouted('POST /api/providers/:id/login/code', (req, res, url, params) => handleProviderLoginCode(req, res, params['1'])),
|
|
62
109
|
},
|
|
63
110
|
]);
|
|
64
111
|
}
|
|
@@ -56,6 +56,31 @@ async function handleResumeFromStore(req, res, deps = _deps) {
|
|
|
56
56
|
const cwd = _firstText(ctm.cwd, ctm.project_path, agent.project_path, body.cwd);
|
|
57
57
|
if (!cwd) return send(res, 409, { error: 'no_cwd', message: 'No working directory is known for this session.' });
|
|
58
58
|
|
|
59
|
+
// D-5 (session-exit-close-truth §5): the rebuild is proven safe, never assumed. Ask the resume
|
|
60
|
+
// ladder -- exactly as the resume-rung route does -- whether the original transcript is still on
|
|
61
|
+
// this machine, and refuse the lossy reconstruction when it is. Only the ladder's refusal to
|
|
62
|
+
// answer (a throw) refuses too; an unrecognized rung is not evidence of an intact transcript.
|
|
63
|
+
try {
|
|
64
|
+
const resolve = deps.resolveResumeRung || resolveResumeRungOffThread;
|
|
65
|
+
const ladder = await resolve({ ..._ladderInput(identity, ctm.id, provider), dbPath: deps.getDbPath(), trashRoot: deps.trashRoot });
|
|
66
|
+
const rung = String((ladder && ladder.rung) || '');
|
|
67
|
+
if ((rung === 'native' && ladder.offerStoreFirst !== true) || rung === 'restore' || rung === 'trash') {
|
|
68
|
+
return send(res, 409, {
|
|
69
|
+
error: 'native_transcript_available',
|
|
70
|
+
rung,
|
|
71
|
+
message: 'The original transcript is still on this machine. Resume the session instead.',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// HANDLE: the ladder could not answer (read pool down, probes failed). Refuse rather than
|
|
76
|
+
// reconstruct on a guess; the card's "Check again" re-asks.
|
|
77
|
+
console.error('[resume-from-store] rung check failed:', String((error && error.message) || error));
|
|
78
|
+
return send(res, 409, {
|
|
79
|
+
error: 'rung_unknown',
|
|
80
|
+
message: 'CTM could not check whether the original transcript still exists. Try again.',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
59
84
|
try {
|
|
60
85
|
const build = deps.buildResumeContext || buildResumeContextOffThread;
|
|
61
86
|
const plan = await build({ dbPath: deps.getDbPath(), ctmSessionId: ctm.id });
|