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,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// §5 D-9 / §9 S-5, S-8: how many requests this proxy is still carrying, and what it was carrying when
|
|
4
|
+
// it last stopped. Both truths live here on purpose -- the exit record is nothing but the counter read
|
|
5
|
+
// at exit, and a second copy of that number is exactly how the two would drift apart.
|
|
6
|
+
|
|
7
|
+
const fsp = require('node:fs/promises');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
|
|
10
|
+
// The health probe is itself a request, so counting it would make the counter unobservable from the one
|
|
11
|
+
// surface that reports it (the probe would always see its own 1). Only these two status paths are
|
|
12
|
+
// exempt; every other path is counted, keep-alive or not.
|
|
13
|
+
const UNCOUNTED_PATHS = new Set(['/_health', '/_status']);
|
|
14
|
+
|
|
15
|
+
const EXIT_RECORD_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
function _pathOf(url) {
|
|
18
|
+
const raw = String(url || '');
|
|
19
|
+
const cut = raw.indexOf('?');
|
|
20
|
+
return cut === -1 ? raw : raw.slice(0, cut);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The process's own in-flight stream counter.
|
|
25
|
+
*
|
|
26
|
+
* @param {{startedAt?: string}} [options] `startedAt` is overridable only so a test can pin it.
|
|
27
|
+
*/
|
|
28
|
+
function createStreamAccounting({ startedAt = new Date().toISOString() } = {}) {
|
|
29
|
+
let openStreams = 0;
|
|
30
|
+
// §5 D-3: the loaded-code hash this process published, set ONCE after listen by
|
|
31
|
+
// proxy-instance-record.publishInstanceRecord(). Read here so the /_health answer and the on-disk
|
|
32
|
+
// record can never describe different code; null until then (a start that never binds publishes none).
|
|
33
|
+
// §5 D-7 (WP-2): `generation` and `draining` travel the same way -- the successor of a handoff is
|
|
34
|
+
// generation N+1 and says so on /_health while its predecessor is still draining.
|
|
35
|
+
let identity = null;
|
|
36
|
+
// §5 D-7 step 7: the drain ends when this counter reaches zero, so the DECREMENT is what notifies --
|
|
37
|
+
// there is no polling loop and no second copy of the count.
|
|
38
|
+
const idleWaiters = new Set();
|
|
39
|
+
|
|
40
|
+
function _notifyIdle() {
|
|
41
|
+
const waiters = [...idleWaiters];
|
|
42
|
+
idleWaiters.clear();
|
|
43
|
+
for (const waiter of waiters) {
|
|
44
|
+
try {
|
|
45
|
+
waiter();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// REPORT: a drain waiter that throws is not the counter's problem; the drain deadline owns the
|
|
48
|
+
// fallback, so the failure is worth one line and nothing else.
|
|
49
|
+
process.stderr.write(`[model-proxy] drain waiter failed: ${(err && err.message) || 'unknown'}\n`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
/**
|
|
56
|
+
* Count every request this server is still answering. An idle keep-alive SOCKET carries no request
|
|
57
|
+
* and is never counted; a request is in flight until its response emits 'close' (whether it
|
|
58
|
+
* completed, errored or was cut by the client).
|
|
59
|
+
*/
|
|
60
|
+
track(server) {
|
|
61
|
+
server.on('request', (req, res) => {
|
|
62
|
+
if (UNCOUNTED_PATHS.has(_pathOf(req.url))) return;
|
|
63
|
+
openStreams += 1;
|
|
64
|
+
res.once('close', () => {
|
|
65
|
+
openStreams -= 1;
|
|
66
|
+
if (openStreams === 0) _notifyIdle();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
count() {
|
|
71
|
+
return openStreams;
|
|
72
|
+
},
|
|
73
|
+
snapshot() {
|
|
74
|
+
return { openStreams, startedAt, identity };
|
|
75
|
+
},
|
|
76
|
+
/** §5 D-3: the code this process loaded, published once; what /_health reports from. */
|
|
77
|
+
setIdentity(next) {
|
|
78
|
+
identity = next
|
|
79
|
+
? {
|
|
80
|
+
closureHash: next.closureHash || null,
|
|
81
|
+
closureFiles: Array.isArray(next.closureFiles) ? next.closureFiles : [],
|
|
82
|
+
generation: Number.isSafeInteger(next.generation) && next.generation > 0 ? next.generation : 1,
|
|
83
|
+
draining: next.draining === true,
|
|
84
|
+
}
|
|
85
|
+
: null;
|
|
86
|
+
},
|
|
87
|
+
/** §5 D-7 step 8: the predecessor answers /_health with draining:true from here until it exits. */
|
|
88
|
+
setDraining(value) {
|
|
89
|
+
const current = identity || { closureHash: null, closureFiles: [], generation: 1, draining: false };
|
|
90
|
+
identity = { ...current, draining: value === true };
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* §5 D-7 step 7: called ONCE by the handoff drain, on the decrement that brings the count to zero.
|
|
94
|
+
* @returns {Function} unsubscribe, so a drain that was already forced does not hold a callback
|
|
95
|
+
*/
|
|
96
|
+
onIdle(callback) {
|
|
97
|
+
idleWaiters.add(callback);
|
|
98
|
+
return () => idleWaiters.delete(callback);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* §9 S-8: the process-identity fields the `/_health` answer reports alongside its existing ones. The
|
|
105
|
+
* count is read live, never cached, so the answer can never describe an earlier moment. `generation`
|
|
106
|
+
* and `draining` come from the published identity (a handoff successor is generation N+1 and reports
|
|
107
|
+
* `draining:true` while its predecessor drains); a process that published none reports today's
|
|
108
|
+
* 1/false.
|
|
109
|
+
*/
|
|
110
|
+
function healthFields(accounting) {
|
|
111
|
+
const snapshot = accounting.snapshot();
|
|
112
|
+
const identity = snapshot.identity || {};
|
|
113
|
+
return {
|
|
114
|
+
pid: process.pid,
|
|
115
|
+
generation: identity.generation || 1,
|
|
116
|
+
protocol: 'proxy-v1',
|
|
117
|
+
floor_version: 0,
|
|
118
|
+
open_streams: snapshot.openStreams,
|
|
119
|
+
draining: identity.draining === true,
|
|
120
|
+
started_at: snapshot.startedAt,
|
|
121
|
+
// §5 D-3: what CTM compares against its own disk hash to decide adopt vs hand off.
|
|
122
|
+
closure_hash: identity.closureHash || null,
|
|
123
|
+
closure_files: identity.closureFiles || [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* §9 S-8: the health fields merged into whatever JSON body the responder writes. The relay keeps
|
|
129
|
+
* ownership of its own five fields (unchanged, names and types); this only adds what D-9/S-8 own, and a
|
|
130
|
+
* non-JSON end (there is none today) is passed through untouched rather than guessed at.
|
|
131
|
+
*/
|
|
132
|
+
function withHealthFields(res, accounting) {
|
|
133
|
+
const end = res.end.bind(res);
|
|
134
|
+
res.end = (chunk, ...rest) => {
|
|
135
|
+
let body;
|
|
136
|
+
try {
|
|
137
|
+
body = JSON.parse(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk));
|
|
138
|
+
} catch {
|
|
139
|
+
return end(chunk, ...rest); // expected: only the health JSON body is augmented
|
|
140
|
+
}
|
|
141
|
+
return end(JSON.stringify({ ...body, ...healthFields(accounting) }), ...rest);
|
|
142
|
+
};
|
|
143
|
+
return res;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function exitRecordPath(dataDir) {
|
|
147
|
+
return path.join(dataDir, 'oauth-proxy', 'last-exit.json');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* §9 S-5: the previous exit, written once. Atomic (tmp + rename) and owner-only, so a concurrent reader
|
|
152
|
+
* sees either the whole record or no record — never a half-written one. Async like the budget journals
|
|
153
|
+
* beside it (request-budget-store.js): the proxy awaits the write before it exits, and the supervisor
|
|
154
|
+
* reads it off the main thread.
|
|
155
|
+
* @returns {Promise<object>} the record written
|
|
156
|
+
*/
|
|
157
|
+
async function writeExitRecord({ dataDir, reason, openStreams }) {
|
|
158
|
+
const record = { v: EXIT_RECORD_VERSION, at: new Date().toISOString(), pid: process.pid, reason, openStreams };
|
|
159
|
+
const target = exitRecordPath(dataDir);
|
|
160
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
161
|
+
await fsp.mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
162
|
+
await fsp.writeFile(tmp, `${JSON.stringify(record)}\n`, { mode: 0o600 });
|
|
163
|
+
await fsp.rename(tmp, target);
|
|
164
|
+
return record;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @returns {Promise<object|null>} the recorded previous exit, or null when there is none to report. A
|
|
169
|
+
* truncated or hand-edited record is reported as absent rather than thrown: it is evidence of nothing.
|
|
170
|
+
*/
|
|
171
|
+
async function readExitRecord({ dataDir }) {
|
|
172
|
+
let raw;
|
|
173
|
+
try {
|
|
174
|
+
raw = await fsp.readFile(exitRecordPath(dataDir), 'utf8');
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (err && err.code === 'ENOENT') return null; // expected: no previous exit was recorded
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const record = JSON.parse(raw);
|
|
181
|
+
return record && typeof record === 'object' ? record : null;
|
|
182
|
+
} catch {
|
|
183
|
+
return null; // expected: an unparseable record is not evidence; report it as absent
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** One exit is reported once: the supervisor reads the record, then clears it. */
|
|
188
|
+
async function clearExitRecord({ dataDir }) {
|
|
189
|
+
try {
|
|
190
|
+
await fsp.rm(exitRecordPath(dataDir), { force: true });
|
|
191
|
+
} catch (err) {
|
|
192
|
+
// REPORT: a record that cannot be removed is stale truth on disk, which is worth a line.
|
|
193
|
+
console.error(`[model-proxy] exit record not removed: ${(err && err.code) || 'unknown'}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
createStreamAccounting,
|
|
199
|
+
healthFields,
|
|
200
|
+
withHealthFields,
|
|
201
|
+
writeExitRecord,
|
|
202
|
+
readExitRecord,
|
|
203
|
+
clearExitRecord,
|
|
204
|
+
exitRecordPath,
|
|
205
|
+
};
|
|
@@ -781,6 +781,81 @@ function tokenCount() {
|
|
|
781
781
|
return _tokens.size;
|
|
782
782
|
}
|
|
783
783
|
|
|
784
|
+
// ── §9 S-10 / §5 D-7 state export (WP-2 handoff) ────────────────────────────────────────────────
|
|
785
|
+
//
|
|
786
|
+
// The token registry is memory-only, and the handoff successor is a DIFFERENT process, so the rows
|
|
787
|
+
// have to travel over IPC. Two shapes live in `_tokens` and they are not equally serializable:
|
|
788
|
+
// * a lead entry is plain data (its `aliases` is the only non-JSON member, a Set);
|
|
789
|
+
// * a worker entry additionally owns a live `budget` handle (lib/model-proxy/request-budget.js: a
|
|
790
|
+
// journal, a clock, in-process callbacks). That handle is PROCESS state, not a row -- it is
|
|
791
|
+
// deliberately NOT serialized, and the imported row carries `budget: null`, exactly the shape
|
|
792
|
+
// `mintWorkerToken` already produces for a token minted without a policy. Authorization
|
|
793
|
+
// (`authorize()`, `tokenEntry()`) is therefore preserved byte-for-byte, while admission accounting
|
|
794
|
+
// stays with the owner that holds the journal: CTM re-mints/replays worker tokens itself, the same
|
|
795
|
+
// way a fresh spawn is served today.
|
|
796
|
+
|
|
797
|
+
/** @returns {Array<{token:string, entry:object}>} every NON-REVOKED row, in IPC-safe (plain JSON) form. */
|
|
798
|
+
function exportAll() {
|
|
799
|
+
const rows = [];
|
|
800
|
+
for (const [token, entry] of _tokens) {
|
|
801
|
+
if (!entry || entry.revoked) continue;
|
|
802
|
+
if (entry.kind === 'lead') {
|
|
803
|
+
rows.push({ token, entry: { ...entry, aliases: [...entry.aliases] } });
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
rows.push({
|
|
807
|
+
token,
|
|
808
|
+
entry: {
|
|
809
|
+
kind: 'worker',
|
|
810
|
+
ctmSessionId: entry.ctmSessionId,
|
|
811
|
+
aliases: [...entry.aliases],
|
|
812
|
+
binding: entry.binding || null,
|
|
813
|
+
expiresAt: entry.expiresAt,
|
|
814
|
+
revoked: false,
|
|
815
|
+
},
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
return rows;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Register each exported row verbatim (§9 S-10). A row that is not shaped like an export is SKIPPED
|
|
823
|
+
* rather than throwing: a successor that cannot read one row must still come up serving the rest, and
|
|
824
|
+
* the row it skipped simply authorizes nothing. Re-registering an identical lead row is a no-op, which
|
|
825
|
+
* is what makes an overlap between this and a later CTM replay harmless.
|
|
826
|
+
* @returns {number} how many rows were registered
|
|
827
|
+
*/
|
|
828
|
+
function importAll(rows) {
|
|
829
|
+
if (!Array.isArray(rows)) return 0;
|
|
830
|
+
let imported = 0;
|
|
831
|
+
for (const row of rows) {
|
|
832
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) continue;
|
|
833
|
+
const { token, entry } = row;
|
|
834
|
+
if (typeof token !== 'string' || !entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
835
|
+
if (!Array.isArray(entry.aliases) || entry.aliases.some((alias) => typeof alias !== 'string')) continue;
|
|
836
|
+
const next = entry.kind === 'lead'
|
|
837
|
+
? { ...entry, aliases: new Set(entry.aliases), expiresAt: Infinity, revoked: entry.revoked === true }
|
|
838
|
+
: {
|
|
839
|
+
kind: 'worker',
|
|
840
|
+
ctmSessionId: entry.ctmSessionId,
|
|
841
|
+
aliases: new Set(entry.aliases),
|
|
842
|
+
binding: entry.binding || null,
|
|
843
|
+
expiresAt: typeof entry.expiresAt === 'number' ? entry.expiresAt : Infinity,
|
|
844
|
+
revoked: entry.revoked === true,
|
|
845
|
+
// The budget handle is process state and is re-derived by its owner, never carried across
|
|
846
|
+
// processes (see the export note above).
|
|
847
|
+
budget: null,
|
|
848
|
+
};
|
|
849
|
+
_tokens.set(token, next);
|
|
850
|
+
// The certificate -> token index is part of the registry, so a rehydrated mTLS owner keeps
|
|
851
|
+
// resolving by fingerprint without a replay from CTM.
|
|
852
|
+
const fingerprint = _resolvedNativeCertFingerprint(next);
|
|
853
|
+
if (fingerprint) _nativeCertOwners.set(fingerprint, token);
|
|
854
|
+
imported += 1;
|
|
855
|
+
}
|
|
856
|
+
return imported;
|
|
857
|
+
}
|
|
858
|
+
|
|
784
859
|
module.exports = {
|
|
785
860
|
TOKEN_PREFIX,
|
|
786
861
|
TOKEN_RE,
|
|
@@ -819,4 +894,7 @@ module.exports = {
|
|
|
819
894
|
stopWorkerBudget,
|
|
820
895
|
resetTokens,
|
|
821
896
|
tokenCount,
|
|
897
|
+
// §9 S-10 (WP-2 handoff): the memory-only rows, in IPC-safe form, and their verbatim re-registration.
|
|
898
|
+
exportAll,
|
|
899
|
+
importAll,
|
|
822
900
|
};
|
|
@@ -9,6 +9,8 @@ const SAFE_MESSAGES = Object.freeze({
|
|
|
9
9
|
rate_limit_exceeded: 'The provider is rate limiting requests.',
|
|
10
10
|
server_is_overloaded: 'The provider is temporarily overloaded.',
|
|
11
11
|
server_error: 'The provider could not complete the request.',
|
|
12
|
+
model_not_invocable: 'This route cannot invoke this model id; it needs the model’s inference-profile id.',
|
|
13
|
+
model_access_denied: 'The route’s provider account denies access to this model.',
|
|
12
14
|
});
|
|
13
15
|
|
|
14
16
|
const SAFE_CODE = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
@@ -16,6 +18,7 @@ const SAFE_PROVIDER_CODES = new Set([
|
|
|
16
18
|
'invalid_prompt', 'invalid_request', 'invalid_request_error', 'model_not_found',
|
|
17
19
|
'context_length_exceeded', 'insufficient_quota', 'rate_limit_exceeded',
|
|
18
20
|
'server_is_overloaded', 'overloaded_error', 'server_error', 'internal_error',
|
|
21
|
+
'model_not_invocable', 'model_access_denied',
|
|
19
22
|
]);
|
|
20
23
|
|
|
21
24
|
function _stringCode(value) {
|
|
@@ -36,6 +39,12 @@ function _classify(code, status, message) {
|
|
|
36
39
|
if (/insufficient[_ -]?quota|quota[_ -]?exceeded|billing/.test(folded)) return 'insufficient_quota';
|
|
37
40
|
if (/rate[_ -]?limit|too many requests/.test(folded) || status === 429) return 'rate_limit_exceeded';
|
|
38
41
|
if (/overloaded|capacity[_ -]?exceeded/.test(folded)) return 'server_is_overloaded';
|
|
42
|
+
// D-5 (docs/routed-anthropic-lead-effort-levels-2026-10-25.html): a permission denial does not clear
|
|
43
|
+
// on retry, and a 400/422 naming on-demand/inference-profile is a route/model-id mismatch, not a bad
|
|
44
|
+
// prompt. Both run after the four message-driven classes above (a 429 naming "inference profile" stays
|
|
45
|
+
// rate_limit_exceeded) and before the generic invalid/4xx line. The provider's own prose is never copied.
|
|
46
|
+
if (status === 401 || status === 403 || code === '401' || code === '403') return 'model_access_denied';
|
|
47
|
+
if (/on-demand throughput|inference profile/.test(folded) && (status === 400 || status === 422 || code === '400' || code === '422')) return 'model_not_invocable';
|
|
39
48
|
if (/invalid|model[_ -]?not[_ -]?found|history/.test(folded) || ['400', '404', '422'].includes(code) || [400, 404, 422].includes(status)) return 'invalid_prompt';
|
|
40
49
|
return 'server_error';
|
|
41
50
|
}
|
|
@@ -5,16 +5,36 @@ const { StringDecoder } = require('node:string_decoder');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const { codexProxyProviderArgs } = require('./codex-launch-health');
|
|
7
7
|
// S-5: the adapter id and the qualified-version membership test come from the one table.
|
|
8
|
-
const { CODEX_CONTROL_ADAPTER, isQualifiedCodexVersion } = require('./codex-harness-qualification');
|
|
8
|
+
const { CODEX_CONTROL_ADAPTER, isQualifiedCodexVersion, isNonElevatedCyberAccessProgram } = require('./codex-harness-qualification');
|
|
9
9
|
|
|
10
10
|
const QUALIFIED_ADAPTER = CODEX_CONTROL_ADAPTER;
|
|
11
11
|
const MAX_FRAME_BYTES = 1024 * 1024;
|
|
12
|
+
const TURNS_PAGE_LIMIT = 8;
|
|
12
13
|
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
13
14
|
const MAX_TIMEOUT_MS = 30_000;
|
|
14
15
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15
16
|
const POLICY_CONFIG_KEYS = new Set(['approval_policy', 'sandbox_mode', 'sandbox_workspace_write.network_access',
|
|
16
17
|
'sandbox_workspace_write.writable_roots', 'sandbox_workspace_write.allowed_domains',
|
|
17
18
|
'sandbox_workspace_write.exclude_tmpdir_env_var', 'sandbox_workspace_write.exclude_slash_tmp']);
|
|
19
|
+
const HANDOFF_SHA_RE = /^[a-f0-9]{64}$/;
|
|
20
|
+
// D-6/D-7 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §6.4): the plaintext
|
|
21
|
+
// handoff the source model wrote, verbatim from Codex rust-v0.163.0
|
|
22
|
+
// codex-rs/prompts/templates/compact/summary_prefix.md (Apache-2.0, attributed there). It is the
|
|
23
|
+
// same preamble Codex puts in front of a native summary, so the target reads it as one.
|
|
24
|
+
const SUMMARY_PREFIX = 'Another language model started to solve this problem and produced a summary of its thinking process. You also have access to the state of the tools that were used by that language model. Use this to build on the work that has already been done and avoid duplicating work. Here is the summary produced by the other language model, use the information in this summary to assist with your own analysis:';
|
|
25
|
+
// The ONE marker grammar the scanner (lib/history-transition-read-op.js HANDOFF_MARKER) and the
|
|
26
|
+
// proxy (lib/model-proxy/collaboration-codec.js) both read. The sha binds the summary to exactly
|
|
27
|
+
// one sealed compaction item; the model name is what the source model was when it wrote it.
|
|
28
|
+
const HANDOFF_MARKER_PREFIX = '<ctm_handoff compaction_sha256="';
|
|
29
|
+
|
|
30
|
+
function handoffMessage(handoff) {
|
|
31
|
+
if (!isRecord(handoff) || !HANDOFF_SHA_RE.test(String(handoff.compactionSha256 || ''))
|
|
32
|
+
|| typeof handoff.text !== 'string' || !handoff.text.trim()
|
|
33
|
+
|| !nonempty(handoff.sourceModel)) throw adapterError('native_handoff_inject_failed');
|
|
34
|
+
const body = `${HANDOFF_MARKER_PREFIX}${handoff.compactionSha256}" source_model="${handoff.sourceModel}">\n`
|
|
35
|
+
+ SUMMARY_PREFIX + '\n\n' + handoff.text + '\n</ctm_handoff>';
|
|
36
|
+
return { type: 'message', role: 'developer', content: [{ type: 'input_text', text: body }] };
|
|
37
|
+
}
|
|
18
38
|
|
|
19
39
|
function createCodexNativeHistoryControl({ sourceRecipe, targetRecipe } = {}) {
|
|
20
40
|
const source = validateSource(sourceRecipe);
|
|
@@ -88,11 +108,14 @@ function validatePolicyOverrides(value) {
|
|
|
88
108
|
return Object.freeze(output);
|
|
89
109
|
}
|
|
90
110
|
|
|
91
|
-
async function forkCheckpoint(source, target, checkpoint, { signal, timeoutMs = DEFAULT_TIMEOUT_MS, targetAuthToken } = {}) {
|
|
111
|
+
async function forkCheckpoint(source, target, checkpoint, { signal, timeoutMs = DEFAULT_TIMEOUT_MS, targetAuthToken, handoff } = {}) {
|
|
92
112
|
const sourceThreadId = nonempty(checkpoint?.sourceThreadId);
|
|
93
113
|
const lastTurnId = nonempty(checkpoint?.lastTurnId);
|
|
94
114
|
const sourceGeneration = nonempty(checkpoint?.sourceGeneration);
|
|
95
115
|
if (!UUID_RE.test(sourceThreadId) || !lastTurnId || !sourceGeneration) throw adapterError('source_checkpoint_invalid');
|
|
116
|
+
// D-7: the handoff message is validated BEFORE the fork starts, so an unusable summary can never
|
|
117
|
+
// leave a half-created target thread behind.
|
|
118
|
+
const handoffItem = handoff === undefined || handoff === null ? null : handoffMessage(handoff);
|
|
96
119
|
if (signal?.aborted) throw adapterError('transition_cancelled');
|
|
97
120
|
const deadlineMs = Number.isSafeInteger(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
98
121
|
const deadlineAt = Date.now() + deadlineMs;
|
|
@@ -106,13 +129,22 @@ async function forkCheckpoint(source, target, checkpoint, { signal, timeoutMs =
|
|
|
106
129
|
});
|
|
107
130
|
if (!initialized || rpc.closed) throw adapterError('native_initialize_failed');
|
|
108
131
|
await rpc.notify('initialized', {});
|
|
132
|
+
// excludeTurns: a full-history fork response is the whole thread in one frame (a live 23 MB
|
|
133
|
+
// rollout hydrates to 5.5 MB); only the new thread id is read from it.
|
|
109
134
|
const result = await rpc.request('thread/fork', {
|
|
110
|
-
threadId: sourceThreadId, lastTurnId, model: target.modelId, ephemeral: false,
|
|
135
|
+
threadId: sourceThreadId, lastTurnId, model: target.modelId, ephemeral: false, excludeTurns: true,
|
|
111
136
|
});
|
|
112
137
|
if (signal?.aborted) throw adapterError('transition_cancelled');
|
|
113
138
|
const targetThreadId = nonempty(result?.thread?.id || result?.threadId || result?.thread_id);
|
|
114
139
|
createdTargetId = targetThreadId;
|
|
115
140
|
if (!UUID_RE.test(targetThreadId) || targetThreadId === sourceThreadId) throw adapterError('native_fork_checkpoint_invalid');
|
|
141
|
+
// D-7: the covered compaction's plaintext handoff is persisted INTO the forked thread as one
|
|
142
|
+
// developer message. Codex sends the sealed `compaction` item on verbatim; the proxy drops it
|
|
143
|
+
// only when this same request carries the marker, so a failed inject must fail the fork closed.
|
|
144
|
+
if (handoffItem) {
|
|
145
|
+
try { await rpc.request('thread/inject_items', { threadId: targetThreadId, items: [handoffItem] }); }
|
|
146
|
+
catch { throw adapterError('native_handoff_inject_failed'); }
|
|
147
|
+
}
|
|
116
148
|
// Close the creating process before the readback. A new app-server must recover the
|
|
117
149
|
// fork from disk; resuming in the process that created it could pass from cache alone.
|
|
118
150
|
await rpc.close();
|
|
@@ -171,21 +203,33 @@ async function coldResume(source, args, target, threadId, expectedLastTurnId, si
|
|
|
171
203
|
});
|
|
172
204
|
if (!initialized || rpc.closed) throw adapterError('native_readback_initialize_failed');
|
|
173
205
|
await rpc.notify('initialized', {});
|
|
174
|
-
const resumed = await rpc.request('thread/resume', { threadId });
|
|
206
|
+
const resumed = await rpc.request('thread/resume', { threadId, excludeTurns: true });
|
|
175
207
|
const resumedId = nonempty(resumed?.thread?.id || resumed?.threadId || resumed?.thread_id);
|
|
176
208
|
if (resumedId !== threadId) throw adapterError('native_fork_cold_resume_mismatch');
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
const turns = readThread?.turns;
|
|
180
|
-
if (readThread?.id !== threadId || !Array.isArray(turns)) throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
|
|
181
|
-
const restored = turns.find((turn) => turn?.id === expectedLastTurnId && turn?.status === 'completed');
|
|
182
|
-
if (!restored) throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
|
|
209
|
+
const restored = await findTurn(rpc, threadId, expectedLastTurnId);
|
|
210
|
+
if (restored?.status !== 'completed') throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
|
|
183
211
|
return { ...resumed, restoredCheckpoint: { turnId: restored.id, status: restored.status } };
|
|
184
212
|
} finally {
|
|
185
213
|
await rpc.close();
|
|
186
214
|
}
|
|
187
215
|
}
|
|
188
216
|
|
|
217
|
+
// Codex rust-v0.163.0 thread/turns/list, newest first and without items: the fork ends at the
|
|
218
|
+
// checkpoint, so it is on the first page, and a page stays ~2 KB however long the thread is
|
|
219
|
+
// (measured on a 24-turn thread: 3 pages of 8, largest frame 2,021 B).
|
|
220
|
+
async function findTurn(rpc, threadId, turnId) {
|
|
221
|
+
let cursor = null;
|
|
222
|
+
do {
|
|
223
|
+
const page = await rpc.request('thread/turns/list', { threadId, limit: TURNS_PAGE_LIMIT,
|
|
224
|
+
sortDirection: 'desc', itemsView: 'notLoaded', ...(cursor ? { cursor } : {}) });
|
|
225
|
+
if (!Array.isArray(page?.data)) throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
|
|
226
|
+
const turn = page.data.find((item) => item?.id === turnId);
|
|
227
|
+
if (turn) return turn;
|
|
228
|
+
cursor = nonempty(page.nextCursor);
|
|
229
|
+
} while (cursor);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
189
233
|
function buildAppServerArgs(target) {
|
|
190
234
|
const args = codexProxyProviderArgs({
|
|
191
235
|
providerId: target.providerId, name: target.providerName, proxyPort: target.proxyPort,
|
|
@@ -200,7 +244,7 @@ function buildAppServerArgs(target) {
|
|
|
200
244
|
return args;
|
|
201
245
|
}
|
|
202
246
|
|
|
203
|
-
function createBoundedRpc(child, { signal, timeoutMs }) {
|
|
247
|
+
function createBoundedRpc(child, { signal, timeoutMs, allowTurn = false, onNotification = null }) {
|
|
204
248
|
let buffer = '';
|
|
205
249
|
let closed = false;
|
|
206
250
|
let nextId = 1;
|
|
@@ -240,11 +284,14 @@ function createBoundedRpc(child, { signal, timeoutMs }) {
|
|
|
240
284
|
|| (message.params !== undefined && !isRecord(message.params))) {
|
|
241
285
|
fail(adapterError('native_rpc_frame_invalid')); void terminateOnce(); return;
|
|
242
286
|
}
|
|
243
|
-
|
|
287
|
+
// D-6: the ephemeral handoff fork is the ONE caller that runs a model turn on purpose, and it
|
|
288
|
+
// asks for that explicitly. Every fork/readback path keeps the fail-closed guard.
|
|
289
|
+
if (!allowTurn && /^(?:turn|item)\//.test(message.method)) {
|
|
244
290
|
fail(adapterError('native_fork_emitted_model_turn'));
|
|
245
291
|
void terminateOnce();
|
|
246
292
|
return;
|
|
247
293
|
}
|
|
294
|
+
if (onNotification) onNotification(message.method, message.params);
|
|
248
295
|
continue;
|
|
249
296
|
}
|
|
250
297
|
if (!Number.isSafeInteger(message.id) || !waiters.has(message.id)) {
|
|
@@ -369,7 +416,7 @@ function absolute(value) { return typeof value === 'string' && path.isAbsolute(v
|
|
|
369
416
|
function nonempty(value) { return typeof value === 'string' && value.trim() ? value.trim() : ''; }
|
|
370
417
|
function validAlias(value) { return typeof value === 'string' && /^[a-z0-9][a-z0-9._-]{0,63}$/i.test(value); }
|
|
371
418
|
function validEnvKey(value) { return typeof value === 'string' && /^[A-Z_][A-Z0-9_]{0,63}$/.test(value); }
|
|
372
|
-
function validCyberAccessProgram(value) { return value
|
|
419
|
+
function validCyberAccessProgram(value) { return isNonElevatedCyberAccessProgram(value); }
|
|
373
420
|
function validApprovalPolicy(value) { return ['never', 'on-request', 'on-failure', 'untrusted'].includes(value); }
|
|
374
421
|
function validSandboxPolicy(value) {
|
|
375
422
|
return isRecord(value) && ['read-only', 'workspace-write', 'danger-full-access'].includes(value.type)
|
|
@@ -384,4 +431,5 @@ function sameSandboxPolicy(a, b) {
|
|
|
384
431
|
}
|
|
385
432
|
function isRecord(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
|
|
386
433
|
|
|
387
|
-
module.exports = { createCodexNativeHistoryControl, buildAppServerArgs, buildAppServerEnv, QUALIFIED_ADAPTER
|
|
434
|
+
module.exports = { createCodexNativeHistoryControl, buildAppServerArgs, buildAppServerEnv, QUALIFIED_ADAPTER,
|
|
435
|
+
createBoundedRpc, terminateChild, handoffMessage, SUMMARY_PREFIX, HANDOFF_MARKER_PREFIX };
|
|
@@ -53,6 +53,7 @@ const { spawn } = require('node:child_process');
|
|
|
53
53
|
|
|
54
54
|
const { LAUNCH_PLAN_DEFAULT_REQUEST_TIMEOUT_MS } = require('./launch-plan-pool');
|
|
55
55
|
const { buildNativeLeadBaseUrl } = require('./native-lead-endpoint');
|
|
56
|
+
const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
|
|
56
57
|
const {
|
|
57
58
|
NATIVE_LEAD_TLS_CODES,
|
|
58
59
|
LOOPBACK_ADDRESS,
|
|
@@ -190,7 +191,7 @@ async function _ensureGeneratedDirAsync(dirPath, dataDir) {
|
|
|
190
191
|
|| base.stat.isSymbolicLink()
|
|
191
192
|
|| !base.stat.isDirectory()
|
|
192
193
|
|| (typeof process.getuid === 'function' && Number(base.stat.uid) !== process.getuid())
|
|
193
|
-
|| (
|
|
194
|
+
|| !isWritableOnlyByOwner(base.stat)) {
|
|
194
195
|
_fail(NATIVE_LEAD_SETTINGS_CLIENT_CODES.RESERVE_FAILED);
|
|
195
196
|
}
|
|
196
197
|
let current = dataDir;
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
const fs = require('node:fs');
|
|
39
39
|
const path = require('node:path');
|
|
40
40
|
const crypto = require('node:crypto');
|
|
41
|
+
const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
|
|
41
42
|
|
|
42
43
|
// --- fixed, public vocabulary ---------------------------------------------------------------
|
|
43
44
|
|
|
@@ -283,7 +284,7 @@ function _assertPrivateRoot(dirPath) {
|
|
|
283
284
|
}
|
|
284
285
|
if (stat.isSymbolicLink() || !stat.isDirectory()) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
|
|
285
286
|
if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
|
|
286
|
-
if ((stat
|
|
287
|
+
if (!isWritableOnlyByOwner(stat)) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
|
|
287
288
|
return stat;
|
|
288
289
|
}
|
|
289
290
|
|
|
@@ -380,18 +381,31 @@ function syncWritePrivateFileExclusive(filePath, contents, { code = NATIVE_LEAD_
|
|
|
380
381
|
/**
|
|
381
382
|
* The deterministic snapshot seam shared by both private readers: after a bounded read, the descriptor
|
|
382
383
|
* must still describe EXACTLY the file that was opened — same device/inode (a replacement), same size
|
|
383
|
-
* and same mtime
|
|
384
|
-
*
|
|
384
|
+
* and same mtime (a same-size mutation), same uid/mode (a permissions change). Pure, so the check is
|
|
385
|
+
* unit-testable without a filesystem race or any real-time threshold.
|
|
386
|
+
*
|
|
387
|
+
* ctime is compared only while the link count is UNCHANGED (D-8,
|
|
388
|
+
* docs/nightly-followups-r3-pty-selector-flakes-2026-10-22.html). A hard-link count change moves ctime
|
|
389
|
+
* by itself — unlinking a sibling link (macOS, probe I-4: nlink 2 -> 1, mtime equal, ctime different)
|
|
390
|
+
* or creating one (Linux) rewrites the inode's ctime without touching the bytes or their mtime. That is
|
|
391
|
+
* exactly the window the authority publisher occupies: it links the candidate to the authority
|
|
392
|
+
* (`fs.linkSync(candidateFile, authorityPath)`) and then unlinks its own candidate link, so a reader
|
|
393
|
+
* sitting between its two fstats sees only the link-count swap and would otherwise fail closed with
|
|
394
|
+
* `native_tls_authority_invalid` on a file nobody modified. The nlink delta fully explains the ctime
|
|
395
|
+
* delta, so the ctime comparison is skipped when nlink moved; dev, ino, size, mtimeMs, uid and mode are
|
|
396
|
+
* still compared unconditionally, which keeps the same-size-write detection for the case that matters
|
|
397
|
+
* (a rewrite that preserves mtime while nlink is unchanged).
|
|
385
398
|
*
|
|
386
399
|
* @returns {boolean} true when `after` is still the same, unmodified, correctly-permissioned file.
|
|
387
400
|
*/
|
|
388
401
|
function isSamePrivateFileSnapshot(opened, after) {
|
|
389
402
|
if (!opened || !after) return false;
|
|
403
|
+
const linkCountChanged = Number(after.nlink) !== Number(opened.nlink);
|
|
390
404
|
return Number(after.dev) === Number(opened.dev)
|
|
391
405
|
&& Number(after.ino) === Number(opened.ino)
|
|
392
406
|
&& Number(after.size) === Number(opened.size)
|
|
393
407
|
&& Number(after.mtimeMs) === Number(opened.mtimeMs)
|
|
394
|
-
&& Number(after.ctimeMs) === Number(opened.ctimeMs)
|
|
408
|
+
&& (linkCountChanged || Number(after.ctimeMs) === Number(opened.ctimeMs))
|
|
395
409
|
&& Number(after.uid) === Number(opened.uid)
|
|
396
410
|
&& Number(after.mode) === Number(opened.mode);
|
|
397
411
|
}
|