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
|
@@ -45,15 +45,20 @@
|
|
|
45
45
|
// handleSessionsRepoint above.
|
|
46
46
|
|
|
47
47
|
const { registerApiRoutes } = require('../api-router');
|
|
48
|
+
// WP-6 (§7 ownership routing): the mirror-session lifecycle seam. Required here rather than
|
|
49
|
+
// injected so the route table needs no server.js plumbing beyond the deps it already has; the
|
|
50
|
+
// module resolves its own dependencies lazily and is inert until a machine is registered.
|
|
51
|
+
const remoteMachineCatalog = require('../remote-machine-catalog');
|
|
48
52
|
const workerAdmission = require('../worker-admission');
|
|
49
53
|
const workerObservability = require('../session-create/worker-observability');
|
|
54
|
+
const { createWaitWatcherReadTail, WATCH_MODES } = require('../session-create/worker-wait-watcher');
|
|
50
55
|
// Caller door D-11 (delegate-followups-lead-posture-boot-truth-brain-successors-caller-door-
|
|
51
56
|
// 2026-10-10.html §5): the runtime bearer the four caller-context routes require even on
|
|
52
57
|
// loopback. resolveRuntimeGoalToken reads CTM_TOKEN -> WALLE_CTM_TOKEN -> CTM_API_TOKEN
|
|
53
58
|
// (server.js boot sets WALLE_CTM_TOKEN to the runtime token); equalToken is the existing
|
|
54
59
|
// constant-time compare -- an EMPTY token never authenticates (equalToken('','') is false).
|
|
55
60
|
const { resolveRuntimeGoalToken, equalToken } = require('../runtime-goal-api');
|
|
56
|
-
const { NEXT_STEPS } = require('../worker-failure-classify');
|
|
61
|
+
const { NEXT_STEPS, waitNextStep } = require('../worker-failure-classify');
|
|
57
62
|
const { serveNativeSubagentReview, resolveWorkerTranscriptTarget } = require('./native-subagent-review');
|
|
58
63
|
const { normalizeAgentType } = require('../agent-capabilities');
|
|
59
64
|
|
|
@@ -530,7 +535,9 @@ async function handleDelegateParent(req, res, url) {
|
|
|
530
535
|
// contents, capped at 64 KB) when the report is present -- walle_worker_status needs the
|
|
531
536
|
// text itself, not just report_present, so a lead agent can read a finished worker's report
|
|
532
537
|
// through the MCP tool without a second round-trip.
|
|
533
|
-
|
|
538
|
+
// D-3 (lead-cost-routing-round2-2026-10-24.html §5 D-3, §6.2): the cap has ONE definition,
|
|
539
|
+
// in worker-observability, shared with the status reader that counts citations from the same text.
|
|
540
|
+
const { WORKER_REPORT_TEXT_CAP_BYTES } = workerObservability;
|
|
534
541
|
|
|
535
542
|
// Lifecycle WP-5 (delegated-worker-lifecycle-identity-observability-2026-09-27.html §5 D-10,
|
|
536
543
|
// §9 S-13): the terminal statuses D-10's retain-then-hide clock applies to.
|
|
@@ -546,6 +553,67 @@ const _WORKER_TERMINAL_STATUSES = new Set(['report_ready', 'failed', 'exited']);
|
|
|
546
553
|
const WORKER_WAIT_MIN_MS = 1000;
|
|
547
554
|
const WORKER_WAIT_MAX_MS = 540000;
|
|
548
555
|
|
|
556
|
+
// WP-2 (lead-cost-routing-round1-2026-10-21.html §5 D-5): Codex errors an MCP tool call after
|
|
557
|
+
// 300 s and ignores progress notifications, so a Codex LEAD's wait is held under that ceiling --
|
|
558
|
+
// 280 s leaves room for the status payload and the stdio hop. Claude leads keep
|
|
559
|
+
// WORKER_WAIT_MAX_MS; a caller whose harness cannot be read is never capped, because an unknown
|
|
560
|
+
// harness must not silently shorten a wait it cannot justify.
|
|
561
|
+
const CODEX_LEAD_WAIT_CAP_MS = 280000;
|
|
562
|
+
|
|
563
|
+
// The caller's harness: the live session's own `type` (CtmSession.type, server-state.js) first,
|
|
564
|
+
// then the agent_sessions row's `provider` column through the reader this file already uses for
|
|
565
|
+
// the lead (getAgentSessionByCtmId -- see delegate-worker.js's getWorkerStatus). '' means
|
|
566
|
+
// unknown; callers must treat that as "no cap", never as Claude.
|
|
567
|
+
function leadHarnessType(ctmSessionId) {
|
|
568
|
+
const id = String(ctmSessionId || '').trim();
|
|
569
|
+
if (!id) return '';
|
|
570
|
+
try {
|
|
571
|
+
const live = typeof _deps.getSession === 'function' ? _deps.getSession(id) : null;
|
|
572
|
+
const liveType = live && typeof live.type === 'string' ? live.type.trim() : '';
|
|
573
|
+
if (liveType) return normalizeAgentType(liveType) || liveType;
|
|
574
|
+
} catch { /* expected: an unreadable session map falls through to the agent-session row below */ }
|
|
575
|
+
try {
|
|
576
|
+
const row = typeof _deps.dbModule?.getAgentSessionByCtmId === 'function'
|
|
577
|
+
? _deps.dbModule.getAgentSessionByCtmId(id)
|
|
578
|
+
: null;
|
|
579
|
+
const provider = row && typeof row.provider === 'string' ? row.provider.trim() : '';
|
|
580
|
+
if (provider) return normalizeAgentType(provider) || provider;
|
|
581
|
+
} catch { /* expected: an unreadable row leaves the harness unknown -- the caller is not capped */ }
|
|
582
|
+
return '';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// D-5: the Codex cap applied to an already-clamped wait.
|
|
586
|
+
function capWaitMsForLead(waitMs, leadCtmSessionId) {
|
|
587
|
+
return leadHarnessType(leadCtmSessionId) === 'codex' ? Math.min(waitMs, CODEX_LEAD_WAIT_CAP_MS) : waitMs;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// WP-1 (D-3/D-4): `?watch=on|silent|off` on the two wait routes (default 'on'). Returns the
|
|
591
|
+
// validated mode, or null for anything else -- the caller answers 400 watch_invalid.
|
|
592
|
+
function parseWatchMode(url) {
|
|
593
|
+
const raw = url && url.searchParams ? url.searchParams.get('watch') : null;
|
|
594
|
+
if (raw === null) return 'on';
|
|
595
|
+
const value = String(raw).trim();
|
|
596
|
+
return WATCH_MODES.has(value) ? value : null;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// WP-1: the wait routes' watcher wiring -- the production model call (lib/background-llm.js via
|
|
600
|
+
// worker-observability.js, never a provider client here), the SAME status reader the route
|
|
601
|
+
// already reads through, the worker's own transcript tail, and the worker row's own pin
|
|
602
|
+
// (route_alias + ':' + model; a row without one starts no watcher).
|
|
603
|
+
function waitWatchWiring() {
|
|
604
|
+
const statusReader = _deps.delegateWorker.getWorkerStatusWithProgress || _deps.delegateWorker.getWorkerStatus;
|
|
605
|
+
return {
|
|
606
|
+
callModel: workerObservability.createWaitWatcherCallModel(),
|
|
607
|
+
getStatus: (id) => statusReader.call(_deps.delegateWorker, id),
|
|
608
|
+
readTail: createWaitWatcherReadTail({ dbModule: _deps.dbModule }),
|
|
609
|
+
workerRoute: (id) => {
|
|
610
|
+
const row = _deps.dbModule && typeof _deps.dbModule.getWorkerSession === 'function' ? _deps.dbModule.getWorkerSession(id) : null;
|
|
611
|
+
if (!row || !row.route_alias || !row.model) return null;
|
|
612
|
+
return { route: String(row.route_alias), model: String(row.model) };
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
549
617
|
// D-10b: ?wait_ms=N parks this request on the worker-change registry (no polling) and then
|
|
550
618
|
// answers with the ordinary status read plus woke_on / waited_ms.
|
|
551
619
|
// WP-5 (D-12, D-15): ?lead_agent_session_id=<id> makes the single-worker wait ALSO wake when a
|
|
@@ -558,6 +626,8 @@ const WORKER_WAIT_MAX_MS = 540000;
|
|
|
558
626
|
// read without wait_ms stays exactly as today (bearer-free, loopback auto-admin).
|
|
559
627
|
async function handleWorkerStatus(req, res, sessionId, url) {
|
|
560
628
|
let wait = null;
|
|
629
|
+
const watchMode = parseWatchMode(url);
|
|
630
|
+
if (watchMode === null) return _deps.sendJsonResponse(res, 400, { error: 'watch_invalid' });
|
|
561
631
|
const rawWait = url && url.searchParams ? url.searchParams.get('wait_ms') : null;
|
|
562
632
|
if (rawWait !== null) {
|
|
563
633
|
if (!requireRuntimeBearer(req, res)) return undefined;
|
|
@@ -593,22 +663,33 @@ async function handleWorkerStatus(req, res, sessionId, url) {
|
|
|
593
663
|
return _deps.sendJsonResponse(res, 403, { error: 'worker_not_owned', next_step: NEXT_STEPS.worker_not_owned });
|
|
594
664
|
}
|
|
595
665
|
}
|
|
666
|
+
// WP-2 (D-5): the caller's harness is only known once the lead resolved above, so the Codex
|
|
667
|
+
// cap is applied here rather than at the clamp -- a Codex lead never parks past Codex's 300 s
|
|
668
|
+
// MCP call ceiling. No lead param (or an unreadable one) means no cap.
|
|
669
|
+
wait.waitMs = capWaitMsForLead(wait.waitMs, leadCtmSessionId);
|
|
596
670
|
const handle = workerObservability.waitForWorkers({
|
|
597
671
|
workerIds: [sessionId], leadId: leadCtmSessionId || null, waitMs: wait.waitMs,
|
|
598
672
|
generationById: { [sessionId]: liveGeneration },
|
|
673
|
+
...(watchMode === 'off' ? {} : { watchMode, ...waitWatchWiring() }),
|
|
599
674
|
});
|
|
675
|
+
wait.decisions = [];
|
|
600
676
|
let gone = false;
|
|
601
677
|
res.on('close', () => { if (!res.writableEnded) { gone = true; handle.release(); } });
|
|
602
678
|
try {
|
|
603
679
|
const statusReader = _deps.delegateWorker.getWorkerStatusWithProgress || _deps.delegateWorker.getWorkerStatus;
|
|
604
680
|
const first = await statusReader.call(_deps.delegateWorker, sessionId);
|
|
605
681
|
if (gone) return undefined;
|
|
606
|
-
|
|
682
|
+
// WP-1 (session-stops-followups-2026-10-24.html D-1): a report written while the process
|
|
683
|
+
// is still alive is not terminal -- park and let the real exit wake us.
|
|
684
|
+
if (first && workerObservability.isFinalWorkerStatus(first)) {
|
|
607
685
|
handle.release();
|
|
608
686
|
wait.wokeOn = 'terminal';
|
|
609
687
|
} else {
|
|
610
688
|
if (first && first.generation != null) handle.setGeneration(sessionId, first.generation);
|
|
611
|
-
|
|
689
|
+
const wake = await handle.promise;
|
|
690
|
+
wait.wokeOn = wake.why;
|
|
691
|
+
wait.watcherReason = wake.detail && typeof wake.detail.reason === 'string' ? wake.detail.reason : null;
|
|
692
|
+
wait.decisions = wake.decisions || [];
|
|
612
693
|
wait.waitedMs = Date.now() - startedAt;
|
|
613
694
|
if (gone) return undefined;
|
|
614
695
|
}
|
|
@@ -626,7 +707,23 @@ async function handleWorkerStatus(req, res, sessionId, url) {
|
|
|
626
707
|
async function handleWorkerStatusRead(req, res, sessionId, url, wait) {
|
|
627
708
|
try {
|
|
628
709
|
const result = await workerStatusReadPayload(sessionId, url);
|
|
629
|
-
if (wait && result) {
|
|
710
|
+
if (wait && result) {
|
|
711
|
+
result.woke_on = wait.wokeOn;
|
|
712
|
+
result.waited_ms = wait.waitedMs;
|
|
713
|
+
// WP-1 (D-3): the watcher's own outcome rides beside woke_on -- why it woke the lead and
|
|
714
|
+
// every decision it recorded (empty when no tick ran or no watcher started).
|
|
715
|
+
result.watcher_reason = wait.watcherReason || null;
|
|
716
|
+
result.watcher_decisions = wait.decisions || [];
|
|
717
|
+
// WP-2 (session-stops-mid-job-2026-10-24.html D-9, AC-2a): a non-terminal wake names
|
|
718
|
+
// the worker that is still live and tells the lead to re-issue the wait -- this wait
|
|
719
|
+
// covered exactly one worker, so its own status is the whole answer. A terminal wake
|
|
720
|
+
// leaves nothing to wait on: null.
|
|
721
|
+
// WP-1 (session-stops-followups-2026-10-24.html D-1/D-4): a provisional report_ready is
|
|
722
|
+
// non-final, so the lead is told to keep waiting -- and the status words say why.
|
|
723
|
+
result.lead_next_step = (typeof result.status === 'string' && !workerObservability.isFinalWorkerStatus(result))
|
|
724
|
+
? waitNextStep({ workerId: sessionId, status: waitStatusLabel(result) })
|
|
725
|
+
: null;
|
|
726
|
+
}
|
|
630
727
|
return _deps.sendJsonResponse(res, 200, result);
|
|
631
728
|
} catch (error) {
|
|
632
729
|
const status = Number(error && error.status) || 400;
|
|
@@ -647,6 +744,8 @@ async function handleWorkerStatusRead(req, res, sessionId, url, wait) {
|
|
|
647
744
|
async function handleWorkersWait(req, res, url) {
|
|
648
745
|
// Caller door D-11: the whole-set wait requires the runtime bearer even on loopback.
|
|
649
746
|
if (!requireRuntimeBearer(req, res)) return undefined;
|
|
747
|
+
const watchMode = parseWatchMode(url);
|
|
748
|
+
if (watchMode === null) return _deps.sendJsonResponse(res, 400, { error: 'watch_invalid' });
|
|
650
749
|
const rawLead = url && url.searchParams ? String(url.searchParams.get('lead_agent_session_id') || '').trim() : '';
|
|
651
750
|
if (!rawLead) return _deps.sendJsonResponse(res, 400, { error: 'wait_scope_unresolved' });
|
|
652
751
|
if (rawLead.length > 160 || /[\x00-\x1f\x7f]/.test(rawLead)) {
|
|
@@ -657,7 +756,12 @@ async function handleWorkersWait(req, res, url) {
|
|
|
657
756
|
if (!leadCtmSessionId) return _deps.sendJsonResponse(res, 400, { error: 'wait_scope_unresolved' });
|
|
658
757
|
const rawWait = url && url.searchParams ? url.searchParams.get('wait_ms') : null;
|
|
659
758
|
if (rawWait !== null && !/^\d+$/.test(rawWait)) return _deps.sendJsonResponse(res, 400, { error: 'wait_ms_invalid' });
|
|
660
|
-
|
|
759
|
+
// WP-2 (D-5): a Codex lead's wait is held under Codex's 300 s MCP call ceiling right after the
|
|
760
|
+
// ordinary clamp; the route-scoped `?route=` wait below shares the same capped value.
|
|
761
|
+
const waitMs = capWaitMsForLead(
|
|
762
|
+
Math.min(WORKER_WAIT_MAX_MS, Math.max(WORKER_WAIT_MIN_MS, rawWait !== null ? Number(rawWait) : 240000)),
|
|
763
|
+
leadCtmSessionId,
|
|
764
|
+
);
|
|
661
765
|
// D-7: an optional `route` parks on the alias's hold instead of the lead's workers.
|
|
662
766
|
const rawRoute = url && url.searchParams ? url.searchParams.get('route') : null;
|
|
663
767
|
if (rawRoute !== null) {
|
|
@@ -676,6 +780,8 @@ async function handleWorkersWait(req, res, url) {
|
|
|
676
780
|
waited_ms: Date.now() - routeStartedAt,
|
|
677
781
|
route: rawRoute,
|
|
678
782
|
admission: workerAdmission.snapshot()[rawRoute] || null,
|
|
783
|
+
// WP-2 (D-9): a route hold covers no worker of this lead -- nothing is live to wait on.
|
|
784
|
+
lead_next_step: null,
|
|
679
785
|
});
|
|
680
786
|
}
|
|
681
787
|
let rows = [];
|
|
@@ -694,6 +800,8 @@ async function handleWorkersWait(req, res, url) {
|
|
|
694
800
|
const startedAt = Date.now();
|
|
695
801
|
let wokeOn = 'timeout';
|
|
696
802
|
let wokeWorkerId = null;
|
|
803
|
+
let watcherReason = null;
|
|
804
|
+
let watcherDecisions = [];
|
|
697
805
|
if (pendingTerminal) {
|
|
698
806
|
wokeOn = 'terminal';
|
|
699
807
|
wokeWorkerId = pendingTerminal.ctm_session_id;
|
|
@@ -703,6 +811,7 @@ async function handleWorkersWait(req, res, url) {
|
|
|
703
811
|
for (const row of rows) generationById[row.ctm_session_id] = _deps.getSession(row.ctm_session_id)?._workerGeneration;
|
|
704
812
|
const handle = workerObservability.waitForWorkers({
|
|
705
813
|
workerIds: rows.map((row) => row.ctm_session_id), leadId: leadCtmSessionId, waitMs, generationById,
|
|
814
|
+
...(watchMode === 'off' ? {} : { watchMode, ...waitWatchWiring() }),
|
|
706
815
|
});
|
|
707
816
|
let gone = false;
|
|
708
817
|
res.on('close', () => { if (!res.writableEnded) { gone = true; handle.release(); } });
|
|
@@ -717,7 +826,9 @@ async function handleWorkersWait(req, res, url) {
|
|
|
717
826
|
try {
|
|
718
827
|
const first = await statusReader.call(_deps.delegateWorker, row.ctm_session_id);
|
|
719
828
|
if (first && first.generation != null) handle.setGeneration(row.ctm_session_id, first.generation);
|
|
720
|
-
|
|
829
|
+
// WP-1 (session-stops-followups-2026-10-24.html D-1): provisional is not terminal here
|
|
830
|
+
// either -- a live worker's report is not mail.
|
|
831
|
+
if (first && workerObservability.isFinalWorkerStatus(first) && !row.consumed_at && !row.acknowledged_at) {
|
|
721
832
|
handle.release(); wokeOn = 'terminal'; wokeWorkerId = row.ctm_session_id; break;
|
|
722
833
|
}
|
|
723
834
|
} catch { /* expected: one unreadable worker never fails the whole-set wait */ }
|
|
@@ -726,6 +837,10 @@ async function handleWorkersWait(req, res, url) {
|
|
|
726
837
|
const wake = await handle.promise;
|
|
727
838
|
wokeOn = wake.why;
|
|
728
839
|
wokeWorkerId = wake.workerId;
|
|
840
|
+
// WP-1 (D-3): the watcher's own outcome (why it woke the lead, and every decision it
|
|
841
|
+
// recorded) rides beside woke_on.
|
|
842
|
+
watcherReason = wake.detail && typeof wake.detail.reason === 'string' ? wake.detail.reason : null;
|
|
843
|
+
watcherDecisions = wake.decisions || [];
|
|
729
844
|
}
|
|
730
845
|
} finally {
|
|
731
846
|
handle.release();
|
|
@@ -755,11 +870,40 @@ async function handleWorkersWait(req, res, url) {
|
|
|
755
870
|
return _deps.sendJsonResponse(res, 200, {
|
|
756
871
|
woke_on: wokeOn,
|
|
757
872
|
waited_ms: waitedMs,
|
|
873
|
+
watcher_reason: watcherReason,
|
|
874
|
+
watcher_decisions: watcherDecisions,
|
|
758
875
|
worker: workerPayload,
|
|
759
876
|
workers: summary,
|
|
877
|
+
// WP-2 (D-9, AC-2a): non-null while some worker this wait covered is still live. The
|
|
878
|
+
// woken worker is named first (a wake on an advisory/generation/user_input/`watcher`
|
|
879
|
+
// leaves it working); a terminal wake names the first still-live row of the summary
|
|
880
|
+
// instead, so a lead with several workers is never told to stop waiting while one runs.
|
|
881
|
+
lead_next_step: waitLeadNextStep({ wokeWorkerId, workerPayload, summary }),
|
|
760
882
|
});
|
|
761
883
|
}
|
|
762
884
|
|
|
885
|
+
// WP-2 (D-9): the whole-set wait's lead_next_step. The woken worker wins when its own payload
|
|
886
|
+
// says it is non-terminal; otherwise the first non-terminal row of the summary is named; an
|
|
887
|
+
// all-terminal wake (and a set that reads empty) is null.
|
|
888
|
+
function waitLeadNextStep({ wokeWorkerId, workerPayload, summary }) {
|
|
889
|
+
const wokenStatus = workerPayload && typeof workerPayload.status === 'string' ? workerPayload.status : '';
|
|
890
|
+
// WP-1 (session-stops-followups-2026-10-24.html D-1/D-4): a provisional woken payload is
|
|
891
|
+
// still live, so it is named with the provisional label rather than read as finished.
|
|
892
|
+
if (wokeWorkerId && wokenStatus && !workerObservability.isFinalWorkerStatus(workerPayload) && !workerPayload.error) {
|
|
893
|
+
return waitNextStep({ workerId: wokeWorkerId, status: waitStatusLabel(workerPayload) });
|
|
894
|
+
}
|
|
895
|
+
const live = (Array.isArray(summary) ? summary : []).find((row) => row && row.terminal === false && row.ctm_session_id);
|
|
896
|
+
return live ? waitNextStep({ workerId: live.ctm_session_id, status: live.status }) : null;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// WP-1 (session-stops-followups-2026-10-24.html D-4): the status words lead_next_step names. A
|
|
900
|
+
// bare "report_ready" reads as finished; the provisional case says the process is still running.
|
|
901
|
+
function waitStatusLabel(payload) {
|
|
902
|
+
return payload && payload.report_provisional === true
|
|
903
|
+
? `${payload.status} (provisional: its process is still running)`
|
|
904
|
+
: String(payload && payload.status || '');
|
|
905
|
+
}
|
|
906
|
+
|
|
763
907
|
// The body of the former handleWorkerStatusRead inline path, factored verbatim (WP-5 D-15:
|
|
764
908
|
// "reuse that code path; do not duplicate it") so the single-id read and the whole-set
|
|
765
909
|
// wait's `worker` payload run the SAME reader call, the same markAgentConsumed /
|
|
@@ -767,6 +911,8 @@ async function handleWorkersWait(req, res, url) {
|
|
|
767
911
|
// throw propagates to the caller to map.
|
|
768
912
|
async function workerStatusReadPayload(sessionId, url) {
|
|
769
913
|
try {
|
|
914
|
+
// WP-2 (worker-terminal-stamp-durable 2026-10-26 D-2): a status read of a dead-but-unstamped worker repairs the row first.
|
|
915
|
+
if (typeof _deps.delegateWorker.repairExitedWorkerRow === 'function') { try { _deps.delegateWorker.repairExitedWorkerRow(sessionId); } catch { /* expected: a repair that throws must not fail the read; the next read retries */ } }
|
|
770
916
|
const statusReader = _deps.delegateWorker.getWorkerStatusWithProgress || _deps.delegateWorker.getWorkerStatus;
|
|
771
917
|
const result = await statusReader.call(_deps.delegateWorker, sessionId);
|
|
772
918
|
if (result && typeof result.terminal_path === 'string') {
|
|
@@ -1265,10 +1411,16 @@ function register() {
|
|
|
1265
1411
|
// POST /api/sessions/:id/park, /api/sessions/:id/pin, /api/sessions/:id/wake
|
|
1266
1412
|
method: 'POST',
|
|
1267
1413
|
regex: /^\/api\/sessions\/([^/]+)\/(park|revive|pin|wake)$/,
|
|
1268
|
-
handler: (req, res, url, params) => {
|
|
1414
|
+
handler: async (req, res, url, params) => {
|
|
1269
1415
|
let sessionId = '';
|
|
1270
1416
|
try { sessionId = decodeURIComponent(params['1'] || ''); }
|
|
1271
1417
|
catch { return _deps.sendJsonResponse(res, 400, { ok: false, error: 'invalid_session_id' }); }
|
|
1418
|
+
// WP-6 (§7 ownership routing, AC-6b): a MIRROR session's lifecycle action belongs to the
|
|
1419
|
+
// BOX — the relayed frame carries the ORIGINAL id, and the box's own handler is the one
|
|
1420
|
+
// that owns the PTY (D-2). A local session is untouched: the lookup misses and the existing
|
|
1421
|
+
// handler below runs byte-identically. An unreachable box is a 503 machine_unreachable,
|
|
1422
|
+
// never a local act on a session this hub does not have.
|
|
1423
|
+
if (await remoteMachineCatalog.forwardLifecycleRequest(req, res, sessionId, params['2'])) return undefined;
|
|
1272
1424
|
if (params['2'] === 'park') return _deps.apiSessionPark(req, res, sessionId);
|
|
1273
1425
|
if (params['2'] === 'pin') return _deps.apiSessionPin(req, res, sessionId);
|
|
1274
1426
|
if (params['2'] === 'wake') return _deps.apiSessionWake(req, res, sessionId);
|
|
@@ -123,10 +123,23 @@ async function serveTranscriptHistoryV2(options) {
|
|
|
123
123
|
// reader-seam-content-authority R-4 (WP-2): `fence` STAYS on the wire for compatibility -- an older client still reads it, and dropping a field is
|
|
124
124
|
// not what this design asks for -- but a client built from that design ignores it: the seam join is proven by CONTENT (row-join.js findJoin), never
|
|
125
125
|
// by paint time, so the boundary only ever places the painted DRAW start (`index`), never a proof.
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
if (result && result.ok === true && typeof askPaintedAfter === 'function' && Number.isFinite(result.snapshot && result.snapshot.mtimeMs)) {
|
|
127
|
+
// seam-refresh-painted-after (live incident 2026-10-25 18:55Z): the proof belongs to the WINDOW, not to the request
|
|
128
|
+
// shape. The newest window is the one whose answer reaches the transcript's tail -- `newerCursor === null` (the Claude
|
|
129
|
+
// lane sets exactly that when `reachedSnapshotTail`), whether or not the request carried a cursor. The client's D-18
|
|
130
|
+
// ladder re-asks page 0 one second later CURSORED (`refreshSeamPage`: `cursor = raw.olderCursor`, direction `newer`) --
|
|
131
|
+
// the same newest window -- and joins that answer exactly like the entry landing, so an answer that reaches the tail
|
|
132
|
+
// must carry the proof either way: without it the join falls back to the store's contiguous start and 259 un-anchored
|
|
133
|
+
// rows appear below page 0 while scrollTop stays, dropping the live terminal. A cursored answer that still has a
|
|
134
|
+
// `newerCursor` is an older window (or a `newer` page that did not reach the tail) the client never joins: no ask for
|
|
135
|
+
// it. A host that answers with no `newerCursor` field at all predates it, and there the request shape is the only tail
|
|
136
|
+
// signal there is: `!cursor`, exactly as before.
|
|
137
|
+
const windowReachesTail = 'newerCursor' in result ? result.newerCursor === null : !cursor;
|
|
138
|
+
if (windowReachesTail) {
|
|
139
|
+
const reply = await askPaintedAfter(sessionId, result.snapshot.mtimeMs - PAINT_AFTER_WRITE_MS, result.snapshot.mtimeMs).catch(() => null /* expected: the proof is optional; no field is the honest answer */);
|
|
140
|
+
if (reply && reply.known === true && Number.isFinite(reply.epoch)) result.paintedAfter = { epoch: reply.epoch, index: Number.isFinite(reply.paintedAfter) ? reply.paintedAfter : null, ...('paintedFence' in reply ? { fence: Number.isFinite(reply.paintedFence) ? reply.paintedFence : null } : {}) };
|
|
141
|
+
if (controller.signal.aborted || res.destroyed) return;
|
|
142
|
+
}
|
|
130
143
|
}
|
|
131
144
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
132
145
|
res.end(JSON.stringify(result));
|
|
@@ -87,9 +87,14 @@ function configure({
|
|
|
87
87
|
invalidateWorktreeCaches,
|
|
88
88
|
installRoot,
|
|
89
89
|
apiListWorktrees,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
// WP-4 (§6.5, §9 S-10): machine-aware by construction. The route below stays the plain
|
|
91
|
+
// dispatch shape worktree-create-job-log.test.js pins — the remote-machine decision lives in
|
|
92
|
+
// this wrapper, so a request naming a remote box creates the worktree ON the box and every
|
|
93
|
+
// other request (no `machine`, or `machine` = this instance) is the unmodified local handler.
|
|
94
|
+
apiCreateWorktree: machineRoutedCreateWorktree(apiCreateWorktree),
|
|
95
|
+
// Hub WP-5: an async create on a box returns the BOX's job id, so its poll and cancel follow it.
|
|
96
|
+
apiCancelWorktreeCreateJob: machineRouted('POST /api/worktrees/create-jobs/:id/cancel', apiCancelWorktreeCreateJob),
|
|
97
|
+
apiGetWorktreeCreateJob: machineRouted('GET /api/worktrees/create-jobs/:id', apiGetWorktreeCreateJob),
|
|
93
98
|
apiSyncWorktree,
|
|
94
99
|
apiSyncAllWorktrees,
|
|
95
100
|
apiWorktreeInventory,
|
|
@@ -459,41 +464,37 @@ function apiFinishWorktree(req, res) {
|
|
|
459
464
|
});
|
|
460
465
|
}
|
|
461
466
|
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
// so the phases are read off the job snapshot the handler writes back -- one line per phase change
|
|
467
|
-
// and one per terminal phase. Logging only: the response is passed through untouched.
|
|
468
|
-
const _worktreeJobPhases = new Map();
|
|
467
|
+
// The [worktree-job] log lines moved back to their single state chokepoint
|
|
468
|
+
// (server.js _recordWorktreeCreateJobProgress) in WP-4 of nightly-browser-tier-round2-2026-10-21:
|
|
469
|
+
// this file's response-body observer only saw the two dispatch routes, so the GET poll route
|
|
470
|
+
// and every transition it did not happen to answer went unlogged. Dispatch-only again.
|
|
469
471
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
472
|
+
// WP-4 of remote-machine-hub-sessions-2026-10-22 (§6.5, §9 S-10): remote-machine routing for
|
|
473
|
+
// POST /api/worktrees/create. The decision is made BEFORE the local handler, so a request naming a
|
|
474
|
+
// remote box has its worktree created ON the box (the returned path is box-local, which is exactly
|
|
475
|
+
// what the caller then passes as the session cwd) — and a request with no `machine`, or one naming
|
|
476
|
+
// THIS instance, falls through to the injected apiCreateWorktree unchanged.
|
|
477
|
+
//
|
|
478
|
+
// It wraps the DEP rather than the route on purpose: worktree-create-job-log.test.js pins the
|
|
479
|
+
// dispatch shape (`handler: (req, res) => _deps.apiCreateWorktree(req, res),`) and asserts no
|
|
480
|
+
// response-observer came back to this file — so the routing belongs on the handler it dispatches
|
|
481
|
+
// to, not on the registration line.
|
|
482
|
+
//
|
|
483
|
+
// This is the only one of the four S-9/S-10 routes this file owns; the other three are answered by
|
|
484
|
+
// the lead's one-line hook in server.js's apiGetProjects/apiPathStatus/apiPathSuggest (same module,
|
|
485
|
+
// same contract) — so this file proves the wiring, and the integration test drives the other three
|
|
486
|
+
// through lib/remote-machine-proxy.js directly.
|
|
487
|
+
function machineRouted(route, localHandler) {
|
|
488
|
+
return function apiWorktreeMachineRouted(req, res) {
|
|
489
|
+
const remoteMachineProxy = require('../remote-machine-proxy');
|
|
490
|
+
if (!remoteMachineProxy.isMachineRouted(req, null)) return localHandler(req, res);
|
|
491
|
+
return remoteMachineProxy.maybeProxyMachineRequest(req, res, null, { route })
|
|
492
|
+
.then((handled) => (handled ? undefined : localHandler(req, res)));
|
|
493
|
+
};
|
|
482
494
|
}
|
|
483
495
|
|
|
484
|
-
function
|
|
485
|
-
return (
|
|
486
|
-
const end = res.end.bind(res);
|
|
487
|
-
res.end = (chunk, ...rest) => {
|
|
488
|
-
let parsed = null;
|
|
489
|
-
try {
|
|
490
|
-
parsed = typeof chunk === 'string' && chunk ? JSON.parse(chunk) : null;
|
|
491
|
-
} catch { parsed = null; }
|
|
492
|
-
if (parsed && typeof parsed === 'object') _logWorktreeJobPhase(parsed);
|
|
493
|
-
return end(chunk, ...rest);
|
|
494
|
-
};
|
|
495
|
-
return handler(req, res);
|
|
496
|
-
};
|
|
496
|
+
function machineRoutedCreateWorktree(localHandler) {
|
|
497
|
+
return machineRouted('POST /api/worktrees/create', localHandler);
|
|
497
498
|
}
|
|
498
499
|
|
|
499
500
|
function register() {
|
|
@@ -511,12 +512,12 @@ function register() {
|
|
|
511
512
|
{
|
|
512
513
|
method: 'POST',
|
|
513
514
|
path: '/api/worktrees/create',
|
|
514
|
-
handler:
|
|
515
|
+
handler: (req, res) => _deps.apiCreateWorktree(req, res),
|
|
515
516
|
},
|
|
516
517
|
{
|
|
517
518
|
method: 'POST',
|
|
518
519
|
regex: /^\/api\/worktrees\/create-jobs\/([^/]+)\/cancel$/,
|
|
519
|
-
handler:
|
|
520
|
+
handler: (req, res) => _deps.apiCancelWorktreeCreateJob(req, res),
|
|
520
521
|
},
|
|
521
522
|
{
|
|
522
523
|
method: 'GET',
|
|
@@ -54,4 +54,19 @@ function evaluateDrift({ text, providerMatched, waitingForInput } = {}) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// Returns the drift record when no provider recognizes `text` and evaluateDrift raises; else null.
|
|
58
|
+
// This is the entry point callers use at the moment they PUNT on a live menu — so the
|
|
59
|
+
// "no provider matched" decision and the drift evaluation cannot drift apart.
|
|
60
|
+
// Never throws: an evaluation (or detection) error is REPORTED — a comment-only catch here
|
|
61
|
+
// would hide the alarm's own failure, which is exactly the silence this module exists to break.
|
|
62
|
+
function driftIfUnrecognized({ text, detectProvider } = {}) {
|
|
63
|
+
try {
|
|
64
|
+
if (detectProvider && detectProvider(text)) return null;
|
|
65
|
+
return evaluateDrift({ text, providerMatched: false, waitingForInput: true });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.warn('[approval-drift] evaluation failed:', err && err.message ? err.message : err);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { evaluateDrift, driftIfUnrecognized };
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
const ANCHOR_RE = /Esc to cancel|esc to cancel|Press enter to confirm|\(esc or n\)/;
|
|
4
4
|
const YES_RE = /^\s*(?:[❯›▶▸>]\s*)?\d+\.\s*(?:Yes|Allow)\b/i;
|
|
5
5
|
const CURSOR_RUN_RE = /^\s*(?:[▸❯›▶→>-]+\s*)?Run\s*\(once\)\s*\(y\)/i;
|
|
6
|
-
|
|
6
|
+
// Codex's MCP approval asks 'Allow <actor> to run tool "<tool>"?' where the
|
|
7
|
+
// actor is "the <server> MCP server", "this app", or the connector's display
|
|
8
|
+
// name (e.g. "Google Drive") — hence both the bare "Allow this/the" form below
|
|
9
|
+
// and the "Allow … to run tool" form.
|
|
10
|
+
const QUESTION_RE = /Would you like to|Do you want to|Run this command\?|Allow (?:this|the)|Allow .+ to run tool|needs? (?:your )?(?:approval|permission)/i;
|
|
7
11
|
const SELECTION_RE = /[❯›▶▸→]|^\s*>\s*\d+\./;
|
|
8
12
|
|
|
9
13
|
function lineStyled(line) {
|
|
@@ -184,22 +184,79 @@ function kernelJoin(absPath) {
|
|
|
184
184
|
return acc;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// D-5: at most this many symlink-follow restarts before the resolution is
|
|
188
|
+
// declared unresolvable (Linux MAXSYMLINKS).
|
|
189
|
+
const MAX_SYMLINK_RESTARTS = 40;
|
|
190
|
+
|
|
191
|
+
// lstat WITHOUT following the link: `stat` is the link's own Stats (non-null
|
|
192
|
+
// even for a dangling link), `stat: null` is a plain ENOENT — "not there at
|
|
193
|
+
// all, keep walking upward as today" — and `error: true` is any other failure
|
|
194
|
+
// (EACCES, ELOOP, ENAMETOOLONG), which the caller must fail closed on.
|
|
195
|
+
function lstatNoFollow(p) {
|
|
196
|
+
try {
|
|
197
|
+
return { stat: fs.lstatSync(p) };
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (err && err.code === 'ENOENT') return { stat: null };
|
|
200
|
+
/* expected: any other lstat failure is unresolvable — fail closed rather
|
|
201
|
+
than guess the scope of a path we could not even inspect */
|
|
202
|
+
return { error: true };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
187
206
|
// `lexical` must already be an absolute, normalized path. Walks upward until
|
|
188
207
|
// it finds a segment that exists, resolves THAT segment's real, canonical
|
|
189
208
|
// (case-normalized) path, then rejoins the missing tail segments (their case
|
|
190
209
|
// is whatever the caller/typed them — irrelevant for scope roots, and
|
|
191
210
|
// protected-pattern matching is itself case-insensitive).
|
|
211
|
+
//
|
|
212
|
+
// D-5/D-6: a DANGLING symlink is followed (bounded by MAX_SYMLINK_RESTARTS)
|
|
213
|
+
// rather than treated as a missing component — `fs.existsSync` follows the
|
|
214
|
+
// link and reports false for a dangling one, so the plain upward walk would
|
|
215
|
+
// strip the link NAME off into `tail` and rejoin it under the in-scope
|
|
216
|
+
// parent, classifying a write that really lands in the link's target as
|
|
217
|
+
// in-scope. Returns null when the path cannot be resolved (restart budget
|
|
218
|
+
// exhausted, or lstat/readlink failed with anything but ENOENT); callers
|
|
219
|
+
// treat null as "unresolvable ⇒ out of scope".
|
|
192
220
|
function resolveDeepestExisting(lexical) {
|
|
193
221
|
let cur = lexical;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
// path
|
|
197
|
-
|
|
222
|
+
let tail = [];
|
|
223
|
+
let restarts = 0;
|
|
224
|
+
// Bounded by path depth plus the restart budget; a pathological input can't
|
|
225
|
+
// loop forever since path.dirname('/') === '/' and the loop exits at the
|
|
226
|
+
// root either way.
|
|
227
|
+
const maxSteps = 64 + MAX_SYMLINK_RESTARTS;
|
|
228
|
+
for (let i = 0; i < maxSteps && cur !== path.sep; i += 1) {
|
|
198
229
|
if (fs.existsSync(cur)) {
|
|
199
230
|
const real = nativeRealpath(cur);
|
|
200
231
|
const base = real || cur;
|
|
201
232
|
return tail.length ? path.join(base, ...tail) : base;
|
|
202
233
|
}
|
|
234
|
+
const lstat = lstatNoFollow(cur);
|
|
235
|
+
if (lstat.error) return null;
|
|
236
|
+
if (lstat.stat && lstat.stat.isSymbolicLink()) {
|
|
237
|
+
if (restarts >= MAX_SYMLINK_RESTARTS) return null;
|
|
238
|
+
restarts += 1;
|
|
239
|
+
let text = null;
|
|
240
|
+
try {
|
|
241
|
+
text = fs.readlinkSync(cur);
|
|
242
|
+
} catch {
|
|
243
|
+
/* expected: the link vanished or is unreadable — fail closed */
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
// Resolve the link text against the link's REAL parent, kernel-style
|
|
247
|
+
// (absolute text wins; relative text hangs off the parent), then re-join
|
|
248
|
+
// the already-stripped tail and re-run it through kernelJoin so no `..`
|
|
249
|
+
// inside the link text is collapsed lexically before a symlink ahead of
|
|
250
|
+
// it is followed.
|
|
251
|
+
const parent = path.dirname(cur);
|
|
252
|
+
const parentReal = nativeRealpath(parent) || parent;
|
|
253
|
+
const target = path.isAbsolute(text) ? text : `${parentReal}/${text}`;
|
|
254
|
+
// NB: the link's own NAME is NOT part of the result — the target
|
|
255
|
+
// replaces it, exactly as the kernel does.
|
|
256
|
+
cur = kernelJoin(tail.length ? `${target}/${tail.join('/')}` : target);
|
|
257
|
+
tail = [];
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
203
260
|
tail.unshift(path.basename(cur));
|
|
204
261
|
const parent = path.dirname(cur);
|
|
205
262
|
if (parent === cur) break;
|
|
@@ -342,6 +399,11 @@ function classifyPathDetailed(p, opts = {}) {
|
|
|
342
399
|
}
|
|
343
400
|
}
|
|
344
401
|
|
|
402
|
+
// D-6: an unresolvable path (symlink loop / restart budget exhausted /
|
|
403
|
+
// lstat-readlink failure) cannot be proven in scope; it fails closed AFTER
|
|
404
|
+
// the protected check above, so a protected literal still classifies protected.
|
|
405
|
+
if (!resolved) return { scope: 'out_of_scope' };
|
|
406
|
+
|
|
345
407
|
// C1: the RESOLVED path (not the lexical one) must be under an in-scope
|
|
346
408
|
// root — a symlink anywhere in the chain that points outside scope must
|
|
347
409
|
// not win just because the lexical spelling looked like it was under cwd.
|
|
@@ -380,6 +442,8 @@ function scopeOfRmTarget(target, ctx, recursive) {
|
|
|
380
442
|
const lexical = path.isAbsolute(t) ? path.normalize(t) : path.resolve(ctx.cwd, t);
|
|
381
443
|
if (leafIsSymlink(lexical)) return 'out_of_scope';
|
|
382
444
|
const resolved = detail.resolved || resolveDeepestExisting(lexical);
|
|
445
|
+
// D-6: same fail-closed rule as classifyPathDetailed.
|
|
446
|
+
if (!resolved) return 'out_of_scope';
|
|
383
447
|
if (SCRATCH_RM_RE.test(resolved) || SCRATCH_RM_RE.test(lexical)) return 'in_scope';
|
|
384
448
|
const tmpdirAbs = normalizeRoot(ctx.tmpdir || os.tmpdir());
|
|
385
449
|
const tmpdirReal = nativeRealpath(tmpdirAbs) || tmpdirAbs;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
-
const { ALL_SCOPES } = require('./auth-rules');
|
|
4
|
+
const { ALL_SCOPES, PEER_CONTROL_SCOPE } = require('./auth-rules');
|
|
5
5
|
|
|
6
6
|
const TEST_REMOTE_HEADER = 'x-ctm-test-remote';
|
|
7
7
|
const resolvedEnvCredentialIdentities = new WeakMap();
|
|
@@ -195,6 +195,31 @@ function parseSyncPeerTokens(raw = '') {
|
|
|
195
195
|
|
|
196
196
|
function resolveSyncPeerToken(token, req, env = process.env) {
|
|
197
197
|
const peerId = peerIdFromRequest(req);
|
|
198
|
+
// WP-2a (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §6.2, D-7/D-8): the pool
|
|
199
|
+
// secret is the rev-2 replacement for the hand-copied env token. This is the FIRST branch so a
|
|
200
|
+
// `ctmpool1_` bearer never reaches the env path (and CTM_SYNC_PEER_ALLOWLIST — an env-era knob —
|
|
201
|
+
// does not apply to it). verification is SYNCHRONOUS over the loaded in-memory pool state: no I/O
|
|
202
|
+
// on the auth path. The resolved principal carries the SAME scopes as the env peer
|
|
203
|
+
// ({'sync','peer-control'}), so every existing peer-control rule row applies unchanged.
|
|
204
|
+
if (String(token || '').startsWith(require('./machine-pool').TOKEN_PREFIX)) {
|
|
205
|
+
const pool = require('./machine-pool').getMachinePool();
|
|
206
|
+
if (!pool.verify(token)) return { authenticated: false, source: 'machine-pool-invalid' };
|
|
207
|
+
const poolId = pool.poolId();
|
|
208
|
+
const authenticatedPeerId = peerId || (poolId ? `pool-${poolId}` : '');
|
|
209
|
+
return {
|
|
210
|
+
authenticated: true,
|
|
211
|
+
deviceId: `machine-pool-${authenticatedPeerId || hashTokenForLog(token)}`,
|
|
212
|
+
peerInstanceId: authenticatedPeerId,
|
|
213
|
+
peer_instance_id: authenticatedPeerId,
|
|
214
|
+
grantedPeerIds: authenticatedPeerId ? new Set([authenticatedPeerId]) : new Set(),
|
|
215
|
+
granted_peer_ids: authenticatedPeerId ? [authenticatedPeerId] : [],
|
|
216
|
+
scopes: new Set(['sync', PEER_CONTROL_SCOPE]),
|
|
217
|
+
stepUpFresh: false,
|
|
218
|
+
tokenLabel: 'machine-pool',
|
|
219
|
+
source: 'machine-pool',
|
|
220
|
+
tokenHashPrefix: hashTokenForLog(token),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
198
223
|
const peerTokens = parseSyncPeerTokens(env.CTM_SYNC_PEER_TOKENS || '');
|
|
199
224
|
let tokenLabel = 'sync-peer';
|
|
200
225
|
let authenticatedPeerId = '';
|
|
@@ -231,7 +256,12 @@ function resolveSyncPeerToken(token, req, env = process.env) {
|
|
|
231
256
|
peer_instance_id: authenticatedPeerId,
|
|
232
257
|
grantedPeerIds: authenticatedPeerId ? new Set([authenticatedPeerId]) : new Set(),
|
|
233
258
|
granted_peer_ids: authenticatedPeerId ? [authenticatedPeerId] : [],
|
|
234
|
-
|
|
259
|
+
// WP-1 (docs/remote-machine-hub-sessions-2026-10-22.html §5 D-7, §6.2, §9 S-17): the SAME
|
|
260
|
+
// bearer (CTM_SYNC_PEER_TOKEN / CTM_SYNC_PEER_TOKENS — no new secret, no new env var) now
|
|
261
|
+
// resolves to {'sync','peer-control'}. `sync` keeps working exactly as before; the new
|
|
262
|
+
// capability is consumed only by the rule rows that declare `peerControl: true`
|
|
263
|
+
// (lib/auth-rules.js), which is the §6.2 allowlist and nothing else.
|
|
264
|
+
scopes: new Set(['sync', PEER_CONTROL_SCOPE]),
|
|
235
265
|
stepUpFresh: false,
|
|
236
266
|
tokenLabel,
|
|
237
267
|
source: 'sync-peer-token',
|