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,520 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-4 (docs/remote-machine-hub-sessions-2026-10-22.html §6.5, §7, §8 AC-4a/AC-4c, §9 S-9/S-10):
|
|
4
|
+
// hub-side proxy for the four picker/create routes, plus the same-repo match helper.
|
|
5
|
+
//
|
|
6
|
+
// The picker asks the HUB for projects/suggestions/status, but when a machine is selected the
|
|
7
|
+
// answers must come from the BOX — the box's filesystem is the truth about where a session on the
|
|
8
|
+
// box can start. So each of the four routes gains one optional routing parameter:
|
|
9
|
+
//
|
|
10
|
+
// ?machine=<instance_id> or X-CTM-Machine: <instance_id>
|
|
11
|
+
//
|
|
12
|
+
// Routing never reads the body: a POST that is not machine-routed reaches the local handler with
|
|
13
|
+
// its request stream untouched.
|
|
14
|
+
//
|
|
15
|
+
// The single entry point is `maybeProxyMachineRequest(req, res, url, { route })`. It returns
|
|
16
|
+
// true — it fully answered the request (proxied, or a machine_unknown / machine_unreachable /
|
|
17
|
+
// peer_credential_missing / machine_source_mixed refusal)
|
|
18
|
+
// false — nothing was decided here; the caller MUST fall through to the local handler
|
|
19
|
+
//
|
|
20
|
+
// The rules that drive the shape:
|
|
21
|
+
// AC-4a an unknown machine is a 404 `machine_unknown`; an unreachable one is a 502
|
|
22
|
+
// `machine_unreachable` whose body NAMES the machine (`machineName`), because the copy
|
|
23
|
+
// the picker renders is the only thing the user sees.
|
|
24
|
+
// D-7 the ONLY credential is the peer bearer (`buildPeerControlHeaders`, imported from
|
|
25
|
+
// lib/remote-machine-create.js). No new env var, no new secret, no new token mint.
|
|
26
|
+
// D-1/D-2 the bearer is mandatory on EVERY leg, including a loopback/LAN or SSH-forwarded box:
|
|
27
|
+
// no call is ever sent bare, and a credential the hub does not have is a 503
|
|
28
|
+
// `peer_credential_missing` (never `machine_unreachable`) whose copy names the env var
|
|
29
|
+
// and the machine. Loopback position is not a credential.
|
|
30
|
+
// local `machine` absent, or `machine` == this instance's own minted id, is BIT-IDENTICAL to
|
|
31
|
+
// today: the hook returns false without touching req or res and the local handler
|
|
32
|
+
// answers. No client/public change is involved (WP-5 owns the UI).
|
|
33
|
+
//
|
|
34
|
+
// Dependency note: this module must NOT require lib/hosted-sync-worker.js (dependency-cycle lint —
|
|
35
|
+
// machine-registry's header explains it). The peer credential comes from
|
|
36
|
+
// lib/remote-machine-create.js, which builds it from env itself; the machine list comes from
|
|
37
|
+
// lib/machine-registry.js through the same db-owner Proxy lib/api/machines.js uses.
|
|
38
|
+
|
|
39
|
+
const { normalizeGitRemote } = require('../../shared/git-remote-key');
|
|
40
|
+
|
|
41
|
+
const MACHINE_HEADER = 'x-ctm-machine';
|
|
42
|
+
const MACHINE_QUERY_PARAM = 'machine';
|
|
43
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
44
|
+
const BODY_LIMIT_BYTES = 1024 * 1024;
|
|
45
|
+
|
|
46
|
+
// The four routes this WP proxies (§9 S-9/S-10). Keyed `METHOD /pathname`, the same spelling the
|
|
47
|
+
// lead passes as `{ route }`.
|
|
48
|
+
const PROXIED_ROUTES = Object.freeze([
|
|
49
|
+
'GET /api/projects',
|
|
50
|
+
'GET /api/path-suggest',
|
|
51
|
+
'POST /api/path-status',
|
|
52
|
+
'POST /api/worktrees/create',
|
|
53
|
+
// WP-5 amendment: the async create's progress poll + cancel live on the box that owns the job.
|
|
54
|
+
// The wrapper (lib/api/worktrees.js) names these templated keys; the REAL path is forwarded.
|
|
55
|
+
'GET /api/worktrees/create-jobs/:id',
|
|
56
|
+
'POST /api/worktrees/create-jobs/:id/cancel',
|
|
57
|
+
// WP-1 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1 / S-5): host stats are read
|
|
58
|
+
// per machine by the picker/dashboard, over the same ?machine= + peer-bearer leg as the rows
|
|
59
|
+
// above. The box's response is self-describing (`instanceId`), so nothing hub-specific is added.
|
|
60
|
+
'GET /api/host/stats',
|
|
61
|
+
// WP-2a (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.2, D-4 rev 2): the machine's
|
|
62
|
+
// OWN settings (display name / favorite / hidden). A user on any primary edits a remote member
|
|
63
|
+
// through the existing ?machine= + peer-bearer leg, exactly like the routes above — the member
|
|
64
|
+
// sees the pool principal. Both verbs are proxied (D-13: GET read + POST admin, peerControl).
|
|
65
|
+
'GET /api/machine-self',
|
|
66
|
+
'POST /api/machine-self',
|
|
67
|
+
// WP-3 (remote-agent-sign-in-from-hub-browser-2026-10-25.html §5 D-7/D-9, §6, S-11..S-14): the
|
|
68
|
+
// coding-agent OAuth sign-in trio, so a hub page (desktop or phone) can read a BOX's sign-in
|
|
69
|
+
// truth and start/complete a sign-in ON that box without opening the box's own URL. The two
|
|
70
|
+
// POSTs are admin + remote step-up + peerControl (lib/auth-rules.js); the GET is the plain read
|
|
71
|
+
// it already was. The templated POST keys use the same `:id` spelling as the create-job rows.
|
|
72
|
+
'GET /api/providers/login-state',
|
|
73
|
+
'POST /api/providers/:id/login',
|
|
74
|
+
'POST /api/providers/:id/login/code',
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// S-10 mixed-request refusal: a create that names a REMOTE machine must carry the BOX-owned source
|
|
78
|
+
// repo in `cwd`. These are the hub-local worktree identities (WP-5's picker would have produced
|
|
79
|
+
// them locally) — present alongside a remote machine they disagree about WHERE the worktree lives.
|
|
80
|
+
const LOCAL_SOURCE_FIELDS = Object.freeze(['localWorktreePath', 'localPath', 'hubCwd', 'sourceMachine']);
|
|
81
|
+
|
|
82
|
+
// AC-4c recency: the /api/projects row fields that order two checkouts of the SAME repo. Today's
|
|
83
|
+
// rows carry none of them (server.js apiGetProjects returns {id, path, exists}); the list is here
|
|
84
|
+
// so a row that gains one is preferred without touching this module again, and the array-order
|
|
85
|
+
// fallback (first row wins) is what a keyless row set gets.
|
|
86
|
+
const RECENCY_FIELDS = Object.freeze([
|
|
87
|
+
'lastUsedAt', 'lastActivityAt', 'updatedAt', 'mtimeMs', 'mtime', 'lastSeenAt', 'createdAt',
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
let _injected = null;
|
|
91
|
+
|
|
92
|
+
/** Test/host seam: override any production dependency (registry, db, env, fetch, headers, …). */
|
|
93
|
+
function configure(deps) {
|
|
94
|
+
_injected = deps && typeof deps === 'object' ? deps : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function _string(value, max = 1000) {
|
|
98
|
+
return String(value == null ? '' : value).trim().slice(0, max);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _trimUrl(url) {
|
|
102
|
+
return _string(url, 1000).replace(/\/+$/, '');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* WP-1 (D-2): the missing-credential verdict for both lanes this module owns. The code and the
|
|
107
|
+
* copy live in lib/remote-machine-create.js — the ONE module that owns the peer credential — and
|
|
108
|
+
* are required lazily for the same reason the header builder is (this module must never pull
|
|
109
|
+
* lib/hosted-sync-worker.js into its graph; see the dependency note at the top).
|
|
110
|
+
*
|
|
111
|
+
* @returns {{code: string, error: string}}
|
|
112
|
+
*/
|
|
113
|
+
function _peerCredentialMissing(machineName) {
|
|
114
|
+
const mod = require('./remote-machine-create');
|
|
115
|
+
return { code: mod.PEER_CREDENTIAL_MISSING, error: mod.peerCredentialMissingMessage(machineName) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Lazy: the pure helpers (matchBoxProject, normalizeGitRemote) and the unit test must never load
|
|
119
|
+
// db.js / the owner client. Mirrors lib/remote-machine-create.js's seam.
|
|
120
|
+
function _productionDeps() {
|
|
121
|
+
return {
|
|
122
|
+
env: process.env,
|
|
123
|
+
// The same handle server.js hands lib/api/machines.js: the DB-owner Proxy, so the machine
|
|
124
|
+
// list read here is the one the routes read.
|
|
125
|
+
db: require('../db-owner-client'),
|
|
126
|
+
registry: require('./machine-registry'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function _deps(overrides) {
|
|
131
|
+
return Object.assign({}, _productionDeps(), _injected || {}, overrides || {});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------------------------
|
|
135
|
+
// Pure helpers (exported — AC-4c's unit cases live with the normalization table)
|
|
136
|
+
// ---------------------------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* AC-4c: the box path whose checkout is the SAME repo as a hub project.
|
|
140
|
+
*
|
|
141
|
+
* Exact key match on `gitRemoteKey`, never a prefix/substring match (two different repos under
|
|
142
|
+
* one owner must not collide). Several checkouts of one repo are legal — the picker then opens
|
|
143
|
+
* the most recently used one, per RECENCY_FIELDS, falling back to array order so the result is
|
|
144
|
+
* deterministic for a row set that carries no timestamps at all.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} localProjectKey normalizeGitRemote(...) of the hub-side project
|
|
147
|
+
* @param {Array<{gitRemoteKey?: string, path?: string}>} boxProjects the BOX's /api/projects rows
|
|
148
|
+
* @returns {{path: string, project: object}|null}
|
|
149
|
+
*/
|
|
150
|
+
function matchBoxProject(localProjectKey, boxProjects) {
|
|
151
|
+
const key = _string(localProjectKey, 1000);
|
|
152
|
+
if (!key || !Array.isArray(boxProjects)) return null;
|
|
153
|
+
let best = null;
|
|
154
|
+
let bestScore = null;
|
|
155
|
+
for (let index = 0; index < boxProjects.length; index += 1) {
|
|
156
|
+
const row = boxProjects[index];
|
|
157
|
+
if (!row || typeof row !== 'object') continue;
|
|
158
|
+
if (_string(row.gitRemoteKey, 1000) !== key) continue;
|
|
159
|
+
const score = _recencyScore(row, index);
|
|
160
|
+
if (!best || score > bestScore) {
|
|
161
|
+
best = row;
|
|
162
|
+
bestScore = score;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!best) return null;
|
|
166
|
+
return { path: _string(best.path, 4000), project: best };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function _recencyScore(row, index) {
|
|
170
|
+
for (const field of RECENCY_FIELDS) {
|
|
171
|
+
const raw = row[field];
|
|
172
|
+
if (raw == null || raw === '') continue;
|
|
173
|
+
const numeric = typeof raw === 'number' ? raw : Date.parse(String(raw));
|
|
174
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
175
|
+
}
|
|
176
|
+
// No timestamp anywhere: the earliest row wins, so the same input always yields the same path.
|
|
177
|
+
// (`index ? -index : 0` rather than `-index`: -0 is never what a score should compare as.)
|
|
178
|
+
return index ? -index : 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The routing machine id, or '' when this request is not machine-routed at all.
|
|
183
|
+
* Query/header are consulted first so the common GET path never touches the request body.
|
|
184
|
+
*/
|
|
185
|
+
function _machineFromRequest(req, url) {
|
|
186
|
+
let fromQuery = '';
|
|
187
|
+
try { fromQuery = _string(url && url.searchParams ? url.searchParams.get(MACHINE_QUERY_PARAM) : '', 160); } catch { /* expected: a url-like without searchParams */ }
|
|
188
|
+
if (fromQuery) return fromQuery;
|
|
189
|
+
const headers = (req && req.headers) || {};
|
|
190
|
+
return _string(headers[MACHINE_HEADER] || headers['X-CTM-Machine'] || '', 160);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// The process's own MINTED instance id (D-3: never hostname, env tag or URL) — the same source
|
|
194
|
+
// lib/remote-machine-create.js uses for its local-vs-remote decision.
|
|
195
|
+
/** Synchronous pre-check for route wiring: does this request name a machine at all? */
|
|
196
|
+
function isMachineRouted(req, url) {
|
|
197
|
+
return !!_machineFromRequest(req, _resolveUrl(req, url));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function _localInstanceId(deps) {
|
|
201
|
+
if (typeof deps.localInstanceId === 'string') return _string(deps.localInstanceId, 160);
|
|
202
|
+
try {
|
|
203
|
+
const identity = require('./instance-identity-access').getInstanceIdentity({ required: false });
|
|
204
|
+
return _string(identity && identity.instanceId, 160);
|
|
205
|
+
} catch {
|
|
206
|
+
return ''; /* expected: identity not minted yet; an unknown local id never matches a box row */
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function _resolveUrl(req, url) {
|
|
211
|
+
if (url && typeof url.searchParams !== 'undefined') return url;
|
|
212
|
+
try { return new URL((req && req.url) || '/', 'http://localhost'); } catch { return new URL('/', 'http://localhost'); }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function _routeKey(req, url, options) {
|
|
216
|
+
if (options && options.route) return String(options.route);
|
|
217
|
+
const method = String((req && req.method) || 'GET').toUpperCase();
|
|
218
|
+
return `${method} ${url.pathname}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _sendJson(res, status, payload) {
|
|
222
|
+
const body = JSON.stringify(payload);
|
|
223
|
+
if (res.headersSent) return true;
|
|
224
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
|
|
225
|
+
res.end(body);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------------------------
|
|
230
|
+
// Request-body handling
|
|
231
|
+
// ---------------------------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
/** Read the whole request body (only ever called once the request is known to be machine-routed). */
|
|
234
|
+
function _readRawBody(req) {
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
let raw = '';
|
|
237
|
+
let size = 0;
|
|
238
|
+
let done = false;
|
|
239
|
+
req.on('data', (chunk) => {
|
|
240
|
+
if (done) return;
|
|
241
|
+
size += chunk.length;
|
|
242
|
+
if (size > BODY_LIMIT_BYTES) {
|
|
243
|
+
done = true;
|
|
244
|
+
reject(new Error('request_body_too_large'));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
raw += chunk;
|
|
248
|
+
});
|
|
249
|
+
req.on('end', () => { if (!done) { done = true; resolve(raw); } });
|
|
250
|
+
req.on('error', (error) => { if (!done) { done = true; reject(error); } });
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------------------------
|
|
255
|
+
// The hook
|
|
256
|
+
// ---------------------------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* §6.5 entry point. Answers the request itself when it names a machine; otherwise defers.
|
|
260
|
+
*
|
|
261
|
+
* @param {import('http').IncomingMessage} req
|
|
262
|
+
* @param {import('http').ServerResponse} res
|
|
263
|
+
* @param {URL|null} url parsed request url; derived from req.url when null/omitted
|
|
264
|
+
* @param {{route?: string}} [options] `'METHOD /pathname'`; derived from req when omitted
|
|
265
|
+
* @returns {Promise<boolean>} true = answered (do NOT run the local handler)
|
|
266
|
+
*/
|
|
267
|
+
async function maybeProxyMachineRequest(req, res, url, options = {}) {
|
|
268
|
+
const deps = _deps(options.deps);
|
|
269
|
+
const parsed = _resolveUrl(req, url);
|
|
270
|
+
const route = _routeKey(req, parsed, options);
|
|
271
|
+
if (!PROXIED_ROUTES.includes(route)) return false;
|
|
272
|
+
|
|
273
|
+
const isPost = route.startsWith('POST ');
|
|
274
|
+
const machine = _machineFromRequest(req, parsed);
|
|
275
|
+
if (!machine) return false;
|
|
276
|
+
let rawBody = null;
|
|
277
|
+
let parsedBody = null;
|
|
278
|
+
|
|
279
|
+
// This instance IS the named machine (§6.5) -> the local handler is the box handler. Checked
|
|
280
|
+
// BEFORE the registry lookup because a hub does not list ITSELF among its boxes: `machine=<my own
|
|
281
|
+
// id>` is the "I am the box" spelling, not an unknown machine.
|
|
282
|
+
const localId = _localInstanceId(deps);
|
|
283
|
+
if (localId && machine === localId) return false;
|
|
284
|
+
|
|
285
|
+
// P-3 (rev 2) one-hop guard. Every member-to-member request carries X-CTM-Forwarded-By. A request
|
|
286
|
+
// that carries it AND names a machine that is NOT this instance is asking us to forward an
|
|
287
|
+
// already-forwarded request to a third machine — the A→B→C chain / A→B→A loop P-3 forbids.
|
|
288
|
+
// Nothing is dialled. A request naming no machine, or naming THIS instance, is unchanged above:
|
|
289
|
+
// the browser's own direct request (no header) and a member's request to run locally both pass.
|
|
290
|
+
const forwardedBy = require('./remote-machine-create').forwardedByOf(req);
|
|
291
|
+
if (forwardedBy) {
|
|
292
|
+
return _sendJson(res, 400, {
|
|
293
|
+
ok: false,
|
|
294
|
+
code: 'machine_forward_loop',
|
|
295
|
+
error: `This request was already forwarded by ${forwardedBy}; a forwarded request is never forwarded again`,
|
|
296
|
+
machine,
|
|
297
|
+
forwardedBy,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const registry = deps.registry || require('./machine-registry');
|
|
302
|
+
const { machines } = registry.listMachines({ db: deps.db });
|
|
303
|
+
const target = (machines || []).find((row) => row.instanceId === machine);
|
|
304
|
+
if (!target) {
|
|
305
|
+
return _sendJson(res, 404, { ok: false, code: 'machine_unknown', error: `Unknown machine: ${machine}`, machine });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const machineName = _string(target.displayName, 200) || machine;
|
|
309
|
+
const unreachable = (extra = {}) => _sendJson(res, 502, {
|
|
310
|
+
ok: false,
|
|
311
|
+
code: 'machine_unreachable',
|
|
312
|
+
error: `Machine ${machineName} is unreachable`,
|
|
313
|
+
machine,
|
|
314
|
+
machineName,
|
|
315
|
+
...extra,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// S-10: a create that mixes a hub-local worktree source with a remote machine is refused rather
|
|
319
|
+
// than silently creating a worktree on the hub (whose path the caller would then hand to the box).
|
|
320
|
+
if (route === 'POST /api/worktrees/create') {
|
|
321
|
+
if (parsedBody == null) {
|
|
322
|
+
if (rawBody == null) {
|
|
323
|
+
try { rawBody = await _readRawBody(req); } catch { rawBody = ''; }
|
|
324
|
+
}
|
|
325
|
+
try { parsedBody = rawBody.trim() ? JSON.parse(rawBody) : {}; } catch { parsedBody = null; }
|
|
326
|
+
}
|
|
327
|
+
if (!parsedBody || typeof parsedBody !== 'object') {
|
|
328
|
+
return _sendJson(res, 400, { ok: false, code: 'invalid_json', error: 'Invalid JSON body' });
|
|
329
|
+
}
|
|
330
|
+
const mixed = LOCAL_SOURCE_FIELDS.filter((field) => {
|
|
331
|
+
const value = parsedBody[field];
|
|
332
|
+
if (value == null || value === '' || value === false) return false;
|
|
333
|
+
if (field === 'sourceMachine') return _string(value, 160) !== machine;
|
|
334
|
+
return true;
|
|
335
|
+
});
|
|
336
|
+
if (mixed.length) {
|
|
337
|
+
return _sendJson(res, 400, {
|
|
338
|
+
ok: false,
|
|
339
|
+
code: 'machine_source_mixed',
|
|
340
|
+
error: `Worktree create names machine ${machineName} and a local worktree source (${mixed.join(', ')}); a remote worktree must be created on the machine`,
|
|
341
|
+
machine,
|
|
342
|
+
machineName,
|
|
343
|
+
mixedFields: mixed,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const baseUrl = _trimUrl(target.url);
|
|
349
|
+
if (!baseUrl) return unreachable({ error: `Machine ${machineName} has no URL` });
|
|
350
|
+
|
|
351
|
+
const buildHeaders = deps.buildPeerControlHeaders
|
|
352
|
+
|| require('./remote-machine-create').buildPeerControlHeaders;
|
|
353
|
+
// P-8: the bearer is chosen per TARGET — `machine` is the box this request is being proxied to,
|
|
354
|
+
// so a legacy env-token box that never joined this hub's pool is dialled with the env bearer.
|
|
355
|
+
const peerHeaders = buildHeaders(deps.env || process.env, { instanceId: machine });
|
|
356
|
+
// WP-1 (D-1/D-2): the bearer is mandatory on every leg, so a missing one is answered HERE, with
|
|
357
|
+
// its own code and status, and no request is ever sent bare. `unreachable` keeps meaning exactly
|
|
358
|
+
// what it says: the box was dialled and did not answer.
|
|
359
|
+
if (!peerHeaders) {
|
|
360
|
+
const missing = _peerCredentialMissing(machineName);
|
|
361
|
+
return _sendJson(res, 503, {
|
|
362
|
+
ok: false,
|
|
363
|
+
code: missing.code,
|
|
364
|
+
error: missing.error,
|
|
365
|
+
machine,
|
|
366
|
+
machineName,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Strip the routing field from what the box sees: `machine` is the hub's protocol, not the
|
|
371
|
+
// box's, and the box's own handlers must stay byte-identical to the pre-WP-4 ones.
|
|
372
|
+
const forwardUrl = new URL(parsed.toString());
|
|
373
|
+
forwardUrl.searchParams.delete(MACHINE_QUERY_PARAM);
|
|
374
|
+
const forwardTarget = `${baseUrl}${forwardUrl.pathname}${forwardUrl.search}`;
|
|
375
|
+
|
|
376
|
+
const fetchImpl = deps.fetchImpl || deps.fetch || globalThis.fetch;
|
|
377
|
+
if (typeof fetchImpl !== 'function') return unreachable({ error: 'fetch_unavailable' });
|
|
378
|
+
|
|
379
|
+
const timeoutMs = Number(deps.timeoutMs || DEFAULT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
|
|
380
|
+
const timeoutFn = typeof deps.setTimeoutFn === 'function' ? deps.setTimeoutFn : setTimeout;
|
|
381
|
+
const clearFn = typeof deps.clearTimeoutFn === 'function' ? deps.clearTimeoutFn : clearTimeout;
|
|
382
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
383
|
+
let timer = null;
|
|
384
|
+
const timeout = new Promise((resolve) => {
|
|
385
|
+
timer = timeoutFn(() => {
|
|
386
|
+
try { if (controller) controller.abort(); } catch { /* expected: abort on a settled fetch */ }
|
|
387
|
+
resolve({ __timeout: true });
|
|
388
|
+
}, timeoutMs);
|
|
389
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
let payload;
|
|
393
|
+
if (isPost) {
|
|
394
|
+
if (rawBody == null) {
|
|
395
|
+
try { rawBody = await _readRawBody(req); } catch { rawBody = ''; }
|
|
396
|
+
}
|
|
397
|
+
let outBody = {};
|
|
398
|
+
try { outBody = rawBody.trim() ? JSON.parse(rawBody) : {}; } catch { outBody = {}; }
|
|
399
|
+
if (outBody && typeof outBody === 'object') delete outBody.machine;
|
|
400
|
+
payload = JSON.stringify(outBody);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const requestOptions = {
|
|
404
|
+
method: String(req.method || 'GET').toUpperCase(),
|
|
405
|
+
headers: {
|
|
406
|
+
...peerHeaders,
|
|
407
|
+
...(isPost ? { 'Content-Type': 'application/json' } : {}),
|
|
408
|
+
},
|
|
409
|
+
...(isPost ? { body: payload } : {}),
|
|
410
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
411
|
+
redirect: 'manual',
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
let response;
|
|
415
|
+
try {
|
|
416
|
+
response = await Promise.race([Promise.resolve(fetchImpl(forwardTarget, requestOptions)), timeout]);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (timer) clearFn(timer);
|
|
419
|
+
return unreachable({ detail: _string(error && error.message, 300) });
|
|
420
|
+
}
|
|
421
|
+
if (timer) clearFn(timer);
|
|
422
|
+
if (!response || response.__timeout) return unreachable({ detail: 'timeout' });
|
|
423
|
+
|
|
424
|
+
let text = '';
|
|
425
|
+
try {
|
|
426
|
+
text = await response.text();
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return unreachable({ detail: _string(error && error.message, 300) });
|
|
429
|
+
}
|
|
430
|
+
if (res.headersSent) return true;
|
|
431
|
+
// The box's status and body are returned VERBATIM (§6.5): the picker parses the box's own
|
|
432
|
+
// payload shape, and a hub that re-shaped it would drift from the local case.
|
|
433
|
+
res.writeHead(Number(response.status) || 502, {
|
|
434
|
+
'Content-Type': _string(response.headers && response.headers.get && response.headers.get('content-type'), 200)
|
|
435
|
+
|| 'application/json',
|
|
436
|
+
});
|
|
437
|
+
res.end(text);
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* WP-6 (§7 ownership routing, AC-6b): send ONE request to a box over the SAME credential and with
|
|
443
|
+
* the SAME verbatim-response contract `maybeProxyMachineRequest` uses for the picker routes — the
|
|
444
|
+
* caller (`lib/remote-machine-catalog.js`) is what decides that the id belongs to the box, and what
|
|
445
|
+
* its own refusal looks like when the box cannot be reached.
|
|
446
|
+
*
|
|
447
|
+
* @param {{machine: object, pathname: string, search?: string, method?: string, body?: string}} request
|
|
448
|
+
* @returns {Promise<{ok: true, status: number, contentType: string, text: string}|{ok: false, code: string, error: string}>}
|
|
449
|
+
*/
|
|
450
|
+
async function forwardMachineHttp({ machine, pathname, search = '', method = 'POST', body = '', deps = {} } = {}) {
|
|
451
|
+
const d = _deps(deps);
|
|
452
|
+
const baseUrl = _trimUrl(machine && machine.url);
|
|
453
|
+
if (!baseUrl) return { ok: false, code: 'machine_unreachable', error: 'machine_url_missing' };
|
|
454
|
+
const buildHeaders = d.buildPeerControlHeaders || require('./remote-machine-create').buildPeerControlHeaders;
|
|
455
|
+
// P-8: same per-target choice as maybeProxyMachineRequest — the catalog knows which box this is.
|
|
456
|
+
const peerHeaders = buildHeaders(d.env || process.env, { instanceId: _string(machine && machine.instanceId, 160) });
|
|
457
|
+
// WP-1 (D-2): the caller (lib/remote-machine-catalog.js) turns this code into a 503 with the
|
|
458
|
+
// same body; keeping `machine_unreachable` out of it is what makes the config error greppable.
|
|
459
|
+
if (!peerHeaders) {
|
|
460
|
+
const missing = _peerCredentialMissing(_string(machine && machine.displayName, 200));
|
|
461
|
+
return { ok: false, code: missing.code, error: missing.error };
|
|
462
|
+
}
|
|
463
|
+
const fetchImpl = d.fetchImpl || d.fetch || globalThis.fetch;
|
|
464
|
+
if (typeof fetchImpl !== 'function') return { ok: false, code: 'machine_unreachable', error: 'fetch_unavailable' };
|
|
465
|
+
const timeoutMs = Number(d.timeoutMs || DEFAULT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
|
|
466
|
+
const timeoutFn = typeof d.setTimeoutFn === 'function' ? d.setTimeoutFn : setTimeout;
|
|
467
|
+
const clearFn = typeof d.clearTimeoutFn === 'function' ? d.clearTimeoutFn : clearTimeout;
|
|
468
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
469
|
+
let timer = null;
|
|
470
|
+
const timeout = new Promise((resolve) => {
|
|
471
|
+
timer = timeoutFn(() => {
|
|
472
|
+
try { if (controller) controller.abort(); } catch { /* expected: abort on a settled fetch */ }
|
|
473
|
+
resolve({ __timeout: true });
|
|
474
|
+
}, timeoutMs);
|
|
475
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
476
|
+
});
|
|
477
|
+
const verb = String(method || 'POST').toUpperCase();
|
|
478
|
+
const hasBody = verb !== 'GET' && verb !== 'HEAD';
|
|
479
|
+
let response;
|
|
480
|
+
try {
|
|
481
|
+
response = await Promise.race([Promise.resolve(fetchImpl(`${baseUrl}${pathname}${search}`, {
|
|
482
|
+
method: verb,
|
|
483
|
+
headers: { ...peerHeaders, ...(hasBody ? { 'Content-Type': 'application/json' } : {}) },
|
|
484
|
+
...(hasBody ? { body: body || '' } : {}),
|
|
485
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
486
|
+
redirect: 'manual',
|
|
487
|
+
})), timeout]);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (timer) clearFn(timer);
|
|
490
|
+
return { ok: false, code: 'machine_unreachable', error: _string(error && error.message, 300) };
|
|
491
|
+
}
|
|
492
|
+
if (timer) clearFn(timer);
|
|
493
|
+
if (!response || response.__timeout) return { ok: false, code: 'machine_unreachable', error: 'timeout' };
|
|
494
|
+
let text = '';
|
|
495
|
+
try {
|
|
496
|
+
text = await response.text();
|
|
497
|
+
} catch (error) {
|
|
498
|
+
return { ok: false, code: 'machine_unreachable', error: _string(error && error.message, 300) };
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
ok: true,
|
|
502
|
+
status: Number(response.status) || 502,
|
|
503
|
+
contentType: _string(response.headers && response.headers.get && response.headers.get('content-type'), 200) || 'application/json',
|
|
504
|
+
text,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
module.exports = {
|
|
509
|
+
MACHINE_HEADER,
|
|
510
|
+
MACHINE_QUERY_PARAM,
|
|
511
|
+
DEFAULT_TIMEOUT_MS,
|
|
512
|
+
PROXIED_ROUTES,
|
|
513
|
+
configure,
|
|
514
|
+
isMachineRouted,
|
|
515
|
+
maybeProxyMachineRequest,
|
|
516
|
+
forwardMachineHttp,
|
|
517
|
+
matchBoxProject,
|
|
518
|
+
normalizeGitRemote,
|
|
519
|
+
_recencyScore,
|
|
520
|
+
};
|