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
|
@@ -15,6 +15,9 @@ const {
|
|
|
15
15
|
bindObservedDatabaseGenerations,
|
|
16
16
|
} = require('./conversation-storage-generation-evidence');
|
|
17
17
|
const { inspectCompletedMarker } = require('./conversation-storage-completed-marker');
|
|
18
|
+
// WP-3 (clock-free r5 R-4..R-8): walle liveness from the write-lock identity record, the
|
|
19
|
+
// proven-owned daemon stop before the first walle write, and a fail-closed foreign holder.
|
|
20
|
+
const { ensureWalleOwnerStopped } = require('./conversation-storage-walle-owner');
|
|
18
21
|
// No-op when options.profile is absent so behaviour is byte-identical to today: this
|
|
19
22
|
// module only ever calls profile.measure/measureAsync around existing calls, never adds
|
|
20
23
|
// new work, so an absent profile just means the timing calls disappear.
|
|
@@ -475,7 +478,13 @@ function runAutomaticConversationMigration(options = {}) {
|
|
|
475
478
|
}
|
|
476
479
|
}
|
|
477
480
|
if (!state.irreversibleAt) {
|
|
478
|
-
|
|
481
|
+
// R-5/R-8: stop this instance's PROVEN-OWNED Wall-E before the first walle write, and
|
|
482
|
+
// probe only the databases this run will mutate -- `specs` included a live walle in a
|
|
483
|
+
// CTM-only rerun, which blocked the boot on a database it was never going to touch.
|
|
484
|
+
const walleSpecForPreflight = specs.find((spec) => spec.id === 'walle');
|
|
485
|
+
const walleOwner = walleSpecForPreflight && plannedDatabaseIds.has('walle')
|
|
486
|
+
? ensureWalleOwnerStopped(walleSpecForPreflight.path, options.walleOwner || {}) : null;
|
|
487
|
+
profile.measure('assert_databases_offline', () => _assertDatabasesOffline(plannedSpecs()));
|
|
479
488
|
const headroomDatabaseIds = new Set(plannedDatabaseIds);
|
|
480
489
|
if (headroomDatabaseIds.has('ctm') && specs.some((spec) => spec.id === 'walle')) {
|
|
481
490
|
headroomDatabaseIds.add('walle');
|
|
@@ -486,6 +495,7 @@ function runAutomaticConversationMigration(options = {}) {
|
|
|
486
495
|
options,
|
|
487
496
|
)),
|
|
488
497
|
databaseOwnership: 'offline',
|
|
498
|
+
...(walleOwner ? { walleOwner } : {}),
|
|
489
499
|
};
|
|
490
500
|
checkpoint('preflight');
|
|
491
501
|
checkpoint('inventory');
|
|
@@ -654,6 +664,11 @@ function runAutomaticConversationMigration(options = {}) {
|
|
|
654
664
|
expandWalleDependency = true;
|
|
655
665
|
} finally { ctmDb.close(); walleDb.close(); }
|
|
656
666
|
if (expandWalleDependency) {
|
|
667
|
+
// R-5/R-8 (still inside the !state.irreversibleAt guard above): the expansion adds
|
|
668
|
+
// walle AFTER the preflight probe, so the owner must be proven stopped here too,
|
|
669
|
+
// before rebuild_walle -- and walle alone is re-probed.
|
|
670
|
+
state.preflight.walleOwner = ensureWalleOwnerStopped(walleSpec.path, options.walleOwner || {});
|
|
671
|
+
_assertDatabasesOffline([walleSpec]);
|
|
657
672
|
plannedDatabaseIds.add('walle');
|
|
658
673
|
delete state.cleanBackups.walle;
|
|
659
674
|
state.restartPreflight.plannedDatabaseIds = specs
|
|
@@ -5,6 +5,7 @@ const Database = require('better-sqlite3');
|
|
|
5
5
|
const migration = require('../../scripts/conversation-storage-v2-migration');
|
|
6
6
|
const {
|
|
7
7
|
sanitizeInspectionViolations,
|
|
8
|
+
inspectionErrorClass,
|
|
8
9
|
} = require('../../shared/conversation-storage-v2-contract');
|
|
9
10
|
|
|
10
11
|
// Moved from conversation-storage-auto-migration-lib.js (WP-5 un-golf/split: keeping the
|
|
@@ -170,8 +171,14 @@ function inspectCompletedMarkerSpec(spec, expectedGeneration, generationEvidence
|
|
|
170
171
|
violations,
|
|
171
172
|
...(generationChanged ? { generationChanged: true } : {}),
|
|
172
173
|
};
|
|
173
|
-
} catch {
|
|
174
|
-
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// REPORT: an inspection that throws still plans a rerun (RERUN_CLASS, unchanged), but the
|
|
176
|
+
// boot log now names the error class instead of an empty violation list (clock-free r6 OQ-5).
|
|
177
|
+
return {
|
|
178
|
+
clean: false,
|
|
179
|
+
reasonCodes: ['inspection_failed'],
|
|
180
|
+
violations: [{ code: 'inspection_failed', objectId: inspectionErrorClass(error), count: 1 }],
|
|
181
|
+
};
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
buildConversationMigrationStatus,
|
|
13
13
|
} = require('../lib/conversation-storage-migration-status');
|
|
14
14
|
const { rebindCompletedGenerations } = require('./conversation-storage-generation-evidence');
|
|
15
|
+
const { resolveInstanceTag } = require('../lib/instance-tag');
|
|
15
16
|
const { createPhaseProfile } = require('../lib/phase-profile');
|
|
16
17
|
const {
|
|
17
18
|
sanitizeInspectionViolations,
|
|
@@ -102,7 +103,13 @@ function buildConversationStorageServiceLog(result = {}) {
|
|
|
102
103
|
recoveryArtifactCount: status.recoveryArtifactCount,
|
|
103
104
|
cleanupPendingCount: status.cleanupPendingCount,
|
|
104
105
|
};
|
|
105
|
-
|
|
106
|
+
// R-6 (clock-free r4): restartPreflight describes a migration that was PLANNED by the
|
|
107
|
+
// boot that ran it. An already_complete / generation_rebound boot (result.skipped set)
|
|
108
|
+
// only re-reads the journal, so re-emitting the recorded object would print a stale
|
|
109
|
+
// re-run plan for a boot that ran no migration -- exactly what misled r3's census.
|
|
110
|
+
const restartPreflight = result.skipped == null
|
|
111
|
+
? _restartPreflightForServiceLog(result.restartPreflight)
|
|
112
|
+
: null;
|
|
106
113
|
if (restartPreflight) summary.restartPreflight = restartPreflight;
|
|
107
114
|
return summary;
|
|
108
115
|
}
|
|
@@ -129,6 +136,14 @@ function resolveAutomaticConversationMigrationOptions(env = process.env, homedir
|
|
|
129
136
|
},
|
|
130
137
|
batchLimit: Number(env.CTM_CONVERSATION_MIGRATION_BATCH_ROWS) || undefined,
|
|
131
138
|
batchByteLimit: Number(env.CTM_CONVERSATION_MIGRATION_BATCH_BYTES) || undefined,
|
|
139
|
+
// WP-3 (clock-free r5 R-5/R-6): the kill authority the boot migration uses to prove and
|
|
140
|
+
// stop THIS instance's Wall-E before it touches wall-e-brain.db. Same tag derivation the
|
|
141
|
+
// supervisor uses (server.js:5897), so a dev/test instance can never signal the primary's
|
|
142
|
+
// daemon. Kept out of buildConversationStorageServiceLog, which never serializes options.
|
|
143
|
+
walleOwner: {
|
|
144
|
+
configDir: ctmDataDir,
|
|
145
|
+
instanceTag: resolveInstanceTag({ env, configDir: ctmDataDir, port: parseInt(env.CTM_PORT || '3456', 10) }),
|
|
146
|
+
},
|
|
132
147
|
};
|
|
133
148
|
}
|
|
134
149
|
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-3 / R-4..R-8 of docs/clock-free-tests-followups-r5-2026-10-25.html §5.
|
|
4
|
+
//
|
|
5
|
+
// Liveness for the boot migration's destructive walle re-run comes from Wall-E's own
|
|
6
|
+
// write-lock identity record — `<db>.write-lock`, a 1,024-byte space-padded JSON
|
|
7
|
+
// `{pid,...}` that PERSISTS after release (shared/sqlite-write-lock.js:1-40,250, D-3) —
|
|
8
|
+
// not from a `BEGIN IMMEDIATE` probe. Probe P1 showed that an owner which is alive with an
|
|
9
|
+
// idle connection (or no connection at all) passes the transaction probe, and the purge
|
|
10
|
+
// then rewrites the database under it (the 2026-07-20 column-loss incident, r4 §1.1).
|
|
11
|
+
//
|
|
12
|
+
// The module is synchronous on purpose: bin/ctm-service-entry.js runs this migration
|
|
13
|
+
// BEFORE it requires ../server, and the stop has to finish before the first walle write.
|
|
14
|
+
//
|
|
15
|
+
// R-6: nothing here is ever signalled on a name match. The one pid that may receive a
|
|
16
|
+
// signal is the daemon pid read from `<configDir>/walle.pid`, and only after a single
|
|
17
|
+
// `ps -p <pid>` row proves it is this instance's own Wall-E (lib/walle-kill-authority.js).
|
|
18
|
+
// R-7: any other live write-lock holder stays fail-closed (`conversation_migration_database_busy`),
|
|
19
|
+
// which the caller maps to exit 78 exactly as before.
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const { execFileSync } = require('node:child_process');
|
|
24
|
+
const { processTitleCandidates } = require('../lib/process-title');
|
|
25
|
+
const { isOwnedRow } = require('../lib/walle-kill-authority');
|
|
26
|
+
const { intFlag } = require('../lib/env-flags');
|
|
27
|
+
|
|
28
|
+
const WALLE_PID_FILE = 'walle.pid';
|
|
29
|
+
const WRITE_LOCK_SUFFIX = '.write-lock';
|
|
30
|
+
const PS_TIMEOUT_MS = 5000;
|
|
31
|
+
const PS_MAX_BUFFER = 1024 * 1024;
|
|
32
|
+
// One SIGKILL grace window after the graceful budget is spent; a killed-but-unreaped
|
|
33
|
+
// process is still a live pid until the kernel reaps it.
|
|
34
|
+
const SIGKILL_GRACE_MS = 1000;
|
|
35
|
+
// Same budget, default and clamp as lib/walle-supervisor.js:333,389 — a Wall-E that is
|
|
36
|
+
// legitimately draining inside its own WALLE_SHUTDOWN_DEADLINE_MS gets to finish.
|
|
37
|
+
const CHILD_EXIT_WAIT_ENV = 'WALLE_CHILD_EXIT_WAIT_MS';
|
|
38
|
+
const DEFAULT_CHILD_EXIT_WAIT_MS = 16000;
|
|
39
|
+
const MIN_CHILD_EXIT_WAIT_MS = 5000;
|
|
40
|
+
const MAX_CHILD_EXIT_WAIT_MS = 120000;
|
|
41
|
+
const DEFAULT_LOCK_RELEASE_WAIT_MS = 2000;
|
|
42
|
+
const DEFAULT_POLL_MS = 100;
|
|
43
|
+
|
|
44
|
+
function _childExitWaitMs() {
|
|
45
|
+
return Math.max(MIN_CHILD_EXIT_WAIT_MS, Math.min(MAX_CHILD_EXIT_WAIT_MS,
|
|
46
|
+
intFlag(CHILD_EXIT_WAIT_ENV, DEFAULT_CHILD_EXIT_WAIT_MS)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// R-4: the owner identity, read off disk. null covers ENOENT (nothing ever wrote here),
|
|
50
|
+
// EISDIR and a torn read of a v2 in-place write — every one of those means "no usable
|
|
51
|
+
// identity", never "throw". Shape copied from shared/sqlite-write-lock.js:250 (_readIdentity)
|
|
52
|
+
// and wall-e/bin/brain-purge-legacy-ctm.js:236-245 (liveReason's first check).
|
|
53
|
+
function readWriteLockOwnerPid(dbPath) {
|
|
54
|
+
let record;
|
|
55
|
+
try {
|
|
56
|
+
record = JSON.parse(fs.readFileSync(`${dbPath}${WRITE_LOCK_SUFFIX}`, 'utf8'));
|
|
57
|
+
} catch {
|
|
58
|
+
/* expected: ENOENT (no writer yet), EISDIR (a directory sits at the path) and a torn
|
|
59
|
+
read of an in-place v2 write all mean "no identity readable right now". */
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const pid = Number(record?.pid);
|
|
63
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// R-4: only ESRCH proves the pid is gone. EPERM means the pid EXISTS but is owned by
|
|
67
|
+
// another user — kill(pid,0) probes existence, not killability — so every non-ESRCH
|
|
68
|
+
// error fails toward "alive" (shared/sqlite-write-lock.js:231-245).
|
|
69
|
+
function pidAlive(pid, kill = process.kill) {
|
|
70
|
+
const value = Number(pid);
|
|
71
|
+
if (!Number.isFinite(value) || value <= 0) return false;
|
|
72
|
+
try {
|
|
73
|
+
kill(value, 0);
|
|
74
|
+
return true;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return error?.code !== 'ESRCH';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// A pid that has exited but has not been REAPED is still a live pid for kill(pid,0): the
|
|
81
|
+
// kernel keeps a zombie's pid until its parent waits. libuv only waits when the event loop
|
|
82
|
+
// runs, and this migration blocks the loop for its whole duration (see the module comment:
|
|
83
|
+
// it runs before ../server is required), so a daemon this process spawned -- ppid === myPid,
|
|
84
|
+
// which lib/walle-kill-authority.js:17 explicitly accepts as owned -- stays kill(pid,0)-alive
|
|
85
|
+
// forever after it has exited. Measured on this host: `kill(pid,0)` says alive while
|
|
86
|
+
// `ps -o stat=` says "Z" for the whole blocked window, then the pid disappears once the loop
|
|
87
|
+
// turns. A zombie has exited, so it must read as gone -- otherwise the SIGTERM we just sent
|
|
88
|
+
// would be reported back to the caller as "a live holder that is not this instance's Wall-E".
|
|
89
|
+
// Only consulted when kill(pid,0) already says alive, so the common reparented-to-launchd
|
|
90
|
+
// daemon (ppid 1, reaped by init) never pays for it.
|
|
91
|
+
function _isZombiePid(pid) {
|
|
92
|
+
try {
|
|
93
|
+
const stdout = execFileSync('ps', ['-p', String(pid), '-o', 'stat='], {
|
|
94
|
+
encoding: 'utf8',
|
|
95
|
+
timeout: PS_TIMEOUT_MS,
|
|
96
|
+
maxBuffer: 4096,
|
|
97
|
+
});
|
|
98
|
+
return String(stdout).trim().startsWith('Z');
|
|
99
|
+
} catch {
|
|
100
|
+
/* expected: ps exits non-zero when the pid has no row -- it is gone, or it was reaped
|
|
101
|
+
between the two probes. Either way there is no zombie to report. */
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function _readWalleDaemonPid(configDir) {
|
|
107
|
+
if (!configDir) return null;
|
|
108
|
+
try {
|
|
109
|
+
const pid = Number.parseInt(fs.readFileSync(path.join(String(configDir), WALLE_PID_FILE), 'utf8').trim(), 10);
|
|
110
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
111
|
+
} catch {
|
|
112
|
+
/* expected: no pid file (Wall-E never started for this config dir) or an unreadable
|
|
113
|
+
one both mean "no daemon to stop" — R-7 still fails closed on the write-lock owner. */
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The same synchronous sleep idiom the write-lock module uses (shared/sqlite-write-lock.js:226-229).
|
|
119
|
+
function sleepSync(ms) {
|
|
120
|
+
const buffer = new SharedArrayBuffer(4);
|
|
121
|
+
Atomics.wait(new Int32Array(buffer), 0, 0, ms);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let _parsePsWithPpid = null;
|
|
125
|
+
|
|
126
|
+
// Rows for ONE pid, parsed by the supervisor's own parser so ownership sees an identical
|
|
127
|
+
// row shape (lib/walle-supervisor.js:166-184,205-212). Loaded lazily: the migration runs
|
|
128
|
+
// before ../server boots, and the ps path is only reached when a live owner record exists.
|
|
129
|
+
function _psRow(pid) {
|
|
130
|
+
if (!_parsePsWithPpid) ({ parsePsWithPpid: _parsePsWithPpid } = require('../lib/walle-supervisor'));
|
|
131
|
+
try {
|
|
132
|
+
const stdout = execFileSync('ps', ['-p', String(pid), '-o', 'pid=,ppid=,command='], {
|
|
133
|
+
encoding: 'utf8',
|
|
134
|
+
timeout: PS_TIMEOUT_MS,
|
|
135
|
+
maxBuffer: PS_MAX_BUFFER,
|
|
136
|
+
});
|
|
137
|
+
return _parsePsWithPpid(stdout);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// A non-zero exit is ps answering "no such process" -> an empty row list.
|
|
140
|
+
// Anything else (ps un-runnable, timed out) is the probe itself failing -> null, which
|
|
141
|
+
// ownership must read as "not proven owned", never as "there is no process".
|
|
142
|
+
return Number.isInteger(error?.status) ? [] : null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// R-4/R-5/R-7 liveness, one predicate: gone (ESRCH) or exited-and-not-yet-reaped (zombie).
|
|
147
|
+
function _hasExited(pid, ops) {
|
|
148
|
+
return !pidAlive(pid, ops.kill) || ops.zombie(pid);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Poll until the pid is gone, bounded by BOTH the deadline and an iteration cap: a caller
|
|
152
|
+
// that injects a `now` which does not advance must not spin this loop forever.
|
|
153
|
+
function _waitForExit(pid, timeoutMs, pollMs, ops) {
|
|
154
|
+
if (_hasExited(pid, ops)) return true;
|
|
155
|
+
const budget = Math.max(0, Number(timeoutMs) || 0);
|
|
156
|
+
const poll = Math.max(1, Number(pollMs) || DEFAULT_POLL_MS);
|
|
157
|
+
const deadline = ops.now() + budget;
|
|
158
|
+
const maxIterations = Math.ceil(budget / poll) + 1;
|
|
159
|
+
for (let index = 0; index < maxIterations; index += 1) {
|
|
160
|
+
if (ops.now() >= deadline) break;
|
|
161
|
+
ops.sleep(Math.max(1, Math.min(poll, deadline - ops.now())));
|
|
162
|
+
if (_hasExited(pid, ops)) return true;
|
|
163
|
+
}
|
|
164
|
+
return _hasExited(pid, ops);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// R-5/R-6/R-7. Returns { stopped, pid, waitedMs, signal }; throws
|
|
168
|
+
// `conversation_migration_database_busy` when a live holder survives (R-7).
|
|
169
|
+
function ensureWalleOwnerStopped(walleDbPath, options = {}) {
|
|
170
|
+
const injected = options.ops || {};
|
|
171
|
+
const ops = {
|
|
172
|
+
kill: typeof injected.kill === 'function' ? injected.kill : process.kill,
|
|
173
|
+
psRow: typeof injected.psRow === 'function' ? injected.psRow : _psRow,
|
|
174
|
+
sleep: typeof injected.sleep === 'function' ? injected.sleep : sleepSync,
|
|
175
|
+
now: typeof injected.now === 'function' ? injected.now : Date.now,
|
|
176
|
+
myPid: Number.isInteger(injected.myPid) ? injected.myPid : process.pid,
|
|
177
|
+
// A caller that injects its own process table owns liveness entirely -- probing the
|
|
178
|
+
// real ps for a fake pid would be meaningless (and would fork a process per poll). The
|
|
179
|
+
// real zombie probe is only armed on the real process.kill path.
|
|
180
|
+
zombie: typeof injected.zombie === 'function' ? injected.zombie
|
|
181
|
+
: (typeof injected.kill === 'function' ? () => false : _isZombiePid),
|
|
182
|
+
};
|
|
183
|
+
const instanceTag = options.instanceTag ? String(options.instanceTag) : null;
|
|
184
|
+
const childExitWaitMs = Number.isFinite(options.childExitWaitMs)
|
|
185
|
+
? Math.max(0, Number(options.childExitWaitMs)) : _childExitWaitMs();
|
|
186
|
+
const lockReleaseWaitMs = Number.isFinite(options.lockReleaseWaitMs)
|
|
187
|
+
? Math.max(0, Number(options.lockReleaseWaitMs)) : DEFAULT_LOCK_RELEASE_WAIT_MS;
|
|
188
|
+
const pollMs = Number.isFinite(options.pollMs) ? Math.max(1, Number(options.pollMs)) : DEFAULT_POLL_MS;
|
|
189
|
+
const startedAt = ops.now();
|
|
190
|
+
|
|
191
|
+
const ownerPid = readWriteLockOwnerPid(walleDbPath);
|
|
192
|
+
if (ownerPid === null || ownerPid === ops.myPid || _hasExited(ownerPid, ops)) {
|
|
193
|
+
// No usable record, our own pid, or a provably dead writer: nothing to stop, and no
|
|
194
|
+
// ownership probe is even attempted.
|
|
195
|
+
return { stopped: false, pid: null, waitedMs: 0, signal: null };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let daemonPid = null;
|
|
199
|
+
let signal = null;
|
|
200
|
+
if (options.configDir && instanceTag) {
|
|
201
|
+
const candidate = _readWalleDaemonPid(options.configDir);
|
|
202
|
+
if (candidate !== null && !_hasExited(candidate, ops)) daemonPid = candidate;
|
|
203
|
+
}
|
|
204
|
+
if (daemonPid !== null) {
|
|
205
|
+
const rows = ops.psRow(daemonPid);
|
|
206
|
+
const row = (Array.isArray(rows) ? rows : []).find((entry) => Number(entry?.pid) === daemonPid) || null;
|
|
207
|
+
const owned = isOwnedRow(row, {
|
|
208
|
+
titles: new Set(processTitleCandidates('walle', instanceTag)),
|
|
209
|
+
myPid: ops.myPid,
|
|
210
|
+
});
|
|
211
|
+
if (owned) {
|
|
212
|
+
ops.kill(daemonPid, 'SIGTERM');
|
|
213
|
+
signal = 'SIGTERM';
|
|
214
|
+
if (!_waitForExit(daemonPid, childExitWaitMs, pollMs, ops)) {
|
|
215
|
+
ops.kill(daemonPid, 'SIGKILL');
|
|
216
|
+
signal = 'SIGKILL';
|
|
217
|
+
_waitForExit(daemonPid, SIGKILL_GRACE_MS, pollMs, ops);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// The write-lock owner may be a different pid than the daemon (a worker, or a foreign
|
|
222
|
+
// process). It gets the same short grace — the lock is released by process exit — and
|
|
223
|
+
// then fails closed below. It is never signalled unless it IS the proven daemon pid.
|
|
224
|
+
if (!_hasExited(ownerPid, ops)) _waitForExit(ownerPid, lockReleaseWaitMs, pollMs, ops);
|
|
225
|
+
|
|
226
|
+
if (!_hasExited(ownerPid, ops)) {
|
|
227
|
+
const error = new Error(
|
|
228
|
+
`database is owned by a live process that is not this instance's Wall-E: walle pid=${ownerPid}`,
|
|
229
|
+
);
|
|
230
|
+
error.code = 'conversation_migration_database_busy';
|
|
231
|
+
error.database = 'walle';
|
|
232
|
+
error.ownerPid = ownerPid;
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
stopped: signal !== null,
|
|
237
|
+
pid: daemonPid,
|
|
238
|
+
waitedMs: Math.max(0, ops.now() - startedAt),
|
|
239
|
+
signal,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
ensureWalleOwnerStopped,
|
|
245
|
+
pidAlive,
|
|
246
|
+
readWriteLockOwnerPid,
|
|
247
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// WP-3 (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §6.3, §9 S-23): the pairing CLI.
|
|
5
|
+
//
|
|
6
|
+
// ctm-machine-pair offer [--port <n>] [--json]
|
|
7
|
+
//
|
|
8
|
+
// Mints a pairing offer on the LOCAL CTM (`POST /api/machine-pairing/offers`) and prints the code,
|
|
9
|
+
// the expiry and the whole `ctmpair1:` pairing string, so a terminal user can pair a machine without
|
|
10
|
+
// opening the Machines page.
|
|
11
|
+
//
|
|
12
|
+
// IT NEVER PRINTS A TOKEN. The offer response carries a CODE (a single-use, TTL-bounded, 20^8-space
|
|
13
|
+
// secret) and nothing else secret; the pool token only ever travels member -> box. The response body
|
|
14
|
+
// is re-projected field by field rather than echoed, so a future response field can never leak
|
|
15
|
+
// through this CLI by accident.
|
|
16
|
+
//
|
|
17
|
+
// AUTH. The route is admin + step-up remote (lib/auth-rules.js), and a LOOPBACK request is already
|
|
18
|
+
// admin-scoped with no step-up (`hasScope`/`authorize`: loopback satisfies every scope and
|
|
19
|
+
// `stepUp === 'remote'` is skipped for loopback), so no cookie, bearer or device token is needed —
|
|
20
|
+
// this is the same posture every other loopback drivetool has. `--port` (or `CTM_PORT`, default
|
|
21
|
+
// 3456) is the only knob; the request always goes to 127.0.0.1 so it can never mint an offer on a
|
|
22
|
+
// remote machine.
|
|
23
|
+
//
|
|
24
|
+
// argv parsing only, no prompts: this is meant to be scriptable.
|
|
25
|
+
|
|
26
|
+
const http = require('node:http');
|
|
27
|
+
// CTM_PORT is read through lib/env-flags.js (the one truthiness/number parser) rather than
|
|
28
|
+
// `process.env` directly: bin/flag-lint.js fails a new raw read of a CTM_* flag outside that module,
|
|
29
|
+
// and CTM_PORT is a genuinely shared knob (bin/dev.sh, lib/hosted-primary-runtime.js).
|
|
30
|
+
const { intFlag } = require('../lib/env-flags');
|
|
31
|
+
|
|
32
|
+
const DEFAULT_PORT = 3456;
|
|
33
|
+
|
|
34
|
+
function parseArgs(argv) {
|
|
35
|
+
const envPort = intFlag('CTM_PORT', DEFAULT_PORT);
|
|
36
|
+
const out = { command: '', port: envPort > 0 ? envPort : DEFAULT_PORT, json: false, help: false };
|
|
37
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
38
|
+
const arg = argv[i];
|
|
39
|
+
if (arg === '--port' || arg === '-p') {
|
|
40
|
+
const value = Number(argv[++i]);
|
|
41
|
+
if (Number.isInteger(value) && value > 0) out.port = value;
|
|
42
|
+
} else if (arg === '--json') {
|
|
43
|
+
out.json = true;
|
|
44
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
45
|
+
out.help = true;
|
|
46
|
+
} else if (!out.command) {
|
|
47
|
+
out.command = arg;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function usage(out) {
|
|
54
|
+
out.write('usage: ctm-machine-pair offer [--port <n>] [--json]\n');
|
|
55
|
+
out.write('\n');
|
|
56
|
+
out.write(' offer mint a pairing code on the local CTM and print it\n');
|
|
57
|
+
out.write('\n');
|
|
58
|
+
out.write(' --port <n> CTM port (default: CTM_PORT, else 3456)\n');
|
|
59
|
+
out.write(' --json print the offer as JSON instead of a human report\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One loopback POST. `127.0.0.1` is hard-coded on purpose: a pairing offer must never be mintable on
|
|
64
|
+
* a machine other than the one this process is on.
|
|
65
|
+
*/
|
|
66
|
+
function postLoopback(port, pathname, body) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const payload = Buffer.from(JSON.stringify(body || {}));
|
|
69
|
+
const req = http.request({
|
|
70
|
+
host: '127.0.0.1',
|
|
71
|
+
port,
|
|
72
|
+
path: pathname,
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
'Content-Length': payload.length,
|
|
77
|
+
Accept: 'application/json',
|
|
78
|
+
},
|
|
79
|
+
}, (res) => {
|
|
80
|
+
const chunks = [];
|
|
81
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
82
|
+
res.on('end', () => {
|
|
83
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
84
|
+
let parsed = null;
|
|
85
|
+
try {
|
|
86
|
+
parsed = raw ? JSON.parse(raw) : null;
|
|
87
|
+
} catch {
|
|
88
|
+
parsed = null; // expected: a non-JSON error page is reported by status alone
|
|
89
|
+
}
|
|
90
|
+
resolve({ status: Number(res.statusCode) || 0, body: parsed, raw });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
req.on('error', reject);
|
|
94
|
+
req.end(payload);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatExpiry(expiresAt) {
|
|
99
|
+
const at = Number(expiresAt);
|
|
100
|
+
if (!Number.isFinite(at) || at <= 0) return 'unknown';
|
|
101
|
+
const seconds = Math.max(0, Math.round((at - Date.now()) / 1000));
|
|
102
|
+
return `${new Date(at).toISOString()} (in ${seconds}s)`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main(argv, io) {
|
|
106
|
+
const out = io.out;
|
|
107
|
+
const err = io.err;
|
|
108
|
+
const args = parseArgs(argv);
|
|
109
|
+
if (args.help || !args.command) {
|
|
110
|
+
usage(args.command ? out : err);
|
|
111
|
+
return args.command ? 0 : 2;
|
|
112
|
+
}
|
|
113
|
+
if (args.command !== 'offer') {
|
|
114
|
+
err.write(`ctm-machine-pair: unknown command '${args.command}'\n`);
|
|
115
|
+
usage(err);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
let res = null;
|
|
119
|
+
try {
|
|
120
|
+
res = await postLoopback(args.port, '/api/machine-pairing/offers', {});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
err.write(`ctm-machine-pair: cannot reach CTM on 127.0.0.1:${args.port} — ${error && error.message ? error.message : error}\n`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
if (res.status !== 201 || !res.body || res.body.ok !== true) {
|
|
126
|
+
const code = (res.body && (res.body.error || res.body.code)) || `http_${res.status}`;
|
|
127
|
+
err.write(`ctm-machine-pair: offer refused — ${code}\n`);
|
|
128
|
+
if (code === 'pool_member_exists') err.write(' this machine is already in a pool; leave it first\n');
|
|
129
|
+
if (code === 'pairing_offer_limit') err.write(' three offers are already live; wait for one to expire\n');
|
|
130
|
+
if (code === 'auth_rule_missing' || code === 'unauthorized') err.write(` is CTM running on port ${args.port}?\n`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
// Field-by-field projection: the response is NOT echoed, so a token added to it later cannot leak.
|
|
134
|
+
const offer = {
|
|
135
|
+
code: String(res.body.code || ''),
|
|
136
|
+
expiresAt: Number(res.body.expiresAt) || 0,
|
|
137
|
+
pairingString: String(res.body.pairingString || ''),
|
|
138
|
+
candidates: Array.isArray(res.body.candidates) ? res.body.candidates.map((url) => String(url || '')) : [],
|
|
139
|
+
};
|
|
140
|
+
if (args.json) {
|
|
141
|
+
out.write(`${JSON.stringify(offer, null, 2)}\n`);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
out.write(`Pairing code: ${offer.code}\n`);
|
|
145
|
+
out.write(`Expires: ${formatExpiry(offer.expiresAt)}\n`);
|
|
146
|
+
if (offer.candidates.length) out.write(`Reachable at: ${offer.candidates.join(', ')}\n`);
|
|
147
|
+
out.write(`Pairing string: ${offer.pairingString}\n`);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (require.main === module) {
|
|
152
|
+
main(process.argv.slice(2), { out: process.stdout, err: process.stderr })
|
|
153
|
+
.then((code) => { process.exitCode = code; })
|
|
154
|
+
.catch((error) => {
|
|
155
|
+
process.stderr.write(`ctm-machine-pair: ${error && error.stack ? error.stack : error}\n`);
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { parseArgs, postLoopback, main };
|