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,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-2a (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §5 D-4 rev 2, §6.2, §9 S-25):
|
|
4
|
+
// the machine's OWN user settings (display name override, favorite, hidden).
|
|
5
|
+
//
|
|
6
|
+
// D-4: settings live on the machine they describe. Every other member reads them from this
|
|
7
|
+
// machine's manifest/stats probe, so every primary and every device shows the same values. The
|
|
8
|
+
// registry's `enabled` column stays a per-primary local switch and is unrelated.
|
|
9
|
+
//
|
|
10
|
+
// This module is a small in-memory CACHE over the one-row `machine_self` table (v56). The manifest
|
|
11
|
+
// builder is pure and runs on the identity/ETag path, so it must never read the DB: server.js
|
|
12
|
+
// loads this cache once at boot and the POST /api/machine-self handler updates it on every write.
|
|
13
|
+
|
|
14
|
+
let _deps = { db: null };
|
|
15
|
+
let _cache = { displayName: '', favorite: false, hidden: false, updatedAt: 0 };
|
|
16
|
+
|
|
17
|
+
function configure({ db } = {}) {
|
|
18
|
+
_deps = { db };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function _applyRow(row) {
|
|
22
|
+
const source = row && typeof row === 'object' ? row : {};
|
|
23
|
+
_cache = {
|
|
24
|
+
displayName: String(source.displayName || source.display_name || '').slice(0, 80),
|
|
25
|
+
favorite: Number(source.favorite || 0) === 1,
|
|
26
|
+
hidden: Number(source.hidden || 0) === 1,
|
|
27
|
+
updatedAt: Number(source.updatedAt || source.updated_at || 0) || 0,
|
|
28
|
+
};
|
|
29
|
+
return _cache;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Boot read. A missing/errored row keeps the defaults ('' / false / false) — never throws. */
|
|
33
|
+
async function load() {
|
|
34
|
+
const db = _deps.db;
|
|
35
|
+
if (!db || typeof db.getMachineSelf !== 'function') return current();
|
|
36
|
+
try {
|
|
37
|
+
_applyRow(await db.getMachineSelf());
|
|
38
|
+
} catch {
|
|
39
|
+
// expected: pre-migration / pre-boot handle has no machine_self row — defaults are the truth
|
|
40
|
+
}
|
|
41
|
+
return current();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The manifest/ETag read: synchronous, allocation-free, no DB. */
|
|
45
|
+
function current() {
|
|
46
|
+
return { ..._cache };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** D-4 write: only the given fields change; the row's full shape comes back from the op. */
|
|
50
|
+
async function setSelf(patch = {}, at = Date.now()) {
|
|
51
|
+
const db = _deps.db;
|
|
52
|
+
const result = await db.setMachineSelf({
|
|
53
|
+
displayName: patch.displayName,
|
|
54
|
+
favorite: patch.favorite,
|
|
55
|
+
hidden: patch.hidden,
|
|
56
|
+
at,
|
|
57
|
+
});
|
|
58
|
+
return _applyRow(result);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Test seam: the cache is module state and must never leak between tests. */
|
|
62
|
+
function _resetForTests() {
|
|
63
|
+
_cache = { displayName: '', favorite: false, hidden: false, updatedAt: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { configure, load, current, setSelf, _resetForTests };
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"key": "lib/native-history-control.js\tprocess-launch\tspawn(\tconst child = spawn(source.executablePath, ['app-server', ...args], {",
|
|
23
23
|
"justification": "Provider-history WP-4: starts the owned Codex app-server control child with CTM_PROCESS_ROLE=ctm-native-history-control. The async RPC is limited to one transition, bounded frames/deadlines, abort-driven termination and awaited close; it is not a synchronous filesystem/process probe or an unowned detached spawn."
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
"key": "lib/codex-source-handoff.js\tprocess-launch\tspawn(\tchild = spawn(executablePath, [...HANDOFF_APP_SERVER_ARGS], { cwd: source.workingDirectory,",
|
|
27
|
+
"justification": "Codex lead history handoff WP-2 (D-5/D-6): starts one owned Codex app-server with CTM_PROCESS_ROLE=ctm-source-handoff for an ephemeral fork of the source thread and ONE handoff turn. Async JSON-RPC through the same createBoundedRpc as native-history-control (bounded frames, 240 s deadline, abort-driven termination, awaited close in finally); it runs only when a provider switch must cover a sealed compaction, never on a request hot path."
|
|
28
|
+
},
|
|
25
29
|
{
|
|
26
30
|
"key": "lib/native-subagent-review-page.js\tsync-api\topenSync(\tvfd = fileOps.openSync(t.transcriptPath);",
|
|
27
31
|
"justification": "Owner-reviewed native-subagent-review WP-0: bounded logical-path source identity recheck, closed in finally. The selected-child reader is a worker-only adapter; WP-1 routes production execution exclusively through readNativeSubagentReview in workers/read-pool-worker.js. One body read is capped at 128 KiB plus at most 1024 fence bytes, and each request's compressed/decoded archive ledger is capped at 4 MiB. No primary request handler calls the reader synchronously."
|
|
@@ -62,6 +66,10 @@
|
|
|
62
66
|
"key": "lib/approval-scope.js\tsync-api\tfs.lstatSync(\treturn fs.lstatSync(stripped).isSymbolicLink();",
|
|
63
67
|
"justification": "WP-2 fix round 1 (C2: `rm -rf` through a symlink deletes the real target): checks whether a RECURSIVE rm's leaf target is itself a symlink before counting it as an in-scope scratch delete. One lstat per recursive rm clause, wrapped in try/catch (missing path -> not a symlink), same bounded synchronous approval-decision path."
|
|
64
68
|
},
|
|
69
|
+
{
|
|
70
|
+
"key": "lib/approval-scope.js\tsync-api\tfs.lstatSync(\treturn { stat: fs.lstatSync(p) };",
|
|
71
|
+
"justification": "Dangling-symlink scope (docs/spark-umask-private-group-and-dangling-symlink-scope-2026-10-24.html D-5): an lstat that does NOT follow the link, used only on the existsSync-miss path of resolveDeepestExisting to tell a dangling symlink from a plain missing component while walking one write target to its deepest real ancestor during an approval decision. Bounded to 64 walk steps plus a 40-restart symlink budget; same synchronous approval-decision path as the other approval-scope.js rows. ENOENT continues the walk; any other error fails closed (null resolution, out of scope)."
|
|
72
|
+
},
|
|
65
73
|
{
|
|
66
74
|
"key": "lib/approval-scope.js\tsync-api\tfs.statSync(\ttry { return fs.statSync(p).isDirectory(); } catch { /* expected: does not exist */ return false; }",
|
|
67
75
|
"justification": "WP-2 fix round 1 (I5: a cp/mv destination that is an existing directory is itself a protected-path check target via dest/basename(src)): one stat per cp destination argument, wrapped in try/catch, same bounded synchronous approval-decision path."
|
|
@@ -2058,10 +2066,6 @@
|
|
|
2058
2066
|
"key": "server.js\tsync-api\tfs.existsSync(\texists: fs.existsSync(globalRules),",
|
|
2059
2067
|
"justification": "Pre-existing main-thread site migrated from the preserved baseline; tracked for burn-down."
|
|
2060
2068
|
},
|
|
2061
|
-
{
|
|
2062
|
-
"key": "server.js\tsync-api\tfs.existsSync(\texists: fs.existsSync(projectPath),",
|
|
2063
|
-
"justification": "Pre-existing main-thread site migrated from the preserved baseline; tracked for burn-down."
|
|
2064
|
-
},
|
|
2065
2069
|
{
|
|
2066
2070
|
"key": "server.js\tsync-api\tfs.existsSync(\texists: fs.existsSync(userRules),",
|
|
2067
2071
|
"justification": "Pre-existing main-thread site migrated from the preserved baseline; tracked for burn-down."
|
|
@@ -2753,6 +2757,54 @@
|
|
|
2753
2757
|
{
|
|
2754
2758
|
"key": "lib/harness-model-profile.js\tprocess-launch\texecFile(\texecFile(file, args, options, (error, stdout, stderr) => {",
|
|
2755
2759
|
"justification": "Codex harness version qualification WP-1 (D-2): the lead-model profile MEASURES the installed Codex instead of matching a version literal -- one async `codex debug models` (and one `--version`) per (binary fingerprint, catalog sha) pair, cached in-process and deduped in flight (T-3), run only when a lead switches model or a Codex lead launches, never per request or per keystroke. Async callback form so the hermetic preload's execFile wrapper keeps the {stdout, stderr} shape."
|
|
2760
|
+
},
|
|
2761
|
+
{
|
|
2762
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.mkdirSync(\tfs.mkdirSync(dir, { recursive: true, mode: 0o700 });",
|
|
2763
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-1/D-2): ensureProxyDir runs once per spawn on CTM's main thread (the 0700 oauth-proxy/ directory must exist before the spawn-time log fd below is opened) and once per record publication inside the PROXY process; never a request path. Same dir+mode shape as host-registry.js's ensureHostRegistryDir."
|
|
2764
|
+
},
|
|
2765
|
+
{
|
|
2766
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.writeFileSync(\tfs.writeFileSync(tmp, text, { mode: 0o600, flag: 'wx' });",
|
|
2767
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-2): the atomic 0600 publication (nonce tmp with flag 'wx', then rename -- host-registry.js's shape). On CTM's main thread it runs exactly once per spawn, for writeAdminSecret, which must precede the synchronous spawn so a CTM that dies between spawn and exit still leaves a readable secret for the next boot's adoption; writeInstanceRecord runs in the proxy process, not CTM."
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.renameSync(\tfs.renameSync(tmp, target);",
|
|
2771
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-2): the rename half of the same atomic publication -- once per spawn on CTM's main thread (admin secret), once per listen in the proxy process (instance record); never a request path."
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.unlinkSync(\tfs.unlinkSync(target);",
|
|
2775
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-4/D-5): reaping a stale instance record or admin secret -- once per rejected adoption decision, once per explicit stop(), once per torn-record read at boot; one unlink of a tiny file, never a request path."
|
|
2776
|
+
},
|
|
2777
|
+
{
|
|
2778
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.readFileSync(\traw = fs.readFileSync(instanceRecordPath(dataDir), 'utf8');",
|
|
2779
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-4): start() decides adopt-vs-spawn SYNCHRONOUSLY (six callers and the boot log read its return value) from one <=1 KiB JSON record, once per boot and once per crash-recovery restart; the HTTP half of the decision is async."
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.readFileSync(\traw = fs.readFileSync(adminSecretPath(dataDir), 'utf8');",
|
|
2783
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-4): one <=80-byte read of the admin secret per adoption, and adoption happens at most once per boot; never a request path."
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.openSync(\tconst fd = fs.openSync(target, 'a');",
|
|
2787
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-1): the descriptor the child's stdout/stderr are bound to is opened immediately before the synchronous spawn, once per proxy generation -- the same shape host-registry.js's _openHostLogFd is exempted for (a detached+unref'd child must not inherit CTM's pipes: an orphan dies with EPIPE on its second write)."
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.renameSync(\tfs.renameSync(target, `${target}${LOG_ROTATE_SUFFIX}`);",
|
|
2791
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-1): log rotation is one rename at >64 MiB, decided from the fd just opened, once per spawn; the previous generation stays readable at .1."
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\tfs.openSync(\treturn fs.openSync(target, 'a');",
|
|
2795
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-1): the re-open after rotation -- once per spawn, only when the 64 MiB ceiling was crossed."
|
|
2796
|
+
},
|
|
2797
|
+
{
|
|
2798
|
+
"key": "lib/model-proxy/proxy-instance-record.js\tsync-api\texecFileSync(\treturn execFileSync('ps', ['-o', 'command=', '-p', String(pid)], { encoding: 'utf8', timeout: PS_TIMEOUT_MS })",
|
|
2799
|
+
"justification": "Model-proxy survival WP-1 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-4): ps is the authority that a recorded pid is still THIS proxy before it is adopted or SIGTERMed (a recycled pid must never be signalled, and a dead proxy's pid is free for the OS to hand out). Runs once per boot/crash-recovery start() and once per explicit stop() of an adopted proxy, bounded by a 2 s timeout; never a request path."
|
|
2800
|
+
},
|
|
2801
|
+
{
|
|
2802
|
+
"key": "lib/model-proxy/handoff.js\tprocess-launch\tspawn(\tchild = spawn(process.execPath, [PROXY_SCRIPT], {",
|
|
2803
|
+
"justification": "Model-proxy survival WP-2 (docs/model-proxy-survives-ctm-restart-2026-10-22.html D-7 step 3): the upgrade handoff spawns the successor proxy from THIS file's own path on disk -- detached, its own log descriptor, one IPC channel, and exactly ONCE per process (the admin route answers 409 already_draining to every later caller). It is a spawn of a detached long-lived proxy (the same shape lib/oauth-proxy-supervisor.js's spawn is exempted for), not a request-path child, and it runs in the PROXY process rather than CTM's main thread."
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
"key": "lib/session-create/delegate-worker.js\tsync-api\tfs.readFileSync(\tconst bytes = fs.readFileSync(filePath);",
|
|
2807
|
+
"justification": "Lead-cost routing round 2 D-3 (docs/lead-cost-routing-round2-2026-10-24.html §6.2): getWorkerStatus() counts the distinct file:line citations of the text its report_source names -- one read of the worker's own report or --output-last-message file (tens of KB, capped at WORKER_REPORT_TEXT_CAP_BYTES for counting) per status/wait read of a TERMINAL worker only (non-terminal statuses read nothing), not a hot loop; synchronous for the same reason the report_present fs.existsSync and last-message fs.statSync exemptions above are: getWorkerStatus's §6.2 contract returns synchronously."
|
|
2756
2808
|
}
|
|
2757
2809
|
]
|
|
2758
2810
|
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// READ-ONLY recovery reader for the media journal's terminal draft request cancellations.
|
|
4
|
+
//
|
|
5
|
+
// It preserves lib/media-journal.js's framing contract exactly: an LF-delimited line is decoded with
|
|
6
|
+
// its own StringDecoder, JS-trimmed at both ends, blank lines are skipped, and a committed non-blank
|
|
7
|
+
// line that is not valid JSON FAILS the read -- it is never skipped. What it adds is that only the
|
|
8
|
+
// five root fields this lane owns are ever materialized, so an unselected multi-megabyte value costs
|
|
9
|
+
// one 64KiB window instead of a tree.
|
|
10
|
+
//
|
|
11
|
+
// Two bounded positional passes on ONE read-only FileHandle per file: a framing pass finds each LF
|
|
12
|
+
// and the exact byte range JS-trim keeps, then a parse pass re-reads just that range through the raw
|
|
13
|
+
// stream-json lexer (validation) and its Assembler (selected values only). Lexer, selector and IO
|
|
14
|
+
// errors all propagate; nothing is swallowed. Doubled IO, bounded content memory.
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { StringDecoder } = require('node:string_decoder');
|
|
19
|
+
|
|
20
|
+
// Both are ESM-only, BSD-3-Clause packages; Node 25.2.1's require(esm) is what lets this CJS module
|
|
21
|
+
// use their public exports (never a private prototype path).
|
|
22
|
+
const { jsonParser } = require('stream-json/core/parser.js');
|
|
23
|
+
const { Assembler } = require('stream-json/assembler.js');
|
|
24
|
+
const { none, isMany, getManyValues } = require('stream-chain/core');
|
|
25
|
+
|
|
26
|
+
const { JOURNAL_READ_WINDOW_BYTES, readCursor, readJournalWindow } = require('./media-journal');
|
|
27
|
+
|
|
28
|
+
// The canonical kind this lane exists for, compared exactly as the SQL validator compares it.
|
|
29
|
+
const TERMINAL_DRAFT_REQUEST_CANCEL = 'terminal-draft-request-cancel';
|
|
30
|
+
|
|
31
|
+
// The five root fields this lane owns. A published entry carries these and only these; a field the
|
|
32
|
+
// record lacks stays missing, so the real validator decides whether an incomplete cancellation is
|
|
33
|
+
// usable.
|
|
34
|
+
const CONTROL_ROOT_FIELDS = ['journalId', 'kind', 'sessionId', 'clientRequestId', 'tsMs'];
|
|
35
|
+
|
|
36
|
+
const JOURNAL_FILE_NAME = /^journal-.*\.jsonl$/;
|
|
37
|
+
const LF_BYTE = 0x0a;
|
|
38
|
+
const READER_CLOSED = 'CTM_MEDIA_JOURNAL_READER_CLOSED';
|
|
39
|
+
|
|
40
|
+
// Packed values are deliberately OFF: the raw lexer then emits start/…Chunk/end frames, so an
|
|
41
|
+
// unselected string or number is validated without ever being concatenated into a value.
|
|
42
|
+
const RAW_PARSER_OPTIONS = Object.freeze({
|
|
43
|
+
packKeys: false,
|
|
44
|
+
packStrings: false,
|
|
45
|
+
packNumbers: false,
|
|
46
|
+
jsonStreaming: false,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Bounded positional window reader shared by both passes: never a whole-file and never a whole-line
|
|
50
|
+
// read, and never more than one window in flight beyond the caller's own buffer.
|
|
51
|
+
async function readWindowRange({ handle, filePath, from, to, checkpoint, onWindow }) {
|
|
52
|
+
let position = from;
|
|
53
|
+
while (position < to) {
|
|
54
|
+
await checkpoint();
|
|
55
|
+
const window = await readJournalWindow(handle, filePath, position,
|
|
56
|
+
Math.min(JOURNAL_READ_WINDOW_BYTES, to - position));
|
|
57
|
+
await checkpoint();
|
|
58
|
+
await onWindow(window, position);
|
|
59
|
+
position += window.length;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Narrow the live root-field candidates against one key chunk. Candidates are compared character by
|
|
64
|
+
// character, so an unknown key of any length is rejected without ever being concatenated into a
|
|
65
|
+
// string, and a key longer than the longest field literal is rejected outright.
|
|
66
|
+
function narrowFieldCandidates(selector, chunk) {
|
|
67
|
+
const offset = selector.fieldOffset;
|
|
68
|
+
const next = [];
|
|
69
|
+
for (const field of selector.fieldCandidates) {
|
|
70
|
+
if (field.length < offset + chunk.length) continue;
|
|
71
|
+
let matches = true;
|
|
72
|
+
for (let index = 0; index < chunk.length; index += 1) {
|
|
73
|
+
if (field.charCodeAt(offset + index) !== chunk.charCodeAt(index)) { matches = false; break; }
|
|
74
|
+
}
|
|
75
|
+
if (matches) next.push(field);
|
|
76
|
+
}
|
|
77
|
+
selector.fieldOffset = offset + chunk.length;
|
|
78
|
+
selector.fieldCandidates = next.length ? next : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Selector for ONE record: tracks the root object, the nesting depth and the five root field
|
|
82
|
+
// literals, and assembles ONLY the selected root values. Every other token is consumed for
|
|
83
|
+
// validation and dropped, so an unselected value of any size or depth is never materialized.
|
|
84
|
+
class ControlRecordSelector {
|
|
85
|
+
constructor() {
|
|
86
|
+
this.entry = {};
|
|
87
|
+
this.depth = 0;
|
|
88
|
+
this.rootIsObject = false;
|
|
89
|
+
this.fieldCandidates = null;
|
|
90
|
+
this.fieldOffset = 0;
|
|
91
|
+
this.field = null;
|
|
92
|
+
this.selection = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
accept(token) {
|
|
96
|
+
const name = token.name;
|
|
97
|
+
|
|
98
|
+
// A selected root value is open: every token belongs to it until the value completes.
|
|
99
|
+
if (this.selection) {
|
|
100
|
+
this.pack(token);
|
|
101
|
+
switch (name) {
|
|
102
|
+
case 'startObject':
|
|
103
|
+
case 'startArray':
|
|
104
|
+
this.depth += 1;
|
|
105
|
+
break;
|
|
106
|
+
case 'endObject':
|
|
107
|
+
case 'endArray':
|
|
108
|
+
this.depth -= 1;
|
|
109
|
+
// The selected value started as a child of the root object, so its own container closing
|
|
110
|
+
// returns the depth to 1; a container nested inside it returns to 2 or deeper.
|
|
111
|
+
if (this.depth === 1) this.finishSelection();
|
|
112
|
+
break;
|
|
113
|
+
case 'endString':
|
|
114
|
+
case 'endNumber':
|
|
115
|
+
if (this.selection.type === 'scalar') this.finishSelection();
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
switch (name) {
|
|
124
|
+
case 'startObject':
|
|
125
|
+
case 'startArray':
|
|
126
|
+
if (this.depth === 0) {
|
|
127
|
+
// A root container: an array root has no field names, so it can never be a cancellation.
|
|
128
|
+
this.rootIsObject = name === 'startObject';
|
|
129
|
+
this.depth = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (this.depth === 1 && this.field !== null) this.beginSelection(name, token);
|
|
133
|
+
this.depth += 1;
|
|
134
|
+
return;
|
|
135
|
+
case 'endObject':
|
|
136
|
+
case 'endArray':
|
|
137
|
+
this.depth -= 1;
|
|
138
|
+
return;
|
|
139
|
+
case 'startKey':
|
|
140
|
+
this.field = null;
|
|
141
|
+
if (this.depth === 1 && this.rootIsObject) {
|
|
142
|
+
this.fieldCandidates = CONTROL_ROOT_FIELDS;
|
|
143
|
+
this.fieldOffset = 0;
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
case 'endKey': {
|
|
147
|
+
if (!this.fieldCandidates) return;
|
|
148
|
+
const offset = this.fieldOffset;
|
|
149
|
+
this.field = this.fieldCandidates.find(field => field.length === offset) || null;
|
|
150
|
+
this.fieldCandidates = null;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
case 'stringChunk':
|
|
154
|
+
// Key text arrives as stringChunk framed by startKey/endKey (this library has no keyChunk);
|
|
155
|
+
// an unselected value's text is validated by the lexer and dropped here.
|
|
156
|
+
if (this.fieldCandidates) narrowFieldCandidates(this, token.value);
|
|
157
|
+
return;
|
|
158
|
+
case 'startString':
|
|
159
|
+
case 'startNumber':
|
|
160
|
+
if (this.depth === 1 && this.field !== null) this.beginSelection('scalar', token);
|
|
161
|
+
return;
|
|
162
|
+
case 'trueValue':
|
|
163
|
+
case 'falseValue':
|
|
164
|
+
case 'nullValue':
|
|
165
|
+
if (this.depth === 1 && this.field !== null) {
|
|
166
|
+
this.beginSelection('scalar', token);
|
|
167
|
+
this.finishSelection();
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
default:
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
beginSelection(type, token) {
|
|
176
|
+
this.selection = {
|
|
177
|
+
type,
|
|
178
|
+
field: this.field,
|
|
179
|
+
assembler: new Assembler(),
|
|
180
|
+
inKey: false,
|
|
181
|
+
keyParts: null,
|
|
182
|
+
scalar: null,
|
|
183
|
+
};
|
|
184
|
+
this.field = null;
|
|
185
|
+
this.pack(token);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
finishSelection() {
|
|
189
|
+
const { field, assembler } = this.selection;
|
|
190
|
+
this.selection = null;
|
|
191
|
+
// A duplicate root key keeps the LAST value, exactly like JSON.parse.
|
|
192
|
+
this.entry[field] = assembler.current;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Bridges the raw lexer's frames into the packed tokens the upstream Assembler consumes:
|
|
196
|
+
// startString/…/endString -> stringValue, startNumber/…/endNumber -> numberValue,
|
|
197
|
+
// startKey/…/endKey -> keyValue. Structural and literal tokens pass straight through.
|
|
198
|
+
pack(token) {
|
|
199
|
+
const selection = this.selection;
|
|
200
|
+
switch (token.name) {
|
|
201
|
+
case 'startKey':
|
|
202
|
+
selection.inKey = true;
|
|
203
|
+
selection.keyParts = [];
|
|
204
|
+
return;
|
|
205
|
+
case 'endKey':
|
|
206
|
+
selection.inKey = false;
|
|
207
|
+
selection.assembler.consume({ name: 'keyValue', value: selection.keyParts.join('') });
|
|
208
|
+
selection.keyParts = null;
|
|
209
|
+
return;
|
|
210
|
+
case 'startString':
|
|
211
|
+
selection.scalar = { name: 'stringValue', parts: [] };
|
|
212
|
+
return;
|
|
213
|
+
case 'startNumber':
|
|
214
|
+
selection.scalar = { name: 'numberValue', parts: [] };
|
|
215
|
+
return;
|
|
216
|
+
case 'stringChunk':
|
|
217
|
+
case 'numberChunk':
|
|
218
|
+
if (selection.inKey) selection.keyParts.push(token.value);
|
|
219
|
+
else if (selection.scalar) selection.scalar.parts.push(token.value);
|
|
220
|
+
return;
|
|
221
|
+
case 'endString':
|
|
222
|
+
case 'endNumber': {
|
|
223
|
+
const scalar = selection.scalar;
|
|
224
|
+
selection.scalar = null;
|
|
225
|
+
selection.assembler.consume({ name: scalar.name, value: scalar.parts.join('') });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
default:
|
|
229
|
+
selection.assembler.consume(token);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Same global order as the journal's own pendingEntries(): durable timestamp, then file path, then
|
|
236
|
+
// the record's own byte offset. Applied ONCE, after the whole IO snapshot has been scanned.
|
|
237
|
+
function compareControlRecords(a, b) {
|
|
238
|
+
return Number(a.entry.tsMs || 0) - Number(b.entry.tsMs || 0)
|
|
239
|
+
|| a.filePath.localeCompare(b.filePath)
|
|
240
|
+
|| a.startOffset - b.startOffset;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function createMediaJournalControlReader(options = {}) {
|
|
244
|
+
const directory = String(options.directory || '');
|
|
245
|
+
const assertCurrent = options.assertCurrent;
|
|
246
|
+
if (!directory) throw new TypeError('Media journal control reader requires a directory');
|
|
247
|
+
if (typeof assertCurrent !== 'function') throw new TypeError('Media journal control reader requires an assertCurrent function');
|
|
248
|
+
const root = path.resolve(directory);
|
|
249
|
+
|
|
250
|
+
// Owned resource ledger: the ACTUAL FileHandle objects this reader opened and has not positively
|
|
251
|
+
// closed -- never file descriptors and never pids.
|
|
252
|
+
const owned = new Set();
|
|
253
|
+
const releases = new Map();
|
|
254
|
+
let readOp = null;
|
|
255
|
+
let closeOp = null;
|
|
256
|
+
let retired = false;
|
|
257
|
+
|
|
258
|
+
function closedError(message) {
|
|
259
|
+
return Object.assign(new Error(message || 'Media journal control reader is closed'), { code: READER_CLOSED });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The one boundary check: a retired reader stops a pending read before the next await and again
|
|
263
|
+
// before any byte is read or any token is processed or published.
|
|
264
|
+
async function checkpoint() {
|
|
265
|
+
if (retired) throw closedError('Media journal control reader was closed while reading');
|
|
266
|
+
await assertCurrent();
|
|
267
|
+
if (retired) throw closedError('Media journal control reader was closed while reading');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// One close attempt per handle per call, joined (never duplicated) while an attempt is in flight.
|
|
271
|
+
// A close that cannot be confirmed leaves the object in the ledger; it is never retried in a loop.
|
|
272
|
+
function release(handle) {
|
|
273
|
+
const inFlight = releases.get(handle);
|
|
274
|
+
if (inFlight) return inFlight;
|
|
275
|
+
const attempt = Promise.resolve().then(() => handle.close()).then(
|
|
276
|
+
() => { owned.delete(handle); return { ok: true }; },
|
|
277
|
+
error => ({ ok: false, error }),
|
|
278
|
+
).finally(() => { releases.delete(handle); });
|
|
279
|
+
releases.set(handle, attempt);
|
|
280
|
+
return attempt;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Second pass: re-read exactly one committed record's trimmed byte range and assemble only the
|
|
284
|
+
// root fields this lane owns. Validation is the library lexer's job; a syntax error propagates.
|
|
285
|
+
async function parseControlRecord(handle, filePath, from, to) {
|
|
286
|
+
const selector = new ControlRecordSelector();
|
|
287
|
+
const decoder = new StringDecoder('utf8');
|
|
288
|
+
const lexer = jsonParser(RAW_PARSER_OPTIONS);
|
|
289
|
+
const accept = (out) => {
|
|
290
|
+
if (out === none) return;
|
|
291
|
+
if (isMany(out)) {
|
|
292
|
+
for (const token of getManyValues(out)) selector.accept(token);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
selector.accept(out);
|
|
296
|
+
};
|
|
297
|
+
await readWindowRange({
|
|
298
|
+
handle,
|
|
299
|
+
filePath,
|
|
300
|
+
from,
|
|
301
|
+
to,
|
|
302
|
+
checkpoint,
|
|
303
|
+
onWindow: (window) => {
|
|
304
|
+
const text = decoder.write(window);
|
|
305
|
+
if (text) accept(lexer(text));
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
const tail = decoder.end();
|
|
309
|
+
if (tail) accept(lexer(tail));
|
|
310
|
+
// The single flush, at the known committed LF the range was framed against.
|
|
311
|
+
accept(lexer(none));
|
|
312
|
+
return selector.entry;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// First pass: find every LF, track the exact byte range JS-trim would keep, and hand each
|
|
316
|
+
// discovered record straight to the parse pass -- no array of boundaries, no whole-line buffer.
|
|
317
|
+
async function frameControlRecords(handle, filePath, start, size, results) {
|
|
318
|
+
let decoder = new StringDecoder('utf8');
|
|
319
|
+
let lineStart = start;
|
|
320
|
+
let hasContent = false;
|
|
321
|
+
let leadingTrimBytes = 0;
|
|
322
|
+
let trailingTrimBytes = 0;
|
|
323
|
+
|
|
324
|
+
// Trim runs are reconstructed from complete decoded chunks: a whitespace character is never
|
|
325
|
+
// split across chunks (the decoder holds incomplete sequences), and only REMOVED whitespace is
|
|
326
|
+
// ever measured in bytes, so a replacement character is never counted as trimmed.
|
|
327
|
+
const noteText = (text) => {
|
|
328
|
+
if (!text) return;
|
|
329
|
+
const leading = text.length - text.trimStart().length;
|
|
330
|
+
if (leading === text.length) {
|
|
331
|
+
// All trim whitespace: it extends the leading run while no content has been seen, and the
|
|
332
|
+
// trailing run afterwards.
|
|
333
|
+
if (hasContent) trailingTrimBytes += Buffer.byteLength(text, 'utf8');
|
|
334
|
+
else leadingTrimBytes += Buffer.byteLength(text, 'utf8');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (!hasContent) {
|
|
338
|
+
leadingTrimBytes += Buffer.byteLength(text.slice(0, leading), 'utf8');
|
|
339
|
+
hasContent = true;
|
|
340
|
+
}
|
|
341
|
+
const trailing = text.length - text.trimEnd().length;
|
|
342
|
+
trailingTrimBytes = Buffer.byteLength(text.slice(text.length - trailing), 'utf8');
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
await readWindowRange({
|
|
346
|
+
handle,
|
|
347
|
+
filePath,
|
|
348
|
+
from: start,
|
|
349
|
+
to: size,
|
|
350
|
+
checkpoint,
|
|
351
|
+
onWindow: async (window, windowStart) => {
|
|
352
|
+
let pieceStart = 0;
|
|
353
|
+
for (let index = 0; index < window.length; index += 1) {
|
|
354
|
+
if (window[index] !== LF_BYTE) continue;
|
|
355
|
+
noteText(decoder.write(window.subarray(pieceStart, index)));
|
|
356
|
+
noteText(decoder.end());
|
|
357
|
+
const lfOffset = windowStart + index;
|
|
358
|
+
if (hasContent) {
|
|
359
|
+
// The trimmed range this LF commits. Reported offsets stay the raw line span below, so
|
|
360
|
+
// the journal cursor contract is unchanged.
|
|
361
|
+
const entry = await parseControlRecord(handle, filePath,
|
|
362
|
+
lineStart + leadingTrimBytes, lfOffset - trailingTrimBytes);
|
|
363
|
+
if (String(entry.kind || '').trim() === TERMINAL_DRAFT_REQUEST_CANCEL) {
|
|
364
|
+
results.push({ entry, filePath, startOffset: lineStart, endOffset: lfOffset + 1 });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
decoder = new StringDecoder('utf8');
|
|
368
|
+
lineStart = lfOffset + 1;
|
|
369
|
+
hasContent = false;
|
|
370
|
+
leadingTrimBytes = 0;
|
|
371
|
+
trailingTrimBytes = 0;
|
|
372
|
+
pieceStart = index + 1;
|
|
373
|
+
}
|
|
374
|
+
// An unterminated tail is decoded but never committed: no LF, no record, no flush.
|
|
375
|
+
noteText(decoder.write(window.subarray(pieceStart)));
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function scanJournalFile(filePath, results) {
|
|
381
|
+
await checkpoint();
|
|
382
|
+
const handle = await fs.promises.open(filePath, 'r');
|
|
383
|
+
owned.add(handle);
|
|
384
|
+
let failed = false;
|
|
385
|
+
let failure;
|
|
386
|
+
try {
|
|
387
|
+
await checkpoint();
|
|
388
|
+
// The ONE captured length for this file: every window is clamped to it, so bytes appended
|
|
389
|
+
// after this snapshot are never claimed as consumed.
|
|
390
|
+
const { size } = await handle.stat();
|
|
391
|
+
await checkpoint();
|
|
392
|
+
const cursor = await readCursor(filePath);
|
|
393
|
+
await checkpoint();
|
|
394
|
+
const start = Math.min(size, Math.max(0, Number(cursor) || 0));
|
|
395
|
+
await frameControlRecords(handle, filePath, start, size, results);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
failed = true;
|
|
398
|
+
failure = error;
|
|
399
|
+
}
|
|
400
|
+
// The reader owns this handle either way: released exactly once, here.
|
|
401
|
+
const closed = await release(handle);
|
|
402
|
+
if (failed) {
|
|
403
|
+
if (!closed.ok) {
|
|
404
|
+
throw new AggregateError([failure, closed.error],
|
|
405
|
+
'Media journal control read and owned handle cleanup both failed', { cause: failure });
|
|
406
|
+
}
|
|
407
|
+
throw failure;
|
|
408
|
+
}
|
|
409
|
+
if (!closed.ok) throw closed.error;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function read() {
|
|
413
|
+
await checkpoint();
|
|
414
|
+
if (readOp) return readOp;
|
|
415
|
+
const operation = (async () => {
|
|
416
|
+
await checkpoint();
|
|
417
|
+
let names;
|
|
418
|
+
try {
|
|
419
|
+
names = await fs.promises.readdir(root);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
// A directory that has never existed is the empty journal; every other IO failure is not.
|
|
422
|
+
if (error?.code === 'ENOENT') { await checkpoint(); return []; }
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
await checkpoint();
|
|
426
|
+
const results = [];
|
|
427
|
+
for (const name of names.filter(name => JOURNAL_FILE_NAME.test(name)).sort()) {
|
|
428
|
+
await checkpoint();
|
|
429
|
+
await scanJournalFile(path.join(root, name), results);
|
|
430
|
+
}
|
|
431
|
+
await checkpoint();
|
|
432
|
+
return results.sort(compareControlRecords);
|
|
433
|
+
})();
|
|
434
|
+
// A failed scan stays the reader's answer: a later read() re-delivers THIS failure instead of
|
|
435
|
+
// opening a second handle beside one whose close may not have been confirmed. Only close() ever
|
|
436
|
+
// touches a real handle again.
|
|
437
|
+
readOp = operation.then((ordered) => {
|
|
438
|
+
if (retired) throw closedError();
|
|
439
|
+
return ordered;
|
|
440
|
+
});
|
|
441
|
+
return readOp;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function close() {
|
|
445
|
+
retired = true;
|
|
446
|
+
if (closeOp) return closeOp;
|
|
447
|
+
closeOp = (async () => {
|
|
448
|
+
const pending = readOp;
|
|
449
|
+
if (pending) {
|
|
450
|
+
try {
|
|
451
|
+
await pending;
|
|
452
|
+
} catch (readError) {
|
|
453
|
+
// The read caller already receives this failure; close reports only unconfirmed handles.
|
|
454
|
+
void readError;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
readOp = null;
|
|
458
|
+
const failures = [];
|
|
459
|
+
for (const handle of [...owned]) {
|
|
460
|
+
const closed = await release(handle);
|
|
461
|
+
if (!closed.ok) failures.push(closed.error);
|
|
462
|
+
}
|
|
463
|
+
if (failures.length) {
|
|
464
|
+
throw new AggregateError(failures, 'Media journal control reader left unconfirmed descriptors');
|
|
465
|
+
}
|
|
466
|
+
})().finally(() => { closeOp = null; });
|
|
467
|
+
return closeOp;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return { read, close };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = { createMediaJournalControlReader };
|