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
|
@@ -23,6 +23,18 @@ function textOrEmpty(value) {
|
|
|
23
23
|
return value == null ? '' : value;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// A `usage_reread_prior.fact_key`: the JSON array of a fact's primary-key values, in primary-key
|
|
27
|
+
// column order (WP-3). Every key is written by this module, so a value that cannot be read is a
|
|
28
|
+
// corrupted or future-format row and names no fact.
|
|
29
|
+
function parseFactKey(raw) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(String(raw));
|
|
32
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return null; // expected: an unreadable snapshot key names no fact, so the row is ignored
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
function callParams(call, fileId) {
|
|
27
39
|
// A row that already declares its own identity ambiguous carries no cost, whatever the caller
|
|
28
40
|
// supplied: the INSERT path has no stored row to compare against, so the rule has to hold here
|
|
@@ -657,6 +669,11 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
657
669
|
// wrote under an older reading, not upsert beside them. These run inside
|
|
658
670
|
// upsertBatchTxn, before the new facts, so the swap is atomic.
|
|
659
671
|
//
|
|
672
|
+
// CODEX ONLY since WP-3. A Claude file's re-read is a SNAPSHOT plus an EOF sweep
|
|
673
|
+
// (see "non-destructive Claude re-read" below): the vendor rewrites a compacted
|
|
674
|
+
// transcript to FEWER rows, so "the new reading did not emit it" is not evidence that
|
|
675
|
+
// the fact is gone. The statements here are still exactly the Codex path (D-10).
|
|
676
|
+
//
|
|
660
677
|
// Known limitation: a Codex call key shared by several fork files (a copied parent
|
|
661
678
|
// history) is stored under the FIRST file that wrote it. Resetting that one file
|
|
662
679
|
// deletes the shared row until the file re-reads it -- which happens in the same
|
|
@@ -699,6 +716,168 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
699
716
|
SELECT session_id, prompt_id, 0 FROM usage_prompts
|
|
700
717
|
WHERE source_file_id = @source_file_id AND preview IS NULL`);
|
|
701
718
|
|
|
719
|
+
// --- non-destructive Claude re-read (WP-3 D-7/D-8) ---------------------------
|
|
720
|
+
// Claude Code REWRITES a compacted transcript: it drops every row before the last
|
|
721
|
+
// compact boundary and renames a new file over the path. A re-read therefore sees
|
|
722
|
+
// FEWER rows than the reading that wrote the stored facts, and the WP-8b delete above
|
|
723
|
+
// would destroy usage nothing can re-create (the incident: 4.5 days of a lead
|
|
724
|
+
// session's calls and every launch receipt). The rule for a Claude file is instead:
|
|
725
|
+
//
|
|
726
|
+
// 1. RESET (a re-read from 0): SNAPSHOT the fact keys this file had written into
|
|
727
|
+
// `usage_reread_prior`. Nothing is deleted.
|
|
728
|
+
// 2. EVERY SLICE, after the fact upserts: a fact the slice RE-EMITTED leaves the
|
|
729
|
+
// snapshot (it is not missing), and every vendor id the slice OBSERVED is
|
|
730
|
+
// recorded in `usage_reread_seen`.
|
|
731
|
+
// 3. EOF (`final`): a fact still in the snapshot is deleted IFF its vendor id was
|
|
732
|
+
// OBSERVED (the vendor still has the row and this reading no longer produces the
|
|
733
|
+
// fact) OR its vendor id is not derivable at all (an older identity form, where
|
|
734
|
+
// nothing can prove the vendor dropped it). A fact whose source row the vendor
|
|
735
|
+
// DELETED has no observed id and is KEPT -- that is D-7.
|
|
736
|
+
//
|
|
737
|
+
// Both scratch tables are cleared for the file in the same transaction that writes the
|
|
738
|
+
// checkpoint, so no re-read state outlives the pass. Codex files never touch them.
|
|
739
|
+
const clearRereadSeenStmt = db.prepare('DELETE FROM usage_reread_seen WHERE file_id = ?');
|
|
740
|
+
const clearRereadPriorStmt = db.prepare('DELETE FROM usage_reread_prior WHERE file_id = ?');
|
|
741
|
+
const insertRereadPriorStmt = db.prepare(`
|
|
742
|
+
INSERT OR IGNORE INTO usage_reread_prior (file_id, kind, fact_key, vendor_id)
|
|
743
|
+
VALUES (@file_id, @kind, @fact_key, @vendor_id)`);
|
|
744
|
+
const deleteRereadPriorKeysStmt = db.prepare(`
|
|
745
|
+
DELETE FROM usage_reread_prior
|
|
746
|
+
WHERE file_id = @file_id AND kind = @kind
|
|
747
|
+
AND fact_key IN (SELECT value FROM json_each(@keys))`);
|
|
748
|
+
const rereadPriorCountStmt = db.prepare(
|
|
749
|
+
'SELECT count(*) AS n FROM usage_reread_prior WHERE file_id = ?'
|
|
750
|
+
);
|
|
751
|
+
const selectRereadPriorStmt = db.prepare(
|
|
752
|
+
'SELECT kind, fact_key, vendor_id FROM usage_reread_prior WHERE file_id = ?'
|
|
753
|
+
);
|
|
754
|
+
const selectRereadSeenStmt = db.prepare(
|
|
755
|
+
'SELECT kind, vendor_id FROM usage_reread_seen WHERE file_id = ?'
|
|
756
|
+
);
|
|
757
|
+
const insertRereadSeenStmt = db.prepare(`
|
|
758
|
+
INSERT OR IGNORE INTO usage_reread_seen (file_id, kind, vendor_id)
|
|
759
|
+
VALUES (@file_id, @kind, @vendor_id)`);
|
|
760
|
+
// The sessions a doomed CALL attributed: the rollup joins THROUGH the call row, so they
|
|
761
|
+
// must be re-derived after it goes. (A spawn's session rides its own fact key.) Scoped to
|
|
762
|
+
// THIS file, exactly like the delete it feeds: another source file may own the row now.
|
|
763
|
+
const callSessionsByKeysStmt = db.prepare(`
|
|
764
|
+
SELECT DISTINCT session_id FROM usage_calls
|
|
765
|
+
WHERE call_key IN (SELECT value FROM json_each(@keys))
|
|
766
|
+
AND source_file_id = @source_file_id`);
|
|
767
|
+
// The evidence a doomed PROMPT still holds (an alias's digest is part of its own key).
|
|
768
|
+
// Scoped to this file: the evidence belongs to the row the sweep is about to delete, so a
|
|
769
|
+
// prompt another source now owns is neither read for evidence nor tombstoned here.
|
|
770
|
+
const promptEvidenceByKeyStmt = db.prepare(`
|
|
771
|
+
SELECT provider_message_digest, source_event_digest, preview FROM usage_prompts
|
|
772
|
+
WHERE session_id = ? AND prompt_id = ? AND source_file_id = ?`);
|
|
773
|
+
// The whole-file form of preserveLegacyRedactionsStmt, for ONE doomed prompt (D-8), scoped
|
|
774
|
+
// to the file that still owns the row for the same reason.
|
|
775
|
+
const preserveLegacyRedactionStmt = db.prepare(`
|
|
776
|
+
INSERT OR IGNORE INTO usage_prompt_purges (session_id, prompt_id, purged_at)
|
|
777
|
+
SELECT session_id, prompt_id, 0 FROM usage_prompts
|
|
778
|
+
WHERE session_id = @session_id AND prompt_id = @prompt_id AND preview IS NULL
|
|
779
|
+
AND source_file_id = @source_file_id`);
|
|
780
|
+
|
|
781
|
+
// The six fact kinds a Claude file writes, and the snapshot/delete statements per kind.
|
|
782
|
+
// * `columns` is the fact's PRIMARY KEY in column order: `fact_key` is the JSON array of
|
|
783
|
+
// those values, built identically here and in SQL (`json_array(...)` in the snapshot
|
|
784
|
+
// assignments below), so a key written by one side is always found by the other.
|
|
785
|
+
// * `vendor` is the id the VENDOR gave the record the fact came from -- the thing D-8
|
|
786
|
+
// decides on -- and `seen` is the `usage_reread_seen.kind` that id is recorded under.
|
|
787
|
+
// `null` means "not derivable" and is swept, as WP-8b did.
|
|
788
|
+
// * `sourceScoped` adds `AND source_file_id = @source_file_id` to the DELETE. The primary
|
|
789
|
+
// key alone is not enough: a fact this file snapshotted may have been REWRITTEN by
|
|
790
|
+
// another source file before this file's EOF sweep -- the `.jsonl.bak` sibling writes
|
|
791
|
+
// the same `call_key` and moves its `source_file_id` -- and deleting by key alone would
|
|
792
|
+
// destroy the OTHER source's fact. `alias` is not source-scoped because its own key
|
|
793
|
+
// already ends in `source_file_id`, so a key of another source's alias can never match.
|
|
794
|
+
const CLAUDE_CALL_KEY_RE = /^claude:[^:]*:[^:]*:(.+)$/;
|
|
795
|
+
const REREAD_KINDS = [
|
|
796
|
+
{
|
|
797
|
+
kind: 'call',
|
|
798
|
+
table: 'usage_calls',
|
|
799
|
+
columns: ['call_key'],
|
|
800
|
+
seen: 'response',
|
|
801
|
+
sourceScoped: true,
|
|
802
|
+
key: (row) => JSON.stringify([row.call_key]),
|
|
803
|
+
// Identity is `claude:<session>:<agent>:<message.id>`, so the vendor id is everything after
|
|
804
|
+
// the third colon. A key of any other shape carries no derivable id, so it is swept.
|
|
805
|
+
vendor: (row) => {
|
|
806
|
+
const match = CLAUDE_CALL_KEY_RE.exec(textOrEmpty(row.call_key));
|
|
807
|
+
return match ? match[1] : null;
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
kind: 'prompt',
|
|
812
|
+
table: 'usage_prompts',
|
|
813
|
+
columns: ['session_id', 'prompt_id'],
|
|
814
|
+
seen: 'prompt',
|
|
815
|
+
sourceScoped: true,
|
|
816
|
+
key: (row) => JSON.stringify([row.session_id, row.prompt_id]),
|
|
817
|
+
vendor: (row) => textOrNull(row.prompt_id),
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
kind: 'alias',
|
|
821
|
+
table: 'usage_prompt_aliases',
|
|
822
|
+
columns: ['session_id', 'prompt_id', 'provider_message_digest', 'source_file_id'],
|
|
823
|
+
seen: 'prompt',
|
|
824
|
+
// NOT source-scoped: `source_file_id` is part of this fact's own key.
|
|
825
|
+
key: (row, fileId) => JSON.stringify([
|
|
826
|
+
row.session_id,
|
|
827
|
+
row.prompt_id,
|
|
828
|
+
textOrEmpty(row.provider_message_digest),
|
|
829
|
+
row.source_file_id == null ? fileId : row.source_file_id,
|
|
830
|
+
]),
|
|
831
|
+
vendor: (row) => textOrNull(row.prompt_id),
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
kind: 'spawn',
|
|
835
|
+
table: 'usage_spawns',
|
|
836
|
+
columns: ['session_id', 'tool_use_id'],
|
|
837
|
+
seen: 'tool_use',
|
|
838
|
+
sourceScoped: true,
|
|
839
|
+
key: (row) => JSON.stringify([row.session_id, row.tool_use_id]),
|
|
840
|
+
vendor: (row) => textOrNull(row.tool_use_id),
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
kind: 'delegation',
|
|
844
|
+
table: 'usage_delegations',
|
|
845
|
+
columns: ['session_id', 'call_id'],
|
|
846
|
+
seen: 'tool_use',
|
|
847
|
+
sourceScoped: true,
|
|
848
|
+
key: (row) => JSON.stringify([row.session_id, row.call_id]),
|
|
849
|
+
vendor: (row) => textOrNull(row.call_id),
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
kind: 'attempt',
|
|
853
|
+
table: 'usage_delegation_attempts',
|
|
854
|
+
columns: ['session_id', 'call_id'],
|
|
855
|
+
seen: 'tool_use',
|
|
856
|
+
sourceScoped: true,
|
|
857
|
+
key: (row) => JSON.stringify([row.session_id, row.call_id]),
|
|
858
|
+
vendor: (row) => textOrNull(row.call_id),
|
|
859
|
+
},
|
|
860
|
+
];
|
|
861
|
+
const WHERE_BY_COLUMNS = (columns) => columns.map((column) => `${column} = @${column}`).join(' AND ');
|
|
862
|
+
for (const spec of REREAD_KINDS) {
|
|
863
|
+
spec.selectFileStmt = db.prepare(
|
|
864
|
+
`SELECT ${spec.columns.join(', ')} FROM ${spec.table} WHERE source_file_id = ?`
|
|
865
|
+
);
|
|
866
|
+
// The scope clause is the anti-clobber guard: this file may only delete a fact it still
|
|
867
|
+
// OWNS. Another source may have re-written the row since the snapshot was taken (WP-3).
|
|
868
|
+
const scope = spec.sourceScoped ? ' AND source_file_id = @source_file_id' : '';
|
|
869
|
+
spec.deleteByKeyStmt = db.prepare(
|
|
870
|
+
`DELETE FROM ${spec.table} WHERE ${WHERE_BY_COLUMNS(spec.columns)}${scope}`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const REREAD_KIND_BY_NAME = new Map(REREAD_KINDS.map((spec) => [spec.kind, spec]));
|
|
874
|
+
// The bind object for one snapshot key plus the source scope: the primary-key values, back
|
|
875
|
+
// under their column names, and `source_file_id` for a source-scoped kind.
|
|
876
|
+
const rereadKeyParams = (spec, values, fileId) => ({
|
|
877
|
+
...Object.fromEntries(spec.columns.map((column, index) => [column, values[index]])),
|
|
878
|
+
...(spec.sourceScoped ? { source_file_id: fileId } : {}),
|
|
879
|
+
});
|
|
880
|
+
|
|
702
881
|
// --- reprice pass (WP-5) -----------------------------------------------------
|
|
703
882
|
// Keyset, not OFFSET: every row is read EXACTLY once per pass, so a pass is one scan
|
|
704
883
|
// however many rounds it takes (the caller re-enters with the previous lastKey).
|
|
@@ -829,6 +1008,10 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
829
1008
|
recompute = true,
|
|
830
1009
|
reset = false,
|
|
831
1010
|
price = null,
|
|
1011
|
+
// WP-3: the vendor ids this slice OBSERVED and whether this slice reached EOF. Both are
|
|
1012
|
+
// used only for a Claude file (D-10: Codex ignores them).
|
|
1013
|
+
observed = null,
|
|
1014
|
+
final = false,
|
|
832
1015
|
} = payload;
|
|
833
1016
|
|
|
834
1017
|
const now = Date.now();
|
|
@@ -877,11 +1060,16 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
877
1060
|
}).changes;
|
|
878
1061
|
return { changes, session_id: stored.session_id };
|
|
879
1062
|
};
|
|
880
|
-
// `reset` (WP-8b): the caller is re-reading this file
|
|
881
|
-
// or head-fingerprint change discarded its checkpoint), so the
|
|
882
|
-
// under the OLD reading must be
|
|
883
|
-
//
|
|
1063
|
+
// `reset` (WP-8b, Claude semantics replaced in WP-3): the caller is re-reading this file
|
|
1064
|
+
// from byte 0 (a parser_version or head-fingerprint change discarded its checkpoint), so the
|
|
1065
|
+
// facts the file wrote under the OLD reading must be reconciled before the new ones land.
|
|
1066
|
+
// For a CODEX file that is the eager source-scoped delete; for a CLAUDE file it is a
|
|
1067
|
+
// SNAPSHOT plus an EOF sweep, because the vendor may have removed rows the facts came from.
|
|
1068
|
+
// Read the PREVIOUS identity before the fill-forward upsert mutates the row.
|
|
884
1069
|
const prevFileRow = reset ? (selectFileStmt.get(file.source_path) || null) : null;
|
|
1070
|
+
// The provider decides which reconciliation runs, so it is read from the BATCH (never from
|
|
1071
|
+
// the row the upsert is about to write, which the fill-forward keeps either way).
|
|
1072
|
+
const isClaude = textOrEmpty(file.provider) === 'claude';
|
|
885
1073
|
|
|
886
1074
|
const fileRow = upsertFileStmt.get({
|
|
887
1075
|
source_path: file.source_path,
|
|
@@ -895,7 +1083,23 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
895
1083
|
|
|
896
1084
|
const sessionSet = new Set();
|
|
897
1085
|
let changedFacts = 0;
|
|
898
|
-
if (prevFileRow) {
|
|
1086
|
+
if (prevFileRow && isClaude) {
|
|
1087
|
+
// WP-3 D-7: a Claude re-read deletes NOTHING up front. Every fact the file had written is
|
|
1088
|
+
// recorded as a key in `usage_reread_prior`, and the EOF sweep decides which of them the
|
|
1089
|
+
// new reading is allowed to remove. The observations start over with the re-read: ids seen
|
|
1090
|
+
// by the attempt this one replaced must not license a delete.
|
|
1091
|
+
clearRereadSeenStmt.run(prevFileRow.file_id);
|
|
1092
|
+
for (const spec of REREAD_KINDS) {
|
|
1093
|
+
for (const row of spec.selectFileStmt.all(prevFileRow.file_id)) {
|
|
1094
|
+
insertRereadPriorStmt.run({
|
|
1095
|
+
file_id: prevFileRow.file_id,
|
|
1096
|
+
kind: spec.kind,
|
|
1097
|
+
fact_key: spec.key(row, prevFileRow.file_id),
|
|
1098
|
+
vendor_id: spec.vendor(row),
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
} else if (prevFileRow) {
|
|
899
1103
|
// Every call the file previously wrote is stale: collect the sessions it touched
|
|
900
1104
|
// (their rollups must be re-derived) and delete the rows.
|
|
901
1105
|
for (const r of selectFileCallSessionsStmt.all(prevFileRow.file_id)) {
|
|
@@ -918,11 +1122,16 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
918
1122
|
changedFacts += deleteFileSpawnsStmt.run(prevFileRow.file_id).changes;
|
|
919
1123
|
changedFacts += deleteFileDelegationsStmt.run(prevFileRow.file_id).changes;
|
|
920
1124
|
changedFacts += deleteFileAttemptsStmt.run(prevFileRow.file_id).changes;
|
|
1125
|
+
changedFacts += deleteFileAliasesStmt.run(prevFileRow.file_id).changes;
|
|
1126
|
+
}
|
|
1127
|
+
if (prevFileRow) {
|
|
1128
|
+
// The reported row and a stale agent row are replaced for BOTH providers: neither is a
|
|
1129
|
+
// fact the vendor's rows are the only evidence of (a report is a re-derivable summary of
|
|
1130
|
+
// calls the ledger keeps, and an agent row is derived metadata).
|
|
921
1131
|
for (const r of selectFileCallSessionsForReportedStmt.all(prevFileRow.file_id)) {
|
|
922
1132
|
if (r.session_id != null) sessionSet.add(r.session_id);
|
|
923
1133
|
}
|
|
924
1134
|
changedFacts += deleteFileReportedStmt.run(prevFileRow.file_id).changes;
|
|
925
|
-
changedFacts += deleteFileAliasesStmt.run(prevFileRow.file_id).changes;
|
|
926
1135
|
// A stale agent row (e.g. a v1 Codex thread id that is no longer the main agent)
|
|
927
1136
|
// would show as a phantom subagent; drop it (and re-derive that session's rollup, which
|
|
928
1137
|
// joined through the row). `session_id = NULL` matches nothing.
|
|
@@ -1006,6 +1215,128 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
|
|
|
1006
1215
|
}
|
|
1007
1216
|
if (reported) changedFacts += upsertReportedStmt.run(reportedParams(reported, fileId)).changes;
|
|
1008
1217
|
|
|
1218
|
+
// --- WP-3 D-8: reconcile the snapshot with what this slice saw ------------------
|
|
1219
|
+
// EVERY SLICE, after the facts are written. A fact this slice RE-EMITTED is not missing, so
|
|
1220
|
+
// it leaves the snapshot; the ids this slice OBSERVED are recorded, and the EOF sweep below
|
|
1221
|
+
// deletes a prior fact only when its id is among them. Both halves are gated on a caller that
|
|
1222
|
+
// brought `observed` (the ingestor, for a Claude file), so a plain append -- and a Codex file
|
|
1223
|
+
// -- compiles no statement at all here.
|
|
1224
|
+
if (isClaude && observed) {
|
|
1225
|
+
// With no snapshot there is nothing to reconcile against, and nothing to record: a file
|
|
1226
|
+
// that never reset is simply being appended to.
|
|
1227
|
+
if (rereadPriorCountStmt.get(fileId).n > 0) {
|
|
1228
|
+
const emitted = new Map();
|
|
1229
|
+
const addKeys = (kind, keys) => {
|
|
1230
|
+
if (keys.length === 0) return;
|
|
1231
|
+
emitted.set(kind, [...(emitted.get(kind) || []), ...keys]);
|
|
1232
|
+
};
|
|
1233
|
+
addKeys('call', calls.map((call) => REREAD_KIND_BY_NAME.get('call').key(call)));
|
|
1234
|
+
addKeys('prompt', prompts.map((prompt) => REREAD_KIND_BY_NAME.get('prompt').key(prompt)));
|
|
1235
|
+
addKeys('alias', aliases
|
|
1236
|
+
.filter((alias) => !!textOrEmpty(alias.provider_message_digest))
|
|
1237
|
+
.map((alias) => REREAD_KIND_BY_NAME.get('alias').key(alias, fileId)));
|
|
1238
|
+
addKeys('spawn', spawns.map((spawn) => REREAD_KIND_BY_NAME.get('spawn').key(spawn)));
|
|
1239
|
+
addKeys('delegation', delegations.map((edge) => REREAD_KIND_BY_NAME.get('delegation').key(edge)));
|
|
1240
|
+
addKeys('attempt', attempts.map((attempt) => REREAD_KIND_BY_NAME.get('attempt').key(attempt)));
|
|
1241
|
+
for (const [kind, keys] of emitted) {
|
|
1242
|
+
deleteRereadPriorKeysStmt.run({ file_id: fileId, kind, keys: JSON.stringify(keys) });
|
|
1243
|
+
}
|
|
1244
|
+
const seen = [
|
|
1245
|
+
['response', observed.responses],
|
|
1246
|
+
['prompt', observed.prompts],
|
|
1247
|
+
['tool_use', observed.toolUses],
|
|
1248
|
+
];
|
|
1249
|
+
for (const [kind, ids] of seen) {
|
|
1250
|
+
for (const id of ids || []) {
|
|
1251
|
+
if (id == null) continue;
|
|
1252
|
+
insertRereadSeenStmt.run({ file_id: fileId, kind, vendor_id: id });
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// --- WP-3 D-8: the EOF sweep ---------------------------------------------------
|
|
1259
|
+
// A prior fact that this pass never re-emitted is deleted IFF its vendor id was observed
|
|
1260
|
+
// (the vendor still has the row and this reading no longer produces the fact) OR the fact
|
|
1261
|
+
// carries no derivable vendor id (an older identity form, exactly the WP-8b behaviour). A
|
|
1262
|
+
// fact whose source row the vendor DELETED was never observed and is KEPT -- D-7.
|
|
1263
|
+
if (isClaude && final) {
|
|
1264
|
+
const priorRows = selectRereadPriorStmt.all(fileId);
|
|
1265
|
+
if (priorRows.length > 0) {
|
|
1266
|
+
const seen = new Set(selectRereadSeenStmt.all(fileId).map((row) => `${row.kind}\u0000${row.vendor_id}`));
|
|
1267
|
+
const doomed = new Map();
|
|
1268
|
+
for (const row of priorRows) {
|
|
1269
|
+
const spec = REREAD_KIND_BY_NAME.get(row.kind);
|
|
1270
|
+
if (!spec) continue;
|
|
1271
|
+
if (row.vendor_id != null && !seen.has(`${spec.seen}\u0000${row.vendor_id}`)) continue;
|
|
1272
|
+
const values = parseFactKey(row.fact_key);
|
|
1273
|
+
if (!values) continue;
|
|
1274
|
+
if (!doomed.has(spec.kind)) doomed.set(spec.kind, []);
|
|
1275
|
+
doomed.get(spec.kind).push(values);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Calls first, and their sessions collected BEFORE the rows go: the rollup joins through
|
|
1279
|
+
// the call row, so deleting one changes the rollup of the session it attributed. Both
|
|
1280
|
+
// statements are scoped to THIS file, so a call another source now owns is neither
|
|
1281
|
+
// collected nor deleted.
|
|
1282
|
+
const callValues = doomed.get('call') || [];
|
|
1283
|
+
if (callValues.length > 0) {
|
|
1284
|
+
const keys = JSON.stringify(callValues.map((values) => values[0]));
|
|
1285
|
+
for (const r of callSessionsByKeysStmt.all({ keys, source_file_id: fileId })) {
|
|
1286
|
+
if (r.session_id != null) sessionSet.add(r.session_id);
|
|
1287
|
+
}
|
|
1288
|
+
for (const values of callValues) {
|
|
1289
|
+
changedFacts += REREAD_KIND_BY_NAME.get('call')
|
|
1290
|
+
.deleteByKeyStmt.run({ call_key: values[0], source_file_id: fileId }).changes;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// A prompt that goes takes its redaction evidence with it, exactly as the whole-file
|
|
1295
|
+
// reset did: a legacy bare-NULL preview becomes a durable tombstone first (nothing can
|
|
1296
|
+
// prove it was not an older writer's redaction), and any existing tombstone keeps the
|
|
1297
|
+
// digest that identifies the same event under a new identity. The evidence read is
|
|
1298
|
+
// source-scoped, so it runs only for a prompt row this file still owns -- i.e. one the
|
|
1299
|
+
// DELETE below will actually remove.
|
|
1300
|
+
for (const values of doomed.get('prompt') || []) {
|
|
1301
|
+
const [sessionId, promptId] = values;
|
|
1302
|
+
const stored = promptEvidenceByKeyStmt.get(sessionId, promptId, fileId);
|
|
1303
|
+
if (stored) {
|
|
1304
|
+
if (stored.preview == null) {
|
|
1305
|
+
changedFacts += preserveLegacyRedactionStmt.run({
|
|
1306
|
+
session_id: sessionId, prompt_id: promptId, source_file_id: fileId,
|
|
1307
|
+
}).changes;
|
|
1308
|
+
}
|
|
1309
|
+
changedFacts += capturePurgeEvidence(sessionId, promptId, stored);
|
|
1310
|
+
}
|
|
1311
|
+
changedFacts += REREAD_KIND_BY_NAME.get('prompt')
|
|
1312
|
+
.deleteByKeyStmt.run({ session_id: sessionId, prompt_id: promptId, source_file_id: fileId }).changes;
|
|
1313
|
+
}
|
|
1314
|
+
for (const values of doomed.get('alias') || []) {
|
|
1315
|
+
const [sessionId, promptId, digest] = values;
|
|
1316
|
+
changedFacts += capturePurgeEvidence(sessionId, promptId, { provider_message_digest: digest });
|
|
1317
|
+
changedFacts += REREAD_KIND_BY_NAME.get('alias')
|
|
1318
|
+
.deleteByKeyStmt.run(rereadKeyParams(REREAD_KIND_BY_NAME.get('alias'), values, fileId)).changes;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// A spawn's session rides its own fact key, so the rollup re-derivation needs no lookup
|
|
1322
|
+
// (re-deriving a session that did not move is harmless, unlike skipping one that did).
|
|
1323
|
+
for (const values of doomed.get('spawn') || []) {
|
|
1324
|
+
if (values[0] != null) sessionSet.add(values[0]);
|
|
1325
|
+
changedFacts += REREAD_KIND_BY_NAME.get('spawn')
|
|
1326
|
+
.deleteByKeyStmt.run(rereadKeyParams(REREAD_KIND_BY_NAME.get('spawn'), values, fileId)).changes;
|
|
1327
|
+
}
|
|
1328
|
+
for (const kind of ['delegation', 'attempt']) {
|
|
1329
|
+
const spec = REREAD_KIND_BY_NAME.get(kind);
|
|
1330
|
+
for (const values of doomed.get(kind) || []) {
|
|
1331
|
+
changedFacts += spec.deleteByKeyStmt.run(rereadKeyParams(spec, values, fileId)).changes;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
// The pass is over: no re-read state outlives the transaction that writes its checkpoint.
|
|
1336
|
+
clearRereadPriorStmt.run(fileId);
|
|
1337
|
+
clearRereadSeenStmt.run(fileId);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1009
1340
|
if (checkpoint) updateCheckpointStmt.run(checkpointParams(file.source_path, checkpoint));
|
|
1010
1341
|
|
|
1011
1342
|
const sessions = [...sessionSet];
|
|
@@ -101,6 +101,17 @@ function buildTaskSnapshot(coreDb, ctmSessionId, scope, ledger, { legacyDb = nul
|
|
|
101
101
|
// member that was never read at all.)
|
|
102
102
|
const nativePoisoned = new Set();
|
|
103
103
|
let bytes = turns.reduce((n, row) => n + Buffer.byteLength(row.text || ''), 0);
|
|
104
|
+
// D-3 (WP-1): a prompt written before `usage_prompts.source_file_id` existed carries NO digest and
|
|
105
|
+
// NO alias -- there is nothing to join on. Its `ts` is the transcript row's own `timestamp`, the
|
|
106
|
+
// SAME value the canonical turn recorded as `started_at_ms`, so an EXACT-equality index is a proof
|
|
107
|
+
// (never a window): the legacy prompt and the canonical human turn are the same transcript row.
|
|
108
|
+
const canonicalHumanByTs = new Map();
|
|
109
|
+
for (const turn of turns) {
|
|
110
|
+
if (turn.position_key == null || turn.kind !== 'human') continue;
|
|
111
|
+
const at = Number(turn.ts);
|
|
112
|
+
if (!canonicalHumanByTs.has(at)) canonicalHumanByTs.set(at, []);
|
|
113
|
+
canonicalHumanByTs.get(at).push(turn);
|
|
114
|
+
}
|
|
104
115
|
for (const prompt of ledger.prompts || []) {
|
|
105
116
|
if (!self.has(prompt.session_id) || !['human', 'user_prompt', 'local_command', 'compact_boundary', 'compaction', 'command', null, undefined].includes(prompt.kind)) continue;
|
|
106
117
|
if (prompt.kind === 'command' && fallbackKind(prompt) === 'human') continue;
|
|
@@ -119,6 +130,16 @@ function buildTaskSnapshot(coreDb, ctmSessionId, scope, ledger, { legacyDb = nul
|
|
|
119
130
|
if (incomplete || targets.size === 0) nativePoisoned.add(nativeKey);
|
|
120
131
|
}
|
|
121
132
|
const ref = { session_id: prompt.session_id, prompt_id: prompt.prompt_id };
|
|
133
|
+
// D-3 (WP-1): a SOURCE-LESS prompt (no `source_file_id`) with no digest, no event digest and no
|
|
134
|
+
// aliases has no identity evidence at all, so an EXACT-equal timestamp against a UNIQUE canonical
|
|
135
|
+
// human turn is the only proof available: same transcript row, therefore same turn. Every other
|
|
136
|
+
// shape is left untouched -- a prompt WITH a source file is never matched by time.
|
|
137
|
+
if (targets.size === 0 && !incomplete && prompt.source_less === true
|
|
138
|
+
&& !prompt.provider_message_digest && !prompt.source_event_digest
|
|
139
|
+
&& (!prompt.aliases || prompt.aliases.length === 0)) {
|
|
140
|
+
const sameStart = canonicalHumanByTs.get(Number(prompt.ts));
|
|
141
|
+
if (sameStart && sameStart.length === 1) targets.add(sameStart[0]);
|
|
142
|
+
}
|
|
122
143
|
if (targets.size === 1 && !incomplete) {
|
|
123
144
|
const turn = [...targets][0];
|
|
124
145
|
if (!turn.source_refs.some((r) => qualified(r.session_id, r.prompt_id) === qualified(ref.session_id, ref.prompt_id))) turn.source_refs.push(ref);
|
|
@@ -424,9 +445,22 @@ function readAgentGraph(db, ids) {
|
|
|
424
445
|
const params = { ids: JSON.stringify(ids) };
|
|
425
446
|
if (!columns(db, 'usage_agents').size || !columns(db, 'usage_spawns').has('source_file_id')) return { agents: [], spawns: [], truncated: false };
|
|
426
447
|
const agents = db.prepare(`SELECT session_id,agent_id,parent_tool_use_id FROM usage_agents WHERE session_id IN (SELECT value FROM json_each(@ids)) ORDER BY session_id,agent_id LIMIT ${GRAPH_LIMIT + 1}`).all(params);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
448
|
+
// D-1 (WP-1): a spawn row written BEFORE the `source_file_id` column existed still names the
|
|
449
|
+
// prompt it ran under. The join is therefore a LEFT JOIN and the row survives on its prompt_id
|
|
450
|
+
// alone: the parser sets `prompt_id` only when the spawning file IS the main thread
|
|
451
|
+
// (claude-parse.js), so a sourceless row with a prompt_id is provably a MAIN-THREAD spawn, whose
|
|
452
|
+
// parent agent is the empty (main) agent id. A sourceless row with NO prompt_id proves nothing and
|
|
453
|
+
// is excluded here rather than resolved to an invented owner.
|
|
454
|
+
const spawns = db.prepare(`SELECT s.session_id, s.tool_use_id, s.prompt_id,
|
|
455
|
+
CASE WHEN f.file_id IS NOT NULL THEN f.agent_id
|
|
456
|
+
WHEN s.source_file_id IS NULL AND s.prompt_id IS NOT NULL THEN ''
|
|
457
|
+
ELSE NULL END AS parent_agent_id
|
|
458
|
+
FROM usage_spawns s
|
|
459
|
+
LEFT JOIN usage_files f ON f.file_id = s.source_file_id AND f.session_id = s.session_id
|
|
460
|
+
WHERE s.session_id IN (SELECT value FROM json_each(@ids))
|
|
461
|
+
AND (f.file_id IS NOT NULL OR (s.source_file_id IS NULL AND s.prompt_id IS NOT NULL))
|
|
462
|
+
ORDER BY s.session_id, s.tool_use_id, s.source_file_id
|
|
463
|
+
LIMIT ${GRAPH_LIMIT + 1}`).all(params);
|
|
430
464
|
return { agents: agents.slice(0, GRAPH_LIMIT), spawns: spawns.slice(0, GRAPH_LIMIT), truncated: agents.length > GRAPH_LIMIT || spawns.length > GRAPH_LIMIT };
|
|
431
465
|
}
|
|
432
466
|
|
|
@@ -443,6 +477,9 @@ const DELEGATION_EDGE_LIMIT = 20000;
|
|
|
443
477
|
// the worker record it created. The legacy recovery window (R-6b) is exactly this, so a launch can
|
|
444
478
|
// never be matched to a call older than the deadline that could have produced it.
|
|
445
479
|
const MAX_PROXY_TIMEOUT_MS = 900000;
|
|
480
|
+
// The worker-launch fallback window: the same physical bound as R-6 -- a worker record can never
|
|
481
|
+
// be newer than the launch call that created it by more than the proxy deadline.
|
|
482
|
+
const WORKER_LAUNCH_WINDOW_MS = MAX_PROXY_TIMEOUT_MS;
|
|
446
483
|
function readDelegationEdges(db, workerIds, sessionIds) {
|
|
447
484
|
if (!columns(db, 'usage_delegations').size) return { edges: [], truncated: false };
|
|
448
485
|
// The edge's agent is the agent its file's CALLS are stored under. A Codex root rollout's file
|
|
@@ -652,6 +689,74 @@ function projectTasks(ledgerDb, snapshot, scope, ledger, { grouping = 'heuristic
|
|
|
652
689
|
if (ts !== null && ts >= from && ts <= launch.at_ms) addEdgeTarget(key, row);
|
|
653
690
|
}
|
|
654
691
|
}
|
|
692
|
+
// WP-2: the LAST resort for a worker generation the lead's transcript can no longer prove. When the
|
|
693
|
+
// vendor REWROTE the lead file, neither the accepted edge (WP-0) nor the launch attempt (R-6)
|
|
694
|
+
// survives, and the calls fell into `delegation_edge_missing` even though CTM's OWN records say
|
|
695
|
+
// when the worker was launched (its `created_at` / continuation receipts) and which lead turns
|
|
696
|
+
// exist (the canonical turn index). The fallback charges the generation to the canonical turn that
|
|
697
|
+
// was ACTIVE at the launch instant -- the greatest turn start not after it -- and only when no
|
|
698
|
+
// OTHER canonical turn could have held the launch call: a turn starting inside the proxy deadline
|
|
699
|
+
// window, or two launches of the same generation naming different turns, is AMBIGUOUS, not a
|
|
700
|
+
// guess. It never touches a key the edges or R-6 already filled, and it never runs for a NESTED
|
|
701
|
+
// worker (whose launch was made by another worker, not by a lead turn) or on a canonical window
|
|
702
|
+
// that is unavailable or capped -- a partial turn index cannot prove which turn was active.
|
|
703
|
+
const fallbackUsable = snapshot.canonical_available === true && snapshot.canonical_window_truncated !== true;
|
|
704
|
+
// (worker, generation) keys whose launch-time evidence named more than one possible turn. Kept
|
|
705
|
+
// beside `edgeTargets` rather than inside it: no target was proven, so no bucket may exist.
|
|
706
|
+
const launchAmbiguous = new Set();
|
|
707
|
+
if (fallbackUsable) {
|
|
708
|
+
const canonicalStarts = resolved
|
|
709
|
+
.filter((turn) => turn.position_key != null && Number.isFinite(Number(turn.ts)))
|
|
710
|
+
.map((turn) => ({ turn_key: turn.turn_key, ts: Number(turn.ts) }))
|
|
711
|
+
.sort((a, b) => a.ts - b.ts);
|
|
712
|
+
const scopedWorkers = canonicalStarts.length > 0 ? scope.workers || [] : [];
|
|
713
|
+
for (const worker of scopedWorkers) {
|
|
714
|
+
if (String(worker.parent_ctm_session_id || '') !== String(snapshot.ctm_session_id)) continue;
|
|
715
|
+
const byGeneration = new Map();
|
|
716
|
+
for (const launch of worker.launches || []) {
|
|
717
|
+
const generation = Number(launch.generation);
|
|
718
|
+
if (!Number.isInteger(generation) || generation < 1) continue;
|
|
719
|
+
if (!byGeneration.has(generation)) byGeneration.set(generation, []);
|
|
720
|
+
byGeneration.get(generation).push(launch);
|
|
721
|
+
}
|
|
722
|
+
for (const [generation, launches] of byGeneration) {
|
|
723
|
+
const key = `${worker.ctm_session_id}\u0000${generation}`;
|
|
724
|
+
const existing = edgeTargets.get(key);
|
|
725
|
+
if (existing && existing.size > 0) continue;
|
|
726
|
+
const ends = worker.generation_ends_ms;
|
|
727
|
+
let pick = null;
|
|
728
|
+
let ambiguous = false;
|
|
729
|
+
let unresolved = false;
|
|
730
|
+
for (const launch of launches) {
|
|
731
|
+
// No instant on the launch record: nothing to compare against the turn index.
|
|
732
|
+
const at = finiteOrNull(launch.at_ms);
|
|
733
|
+
if (at === null) { unresolved = true; break; }
|
|
734
|
+
// The turn active at the launch: the greatest canonical start not after it. A launch
|
|
735
|
+
// before EVERY canonical turn has no active turn, so it stays a named gap.
|
|
736
|
+
let start = null;
|
|
737
|
+
for (const candidate of canonicalStarts) {
|
|
738
|
+
if (candidate.ts > at) break;
|
|
739
|
+
start = candidate;
|
|
740
|
+
}
|
|
741
|
+
if (start === null) { unresolved = true; break; }
|
|
742
|
+
// A RESUME is only possible after the previous generation ended: the window opens at the
|
|
743
|
+
// later of the proxy deadline and that boundary.
|
|
744
|
+
let lo = at - WORKER_LAUNCH_WINDOW_MS;
|
|
745
|
+
if (generation >= 2 && Array.isArray(ends) && Number.isFinite(ends[generation - 2])) {
|
|
746
|
+
lo = Math.max(lo, ends[generation - 2]);
|
|
747
|
+
}
|
|
748
|
+
// A turn STARTED inside the window, so the launch call could equally have run under it.
|
|
749
|
+
if (start.ts > lo) { ambiguous = true; continue; }
|
|
750
|
+
if (pick && pick.turn_key !== start.turn_key) { ambiguous = true; continue; }
|
|
751
|
+
pick = start;
|
|
752
|
+
}
|
|
753
|
+
if (unresolved) continue;
|
|
754
|
+
if (ambiguous) { launchAmbiguous.add(key); continue; }
|
|
755
|
+
if (!pick) continue;
|
|
756
|
+
edgeTargets.set(key, new Map([['fallback', { session_id: null, agent_id: '', prompt_id: null, turn_key: pick.turn_key, ts: pick.ts }]]));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
655
760
|
// D-3: the generation a worker row falls in, from the worker's OWN recorded boundaries. A row
|
|
656
761
|
// past every recorded end belongs to the RUNNING generation (`worker.generation`); a row that
|
|
657
762
|
// starts in one generation and ends in another is split, never silently charged to one of them.
|
|
@@ -683,10 +788,19 @@ function projectTasks(ledgerDb, snapshot, scope, ledger, { grouping = 'heuristic
|
|
|
683
788
|
if (ctm == null || String(ctm) === '') return { reason: 'delegation_edge_missing' };
|
|
684
789
|
const resolvedGeneration = generationOf(workerById.get(String(ctm)), row.first_ts, row.last_ts);
|
|
685
790
|
if (resolvedGeneration.reason) return { reason: resolvedGeneration.reason };
|
|
686
|
-
const
|
|
687
|
-
|
|
791
|
+
const edgeKey = `${ctm}\u0000${resolvedGeneration.generation}`;
|
|
792
|
+
const bucket = edgeTargets.get(edgeKey);
|
|
793
|
+
if (!bucket || bucket.size === 0) {
|
|
794
|
+
// WP-2: the launch-time evidence named MORE than one possible turn, so the gap is
|
|
795
|
+
// ambiguity -- a different fact from "no launch record at all".
|
|
796
|
+
if (launchAmbiguous.has(edgeKey)) return { reason: 'delegation_edge_ambiguous' };
|
|
797
|
+
return { reason: 'delegation_edge_missing' };
|
|
798
|
+
}
|
|
688
799
|
if (bucket.size > 1) return { reason: 'delegation_edge_ambiguous' };
|
|
689
800
|
const target = [...bucket.values()][0];
|
|
801
|
+
// A WP-2 fallback target names a canonical turn directly (it never came from a transcript, so it
|
|
802
|
+
// has no session/agent/prompt); the caller labels a non-prompt turn `non_prompt_turn`.
|
|
803
|
+
if (target.turn_key) return { turn: target.turn_key, prompt: null };
|
|
690
804
|
const role = roots.get(target.session_id);
|
|
691
805
|
if (role === 'delegated') {
|
|
692
806
|
// A nested launch carries no timestamp of its own; the launching edge's own time is the
|
|
@@ -744,6 +858,10 @@ function projectTasks(ledgerDb, snapshot, scope, ledger, { grouping = 'heuristic
|
|
|
744
858
|
}
|
|
745
859
|
const edges = spawns.get(qualified(row.session_id, matches[0].parent_tool_use_id)) || [];
|
|
746
860
|
const unique = new Map(edges.map((e) => [qualified(e.parent_agent_id, e.prompt_id), e]));
|
|
861
|
+
// D-2: NO spawn row and SEVERAL spawn rows are different facts. `size === 0` proves the
|
|
862
|
+
// parent link was never recorded; only `size > 1` is a genuine ambiguity. Folding them
|
|
863
|
+
// together misnamed every missing row as a conflict.
|
|
864
|
+
if (unique.size === 0) return { reason: 'native_spawn_missing' };
|
|
747
865
|
if (unique.size !== 1) return { reason: 'native_spawn_ambiguous' };
|
|
748
866
|
const edge = [...unique.values()][0];
|
|
749
867
|
prompt = edge.prompt_id;
|
|
@@ -8,16 +8,13 @@ const DEFAULT_DEADLINES_MS = Object.freeze({
|
|
|
8
8
|
applied: 100,
|
|
9
9
|
painted: 50,
|
|
10
10
|
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
applied: 'supersede-obsolete',
|
|
19
|
-
painted: 'report-only',
|
|
20
|
-
});
|
|
11
|
+
// Every incident is report-only: a missed boundary deadline is latency evidence, never proof
|
|
12
|
+
// that a frame was lost. Under host load keydown->frame-sent runs 400-800 ms on a healthy
|
|
13
|
+
// session, and the replacement keyframe the 'frame-sent' miss used to force re-delivered a
|
|
14
|
+
// screen the client had already applied (B3: a duplicate full-grid write per late frame). Lost
|
|
15
|
+
// frames are repaired by the delivery path's own seq/baseline checks, not by a clock.
|
|
16
|
+
// (codex-running-composer-latency-round5-2026-08-09 §11 WP-6 rollback: "Return watchdog to
|
|
17
|
+
// report-only; preserve diagnostics".)
|
|
21
18
|
|
|
22
19
|
function createVisibleRevisionWatchdog(options = {}) {
|
|
23
20
|
const now = options.now || Date.now;
|
|
@@ -88,12 +85,11 @@ function createVisibleRevisionWatchdog(options = {}) {
|
|
|
88
85
|
deadlineMs,
|
|
89
86
|
previousBoundary,
|
|
90
87
|
missingBoundary,
|
|
91
|
-
recoveryAction: RECOVERY_ACTION[missingBoundary] || 'report-only',
|
|
92
88
|
};
|
|
93
89
|
row.reported = true;
|
|
94
90
|
incidents.push(incident); report(incident);
|
|
95
91
|
recover({ ...incident, scope: row.clientId ? 'client' : 'session' });
|
|
96
|
-
|
|
92
|
+
pending.delete(id);
|
|
97
93
|
}
|
|
98
94
|
return incidents;
|
|
99
95
|
}
|