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
|
@@ -128,9 +128,6 @@ function lifecycleConfigFromEnv(env = process.env) {
|
|
|
128
128
|
prewarm: envBool(env, 'CTM_PREWARM', false),
|
|
129
129
|
prewarmTtlMs: envInt(env, 'CTM_PREWARM_TTL_MS', 60_000, 1_000),
|
|
130
130
|
prewarmTopK: envInt(env, 'CTM_PREWARM_TOP_K', 1, 1),
|
|
131
|
-
freezeTier: idlePark && envBool(env, 'CTM_FREEZE_TIER', false),
|
|
132
|
-
freezeAfterMs: envInt(env, 'CTM_FREEZE_AFTER_MS', 15 * 60_000, 1_000),
|
|
133
|
-
freezeParkAfterMs: 2 * 60 * 60_000,
|
|
134
131
|
// WP-3: default is derived from host RAM (see defaultSessionRssBudgetMB), so the
|
|
135
132
|
// MemoryBudgetController is live out of the box. `CTM_SESSION_RSS_BUDGET_MB=0`
|
|
136
133
|
// remains the explicit disable — a 0 budget makes observe() inert again.
|
|
@@ -147,11 +144,7 @@ function lifecycleConfigFromEnv(env = process.env) {
|
|
|
147
144
|
|
|
148
145
|
function automaticParkSweepPolicy(config, options = {}) {
|
|
149
146
|
const pressure = ['warn', 'critical'].includes(options.pressure) ? options.pressure : 'normal';
|
|
150
|
-
const
|
|
151
|
-
const configuredThresholdMs = Math.max(0, Number(config?.idleParkAfterMs) || 0);
|
|
152
|
-
const idleParkAfterMs = freezeTier
|
|
153
|
-
? Math.max(configuredThresholdMs, Number(config?.freezeParkAfterMs) || 0)
|
|
154
|
-
: configuredThresholdMs;
|
|
147
|
+
const idleParkAfterMs = Math.max(0, Number(config?.idleParkAfterMs) || 0);
|
|
155
148
|
const disconnectCollapseMs = Math.max(
|
|
156
149
|
idleParkAfterMs,
|
|
157
150
|
Number(config?.disconnectCollapseMs) || 0,
|
|
@@ -223,7 +216,7 @@ function normalizeClientKind(value) {
|
|
|
223
216
|
function evaluatePark(session, host, config) {
|
|
224
217
|
const now = Number(host?.now) || Date.now();
|
|
225
218
|
const protections = [];
|
|
226
|
-
if (!session ||
|
|
219
|
+
if (!session || session.state !== 'live') protections.push('not-live');
|
|
227
220
|
if (session?.resumable === false) protections.push('not-resumable'); if (session?.parkable === false) protections.push('not-parkable-class');
|
|
228
221
|
if (session?.liveStatus === 'running' || session?.liveStatus === 'generating' || session?.liveStatus === 'working') protections.push('running');
|
|
229
222
|
if (session?.liveStatus === 'waiting_input' || session?.liveStatus === 'resuming') protections.push('waiting-input');
|
|
@@ -490,37 +483,6 @@ function describePressureCause(signals = {}, thresholds = {}, effectiveLevel = '
|
|
|
490
483
|
|| { kind: 'recovering', level, value: null };
|
|
491
484
|
}
|
|
492
485
|
|
|
493
|
-
function evaluateFreeze(session, host, config) {
|
|
494
|
-
if (config?.freezeTier !== true) {
|
|
495
|
-
return { eligible: false, protections: ['disabled'], idleMs: 0, thresholdMs: Number(config?.freezeAfterMs) || 0 };
|
|
496
|
-
}
|
|
497
|
-
if (!session || session.state !== 'live') {
|
|
498
|
-
return { eligible: false, protections: ['not-live'], idleMs: 0, thresholdMs: Number(config?.freezeAfterMs) || 0 };
|
|
499
|
-
}
|
|
500
|
-
return evaluatePark(session, host, {
|
|
501
|
-
...config,
|
|
502
|
-
idleParkAfterMs: Number(config.freezeAfterMs) || 15 * 60_000,
|
|
503
|
-
disconnectCollapseMs: Number(config.freezeAfterMs) || 15 * 60_000,
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
function selectFreezeVictim(sessions, host, config) {
|
|
508
|
-
const eligible = [];
|
|
509
|
-
for (const session of sessions || []) {
|
|
510
|
-
const result = evaluateFreeze(session, host, config);
|
|
511
|
-
if (result.eligible) eligible.push({ session, result });
|
|
512
|
-
}
|
|
513
|
-
eligible.sort((a, b) => {
|
|
514
|
-
const activityDelta = (Number(a.session.lastMeaningfulActivityAt) || 0)
|
|
515
|
-
- (Number(b.session.lastMeaningfulActivityAt) || 0);
|
|
516
|
-
if (activityDelta !== 0) return activityDelta;
|
|
517
|
-
const rssDelta = sessionMemoryMB(b.session) - sessionMemoryMB(a.session);
|
|
518
|
-
if (rssDelta !== 0) return rssDelta;
|
|
519
|
-
return String(a.session.id).localeCompare(String(b.session.id));
|
|
520
|
-
});
|
|
521
|
-
return eligible[0]?.session || null;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
486
|
class PressureController {
|
|
525
487
|
constructor() {
|
|
526
488
|
this.level = 'normal';
|
|
@@ -766,6 +728,10 @@ function percentile(values, ratio) {
|
|
|
766
728
|
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * ratio) - 1))];
|
|
767
729
|
}
|
|
768
730
|
|
|
731
|
+
// WP-3 (§5 D-8): the skip-reason vocabulary lives in its own module (so server.js can name tokens
|
|
732
|
+
// without growing); re-exported here for the existing importers.
|
|
733
|
+
const { WAKE_SKIP_REASONS } = require('./wake-skip-reasons');
|
|
734
|
+
|
|
769
735
|
class LifecycleDiagnostics {
|
|
770
736
|
constructor({ maxEvents = 200 } = {}) {
|
|
771
737
|
this.maxEvents = Math.max(1, maxEvents);
|
|
@@ -773,12 +739,15 @@ class LifecycleDiagnostics {
|
|
|
773
739
|
this.activationByClient = {};
|
|
774
740
|
this.parkByReason = {};
|
|
775
741
|
this.prewarmByOutcome = {};
|
|
776
|
-
this.freezeByAction = {};
|
|
777
742
|
this.shadow = { candidates: 0, falsePositives: 0, projectedReclaimMB: 0 };
|
|
778
743
|
this.pressureTransitions = 0;
|
|
779
744
|
this.events = [];
|
|
780
745
|
this.wake = new Map();
|
|
781
746
|
this.wakeOutcomes = {};
|
|
747
|
+
// WP-3 (§5 D-6): skip-reason counter for the pre-gate deferred-restore bails -- the ones that
|
|
748
|
+
// precede _wakeStartedAt and so can never be counted by wakeOutcomes ("65 deferred, zero
|
|
749
|
+
// wakes" was invisible for exactly this reason).
|
|
750
|
+
this.wakeSkips = {};
|
|
782
751
|
}
|
|
783
752
|
|
|
784
753
|
_count(target, key) { target[key] = (target[key] || 0) + 1; }
|
|
@@ -813,12 +782,6 @@ class LifecycleDiagnostics {
|
|
|
813
782
|
this._event({ type: 'prewarm', id, outcome: normalized, reason: String(reason || '').slice(0, 48) });
|
|
814
783
|
}
|
|
815
784
|
|
|
816
|
-
recordFreeze({ id, action, idleMs = 0, reason = '' }) {
|
|
817
|
-
const normalized = String(action || 'unknown').slice(0, 32);
|
|
818
|
-
this._count(this.freezeByAction, normalized);
|
|
819
|
-
this._event({ type: 'freeze', id, action: normalized, idleMs: Math.max(0, idleMs), reason: String(reason || '').slice(0, 48) });
|
|
820
|
-
}
|
|
821
|
-
|
|
822
785
|
recordShadow({ id, action = 'candidate', rssMB = 0, activityAfterMs = 0, reason = '', signals = {} }) {
|
|
823
786
|
if (action === 'candidate') {
|
|
824
787
|
this.shadow.candidates += 1;
|
|
@@ -863,6 +826,14 @@ class LifecycleDiagnostics {
|
|
|
863
826
|
this._event({ type: 'wake-outcome', id, provider, outcome, phases: { ...phases } });
|
|
864
827
|
}
|
|
865
828
|
|
|
829
|
+
// WP-3 (§5 D-6/D-8): the sibling of recordWakeOutcome for the pre-gate bails. `reason` is a
|
|
830
|
+
// WAKE_SKIP_REASONS token (CamelCase, machine-readable — the K8s-condition convention); a
|
|
831
|
+
// caller that still passes a legacy human string is recorded verbatim rather than dropped.
|
|
832
|
+
recordWakeSkip({ id = '', provider = 'unknown', reason = 'Unclassified' } = {}) {
|
|
833
|
+
this._count(this.wakeSkips, reason);
|
|
834
|
+
this._event({ type: 'wake-skip', id, provider, reason });
|
|
835
|
+
}
|
|
836
|
+
|
|
866
837
|
snapshot(extra = {}) {
|
|
867
838
|
const wakeLatency = {};
|
|
868
839
|
for (const [provider, entry] of this.wake) {
|
|
@@ -876,10 +847,10 @@ class LifecycleDiagnostics {
|
|
|
876
847
|
activationByClient: { ...this.activationByClient },
|
|
877
848
|
parkByReason: { ...this.parkByReason },
|
|
878
849
|
prewarmByOutcome: { ...this.prewarmByOutcome },
|
|
879
|
-
freezeByAction: { ...this.freezeByAction },
|
|
880
850
|
shadow: { ...this.shadow },
|
|
881
851
|
pressureTransitions: this.pressureTransitions,
|
|
882
852
|
wakeOutcomes: { ...this.wakeOutcomes },
|
|
853
|
+
wakeSkips: { ...this.wakeSkips },
|
|
883
854
|
wakeLatency,
|
|
884
855
|
events: [...this.events],
|
|
885
856
|
...extra,
|
|
@@ -890,6 +861,7 @@ class LifecycleDiagnostics {
|
|
|
890
861
|
module.exports = {
|
|
891
862
|
ActivationGate,
|
|
892
863
|
LifecycleDiagnostics,
|
|
864
|
+
WAKE_SKIP_REASONS,
|
|
893
865
|
MemoryBudgetController,
|
|
894
866
|
PressureController,
|
|
895
867
|
automaticParkSweepPolicy,
|
|
@@ -901,11 +873,9 @@ module.exports = {
|
|
|
901
873
|
classifyWakeOutcome,
|
|
902
874
|
derivePressure,
|
|
903
875
|
describePressureCause,
|
|
904
|
-
evaluateFreeze,
|
|
905
876
|
evaluateManualPark,
|
|
906
877
|
evaluatePark,
|
|
907
878
|
summarizeParkEligibility,
|
|
908
|
-
selectFreezeVictim,
|
|
909
879
|
selectParkVictim,
|
|
910
880
|
sessionMemoryMB,
|
|
911
881
|
};
|
|
@@ -72,6 +72,28 @@ function parkRefusalDetail(protections) {
|
|
|
72
72
|
return 'The session could not be restarted right now. Try again in a moment.';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// WP-0 D-10: a refusal code with no hand-written sentence still gets a family sentence that NAMES
|
|
76
|
+
// the code, so no refusal can reach the person as a generic line. Checked after the explicit map and
|
|
77
|
+
// before any caller-supplied message.
|
|
78
|
+
const HISTORY_REFUSAL_FAMILIES = Object.freeze([
|
|
79
|
+
[/^unsupported_native_/, 'This history carries a record type that has not been qualified for transfer.'],
|
|
80
|
+
[/^native_world_state_/, 'The native Codex world state could not be replayed safely.'],
|
|
81
|
+
[/^native_history_/, 'The native Codex history could not be prepared for transfer.'],
|
|
82
|
+
[/^tool_call_/, 'This history has an incomplete tool exchange, so it cannot be transferred safely.'],
|
|
83
|
+
[/^history_/, 'The source history could not be verified for this provider change.'],
|
|
84
|
+
[/^source_/, 'The source session could not be verified for this provider change.'],
|
|
85
|
+
[/^target_/, 'The target session could not be verified for this provider change.'],
|
|
86
|
+
[/^fork_/, 'The native fork could not be verified in Codex history.'],
|
|
87
|
+
[/^native_(?:rpc|process|fork|adapter|readback|initialize)_/, 'The native Codex process could not complete the handoff.'],
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
function historyRefusalFamilyMessage(reason, tail) {
|
|
91
|
+
for (const [pattern, sentence] of HISTORY_REFUSAL_FAMILIES) {
|
|
92
|
+
if (pattern.test(reason)) return `${sentence} (code: ${reason}) ${tail}`;
|
|
93
|
+
}
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
// History checks run before the source is parked. Keep their bounded reason codes useful to the
|
|
76
98
|
// person making the change without exposing scanner records, paths, or provider credentials.
|
|
77
99
|
function historyPreparationMessage(reason, suppliedMessage = '', { postPark = false } = {}) {
|
|
@@ -83,10 +105,13 @@ function historyPreparationMessage(reason, suppliedMessage = '', { postPark = fa
|
|
|
83
105
|
source_checkpoint_changed_after_park: `The source checkpoint changed during handoff verification. ${recovery}`,
|
|
84
106
|
source_config_policy_unqualified: `The captured Codex configuration changed during handoff verification. ${recovery}`,
|
|
85
107
|
native_history_control_failed: `Codex could not create the native history checkpoint. ${recovery}`,
|
|
108
|
+
native_handoff_inject_failed: `The handoff summary could not be written into the new Codex thread. ${recovery}`,
|
|
109
|
+
fork_target_handoff_missing: `The new Codex thread does not prove it received the handoff summary for its encrypted history. ${recovery}`,
|
|
86
110
|
fork_readback_unverified: `The native fork could not be verified in Codex history. ${recovery}`,
|
|
87
111
|
fork_journal_owner_changed: `The transition owner changed during handoff. ${recovery}`,
|
|
88
112
|
};
|
|
89
|
-
return messages[reason] ||
|
|
113
|
+
return messages[reason] || historyRefusalFamilyMessage(reason, recovery)
|
|
114
|
+
|| `The target handoff could not be verified. ${recovery}`;
|
|
90
115
|
}
|
|
91
116
|
const messages = {
|
|
92
117
|
native_target_profile_unavailable: 'The native Codex model does not publish the context limits needed to verify this history move. The current session is still running unchanged.',
|
|
@@ -100,7 +125,6 @@ function historyPreparationMessage(reason, suppliedMessage = '', { postPark = fa
|
|
|
100
125
|
// WP-2 (docs/codex-encrypted-history-lead-switch-2026-10-17.html §5/§8): the name stays; the copy
|
|
101
126
|
// now says what the user actually has (an encrypted summary) and what did NOT happen to them.
|
|
102
127
|
encrypted_history_not_portable: 'Earlier history exists only as an encrypted summary that the target model cannot read, so it cannot be carried over. The current session is still running unchanged.',
|
|
103
|
-
compacted_source_fork_unqualified: 'This source history was compacted, and its active checkpoint cannot yet be verified across providers. The current session is still running unchanged.',
|
|
104
128
|
source_compaction_unqualified: 'The source history is too large for the target, and safely compacting it is not supported yet. The current session is still running unchanged.',
|
|
105
129
|
history_estimate_unknown: 'The source history size could not be verified safely. The current session is still running unchanged.',
|
|
106
130
|
target_compaction_hash_unknown: 'The target profile cannot verify the source history compaction settings. The current session is still running unchanged.',
|
|
@@ -109,9 +133,41 @@ function historyPreparationMessage(reason, suppliedMessage = '', { postPark = fa
|
|
|
109
133
|
unpaired_tool_history: 'This history has an incomplete tool exchange, so it cannot be transferred safely. The current session is still running unchanged.',
|
|
110
134
|
source_origin_unknown: 'The source provider could not be verified. The current session is still running unchanged.',
|
|
111
135
|
source_origin_unsupported: 'This source provider has no qualified history handoff yet. The current session is still running unchanged.',
|
|
136
|
+
transition_cancelled: 'The history check was cancelled before it finished. The current session is still running unchanged.',
|
|
137
|
+
history_scan_incomplete: 'The source history scan did not finish, so nothing was moved. The current session is still running unchanged.',
|
|
138
|
+
source_checkpoint_unknown: 'The source history has no verified checkpoint to carry over. The current session is still running unchanged.',
|
|
139
|
+
source_previous_model_mismatch: 'The model recorded on the source history does not match the captured source model. The current session is still running unchanged.',
|
|
140
|
+
source_model_changed: 'The source model changed while the history was being prepared. The current session is still running unchanged.',
|
|
141
|
+
lead_source_changed: 'The source session this switch started from is no longer the live one. The current session is still running unchanged.',
|
|
142
|
+
provider_identity_unknown: 'The source or target provider for this history move could not be identified. The current session is still running unchanged.',
|
|
143
|
+
target_budget_unknown: 'The target model does not publish the context budget needed to carry this history over. The current session is still running unchanged.',
|
|
144
|
+
history_estimate_evidence_unknown: 'The source history size was measured, but not in a way that can be verified for this move. The current session is still running unchanged.',
|
|
145
|
+
compaction_hash_mismatch: 'The source and target history compaction settings disagree, so the move cannot be verified safely. The current session is still running unchanged.',
|
|
146
|
+
native_transition_policy_unknown: 'The Codex permission policy for the source or the target could not be read. The current session is still running unchanged.',
|
|
147
|
+
native_transition_policy_mismatch: 'The Codex permission policy on the target differs from the source, so the history cannot be moved safely. The current session is still running unchanged.',
|
|
148
|
+
native_fork_adapter_unqualified: 'This Codex installation or adapter is not qualified to carry this history over. The current session is still running unchanged.',
|
|
149
|
+
native_policy_override_unqualified: 'This source session uses a Codex policy override that cannot yet be reproduced safely for history transfer. The current session is still running unchanged.',
|
|
150
|
+
native_token_usage_record_invalid: 'The Codex usage record for this session could not be verified, so the history size cannot be trusted. The current session is still running unchanged.',
|
|
151
|
+
history_media_not_portable: 'This history contains an image, and the target model cannot receive images. The current session is still running unchanged.',
|
|
152
|
+
// WP-2 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §5 D-5/D-6, §6.5): the
|
|
153
|
+
// encrypted-compaction path. The source model writes a plaintext handoff before the switch, and
|
|
154
|
+
// each way that handoff can fail says which one it was.
|
|
155
|
+
encrypted_compaction_multiple: 'This history holds more than one encrypted compaction, and only one of them can be replaced by a handoff summary. The current session is still running unchanged.',
|
|
156
|
+
encrypted_compaction_handoff_unavailable: 'This history exists only as an encrypted summary, and the model that wrote it cannot be asked to write a plaintext handoff for this provider change. The current session is still running unchanged.',
|
|
157
|
+
handoff_fork_failed: 'The handoff summary could not be started from this session\'s history. The current session is still running unchanged.',
|
|
158
|
+
handoff_turn_failed: 'The source model did not finish writing the handoff summary. The current session is still running unchanged.',
|
|
159
|
+
handoff_empty: 'The source model returned a handoff summary that is too short to carry this history over. The current session is still running unchanged.',
|
|
160
|
+
handoff_too_long: 'The source model returned a handoff summary that is too long to carry this history over. The current session is still running unchanged.',
|
|
161
|
+
handoff_marker_in_text: 'The handoff summary contains text reserved for this move, so it cannot be trusted. The current session is still running unchanged.',
|
|
162
|
+
handoff_timeout: 'The source model did not finish writing the handoff summary in time. The current session is still running unchanged.',
|
|
163
|
+
// Raised while the forked target is being written, but the code is enumerated from its own
|
|
164
|
+
// source file, so it carries a sentence on both paths rather than falling back to the generic one.
|
|
165
|
+
native_handoff_inject_failed: 'The handoff summary could not be written into the new Codex thread. The current session is still running unchanged.',
|
|
112
166
|
};
|
|
113
167
|
const safe = typeof suppliedMessage === 'string' ? suppliedMessage.trim().slice(0, 240) : '';
|
|
114
|
-
return messages[reason]
|
|
168
|
+
return messages[reason]
|
|
169
|
+
|| historyRefusalFamilyMessage(reason, 'The current session is still running unchanged.')
|
|
170
|
+
|| safe || 'History could not be verified for this provider change. The current session is still running unchanged.';
|
|
115
171
|
}
|
|
116
172
|
|
|
117
173
|
function refusal(status, code, extra) {
|
|
@@ -371,6 +427,15 @@ function reconstructSessionModelTransitionSource({ sessionId, source, sourceLaun
|
|
|
371
427
|
const rebound = require('./launch-model-override').ensureCodexResumeTarget(args, source.providerThreadId);
|
|
372
428
|
if (!rebound.ok) return null;
|
|
373
429
|
args = rebound.args;
|
|
430
|
+
} else if (agent === 'claude') {
|
|
431
|
+
// The SAME canonical normalization for a native Claude source: the recorded capture may name its
|
|
432
|
+
// own thread through an explicit `--session-id` (the identity CTM injected at startup) and may
|
|
433
|
+
// still carry that launch's original positional prompt. Only the canonical owner's own
|
|
434
|
+
// `--resume <source thread>` is the source recipe, so the existing extractor below reads a
|
|
435
|
+
// rebound argv instead of rejecting the capture outright.
|
|
436
|
+
const rebound = require('./launch-model-override').ensureClaudeResumeTarget(args, source.providerThreadId);
|
|
437
|
+
if (!rebound.ok) return null;
|
|
438
|
+
args = rebound.args;
|
|
374
439
|
}
|
|
375
440
|
if ((require('./session-history').extractResumeTarget(agent, args)?.sessionId || '') !== String(source.providerThreadId)) return null;
|
|
376
441
|
return { ...context, args };
|
|
@@ -79,14 +79,14 @@ function splitAgentDescendantRows(rows, agentPid) {
|
|
|
79
79
|
const agentRssMB = agentRow ? Math.round(((Number(agentRow.rssKB) || 0) / 1024) * 100) / 100 : 0;
|
|
80
80
|
const pending = [...(byParent.get(agentPid) || [])];
|
|
81
81
|
const seen = new Set();
|
|
82
|
-
let descendantRssKB = 0, descendantGroupLeaderCount = 0; // D-10: a descendant leading its own process group (a background shell's wrapper)
|
|
82
|
+
let descendantRssKB = 0, descendantCpuTimeMs = 0, descendantGroupLeaderCount = 0; // D-10: a descendant leading its own process group (a background shell's wrapper)
|
|
83
83
|
while (pending.length) {
|
|
84
84
|
const pid = pending.pop();
|
|
85
85
|
if (seen.has(pid)) continue;
|
|
86
86
|
seen.add(pid);
|
|
87
87
|
const row = byPid.get(pid);
|
|
88
88
|
if (!row) continue;
|
|
89
|
-
descendantRssKB += Number(row.rssKB) || 0; if (row.pgid === row.pid) descendantGroupLeaderCount += 1;
|
|
89
|
+
descendantRssKB += Number(row.rssKB) || 0; descendantCpuTimeMs += Number(row.cpuTimeMs) || 0; if (row.pgid === row.pid) descendantGroupLeaderCount += 1;
|
|
90
90
|
pending.push(...(byParent.get(pid) || []));
|
|
91
91
|
}
|
|
92
92
|
return {
|
|
@@ -99,7 +99,7 @@ function splitAgentDescendantRows(rows, agentPid) {
|
|
|
99
99
|
// invisible to a pgid-only rollup).
|
|
100
100
|
descendantPids: [...seen],
|
|
101
101
|
descendantGroupLeaderCount,
|
|
102
|
-
descendantRssMB: Math.round((descendantRssKB / 1024) * 100) / 100,
|
|
102
|
+
descendantRssMB: Math.round((descendantRssKB / 1024) * 100) / 100, descendantCpuTimeMs, // WP-0 (bg-shell-liveness): Σ cpuTimeMs of every visited descendant
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -126,6 +126,33 @@ function parseFootprintJson(text) {
|
|
|
126
126
|
return results;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// WP-A P-1 (freeze-probes): the CATEGORIZED `footprint -j` document (no `--noCategories`) --
|
|
130
|
+
// { unit, 'bytes per unit', processes: [{ pid, footprint, categories: { name: { dirty, swapped, ... } } }] }.
|
|
131
|
+
// The 4<->9 GB server swing measured 2026-10-21 lives entirely in the "Malloc Large" category, which
|
|
132
|
+
// the --noCategories summary above cannot name. Pure; returns { totalMB, top: [{name,dirtyMB,swappedMB}] }
|
|
133
|
+
// for the FIRST process, null for unparseable input or a non-byte unit (same rules as parseFootprintJson).
|
|
134
|
+
function parseFootprintRegions(text) {
|
|
135
|
+
let doc;
|
|
136
|
+
try { doc = JSON.parse(String(text ?? '')); } catch { return null; } // not JSON at all (empty stdout on a failed run)
|
|
137
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null;
|
|
138
|
+
if (doc.unit !== 'byte') return null;
|
|
139
|
+
const bytesPerUnit = Number(doc['bytes per unit']) || 1;
|
|
140
|
+
const proc = (Array.isArray(doc.processes) ? doc.processes : []).find(entry => entry && typeof entry === 'object');
|
|
141
|
+
if (!proc) return null;
|
|
142
|
+
const mb = (bytes) => Math.round((bytes / 1048576) * 10) / 10;
|
|
143
|
+
const rows = Object.entries(proc.categories && typeof proc.categories === 'object' ? proc.categories : {})
|
|
144
|
+
.map(([name, region]) => {
|
|
145
|
+
const dirty = (Number(region?.dirty) || 0) * bytesPerUnit;
|
|
146
|
+
const swapped = (Number(region?.swapped) || 0) * bytesPerUnit;
|
|
147
|
+
return { name: String(name).slice(0, 40), dirtyMB: mb(dirty), swappedMB: mb(swapped), total: dirty + swapped };
|
|
148
|
+
})
|
|
149
|
+
.sort((a, b) => b.total - a.total);
|
|
150
|
+
return {
|
|
151
|
+
totalMB: Number.isFinite(proc.footprint) ? mb(proc.footprint * bytesPerUnit) : null,
|
|
152
|
+
top: rows.slice(0, 6).map(({ name, dirtyMB, swappedMB }) => ({ name, dirtyMB, swappedMB })),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
129
156
|
// Cleanup never turns an already-decided sample (number or D-3 null) into a rejected sweep.
|
|
130
157
|
function removeTempFileQuietly(file) {
|
|
131
158
|
return fs.promises.rm(file, { force: true }).then(() => true, () => false);
|
|
@@ -441,18 +468,28 @@ class SessionResourceSampler {
|
|
|
441
468
|
return { agentPid: Number(agentPid), descendantPids: split.descendantPids, descendantGroupLeaderCount: split.descendantGroupLeaderCount };
|
|
442
469
|
}
|
|
443
470
|
|
|
471
|
+
// D-9 (WP-6, clock-free-tests-followups-2026-10-23 §5 D-9/§8 WP-6): ONE whole-table read a caller
|
|
472
|
+
// can share across every rollup in one sweep. Same command/args/limits as sampleProcessGroup's own
|
|
473
|
+
// read, and the same maxBuffer: `ps -axo …` prints ~1.37 MB on this host, above Node's 1 MiB default.
|
|
474
|
+
async sampleProcessTable() {
|
|
475
|
+
const psCommand = this.platform === 'darwin' ? '/bin/ps' : 'ps';
|
|
476
|
+
const { stdout } = await this.exec(psCommand, PS_ARGS, { timeout: 10_000, maxBuffer: 8 * 1024 * 1024 });
|
|
477
|
+
return parseProcessTable(stdout);
|
|
478
|
+
}
|
|
479
|
+
|
|
444
480
|
// D-4: the post-kill census. One ps read; the pgid rollup as before, plus every row whose pid is in
|
|
445
481
|
// `descendantPidsBefore` (and not already in the group). Survivors are merged into pids/procCount/rssMB/cpuPct.
|
|
446
482
|
// A call with no second argument (every pre-existing caller) behaves exactly as before.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
483
|
+
// D-9: `{ rows }` lets a sweep hand in the ONE table it already read (sampleProcessTable above) —
|
|
484
|
+
// then this call execs nothing. The rollup/survivor logic below is shared verbatim by both paths,
|
|
485
|
+
// so a caller that supplies rows cannot diverge from one that does not.
|
|
486
|
+
async sampleProcessGroup(pgid, descendantPidsBefore = [], { rows = null } = {}) {
|
|
487
|
+
const table = Array.isArray(rows) ? rows : await this.sampleProcessTable();
|
|
488
|
+
const group = rollupProcessGroup(table, pgid);
|
|
452
489
|
const before = new Set((descendantPidsBefore || []).map(Number));
|
|
453
490
|
if (!before.size) return group;
|
|
454
491
|
const have = new Set(group.pids);
|
|
455
|
-
const survivors =
|
|
492
|
+
const survivors = table.filter(row => before.has(Number(row.pid)) && !have.has(row.pid)).sort((a, b) => a.pid - b.pid);
|
|
456
493
|
if (!survivors.length) return group;
|
|
457
494
|
const rssKB = survivors.reduce((sum, row) => sum + (Number(row.rssKB) || 0), 0);
|
|
458
495
|
const cpuPct = survivors.reduce((sum, row) => sum + (Number(row.cpuPct) || 0), 0);
|
|
@@ -540,6 +577,26 @@ class SessionResourceSampler {
|
|
|
540
577
|
return results;
|
|
541
578
|
}
|
|
542
579
|
|
|
580
|
+
// WP-A P-1: ONE fresh categorized `/usr/bin/footprint -j <tmp> <pid>` for the MAIN pid, every call.
|
|
581
|
+
// Deliberately NOT the TTL-cached sampleFootprints() path: a stale cache entry cannot name the region
|
|
582
|
+
// that just grew. Same gate/tmp/limits/cleanup mechanics as _sampleFootprintBatch, minus the cache.
|
|
583
|
+
// Never rejects: null on non-darwin, on the primary-only gate, or on any failure. Categorized
|
|
584
|
+
// `footprint` does not suspend its target (measured 2026-10-21), unlike vmmap/sample/leaks.
|
|
585
|
+
async sampleFootprintRegions(pid) {
|
|
586
|
+
const key = Number(pid);
|
|
587
|
+
if (this.platform !== 'darwin' || !(key > 0)) return null;
|
|
588
|
+
if (this.exec === execFileAsync && !hostIdentityAccessAllowed({ env: this.env })) return null;
|
|
589
|
+
const tmp = path.join(os.tmpdir(), `ctm-footprint-regions-${process.pid}-${crypto.randomUUID()}.json`);
|
|
590
|
+
try {
|
|
591
|
+
await this.exec(FOOTPRINT_BIN, ['-j', tmp, String(key)], { timeout: 10_000, maxBuffer: 4 * 1024 * 1024 });
|
|
592
|
+
return parseFootprintRegions(await fs.promises.readFile(tmp, 'utf8'));
|
|
593
|
+
} catch {
|
|
594
|
+
return null; // footprint missing/denied or temp file unreadable (D-3: null, never a throw)
|
|
595
|
+
} finally {
|
|
596
|
+
await removeTempFileQuietly(tmp);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
543
600
|
// D-1: sweep counterpart to sampleFootprints() (same batch and TTL cache), resolving to
|
|
544
601
|
// `{ footprintMB, sampledAt }` per pid so a consumer can tell how fresh an entry is.
|
|
545
602
|
async sampleFootprintSnapshots({ pids, now } = {}) {
|
|
@@ -558,6 +615,7 @@ module.exports = {
|
|
|
558
615
|
FOOTPRINT_SAMPLE_TTL_MS,
|
|
559
616
|
hostMemoryFromVmStat,
|
|
560
617
|
parseFootprintJson,
|
|
618
|
+
parseFootprintRegions,
|
|
561
619
|
parseMemoryPressure,
|
|
562
620
|
parsePageouts,
|
|
563
621
|
parseProcessTable,
|
|
@@ -882,6 +882,15 @@ function classifySessionStandup(session, signals = {}, now = Date.now()) {
|
|
|
882
882
|
resume: session?.resume?.schemaVersion === 2 ? session.resume : null,
|
|
883
883
|
priority: LANE_ORDER.get(lane) ?? 99, backgroundWork: session?.backgroundWork || null, statusReason: session?.statusReason || '',
|
|
884
884
|
verdictAt: Number(session?.verdictAt) || Number(session?.liveStatusAt) || 0,
|
|
885
|
+
// WP-3 (remote-hub-followups D-9): the machine tag rides the card IFF the input session carries
|
|
886
|
+
// it. `_buildStandupSnapshot` stamps local standup sessions EMPTY and remote (box) sessions with
|
|
887
|
+
// the registry values, but ONLY when at least one machine is registered — so a zero-machine hub
|
|
888
|
+
// adds no key here and its snapshot stays byte-identical to the pre-WP-3 output (AC-3a).
|
|
889
|
+
...(session && Object.prototype.hasOwnProperty.call(session, 'machine') ? {
|
|
890
|
+
machine: String(session.machine == null ? '' : session.machine).trim(),
|
|
891
|
+
machineName: String(session.machineName == null ? '' : session.machineName).trim(),
|
|
892
|
+
machineHealth: String(session.machineHealth == null ? '' : session.machineHealth).trim(),
|
|
893
|
+
} : {}),
|
|
885
894
|
};
|
|
886
895
|
// WP1C: the card's bounded own/null authority fields and its shared model label, applied to THIS card
|
|
887
896
|
// object after it is built (see applyStandupModelAuthority in ./coding-session-model-public).
|
|
@@ -970,13 +979,13 @@ function isStandupStartupTaskSeedEnabled(env = process.env) {
|
|
|
970
979
|
// existing classifier can run through unmodified, so a "seeded" card gets exactly the same
|
|
971
980
|
// lane/priority logic as a live one — just starting from persisted metadata instead of a
|
|
972
981
|
// live stream.
|
|
973
|
-
function startupTaskPlaceholderSession(task) {
|
|
982
|
+
function startupTaskPlaceholderSession(task, titleInfo) {
|
|
974
983
|
if (!task) return null;
|
|
975
984
|
const id = String(task.session_id || task.ctm_session_id || '').trim();
|
|
976
985
|
if (!id) return null;
|
|
977
986
|
const placeholder = {
|
|
978
987
|
id,
|
|
979
|
-
label: task.label || '',
|
|
988
|
+
label: String(titleInfo?.title || '').trim() || task.label || '', // D-13/S-26: durable ctm_sessions title; echo only if no row
|
|
980
989
|
cmd: task.cmd || '',
|
|
981
990
|
cwd: task.cwd || '',
|
|
982
991
|
project: task.cwd || '',
|
|
@@ -1015,15 +1024,16 @@ function startupTaskPlaceholderSession(task) {
|
|
|
1015
1024
|
// a startup task appearing more than once). Callers union the result onto their live session
|
|
1016
1025
|
// list; kept separate (rather than mutating/returning a merged array) so the server can push
|
|
1017
1026
|
// each placeholder through the same standupPairs/attention wiring live sessions use.
|
|
1018
|
-
function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks) {
|
|
1027
|
+
function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks, titlesForIds) {
|
|
1019
1028
|
const seen = new Set();
|
|
1020
1029
|
for (const session of existingSessions || []) {
|
|
1021
1030
|
const id = String(session?.id || session?.sessionId || '').trim();
|
|
1022
1031
|
if (id) seen.add(id);
|
|
1023
1032
|
}
|
|
1033
|
+
const titlesById = typeof titlesForIds === 'function' ? titlesForIds((startupTasks || []).map((t) => t?.session_id || t?.ctm_session_id).filter(Boolean)) : null; // D-13/S-26: ONE batched read, never per row
|
|
1024
1034
|
const placeholders = [];
|
|
1025
1035
|
for (const task of startupTasks || []) {
|
|
1026
|
-
const placeholder = startupTaskPlaceholderSession(task);
|
|
1036
|
+
const placeholder = startupTaskPlaceholderSession(task, titlesById?.get?.(task?.session_id || task?.ctm_session_id));
|
|
1027
1037
|
if (!placeholder || seen.has(placeholder.id)) continue;
|
|
1028
1038
|
seen.add(placeholder.id);
|
|
1029
1039
|
placeholders.push(placeholder);
|
|
@@ -1031,6 +1041,57 @@ function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks)
|
|
|
1031
1041
|
return placeholders;
|
|
1032
1042
|
}
|
|
1033
1043
|
|
|
1044
|
+
// WP-3 (D-9): the statuses a REMOTE (box) session row can carry that mean its PTY is live. A row
|
|
1045
|
+
// whose box reports a parked/exited/stopped look (the sync-mirror or last-good degradation) is not
|
|
1046
|
+
// live. The hub never consults a LOCAL PTY helper for these rows — they are not on this host.
|
|
1047
|
+
const REMOTE_ROW_LIVE_STATUSES = new Set([
|
|
1048
|
+
'running', 'busy', 'active', 'working', 'thinking',
|
|
1049
|
+
'waiting_input', 'waiting_for_input', 'waiting',
|
|
1050
|
+
'resuming', 'restoring', 'restarting',
|
|
1051
|
+
]);
|
|
1052
|
+
|
|
1053
|
+
/** The box's own liveness for a remote catalog row (never a local PTY check). */
|
|
1054
|
+
function remoteRowTerminalLive(row) {
|
|
1055
|
+
// The row's OWN status decides when it carries one (a live reachable box row, or the at-rest
|
|
1056
|
+
// mirror's 'parked'); the boolean flags are only a fallback for a row that has no status.
|
|
1057
|
+
const status = String(row?.standupStatus || row?.liveStatus || row?.status || row?.lifecycleState || '')
|
|
1058
|
+
.trim().toLowerCase().replace(/[-\s]+/g, '_');
|
|
1059
|
+
if (status) return REMOTE_ROW_LIVE_STATUSES.has(status);
|
|
1060
|
+
return row?.terminalLive === true || row?.terminal_live === true
|
|
1061
|
+
|| row?.activeCtmSession === true || row?.active_ctm_session === true
|
|
1062
|
+
|| row?.runtimeActive === true || row?.runtime_active === true;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* WP-3 (D-9): a session-shaped object for one row of `remoteMachineCatalog.remoteRows()`, ready for
|
|
1067
|
+
* the same `classifySessionStandup` the local sessions map goes through (`summary: null` — a box row
|
|
1068
|
+
* has no local stream summary). Liveness comes from the row's OWN status; no local PTY helper runs.
|
|
1069
|
+
*
|
|
1070
|
+
* @param {object} row a remote catalog row (already machine-tagged by remoteRows())
|
|
1071
|
+
* @param {{instanceId?: string, displayName?: string, health?: string}} [machineFacts] fallback tag
|
|
1072
|
+
*/
|
|
1073
|
+
function remoteStandupSession(row, machineFacts = {}) {
|
|
1074
|
+
const source = row && typeof row === 'object' ? row : {};
|
|
1075
|
+
const terminalLive = remoteRowTerminalLive(source);
|
|
1076
|
+
const rawStatus = String(source.standupStatus || source.liveStatus || source.status || '').trim();
|
|
1077
|
+
const standupStatus = rawStatus
|
|
1078
|
+
? rawStatus.toLowerCase().replace(/[-\s]+/g, '_')
|
|
1079
|
+
: (terminalLive ? 'running' : 'parked');
|
|
1080
|
+
return {
|
|
1081
|
+
...source,
|
|
1082
|
+
machine: String(source.machine || machineFacts.instanceId || '').trim(),
|
|
1083
|
+
machineName: String(source.machineName || machineFacts.displayName || '').trim(),
|
|
1084
|
+
machineHealth: String(source.machineHealth || machineFacts.health || '').trim(),
|
|
1085
|
+
terminalLive,
|
|
1086
|
+
terminal_live: terminalLive,
|
|
1087
|
+
activeCtmSession: terminalLive,
|
|
1088
|
+
active_ctm_session: terminalLive,
|
|
1089
|
+
runtimeActive: terminalLive,
|
|
1090
|
+
runtime_active: terminalLive,
|
|
1091
|
+
standupStatus,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1034
1095
|
function buildSessionStandupSnapshot({ sessions = [], summaries = [], statuses = [], now = Date.now() } = {}) {
|
|
1035
1096
|
const summaryIndex = buildIndex(summaries);
|
|
1036
1097
|
const statusIndex = buildIndex(statuses);
|
|
@@ -1070,6 +1131,8 @@ module.exports = {
|
|
|
1070
1131
|
startupTaskPlaceholderSession,
|
|
1071
1132
|
selectUnmatchedStartupTaskPlaceholders,
|
|
1072
1133
|
isStandupStartupTaskSeedEnabled,
|
|
1134
|
+
remoteRowTerminalLive,
|
|
1135
|
+
remoteStandupSession,
|
|
1073
1136
|
_private: {
|
|
1074
1137
|
normalizeStatus,
|
|
1075
1138
|
normalizeProvider,
|
|
@@ -72,6 +72,7 @@ const BOUNDARY_CLOSERS = new Set(['end_turn', 'interrupt']);
|
|
|
72
72
|
const BG_END_STATUSES = new Set(['completed', 'failed', 'killed', 'stopped']);
|
|
73
73
|
// renewal span, and the backstop expiry when no process sample arrives (busy-background-shell design D-3); the CLI has no local cap since 2.1.288
|
|
74
74
|
const BG_SHELL_MAX_MS = 2 * 3600_000 + 5 * 60_000;
|
|
75
|
+
const BG_QUIET_MS = 30 * 60_000;
|
|
75
76
|
const BG_DECLARED_MAX_MS = 6 * 3600_000;
|
|
76
77
|
const BG_LABEL_MAX = 160;
|
|
77
78
|
const BG_VIEW_TASKS_MAX = 8;
|
|
@@ -189,6 +190,7 @@ class SessionStatusLedger {
|
|
|
189
190
|
next.set(n.id, {
|
|
190
191
|
...n, firstSeenAt, expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, cap) : cap, source: 'hook', count: 1,
|
|
191
192
|
renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
|
|
193
|
+
activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
|
|
192
194
|
});
|
|
193
195
|
}
|
|
194
196
|
let changed = next.size !== before || !this.backgroundDeclared;
|
|
@@ -222,6 +224,7 @@ class SessionStatusLedger {
|
|
|
222
224
|
expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, computed) : computed,
|
|
223
225
|
source: src, count,
|
|
224
226
|
renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
|
|
227
|
+
activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
|
|
225
228
|
};
|
|
226
229
|
if (leaseMs) entry.leased = true;
|
|
227
230
|
this.background.set(n.id, entry);
|
|
@@ -234,16 +237,28 @@ class SessionStatusLedger {
|
|
|
234
237
|
return this.background.size > 0;
|
|
235
238
|
}
|
|
236
239
|
|
|
240
|
+
// D-1: quiet = a hook-declared shell with no activity evidence for BG_QUIET_MS. Never kills; only drops park protection.
|
|
241
|
+
backgroundQuiet(entry, now) {
|
|
242
|
+
return entry.type === 'shell' && entry.source === 'hook' && now - (entry.activeAt || 0) >= BG_QUIET_MS;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
backgroundActive(now = Date.now()) {
|
|
246
|
+
this._bgExpire(now);
|
|
247
|
+
return [...this.background.values()].some(e => !this.backgroundQuiet(e, now));
|
|
248
|
+
}
|
|
249
|
+
|
|
237
250
|
backgroundView(now = Date.now()) {
|
|
238
251
|
this._bgExpire(now);
|
|
239
252
|
if (!this.background.size) return null;
|
|
240
253
|
const live = [...this.background.values()].sort((a, b) => a.firstSeenAt - b.firstSeenAt);
|
|
241
254
|
const types = {};
|
|
242
255
|
let count = 0;
|
|
256
|
+
let quiet = 0;
|
|
257
|
+
for (const e of live) { if (this.backgroundQuiet(e, now)) quiet += 1; }
|
|
243
258
|
for (const e of live) { types[e.type] = (types[e.type] || 0) + e.count; count += e.count; }
|
|
244
259
|
return {
|
|
245
|
-
count, types,
|
|
246
|
-
tasks: live.slice(0, BG_VIEW_TASKS_MAX).map(e => ({ id: e.id, type: e.type, label: e.label, since: e.firstSeenAt })),
|
|
260
|
+
count, types, quiet,
|
|
261
|
+
tasks: live.slice(0, BG_VIEW_TASKS_MAX).map(e => ({ id: e.id, type: e.type, label: e.label, since: e.firstSeenAt, activeAt: e.activeAt || 0, quiet: this.backgroundQuiet(e, now) })),
|
|
247
262
|
observedAt: this.backgroundObservedAt, declared: this.backgroundDeclared,
|
|
248
263
|
};
|
|
249
264
|
}
|
|
@@ -257,6 +272,7 @@ class SessionStatusLedger {
|
|
|
257
272
|
entries.push({
|
|
258
273
|
id: e.id, type: e.type, label: e.label, firstSeenAt: e.firstSeenAt, expiresAt: e.expiresAt, source: e.source, count: e.count,
|
|
259
274
|
renewedAt: e.renewedAt || 0, renewals: e.renewals || 0, capCrossedAt: e.capCrossedAt || 0,
|
|
275
|
+
activeAt: e.activeAt || 0, outSize: e.outSize || 0, outMtimeMs: e.outMtimeMs || 0,
|
|
260
276
|
});
|
|
261
277
|
}
|
|
262
278
|
return { declared: this.backgroundDeclared, observedAt: this.backgroundObservedAt, entries };
|
|
@@ -279,6 +295,7 @@ class SessionStatusLedger {
|
|
|
279
295
|
firstSeenAt, expiresAt: Math.min(Number(raw.expiresAt), now + bgCapFor(type)),
|
|
280
296
|
source: String(raw.source || 'hook'), count: Math.max(1, Number(raw.count) || 1),
|
|
281
297
|
renewedAt: Number(raw.renewedAt) || 0, renewals: Math.max(0, Number(raw.renewals) || 0), capCrossedAt: Number(raw.capCrossedAt) || 0,
|
|
298
|
+
activeAt: Number(raw.activeAt) > 0 ? Number(raw.activeAt) : now, outSize: Number(raw.outSize) || 0, outMtimeMs: Number(raw.outMtimeMs) || 0,
|
|
282
299
|
});
|
|
283
300
|
n += 1;
|
|
284
301
|
}
|
|
@@ -325,6 +342,28 @@ class SessionStatusLedger {
|
|
|
325
342
|
return renewed;
|
|
326
343
|
}
|
|
327
344
|
|
|
345
|
+
// D-4: silent activity evidence (a process-sample CPU delta). Returns the number of entries whose activeAt moved.
|
|
346
|
+
markBackgroundActive(predicate, at = Date.now()) {
|
|
347
|
+
let moved = 0;
|
|
348
|
+
for (const e of this.background.values()) {
|
|
349
|
+
if (!predicate(e)) continue;
|
|
350
|
+
if (at > (e.activeAt || 0)) { e.activeAt = at; moved += 1; }
|
|
351
|
+
}
|
|
352
|
+
return moved;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// D-4: output-file growth evidence; a never-seen entry counts as growth only when size > 0.
|
|
356
|
+
observeBackgroundOutput(id, { size = 0, mtimeMs = 0 } = {}, at = Date.now()) {
|
|
357
|
+
const e = this.background.get(id);
|
|
358
|
+
if (!e) return false;
|
|
359
|
+
const seen = (e.outSize || 0) > 0 || (e.outMtimeMs || 0) > 0;
|
|
360
|
+
const grew = seen ? (size > (e.outSize || 0) || mtimeMs > (e.outMtimeMs || 0)) : size > 0;
|
|
361
|
+
if (!grew) return false;
|
|
362
|
+
e.activeAt = Math.max(e.activeAt || 0, at);
|
|
363
|
+
e.outSize = size; e.outMtimeMs = mtimeMs;
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
328
367
|
record(kind, { at = Date.now(), source = '', meta = {} } = {}) {
|
|
329
368
|
const prev = this.verdict(at);
|
|
330
369
|
const bgBefore = this._bgPending.length;
|
|
@@ -579,4 +618,4 @@ class SessionStatusLedger {
|
|
|
579
618
|
}
|
|
580
619
|
}
|
|
581
620
|
|
|
582
|
-
module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask };
|
|
621
|
+
module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_QUIET_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask };
|