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
|
@@ -4,7 +4,8 @@ const path = require('path');
|
|
|
4
4
|
const { captureWorkerProgress } = require('./worker-progress');
|
|
5
5
|
const { classifyFailure } = require('../worker-failure-classify');
|
|
6
6
|
const { terminalBudgetFailure, budgetEvidence } = require('../model-proxy/request-budget');
|
|
7
|
-
const { enqueueNotice } = require('./worker-notices');
|
|
7
|
+
const { enqueueNotice, markWorkerLive } = require('./worker-notices');
|
|
8
|
+
const { startWaitWatcher } = require('./worker-wait-watcher');
|
|
8
9
|
|
|
9
10
|
const PROGRESS_CACHE_MS = 5000;
|
|
10
11
|
const EVENT_RECEIPT_LIMIT = 32;
|
|
@@ -13,6 +14,10 @@ const NO_WRITE_ADVISORY_TURN_THRESHOLD = 20;
|
|
|
13
14
|
const NO_WRITE_TERMINAL_TURN_THRESHOLD = 40;
|
|
14
15
|
const EVENT_PRECEDENCE = { started: 0, progress: 1, advisory: 2, terminal: 3 };
|
|
15
16
|
const WORKER_FINAL_RESULT_CAP_BYTES = 64 * 1024;
|
|
17
|
+
// Lead-cost routing round 2 D-3 (§6.2): the ONE report-text byte cap, shared by the status reader
|
|
18
|
+
// (delegate-worker.js counts citations from the bounded text) and the ?include_report=1 reader
|
|
19
|
+
// (lib/api/sessions.js). Same 64 KiB as the final-result cap.
|
|
20
|
+
const WORKER_REPORT_TEXT_CAP_BYTES = 64 * 1024;
|
|
16
21
|
const FILE_PROBE_TTL_MS = 2000;
|
|
17
22
|
const THROTTLED_WINDOW_MS = 60000;
|
|
18
23
|
|
|
@@ -89,10 +94,17 @@ function _removeWaiterRegistration(waiter, ctmSessionId) {
|
|
|
89
94
|
if (!set.size) _waiters.delete(ctmSessionId);
|
|
90
95
|
}
|
|
91
96
|
const _liveWaiters = new Set(); // every not-yet-resolved waiter, once each (waiterCount counts waiters, not registrations)
|
|
92
|
-
function _finishWaiter(waiter, why, workerId) {
|
|
97
|
+
function _finishWaiter(waiter, why, workerId, detail) {
|
|
93
98
|
if (waiter.done) return;
|
|
94
99
|
waiter.done = true;
|
|
95
100
|
clearTimeout(waiter.timer);
|
|
101
|
+
// WP-1 (D-3): a resolved waiter stops its watcher first, so a late model reply is dropped
|
|
102
|
+
// (stop() marks the watcher dead) and no interval timer outlives the wait.
|
|
103
|
+
if (waiter.watch) {
|
|
104
|
+
try { waiter.watch.stop(); }
|
|
105
|
+
catch { /* expected: a watcher stop failure never blocks the resolve */ }
|
|
106
|
+
waiter.watch = null;
|
|
107
|
+
}
|
|
96
108
|
for (const id of waiter.watchedIds) _removeWaiterRegistration(waiter, id);
|
|
97
109
|
_liveWaiters.delete(waiter);
|
|
98
110
|
if (waiter.onHumanSubmit) {
|
|
@@ -100,9 +112,16 @@ function _finishWaiter(waiter, why, workerId) {
|
|
|
100
112
|
humanInputEvents.removeListener('humanSubmit', waiter.onHumanSubmit);
|
|
101
113
|
waiter.onHumanSubmit = null;
|
|
102
114
|
}
|
|
103
|
-
|
|
115
|
+
const result = { why, workerId: workerId != null ? workerId : null };
|
|
116
|
+
// WP-1: the watcher's own result fields exist only on a waiter that ran one -- a caller with
|
|
117
|
+
// no watcher sees exactly today's `{ why, workerId }` shape.
|
|
118
|
+
if (waiter.decisions) {
|
|
119
|
+
result.detail = detail || null;
|
|
120
|
+
result.decisions = waiter.decisions;
|
|
121
|
+
}
|
|
122
|
+
waiter.resolve(result);
|
|
104
123
|
}
|
|
105
|
-
function waitForWorkers({ workerIds, leadId, waitMs, generationById } = {}) {
|
|
124
|
+
function waitForWorkers({ workerIds, leadId, waitMs, generationById, watchMode, callModel, getStatus, readTail, workerRoute } = {}) {
|
|
106
125
|
const ids = Array.isArray(workerIds) ? workerIds.map((id) => String(id || '')).filter(Boolean) : [];
|
|
107
126
|
if (!ids.length) {
|
|
108
127
|
// No watched workers: the wait is still meaningful for a lead (a user_input wake with no
|
|
@@ -111,7 +130,7 @@ function waitForWorkers({ workerIds, leadId, waitMs, generationById } = {}) {
|
|
|
111
130
|
const empty = { generationById: {} };
|
|
112
131
|
return waitForWorkersWithListener([], leadId, waitMs, empty);
|
|
113
132
|
}
|
|
114
|
-
return waitForWorkersWithListener(ids, leadId, waitMs, { generationById });
|
|
133
|
+
return waitForWorkersWithListener(ids, leadId, waitMs, { generationById, watchMode, callModel, getStatus, readTail, workerRoute });
|
|
115
134
|
}
|
|
116
135
|
function waitForWorkersWithListener(ids, leadId, waitMs, state) {
|
|
117
136
|
let release = () => {};
|
|
@@ -124,7 +143,7 @@ function waitForWorkersWithListener(ids, leadId, waitMs, state) {
|
|
|
124
143
|
done: false,
|
|
125
144
|
resolve,
|
|
126
145
|
};
|
|
127
|
-
const finish = (why, workerId) => _finishWaiter(waiter, why, workerId);
|
|
146
|
+
const finish = (why, workerId, detail) => _finishWaiter(waiter, why, workerId, detail);
|
|
128
147
|
waiter.finish = finish;
|
|
129
148
|
waiter.timer = setTimeout(() => finish('timeout'), Math.max(0, Number(waitMs) || 0));
|
|
130
149
|
if (typeof waiter.timer.unref === 'function') waiter.timer.unref();
|
|
@@ -142,12 +161,64 @@ function waitForWorkersWithListener(ids, leadId, waitMs, state) {
|
|
|
142
161
|
humanInputEvents.on('humanSubmit', waiter.onHumanSubmit);
|
|
143
162
|
}
|
|
144
163
|
release = () => finish('timeout');
|
|
164
|
+
// WP-1 (D-3/D-4): the watcher parks BESIDE the waiter -- one tick per watched worker, the
|
|
165
|
+
// worker's own pinned model, and a `watcher`/`watcher_unavailable` wake through the same
|
|
166
|
+
// finish gate as every code wake. It is started only when the caller wired one (a caller
|
|
167
|
+
// with no watchMode/callModel keeps today's behaviour untouched).
|
|
168
|
+
if (ids.length && state.watchMode !== 'off' && typeof state.callModel === 'function') {
|
|
169
|
+
waiter.decisions = [];
|
|
170
|
+
waiter.watch = startWaitWatcher({
|
|
171
|
+
workerIds: ids,
|
|
172
|
+
mode: state.watchMode || 'on',
|
|
173
|
+
wake: (why, detail) => finish(why, detail && detail.worker_id, detail),
|
|
174
|
+
onDecision: (d) => waiter.decisions.push(d),
|
|
175
|
+
callModel: state.callModel,
|
|
176
|
+
getStatus: state.getStatus,
|
|
177
|
+
readTail: state.readTail,
|
|
178
|
+
workerRoute: state.workerRoute,
|
|
179
|
+
}) || null;
|
|
180
|
+
}
|
|
145
181
|
// F1: a waiter registered before the caller knows a worker's current generation (undefined
|
|
146
182
|
// wakes on any generation notice) is narrowed per worker once the first status read lands.
|
|
147
183
|
setGeneration = (workerId, value) => { if (!waiter.done) waiter.generationByCtmId.set(String(workerId), value); };
|
|
148
184
|
});
|
|
149
185
|
return { promise, release, setGeneration };
|
|
150
186
|
}
|
|
187
|
+
|
|
188
|
+
// WP-1 (D-3): the watcher's PRODUCTION model call. It rides the lane every other CTM
|
|
189
|
+
// background completion uses (lib/background-llm.js -> Wall-E's background-completion
|
|
190
|
+
// endpoint), so the resolved provider, the fallback chain and the circuit breakers stay the
|
|
191
|
+
// routing authority's and CTM builds no provider client of its own. The pin is the caller's
|
|
192
|
+
// `model` (the worker row's own route_alias + ':' + model, never a second route).
|
|
193
|
+
// A failure -- transport, an unroutable pin (route_throttled / route_unavailable /
|
|
194
|
+
// model_not_found), or a non-2xx decision -- answers `{ ok: false, error }`, which is exactly
|
|
195
|
+
// what the watcher's D-4 consecutive-failure counter reads.
|
|
196
|
+
function createWaitWatcherCallModel() {
|
|
197
|
+
return async function callWaitWatcherModel({ feature, model, system, messages, maxTokens, timeoutMs, sessionId }) {
|
|
198
|
+
let llm;
|
|
199
|
+
try {
|
|
200
|
+
llm = require('../background-llm');
|
|
201
|
+
// D-4: the watcher never takes a second route. background-llm's last-ditch Ollama hop
|
|
202
|
+
// fires on a Wall-E transport error unless the fallback setting is off, so this call
|
|
203
|
+
// carries an env copy with it off -- a transport failure is then `{ ok: false }` and the
|
|
204
|
+
// consecutive-failure counter ends the watcher with `watcher_unavailable`.
|
|
205
|
+
const env = { ...process.env, CTM_BACKGROUND_LLM_FALLBACK_PROVIDER: 'off' };
|
|
206
|
+
// WP-4a (D-9): the tick's `sessionId` is the watched worker's ctm_session_id (WP-1's
|
|
207
|
+
// per-worker tick). background-llm.js forwards it into the POST body only when it is a
|
|
208
|
+
// non-empty string, and Wall-E's ledger then keys the row by it -- so the cell's watcher
|
|
209
|
+
// spend is exactly the ledger rows of that cell's own workers.
|
|
210
|
+
const reply = await llm.callBackgroundLlm(messages, {
|
|
211
|
+
task: feature, model, system, maxTokens, timeoutMs, env,
|
|
212
|
+
...(typeof sessionId === 'string' && sessionId ? { sessionId } : {}),
|
|
213
|
+
});
|
|
214
|
+
const text = typeof reply?.text === 'string' ? reply.text : llm.extractBackgroundLlmText(reply);
|
|
215
|
+
return { ok: true, text };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return { ok: false, error: String((error && (error.code || error.message)) || 'watch_call_failed').slice(0, 200) };
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
151
222
|
// D-7: a route waiter is the same record shape in the same `_waiters` registry, under the key
|
|
152
223
|
// `route:<alias>`. Timer = min(waitMs, remaining hold) when the snapshot has a horizon, else waitMs.
|
|
153
224
|
// The registration/wake checks read snapshot() (never admit()) so a wait is never counted as a refusal.
|
|
@@ -634,6 +705,10 @@ function createWorkerObservability({ getDeps, readWorkerRuntime, getBaseStatus }
|
|
|
634
705
|
try { enqueueNotice(receipt); } catch { /* expected: the lead's hook notice is best-effort; walle_worker_wait and the sentinel still report */ }
|
|
635
706
|
_wakeWaiters(ctmSessionId, 'terminal');
|
|
636
707
|
auditScopeAtTerminal(ctmSessionId).catch((error) => reportObservationFailure(deps, ctmSessionId, error));
|
|
708
|
+
} else if (kind === 'started') {
|
|
709
|
+
// WP-0 (D-2): the live-worker index is maintained beside the notice queue from the same
|
|
710
|
+
// receipts. Memory only, so a failure here can never affect the receipt's persistence.
|
|
711
|
+
try { markWorkerLive(receipt); } catch { /* expected: the live index is best-effort; the notice queue and walle_worker_wait still report */ }
|
|
637
712
|
}
|
|
638
713
|
return receipt;
|
|
639
714
|
}
|
|
@@ -911,8 +986,22 @@ function withIncompleteStreams(runtime, requestBudget) {
|
|
|
911
986
|
return { ...requestBudget, incomplete_streams: count, incomplete_stream_ms: ms };
|
|
912
987
|
}
|
|
913
988
|
|
|
989
|
+
// D-1 (session-stops-followups-2026-10-24.html): the terminal statuses a LIVE payload can carry.
|
|
990
|
+
// The DB-row readers in lib/api/sessions.js keep their own copy (_WORKER_TERMINAL_STATUSES):
|
|
991
|
+
// a row's terminal_status is written only at a real exit, so it can never be provisional.
|
|
992
|
+
const WORKER_TERMINAL_STATUSES = new Set(['report_ready', 'failed', 'exited']);
|
|
993
|
+
|
|
994
|
+
/** D-1: a status payload is final when it is terminal AND not a report written while the process
|
|
995
|
+
* is still alive (delegate-worker.js report_provisional). An absent field is final, so DB rows
|
|
996
|
+
* and fixtures keep today's meaning.
|
|
997
|
+
* @param {{status?: string, report_provisional?: boolean}|null|undefined} status
|
|
998
|
+
* @returns {boolean} */
|
|
999
|
+
function isFinalWorkerStatus(status) {
|
|
1000
|
+
return !!status && WORKER_TERMINAL_STATUSES.has(status.status) && status.report_provisional !== true;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
914
1003
|
function pollAfterMs({ status, requestBudget } = {}) {
|
|
915
|
-
if (
|
|
1004
|
+
if (isFinalWorkerStatus(status)
|
|
916
1005
|
|| (status?.exitCode !== null && status?.exitCode !== undefined)) return 0;
|
|
917
1006
|
const p50 = requestBudget?.requestDurationP50Ms;
|
|
918
1007
|
if (!Number.isInteger(p50) || p50 <= 0) return 30000;
|
|
@@ -921,6 +1010,8 @@ function pollAfterMs({ status, requestBudget } = {}) {
|
|
|
921
1010
|
|
|
922
1011
|
module.exports = {
|
|
923
1012
|
waitForWorkerChange, waitForWorkerChangeHandle, waitForWorkers, notifyWorkerGeneration, notifyWorkerAdvisory, waiterCount, waitForRouteClear, _resetRouteSubscriptionsForTest,
|
|
1013
|
+
createWaitWatcherCallModel,
|
|
1014
|
+
isFinalWorkerStatus,
|
|
924
1015
|
pollAfterMs,
|
|
925
1016
|
withIncompleteStreams,
|
|
926
1017
|
createWorkerObservability,
|
|
@@ -940,6 +1031,7 @@ module.exports = {
|
|
|
940
1031
|
computeExitFailure,
|
|
941
1032
|
boundedWorkerText,
|
|
942
1033
|
isColdDeferredSession,
|
|
1034
|
+
WORKER_REPORT_TEXT_CAP_BYTES,
|
|
943
1035
|
THROTTLED_WINDOW_MS,
|
|
944
1036
|
EVENT_RECEIPT_LIMIT,
|
|
945
1037
|
NO_WRITE_ADVISORY_TURN_THRESHOLD,
|
|
@@ -65,9 +65,17 @@ async function stopWorkerSession(ctmSessionId, { mode = 'after_turn', reason = '
|
|
|
65
65
|
return refuse('worker_not_owned', 403, id, { message: 'Only the lead that created this worker can stop it.', next_step: nextStepFor({ code: 'worker_not_owned' }) });
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// WP-2 (worker-terminal-stamp-durable 2026-10-26 D-2/D-3): a dead process with an unstamped row is repaired first,
|
|
69
|
+
// so the verb below answers the truth (already_terminal), never 'stopping' on a process that no longer exists.
|
|
70
|
+
let repaired = null;
|
|
71
|
+
if (typeof helpers.repairExitedWorkerRow === 'function') {
|
|
72
|
+
try { repaired = helpers.repairExitedWorkerRow(id) || null; } catch { repaired = null; /* expected: a repair that throws leaves the row as read; the stop proceeds on the row's own truth */ }
|
|
73
|
+
if (repaired) { try { row = deps.dbModule.getWorkerSession(id) || row; } catch { /* expected: keep the row already read */ } }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 4. idempotent on a terminal worker -- or on a repaired one whose stamp is still riding the WP-1 retry (D-3: never 'stopping' for a dead process)
|
|
77
|
+
if (repaired || row.terminal_at || TERMINAL_STATUSES.includes(String(row.terminal_status || ''))) {
|
|
78
|
+
return refuse('already_terminal', 200, id, { terminal_status: row.terminal_status || '', generation: Number(row.generation || 1), status: 'terminal', repaired });
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
// 5. the stop record: journaled by the proxy for a proxied worker, on the row for a proxyless one
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 (worker-wait-watcher, D-3/D-4): while a lead's `walle_worker_wait` is parked in CTM,
|
|
4
|
+
// a watcher tick asks the WORKER'S OWN pinned model whether the lead must act now -- the
|
|
5
|
+
// worker is stuck or looping, repeats the same error, asks a question, or works outside its
|
|
6
|
+
// brief. A `WAKE_LEAD` answer wakes the parked wait early with `woke_on: 'watcher'`.
|
|
7
|
+
//
|
|
8
|
+
// The watcher never delays a plain-code wake: it only ever calls `wake` from its own tick, so
|
|
9
|
+
// terminal / generation / advisory / user_input / route_clear / timeout resolve exactly as
|
|
10
|
+
// today, and a code wake that lands during an in-flight model call wins (stop() marks the
|
|
11
|
+
// watcher dead; the late reply is dropped).
|
|
12
|
+
//
|
|
13
|
+
// Two consecutive failures (transport error, a reply with `ok: false`, a timeout, or a reply
|
|
14
|
+
// that carries no decision) end the watcher with `watcher_unavailable` and the lead is back to
|
|
15
|
+
// today's behaviour -- no retry loop, no second route (D-4).
|
|
16
|
+
//
|
|
17
|
+
// The module holds NO network import: the caller injects `callModel` (production wiring lives
|
|
18
|
+
// in worker-observability.js) and the worker's pin is always the worker row's own
|
|
19
|
+
// `route_alias` + ':' + `model` -- no vendor or model id appears here.
|
|
20
|
+
|
|
21
|
+
const { redactSecrets } = require('../redact-secrets');
|
|
22
|
+
|
|
23
|
+
const WATCH_INTERVAL_MS = 30000; // today's lead cadence
|
|
24
|
+
const WATCH_TAIL_CHARS = 60000; // pilot watcher input size
|
|
25
|
+
const WATCH_TIMEOUT_MS = 20000; // background-completion DEFAULT_TIMEOUT_MS (wall-e/http/background-completion-api.js:27)
|
|
26
|
+
const WATCH_MAX_CONSECUTIVE_FAILURES = 2; // D-4
|
|
27
|
+
const WATCH_MAX_CONCURRENT_CALLS = 4;
|
|
28
|
+
const WATCH_SILENT_EVERY = 10;
|
|
29
|
+
const WATCH_MODE_ON = 'on';
|
|
30
|
+
const WATCH_MODE_SILENT = 'silent';
|
|
31
|
+
const WATCH_MODE_OFF = 'off';
|
|
32
|
+
const WATCH_MODES = new Set([WATCH_MODE_ON, WATCH_MODE_SILENT, WATCH_MODE_OFF]);
|
|
33
|
+
const WATCH_FEATURE = 'lead-wait-watch';
|
|
34
|
+
const WATCH_MAX_TOKENS = 300;
|
|
35
|
+
const WATCH_SYSTEM = 'You are a watcher working for a lead coding agent. The lead is waiting on a delegated worker. '
|
|
36
|
+
+ 'Read the worker status and the end of its transcript and decide whether the lead must act now (the worker is stuck '
|
|
37
|
+
+ 'or looping, repeats the same error, asks a question or needs a decision, works outside its brief, or anything else '
|
|
38
|
+
+ 'needs the lead) or whether it is progressing normally and the lead should keep waiting.\n'
|
|
39
|
+
+ 'Answer with one JSON object only: {"decision":"KEEP_WAITING"|"WAKE_LEAD","reason":"<= 25 words"}';
|
|
40
|
+
|
|
41
|
+
// The reply contract is a REGEX, not JSON.parse: a model that wraps its object in prose still
|
|
42
|
+
// answers, and a reply with no decision at all is a failure (D-3/D-4).
|
|
43
|
+
const WATCH_DECISION_RE = /"decision"\s*:\s*"(KEEP_WAITING|WAKE_LEAD)"/;
|
|
44
|
+
const WATCH_REASON_RE = /"reason"\s*:\s*"([^"]{0,200})"/;
|
|
45
|
+
|
|
46
|
+
// D-3: at most WATCH_MAX_CONCURRENT_CALLS model calls in flight across ALL parked waiters. A
|
|
47
|
+
// tick that cannot get a slot is skipped, never queued.
|
|
48
|
+
let _inFlightCalls = 0;
|
|
49
|
+
function _tryAcquireSlot() {
|
|
50
|
+
if (_inFlightCalls >= WATCH_MAX_CONCURRENT_CALLS) return false;
|
|
51
|
+
_inFlightCalls += 1;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function _releaseSlot() {
|
|
55
|
+
if (_inFlightCalls > 0) _inFlightCalls -= 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test-only bookkeeping: every ARMED interval timer is tracked here (a fired timer removes
|
|
59
|
+
// itself at fire time), so _resetForTests() can report how many watchers were still ticking
|
|
60
|
+
// when everything was expected to be stopped.
|
|
61
|
+
let _liveTimers = new Set();
|
|
62
|
+
|
|
63
|
+
function parseWatchReply(text) {
|
|
64
|
+
if (typeof text !== 'string' || !text) return null;
|
|
65
|
+
const match = WATCH_DECISION_RE.exec(text);
|
|
66
|
+
if (!match) return null;
|
|
67
|
+
const reason = WATCH_REASON_RE.exec(text);
|
|
68
|
+
return { decision: match[1], reason: reason ? reason[1].trim() : '' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// One compact status per tick: fixed keys, missing ones null, everything bounded to what the
|
|
72
|
+
// watcher prompt needs (never the whole payload).
|
|
73
|
+
function compactWorkerStatus(status) {
|
|
74
|
+
const source = status && typeof status === 'object' ? status : {};
|
|
75
|
+
const budget = source.request_budget && typeof source.request_budget === 'object' ? source.request_budget : null;
|
|
76
|
+
return {
|
|
77
|
+
status: typeof source.status === 'string' ? source.status : null,
|
|
78
|
+
turns: Number.isInteger(source.turns) ? source.turns : null,
|
|
79
|
+
last_activity_at: Number.isFinite(source.last_activity_at) ? source.last_activity_at : null,
|
|
80
|
+
request_budget: budget ? {
|
|
81
|
+
requestCount: Number.isInteger(budget.requestCount) ? budget.requestCount : null,
|
|
82
|
+
completedToolCalls: Number.isInteger(budget.completedToolCalls) ? budget.completedToolCalls : null,
|
|
83
|
+
incompleteStreams: Number.isInteger(budget.incomplete_streams) ? budget.incomplete_streams : (Number.isInteger(budget.incompleteStreams) ? budget.incompleteStreams : null),
|
|
84
|
+
} : null,
|
|
85
|
+
advisories: Array.isArray(source.advisories) ? source.advisories.slice(0, 8) : null,
|
|
86
|
+
repeated_error: source.repeated_error ?? null,
|
|
87
|
+
observation_repeats: Number.isInteger(source.observation_repeats) ? source.observation_repeats : null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function errorText(value) {
|
|
92
|
+
if (!value) return 'watch_failed';
|
|
93
|
+
if (typeof value === 'string') return value.slice(0, 200);
|
|
94
|
+
return String(value.code || value.message || value).slice(0, 200);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The worker's own pin: the row's route_alias + ':' + model, or null when the row carries no
|
|
98
|
+
// pin (the lead keeps today's behaviour, D-3).
|
|
99
|
+
function workerPin(workerRoute, workerId) {
|
|
100
|
+
if (typeof workerRoute !== 'function') return null;
|
|
101
|
+
let row = null;
|
|
102
|
+
try { row = workerRoute(workerId); }
|
|
103
|
+
catch { return null; /* expected: an unreadable row carries no pin -- the lead keeps today's behaviour */ }
|
|
104
|
+
if (!row || typeof row !== 'object') return null;
|
|
105
|
+
const route = typeof row.route === 'string' ? row.route.trim() : '';
|
|
106
|
+
const model = typeof row.model === 'string' ? row.model.trim() : '';
|
|
107
|
+
if (!route || !model) return null;
|
|
108
|
+
return `${route}:${model}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Start the per-waiter watcher. Returns { stop() } or null when nothing is watched.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} o
|
|
115
|
+
* @param {string[]} o.workerIds
|
|
116
|
+
* @param {'on'|'silent'|'off'} [o.mode='on']
|
|
117
|
+
* @param {(why:'watcher'|'watcher_unavailable', detail:{worker_id:string, reason?:string, error?:string}) => void} o.wake
|
|
118
|
+
* @param {(d:{worker_id:string, at:number, decision:'KEEP_WAITING'|'WAKE_LEAD', silent:boolean, reason?:string}) => void} [o.onDecision]
|
|
119
|
+
* @param {(id:string) => Promise<object|null>|object|null} o.getStatus
|
|
120
|
+
* @param {(id:string, maxChars:number) => Promise<string>} o.readTail
|
|
121
|
+
* @param {(req:{feature:string, model:string, system:string, messages:object[], maxTokens:number, timeoutMs:number, sessionId:string}) => Promise<{ok:boolean, text?:string, error?:string}>} o.callModel
|
|
122
|
+
* @param {(id:string) => {route:string, model:string}|null} o.workerRoute
|
|
123
|
+
* @param {Function} [o.setTimer=setTimeout]
|
|
124
|
+
* @param {Function} [o.clearTimer=clearTimeout]
|
|
125
|
+
* @param {Function} [o.now=Date.now]
|
|
126
|
+
* @returns {{ stop(): void }|null}
|
|
127
|
+
*/
|
|
128
|
+
function startWaitWatcher({
|
|
129
|
+
workerIds, mode = WATCH_MODE_ON, wake, onDecision, getStatus, readTail, callModel, workerRoute,
|
|
130
|
+
setTimer = setTimeout, clearTimer = clearTimeout, now = Date.now,
|
|
131
|
+
} = {}) {
|
|
132
|
+
if (!WATCH_MODES.has(mode) || mode === WATCH_MODE_OFF) return null;
|
|
133
|
+
if (typeof wake !== 'function' || typeof getStatus !== 'function' || typeof readTail !== 'function' || typeof callModel !== 'function') return null;
|
|
134
|
+
const silent = mode === WATCH_MODE_SILENT;
|
|
135
|
+
const targets = [];
|
|
136
|
+
for (const rawId of Array.isArray(workerIds) ? workerIds : []) {
|
|
137
|
+
const id = String(rawId || '');
|
|
138
|
+
if (!id) continue;
|
|
139
|
+
const pinnedModel = workerPin(workerRoute, id);
|
|
140
|
+
if (!pinnedModel) continue; // D-3: a worker with no route_alias/model row starts no watcher
|
|
141
|
+
targets.push({ id, pinnedModel });
|
|
142
|
+
}
|
|
143
|
+
if (!targets.length) return null;
|
|
144
|
+
|
|
145
|
+
const state = { dead: false, timer: null, ticks: 0, failures: new Map() };
|
|
146
|
+
|
|
147
|
+
function clearOwnTimer() {
|
|
148
|
+
if (state.timer == null) return;
|
|
149
|
+
clearTimer(state.timer);
|
|
150
|
+
_liveTimers.delete(state.timer);
|
|
151
|
+
state.timer = null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function stop() {
|
|
155
|
+
if (state.dead) return;
|
|
156
|
+
state.dead = true; // a reply that lands after this is dropped
|
|
157
|
+
clearOwnTimer();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function recordFailure(target, error) {
|
|
161
|
+
if (state.dead) return;
|
|
162
|
+
const consecutive = (state.failures.get(target.id) || 0) + 1;
|
|
163
|
+
state.failures.set(target.id, consecutive);
|
|
164
|
+
if (consecutive < WATCH_MAX_CONSECUTIVE_FAILURES) return;
|
|
165
|
+
state.failures.set(target.id, 0);
|
|
166
|
+
wake('watcher_unavailable', { worker_id: target.id, error: errorText(error) });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The call is bounded by WATCH_TIMEOUT_MS here as well as by the request's own timeoutMs: a
|
|
170
|
+
// wedged lane must resolve to watcher_unavailable, not park the tick forever.
|
|
171
|
+
async function callWithTimeout(target, status, tail) {
|
|
172
|
+
const request = {
|
|
173
|
+
feature: WATCH_FEATURE,
|
|
174
|
+
model: target.pinnedModel,
|
|
175
|
+
system: WATCH_SYSTEM,
|
|
176
|
+
messages: [{ role: 'user', content: `${JSON.stringify(compactWorkerStatus(status))}\n\nTranscript tail:\n${tail}` }],
|
|
177
|
+
maxTokens: WATCH_MAX_TOKENS,
|
|
178
|
+
timeoutMs: WATCH_TIMEOUT_MS,
|
|
179
|
+
// WP-4a (D-9): the ledger attribution key is the WATCHED WORKER's ctm_session_id. Each tick
|
|
180
|
+
// is per worker and every wait-watch outcome row already stores that worker as `worker_id`
|
|
181
|
+
// (WP-1), so the cell's spend is exactly the ledger rows of its own workers -- never the
|
|
182
|
+
// lead's (a lead parks several workers at once; the lead's id would blend their cells).
|
|
183
|
+
sessionId: target.id,
|
|
184
|
+
};
|
|
185
|
+
const call = Promise.resolve().then(() => callModel(request));
|
|
186
|
+
call.catch(() => { /* expected: the watchdog may settle first; a late rejection is never unhandled */ });
|
|
187
|
+
let watchdogId = null;
|
|
188
|
+
const watchdog = new Promise((_resolve, reject) => {
|
|
189
|
+
watchdogId = setTimer(() => reject(new Error('watch_timeout')), WATCH_TIMEOUT_MS);
|
|
190
|
+
if (watchdogId && typeof watchdogId.unref === 'function') watchdogId.unref();
|
|
191
|
+
});
|
|
192
|
+
try {
|
|
193
|
+
return await Promise.race([call, watchdog]);
|
|
194
|
+
} finally {
|
|
195
|
+
if (watchdogId != null) {
|
|
196
|
+
clearTimer(watchdogId);
|
|
197
|
+
watchdogId = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function runWorkerTick(target) {
|
|
203
|
+
if (state.dead) return;
|
|
204
|
+
if (!_tryAcquireSlot()) return; // skipped, never queued
|
|
205
|
+
try {
|
|
206
|
+
let reply;
|
|
207
|
+
try {
|
|
208
|
+
const status = await getStatus(target.id);
|
|
209
|
+
if (state.dead) return;
|
|
210
|
+
const tail = redactSecrets(await readTail(target.id, WATCH_TAIL_CHARS));
|
|
211
|
+
if (state.dead) return;
|
|
212
|
+
reply = await callWithTimeout(target, status, tail);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
recordFailure(target, error);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (state.dead) return;
|
|
218
|
+
if (!reply || reply.ok !== true) {
|
|
219
|
+
recordFailure(target, reply && reply.error);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const parsed = parseWatchReply(reply.text);
|
|
223
|
+
if (!parsed) {
|
|
224
|
+
recordFailure(target, 'watch_reply_unparsed');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
state.failures.set(target.id, 0);
|
|
228
|
+
const decision = {
|
|
229
|
+
worker_id: target.id, at: now(), decision: parsed.decision, silent, reason: parsed.reason,
|
|
230
|
+
};
|
|
231
|
+
if (typeof onDecision === 'function') {
|
|
232
|
+
try { onDecision(decision); }
|
|
233
|
+
catch { /* expected: a decision sink failure never breaks the watcher */ }
|
|
234
|
+
}
|
|
235
|
+
// `silent` records its decisions and never wakes (D-3).
|
|
236
|
+
if (!silent && parsed.decision === 'WAKE_LEAD') wake('watcher', { worker_id: target.id, reason: parsed.reason });
|
|
237
|
+
} finally {
|
|
238
|
+
_releaseSlot();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function schedule() {
|
|
243
|
+
if (state.dead) return;
|
|
244
|
+
const id = setTimer(() => {
|
|
245
|
+
state.timer = null;
|
|
246
|
+
_liveTimers.delete(id);
|
|
247
|
+
tickCycle().catch(() => { /* expected: a tick never throws out; this is the last-resort net */ });
|
|
248
|
+
}, WATCH_INTERVAL_MS);
|
|
249
|
+
state.timer = id;
|
|
250
|
+
_liveTimers.add(id);
|
|
251
|
+
if (id && typeof id.unref === 'function') id.unref();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function tickCycle() {
|
|
255
|
+
try {
|
|
256
|
+
if (state.dead) return;
|
|
257
|
+
state.ticks += 1;
|
|
258
|
+
// silent runs only every WATCH_SILENT_EVERY-th tick; every other tick is a no-op.
|
|
259
|
+
if (!silent || state.ticks % WATCH_SILENT_EVERY === 0) {
|
|
260
|
+
await Promise.all(targets.map((target) => runWorkerTick(target).catch(() => { /* expected: per-worker isolation; failures are counted inside */ })));
|
|
261
|
+
}
|
|
262
|
+
} finally {
|
|
263
|
+
schedule();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
schedule();
|
|
268
|
+
return { stop };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Production readTail: the worker's own provider transcript, tail-first, clipped to
|
|
272
|
+
// WATCH_TAIL_CHARS characters (read through lib/transcript-source.js -- the only module
|
|
273
|
+
// allowed to open a provider transcript).
|
|
274
|
+
function createWaitWatcherReadTail({ dbModule } = {}) {
|
|
275
|
+
return async function readTail(workerId, maxChars = WATCH_TAIL_CHARS) {
|
|
276
|
+
const db = dbModule || require('../../db');
|
|
277
|
+
const agentRow = typeof db?.getAgentSessionByCtmId === 'function' ? db.getAgentSessionByCtmId(workerId) : null;
|
|
278
|
+
const transcriptPath = String((agentRow && agentRow.jsonl_path) || '');
|
|
279
|
+
if (!transcriptPath) return '';
|
|
280
|
+
const transcriptSource = require('../transcript-source');
|
|
281
|
+
const st = await transcriptSource.statTranscript(transcriptPath);
|
|
282
|
+
if (!st || st.exists === false) return '';
|
|
283
|
+
const size = Number(st.size) || 0;
|
|
284
|
+
const start = Math.max(0, size - maxChars * 4);
|
|
285
|
+
const buf = await transcriptSource.readTranscriptRange(transcriptPath, start, size);
|
|
286
|
+
return buf.toString('utf8').slice(-maxChars);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Test-only: clear every armed interval timer and the module-level semaphore, returning how
|
|
291
|
+
// many watchers were still ticking (0 when every watcher was stopped properly).
|
|
292
|
+
function _resetForTests() {
|
|
293
|
+
const cleared = _liveTimers.size;
|
|
294
|
+
for (const id of _liveTimers) clearTimeout(id);
|
|
295
|
+
_liveTimers = new Set();
|
|
296
|
+
_inFlightCalls = 0;
|
|
297
|
+
return cleared;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = {
|
|
301
|
+
startWaitWatcher,
|
|
302
|
+
createWaitWatcherReadTail,
|
|
303
|
+
parseWatchReply,
|
|
304
|
+
compactWorkerStatus,
|
|
305
|
+
WATCH_MODES,
|
|
306
|
+
WATCH_FEATURE,
|
|
307
|
+
WATCH_INTERVAL_MS,
|
|
308
|
+
WATCH_TAIL_CHARS,
|
|
309
|
+
WATCH_TIMEOUT_MS,
|
|
310
|
+
WATCH_MAX_CONSECUTIVE_FAILURES,
|
|
311
|
+
WATCH_MAX_CONCURRENT_CALLS,
|
|
312
|
+
WATCH_SILENT_EVERY,
|
|
313
|
+
WATCH_SYSTEM,
|
|
314
|
+
_resetForTests,
|
|
315
|
+
};
|
|
@@ -118,8 +118,11 @@ function handleSessionGeometryClaim(ws, msg, replayOpts = {}) {
|
|
|
118
118
|
let committedGrid = { cols: 0, rows: 0 };
|
|
119
119
|
let queuedDueAt = 0;
|
|
120
120
|
let broadcastRolesAfterAck = false;
|
|
121
|
+
// WP-2: the diagnostic below runs even when sanitizeGeometryClaim threw (or the claim never
|
|
122
|
+
// reached arbitration), so the sanitized claim is hoisted out of the try to be readable there.
|
|
123
|
+
let claim = null;
|
|
121
124
|
try {
|
|
122
|
-
|
|
125
|
+
claim = sanitizeGeometryClaim(msg, Number(ws.socketGeneration || 0));
|
|
123
126
|
sessionId = claim.id;
|
|
124
127
|
session = getSession(sessionId);
|
|
125
128
|
let currentLease = _geometryLease(session, sessionId);
|
|
@@ -270,6 +273,12 @@ function handleSessionGeometryClaim(ws, msg, replayOpts = {}) {
|
|
|
270
273
|
_recordTerminalGeometryClaimDiagnostic(session, sessionId, ws.clientId, {
|
|
271
274
|
disposition, reason, intent: ['reconnect', 'activation', 'pointer', 'keyboard', 'split-focus', 'revalidate', 'selection'].includes(msg?.intent) ? msg.intent : 'invalid',
|
|
272
275
|
ownerGeneration, measurementId, cols: committedGrid.cols, rows: committedGrid.rows, viewerRegistered,
|
|
276
|
+
// WP-2 (docs/codex-terminal-geometry-sticky-cols-2026-10-24.html §5): `cols` is the
|
|
277
|
+
// COMMITTED grid (pty truth); it is initialised from `session._ptyCols` and only moves on an
|
|
278
|
+
// accepted commit, so a queued/rejected claim for 196 against a 94-col pty logged `cols: 94`
|
|
279
|
+
// with no record of what the client asked for -- the exact hole in the live 94-cols incident.
|
|
280
|
+
claimCols: Math.max(0, Number(claim?.measurement?.cols) || 0),
|
|
281
|
+
claimRows: Math.max(0, Number(claim?.measurement?.rows) || 0),
|
|
273
282
|
});
|
|
274
283
|
_sendTerminalGeometryClaimAckV2(ws, {
|
|
275
284
|
id: sessionId, socketGeneration: Number(ws.socketGeneration || 0), measurementId, expectedOwnerGeneration,
|