create-walle 0.10.1 → 0.10.3
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 +7 -1
- 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 +11 -8
- package/package.json +1 -1
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +55 -29
- package/template/coding-task-manager/bin/claude-hook.js +62 -4
- 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/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -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/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +860 -317
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -7
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +99 -1
- 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/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +43 -12
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- 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 +1150 -0
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +25 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +237 -59
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- 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/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +64 -1
- package/template/coding-task-manager/lib/auth-rules.js +474 -62
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +7 -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/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- 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/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +69 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +55 -20
- 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-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +48 -42
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +70 -4
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +24 -6
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +17 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +104 -13
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +8 -0
- package/template/coding-task-manager/lib/db-domains.js +22 -2
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +6 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +5 -1
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +61 -44
- package/template/coding-task-manager/lib/harness-history-transition.js +52 -11
- 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 +139 -25
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +50 -15
- 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 +475 -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 +141 -310
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +12 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +18 -2
- package/template/coding-task-manager/lib/instance-sync-status.js +33 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +193 -8
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +409 -72
- package/template/coding-task-manager/lib/lead-model-switch.js +115 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +85 -9
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/login-tripwire.js +1 -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 +1494 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +485 -57
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- 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/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +8 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +6 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -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/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +147 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +18 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +7 -5
- package/template/coding-task-manager/lib/native-lead-settings-client.js +46 -5
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +776 -75
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +15 -1
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +264 -24
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- 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/read-pool-client.js +94 -4
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +41 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +76 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1357 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +1233 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +1426 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +654 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +364 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- 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/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- 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 +74 -18
- package/template/coding-task-manager/lib/root-delivery-runtime.js +74 -17
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +168 -34
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- 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/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +179 -20
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +201 -9
- 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 +193 -64
- package/template/coding-task-manager/lib/session-create/response.js +37 -19
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +14 -5
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +50 -2
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +101 -9
- 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-empty.js +13 -10
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +212 -206
- package/template/coding-task-manager/lib/session-jobs.js +20 -10
- package/template/coding-task-manager/lib/session-lifecycle.js +28 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +15 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +147 -6
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- 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/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- 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 +25 -23
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- 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/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +20 -5
- 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 +44 -7
- package/template/coding-task-manager/lib/transcript-store.js +49 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +198 -3
- package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +47 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +130 -27
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +125 -6
- package/template/coding-task-manager/lib/ws/terminal-attach.js +123 -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 +211 -170
- package/template/coding-task-manager/package-lock.json +34 -94
- package/template/coding-task-manager/package.json +7 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- 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 +449 -1
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +766 -465
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +2572 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +658 -73
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +191 -7
- package/template/coding-task-manager/public/js/background-work.js +203 -28
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +181 -13
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +847 -5
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +48 -4
- 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 +991 -0
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- 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 -8
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -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 +4 -2
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +64 -21
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +14 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -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-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +60 -3
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +16 -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 +211 -74
- 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-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +62 -5
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +15 -7
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +366 -8
- package/template/coding-task-manager/public/m/app.js +1006 -74
- package/template/coding-task-manager/public/m/index.html +55 -1
- package/template/coding-task-manager/public/m/machines.js +2147 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- 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/public/usage.html +1 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -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 +2819 -11885
- package/template/coding-task-manager/session-integrity.js +71 -13
- package/template/coding-task-manager/workers/db-owner-worker.js +72 -9
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +93 -12
- package/template/coding-task-manager/workers/session-host-pool-process.js +24 -10
- package/template/coding-task-manager/workers/session-host-process.js +19 -8
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +202 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- 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/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +47 -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/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- 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 +38 -3
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +92 -4
- package/template/wall-e/api-walle.js +26 -1
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +14 -8
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +91 -28
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +39 -7
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +172 -36
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- 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/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +327 -0
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +177 -2
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/jobs/job-runner.js +28 -1
- 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/brain-owner-op-priority.js +9 -1
- 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/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- 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 +398 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +320 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +555 -0
- package/template/wall-e/lib/helper-rules/store.js +669 -0
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +63 -19
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +12 -4
- 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/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +7 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- 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-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +117 -7
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +64 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +77 -2
- package/template/wall-e/mcp-server.js +141 -31
- package/template/wall-e/mcp-worker-schemas.js +25 -7
- package/template/wall-e/memory/ctm-context-client.js +110 -5
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +38 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +13 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +11 -6
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +13 -8
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/tools/local-tools.js +6 -4
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +27 -4
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
- package/template/wall-e/workers/telemetry-worker.js +8 -0
|
@@ -0,0 +1,1348 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// WP-0 (docs/lead-cost-routing-round1-2026-10-21.html §8, §1.2 and §1.6): the read-only lead
|
|
5
|
+
// census, promoted from the 2026-10-21 pilot's scratch scripts (dd/waits4.js, dd/waits5.js,
|
|
6
|
+
// cmpct/scen.js + cmpct/calls.js) into the tool the report quotes before and after every later WP.
|
|
7
|
+
//
|
|
8
|
+
// WHY IT EXISTS. Two questions the design answers with numbers, never with assumptions:
|
|
9
|
+
// (1) §1.2 — how do Codex leads wait, and what does each wake carry? The naive count (grep the
|
|
10
|
+
// tool names) is wrong: Codex leads issue most waits INSIDE code-mode `exec` scripts
|
|
11
|
+
// (`tools.write_stdin(...)`), and a code-mode `wait` on a yielded cell must be attributed to
|
|
12
|
+
// the cell's own script. So every wait-type call is paired with its output by call_id and the
|
|
13
|
+
// wake is classified NEWS or NOTHING — exactly the pilot's rule.
|
|
14
|
+
// (2) §1.6 — how often do leads make exploration bursts (a run of >=3 consecutive lead calls
|
|
15
|
+
// whose replies contain only read/search tools, category precedence per cmpct/calls.js)?
|
|
16
|
+
// Those runs are the Part B target, so the census reports bursts, the lead calls inside them
|
|
17
|
+
// after the first, their cost from usage_calls.cost_micros, and (`--bursts`) the hand-off
|
|
18
|
+
// compliance ratio the design's D-13 veto reads.
|
|
19
|
+
//
|
|
20
|
+
// READ-ONLY. Both databases are opened in place with `{ readOnly: true }` and never written; every
|
|
21
|
+
// rollout is opened only through lib/transcript-source.js (createTranscriptReadStream, which
|
|
22
|
+
// resolves the `.jsonl.zst` sibling). The only file this tool may ever write is the optional
|
|
23
|
+
// `--json <file>`. Output is aggregates only — family labels, counts, percentages, dollars —
|
|
24
|
+
// never a command string, prompt text, transcript path or session id.
|
|
25
|
+
//
|
|
26
|
+
// usage:
|
|
27
|
+
// node bin/lead-wait-census.js --since 2026-09-20T00:00:00Z --until 2026-10-20T17:00:00Z
|
|
28
|
+
// [--bursts] [--command-outcomes] [--json <file>] [--data-dir <dir>]
|
|
29
|
+
//
|
|
30
|
+
// WP-3 (`--command-outcomes`, §5 OQ-2). One row per POLLED COMMAND RUN: a Codex command session — an
|
|
31
|
+
// `exec_command` / `tools.exec_command(...)` call whose output carries a session id — followed by at
|
|
32
|
+
// least one empty-`chars` `write_stdin` poll on that same session. Per family the table reports n,
|
|
33
|
+
// unfinished, how often the lead acted on the partial output before the command exited, kill, the
|
|
34
|
+
// duration percentiles and the partial-output median. The command string is never printed, only its
|
|
35
|
+
// `classifyCommand` family label. `acted_on_partial` is the mechanical ordering rule, not a reading of
|
|
36
|
+
// the lead's intent: after the LAST poll that returned non-empty output and before the exit poll, the
|
|
37
|
+
// lead's first non-wait action is a stdin write (chars) on that session, an edit tool call, or a
|
|
38
|
+
// kill/pkill command — the design's "cited the partial output" (AC-3a) is satisfied by this stricter
|
|
39
|
+
// rule. Ambiguities the brief left open, resolved here and reported by WP-3:
|
|
40
|
+
// * wait set — `sleep`, `wait_agent`, `walle_worker_wait`, `walle_worker_status` and a code-mode
|
|
41
|
+
// cell `wait` are skipped; a `write_stdin` poll is an ACTION (the brief lists "another
|
|
42
|
+
// poll" as false, and case 1 is itself a write_stdin), so an empty poll ends the scan
|
|
43
|
+
// with false;
|
|
44
|
+
// * window — it starts after the LAST non-empty partial poll (the newest thing the lead saw), not
|
|
45
|
+
// the first;
|
|
46
|
+
// * duration — originating call timestamp to the exit poll's OUTPUT timestamp (the moment the exit
|
|
47
|
+
// was observed), so the last poll's own wait is never dropped;
|
|
48
|
+
// * pairing — Codex returns one result per tool call, in call order, so a call is paired with
|
|
49
|
+
// ITS result (`outputResults`): a session id belongs to the call that opened it, and a
|
|
50
|
+
// poll's bytes/exit come from that poll's own result. Scripts that print extra text per
|
|
51
|
+
// call and CLI-form outputs fall back to pairing the output's session ids with the
|
|
52
|
+
// script's commands in order. A poll whose session id is not a numeric literal (a
|
|
53
|
+
// variable, or the `{ session_id, … }` shorthand) cannot be paired and is ignored;
|
|
54
|
+
// * batching — a script that polls and also starts commands or edits (the common real shape) still
|
|
55
|
+
// resolves its polls; such a batch is one ACTION for the ordering rule;
|
|
56
|
+
// * bytes — a poll's bytes are the COMMAND's output (the code-mode JSON `output` field, or the
|
|
57
|
+
// text after Codex's `Output:` header), never Codex's own wrapper lines, so a poll
|
|
58
|
+
// whose command printed nothing counts as empty partial output;
|
|
59
|
+
// * exit — only the brief's two markers count (`"exit_code"` in a poll's result, or the CLI
|
|
60
|
+
// `Process exited with code`). Real leads often print just `r.output`, which drops the
|
|
61
|
+
// exit marker from the transcript, so such a run stays `unfinished` even though it did
|
|
62
|
+
// exit; the tool never infers an exit from the mere absence of later polls.
|
|
63
|
+
|
|
64
|
+
const fs = require('node:fs');
|
|
65
|
+
const os = require('node:os');
|
|
66
|
+
const path = require('node:path');
|
|
67
|
+
const readline = require('node:readline');
|
|
68
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
69
|
+
|
|
70
|
+
const { createTranscriptReadStream } = require('../lib/transcript-source');
|
|
71
|
+
const { getDataDir, getDefaultDataDir } = require('../lib/data-dir');
|
|
72
|
+
|
|
73
|
+
const MAX_LINE = 5e6;
|
|
74
|
+
const CODEX_THREAD_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl/;
|
|
75
|
+
|
|
76
|
+
// WP-5 (§6.8, D-11): a Codex launch with
|
|
77
|
+
// `features.code_mode.direct_only_tool_namespaces=["mcp__wall_e"]` emits the Wall-E calls as BARE
|
|
78
|
+
// names, so one tool appears under two spellings in the same rollout. Both map to the same family;
|
|
79
|
+
// the code-mode spellings stay recognised, so before/after windows stay comparable.
|
|
80
|
+
const WAIT_TOOLS = new Map([
|
|
81
|
+
['write_stdin', 'write_stdin poll'],
|
|
82
|
+
['wait_agent', 'wait_agent'],
|
|
83
|
+
['sleep', 'sleep'],
|
|
84
|
+
['mcp__wall_e__walle_worker_wait', 'walle_worker_wait'],
|
|
85
|
+
['mcp__wall_e__walle_worker_status', 'walle_worker_status'],
|
|
86
|
+
['walle_worker_wait', 'walle_worker_wait'],
|
|
87
|
+
['walle_worker_status', 'walle_worker_status'],
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
class UsageError extends Error {}
|
|
91
|
+
|
|
92
|
+
function usage() {
|
|
93
|
+
return [
|
|
94
|
+
'usage: node bin/lead-wait-census.js --since <ISO> --until <ISO> [--bursts] [--command-outcomes] [--json <file>] [--data-dir <dir>]',
|
|
95
|
+
' --since <ISO> window start, inclusive (2026-09-20T00:00:00Z)',
|
|
96
|
+
' --until <ISO> window end, exclusive',
|
|
97
|
+
' --bursts also print the exploration-burst table (§1.6) and the hand-off ratio',
|
|
98
|
+
' --command-outcomes also print the polled-command-run table (§5 OQ-2): per command family, how',
|
|
99
|
+
' often the lead acted on partial output before the command exited',
|
|
100
|
+
' --json <file> write the same numbers as JSON (the only file this tool may write)',
|
|
101
|
+
' --data-dir <dir> directory holding usage.db and core.db (default: the CTM data dir)',
|
|
102
|
+
].join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseArgs(argv) {
|
|
106
|
+
const parsed = { since: null, until: null, bursts: false, commandOutcomes: false, json: null, dataDir: null, help: false };
|
|
107
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
108
|
+
const arg = argv[i];
|
|
109
|
+
if (arg === '--help' || arg === '-h') { parsed.help = true; continue; }
|
|
110
|
+
if (arg === '--bursts') { parsed.bursts = true; continue; }
|
|
111
|
+
if (arg === '--command-outcomes') { parsed.commandOutcomes = true; continue; }
|
|
112
|
+
if (['--since', '--until', '--json', '--data-dir'].includes(arg)) {
|
|
113
|
+
const value = argv[i + 1];
|
|
114
|
+
if (value === undefined || value.startsWith('--')) throw new UsageError(`${arg} needs a value`);
|
|
115
|
+
i += 1;
|
|
116
|
+
if (arg === '--since') parsed.since = value;
|
|
117
|
+
else if (arg === '--until') parsed.until = value;
|
|
118
|
+
else if (arg === '--json') parsed.json = value;
|
|
119
|
+
else parsed.dataDir = value;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
throw new UsageError(`unknown option ${arg}`);
|
|
123
|
+
}
|
|
124
|
+
for (const key of ['since', 'until']) {
|
|
125
|
+
if (parsed[key] === null) throw new UsageError(`--${key} is required`);
|
|
126
|
+
if (!Number.isFinite(Date.parse(parsed[key]))) throw new UsageError(`--${key} must be an ISO timestamp, got ${parsed[key]}`);
|
|
127
|
+
}
|
|
128
|
+
parsed.w0 = Date.parse(parsed.since);
|
|
129
|
+
parsed.w1 = Date.parse(parsed.until);
|
|
130
|
+
if (!(parsed.w0 < parsed.w1)) throw new UsageError('--since must be earlier than --until');
|
|
131
|
+
return parsed;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The one data directory this tool reads: `--data-dir`, else the CTM data dir when it is the literal default. */
|
|
135
|
+
function resolveDataDir(dataDir) {
|
|
136
|
+
const literal = path.resolve(getDefaultDataDir());
|
|
137
|
+
if (dataDir) return path.resolve(dataDir);
|
|
138
|
+
const resolved = path.resolve(getDataDir());
|
|
139
|
+
return resolved === literal ? resolved : literal;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function openReadOnlyDb(dbPath) {
|
|
143
|
+
return new DatabaseSync(dbPath, { readOnly: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseJson(text) {
|
|
147
|
+
try { return JSON.parse(text); } catch { return null; } /* expected: a rollout line the reader must skip */
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function outputText(output) {
|
|
151
|
+
if (typeof output === 'string') return output;
|
|
152
|
+
if (Array.isArray(output)) return output.map((x) => (x && x.text) || '').join('\n');
|
|
153
|
+
return JSON.stringify(output);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* ------------------------------------------------------------------ *
|
|
157
|
+
* Lead / worker split
|
|
158
|
+
* ------------------------------------------------------------------ */
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The session index: worker provider thread ids, each worker's lane and profile, and the set of
|
|
162
|
+
* LEAD provider-session ids.
|
|
163
|
+
*
|
|
164
|
+
* The lead set is read from core.db's `agent_sessions` — the authoritative provider-session table:
|
|
165
|
+
* a session is a lead when its `ctm_session_id` is not one of `worker_sessions` and it has no
|
|
166
|
+
* `parent_agent_session_id`, i.e. a top-level interactive session rather than a delegated worker or
|
|
167
|
+
* a native sub-agent thread. A worker's `runtime_json.providerThreadId` is NOT a sufficient
|
|
168
|
+
* exclusion on its own (older worker rows carry none) and says nothing about sub-agent threads, so
|
|
169
|
+
* both rules are applied: `agent_sessions` decides membership, the thread ids are the belt-and-braces
|
|
170
|
+
* cross-check.
|
|
171
|
+
*/
|
|
172
|
+
function readWorkerIndex(coreDb) {
|
|
173
|
+
const threadIds = new Set();
|
|
174
|
+
const workerCtmIds = new Set();
|
|
175
|
+
const workerLaneById = new Map();
|
|
176
|
+
for (const row of coreDb.prepare('SELECT ctm_session_id, route_alias, runtime_json, launch_json FROM worker_sessions').iterate()) {
|
|
177
|
+
const runtime = parseJson(row.runtime_json || '') || {};
|
|
178
|
+
const launch = parseJson(row.launch_json || '') || {};
|
|
179
|
+
const ctmSessionId = String(row.ctm_session_id);
|
|
180
|
+
workerCtmIds.add(ctmSessionId);
|
|
181
|
+
if (runtime.providerThreadId) threadIds.add(String(runtime.providerThreadId));
|
|
182
|
+
workerLaneById.set(ctmSessionId, {
|
|
183
|
+
routed: Boolean(row.route_alias),
|
|
184
|
+
review: launch.workerProfile === 'review',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
let sessionRows = null;
|
|
188
|
+
try {
|
|
189
|
+
sessionRows = coreDb.prepare('SELECT agent_session_id, ctm_session_id, parent_agent_session_id FROM agent_sessions').all();
|
|
190
|
+
} catch {
|
|
191
|
+
// expected: a core.db predating `agent_sessions`; the caller then falls back to the
|
|
192
|
+
// worker-thread exclusion alone rather than reporting an empty census.
|
|
193
|
+
sessionRows = null;
|
|
194
|
+
}
|
|
195
|
+
if (sessionRows === null) return { threadIds, workerCtmIds, workerLaneById, leadSessions: null };
|
|
196
|
+
const leadSessions = new Set();
|
|
197
|
+
for (const row of sessionRows) {
|
|
198
|
+
const ctmSessionId = row.ctm_session_id === null || row.ctm_session_id === undefined ? '' : String(row.ctm_session_id);
|
|
199
|
+
if (ctmSessionId && workerCtmIds.has(ctmSessionId)) continue;
|
|
200
|
+
const parent = row.parent_agent_session_id === null || row.parent_agent_session_id === undefined ? '' : String(row.parent_agent_session_id);
|
|
201
|
+
if (parent) continue;
|
|
202
|
+
if (row.agent_session_id) leadSessions.add(String(row.agent_session_id));
|
|
203
|
+
}
|
|
204
|
+
return { threadIds, workerCtmIds, workerLaneById, leadSessions };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The Codex lead threads of the window: threads whose lead calls (agent_id = '', the worker
|
|
209
|
+
* provider threads removed) fall inside it. A rollout is appended to while its session runs, so a
|
|
210
|
+
* thread with no call in the window cannot carry a wait call the census would count.
|
|
211
|
+
*/
|
|
212
|
+
function codexLeadThreads(usageDb, workerThreadIds, args) {
|
|
213
|
+
const leads = new Set();
|
|
214
|
+
const statement = usageDb.prepare("SELECT DISTINCT session_id FROM usage_calls WHERE provider = 'codex' AND agent_id = '' AND ts >= ? AND ts < ?");
|
|
215
|
+
for (const row of statement.iterate(args.w0, args.w1)) {
|
|
216
|
+
const sessionId = String(row.session_id);
|
|
217
|
+
if (!workerThreadIds.has(sessionId)) leads.add(sessionId);
|
|
218
|
+
}
|
|
219
|
+
return leads;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The Codex rollout files of the lead threads, preferring the `.zst` sibling when both exist
|
|
224
|
+
* (the pilot's rule). Only the day directories under the Codex sessions root are walked — no other
|
|
225
|
+
* directory is read.
|
|
226
|
+
*/
|
|
227
|
+
function codexRolloutFiles(sessionsRoot, leadThreads) {
|
|
228
|
+
const byLogical = new Map();
|
|
229
|
+
let years = [];
|
|
230
|
+
try { years = fs.readdirSync(sessionsRoot, { withFileTypes: true }); }
|
|
231
|
+
catch { return byLogical; } /* expected: no Codex rollout tree on this host */
|
|
232
|
+
for (const year of years) {
|
|
233
|
+
if (!year.isDirectory()) continue;
|
|
234
|
+
const yearDir = path.join(sessionsRoot, year.name);
|
|
235
|
+
let months = [];
|
|
236
|
+
try { months = fs.readdirSync(yearDir, { withFileTypes: true }); } catch { continue; } /* expected: unreadable year dir */
|
|
237
|
+
for (const month of months) {
|
|
238
|
+
if (!month.isDirectory()) continue;
|
|
239
|
+
const monthDir = path.join(yearDir, month.name);
|
|
240
|
+
let days = [];
|
|
241
|
+
try { days = fs.readdirSync(monthDir, { withFileTypes: true }); } catch { continue; } /* expected: unreadable month dir */
|
|
242
|
+
for (const day of days) {
|
|
243
|
+
if (!day.isDirectory()) continue;
|
|
244
|
+
const dayDir = path.join(monthDir, day.name);
|
|
245
|
+
let entries = [];
|
|
246
|
+
try { entries = fs.readdirSync(dayDir); } catch { continue; } /* expected: unreadable day dir */
|
|
247
|
+
for (const name of entries) {
|
|
248
|
+
const match = CODEX_THREAD_RE.exec(name);
|
|
249
|
+
if (!match || !leadThreads.has(match[1])) continue;
|
|
250
|
+
const full = path.join(dayDir, name);
|
|
251
|
+
const logical = full.replace(/\.zst$/, '');
|
|
252
|
+
const existing = byLogical.get(logical);
|
|
253
|
+
if (!existing || full.endsWith('.zst')) byLogical.set(logical, full);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return byLogical;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* ------------------------------------------------------------------ *
|
|
262
|
+
* §1.2 — waits (waits4.js + waits5.js)
|
|
263
|
+
* ------------------------------------------------------------------ */
|
|
264
|
+
|
|
265
|
+
/** Wait-tool labels of a call, or null when the call is not a pure wait call (waits4's kindOf). */
|
|
266
|
+
function waitLabelsOf(tools, script) {
|
|
267
|
+
if (!tools.length) return null;
|
|
268
|
+
for (const tool of tools) if (!WAIT_TOOLS.has(tool)) return null;
|
|
269
|
+
if (tools.includes('write_stdin') && /chars\s*:\s*"[^"]+"/.test(script || '')) return null;
|
|
270
|
+
return [...new Set(tools.map((tool) => WAIT_TOOLS.get(tool)))].sort();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** The table's row label (§1.2): `<family> [how called]`, or `mixed` for a multi-wait script. */
|
|
274
|
+
const VIA_LABEL = Object.freeze({ direct: 'direct', exec: 'inside exec', 'cell-wait': 'code-mode cell wait' });
|
|
275
|
+
|
|
276
|
+
function waitRowLabel(labels, via) {
|
|
277
|
+
if (labels.length > 1) return 'mixed';
|
|
278
|
+
return `${labels[0]} [${VIA_LABEL[via] || via}]`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** The exit marker of a `write_stdin` poll output, shared by waits4's outcome() and WP-3's runs. */
|
|
282
|
+
const EXIT_RE = /"exit_code"\s*:\s*-?\d|Process exited with code/;
|
|
283
|
+
|
|
284
|
+
/** waits4's outcome(): NEWS or NOTHING, in the pilot's check order. */
|
|
285
|
+
function waitOutcome(labels, output) {
|
|
286
|
+
const out = String(output || '');
|
|
287
|
+
if (/^Script running with cell ID/m.test(out)) return 'NOTHING: cell still running';
|
|
288
|
+
if (/interrupted by new input|Wait interrupted/i.test(out)) return 'NEWS: user input';
|
|
289
|
+
if (labels.includes('write_stdin poll')) {
|
|
290
|
+
if (EXIT_RE.test(out)) return 'NEWS: process exited';
|
|
291
|
+
if (/"session_id"|Process running/.test(out)) return 'NOTHING: still running';
|
|
292
|
+
}
|
|
293
|
+
if (labels.includes('wait_agent')) {
|
|
294
|
+
if (/"timed_out"\s*:\s*true|timed out/i.test(out)) return 'NOTHING: timed out';
|
|
295
|
+
return 'NEWS: agent update';
|
|
296
|
+
}
|
|
297
|
+
if (labels.includes('walle_worker_wait')) {
|
|
298
|
+
const woke = /"woke_on"\s*:\s*"([a-z_]+)"/.exec(out);
|
|
299
|
+
if (woke) return woke[1] === 'timeout' ? 'NOTHING: timeout' : `NEWS: ${woke[1]}`;
|
|
300
|
+
}
|
|
301
|
+
if (labels.includes('walle_worker_status')) {
|
|
302
|
+
const status = /"status"\s*:\s*"([a-z_]+)"/.exec(out);
|
|
303
|
+
if (status) return /working|starting|throttled/.test(status[1]) ? `NOTHING: ${status[1]}` : `NEWS: ${status[1]}`;
|
|
304
|
+
}
|
|
305
|
+
if (labels.length === 1 && labels[0] === 'sleep') return 'NOTHING: slept';
|
|
306
|
+
return 'unclassified';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Records one RESOLVED wait call. Shared by the `function_call_output`/`custom_tool_call_output` path
|
|
311
|
+
* and WP-5's direct `item_completed` path, so both spellings of one call run the same §1.2
|
|
312
|
+
* NEWS/NOTHING rule (the classifier is not forked) and the same window/drop test.
|
|
313
|
+
*/
|
|
314
|
+
function emitWait(report, call, ts, out) {
|
|
315
|
+
if (!call.inWin) return;
|
|
316
|
+
if (!call.labels || call.labels[0] === 'other' || call.labels[0] === 'unknown cell') {
|
|
317
|
+
report.droppedWait(call.labels ? call.labels[0] : 'not a wait script');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
report.wait({
|
|
321
|
+
ts,
|
|
322
|
+
rowLabel: waitRowLabel(call.labels, call.via),
|
|
323
|
+
labels: call.labels,
|
|
324
|
+
outcome: waitOutcome(call.labels, out),
|
|
325
|
+
yieldMs: call.yieldMs === undefined ? null : call.yieldMs,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The text of an `item_completed` McpToolCall's `item.result`: its `content` text parts, else the value. */
|
|
330
|
+
function mcpToolResultText(result) {
|
|
331
|
+
if (result && Array.isArray(result.content)) return outputText(result.content);
|
|
332
|
+
return outputText(result === undefined || result === null ? '' : result);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function percentile(sorted, p) {
|
|
336
|
+
if (!sorted.length) return null;
|
|
337
|
+
return sorted[Math.floor(p * (sorted.length - 1))];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** One-tool-per-call label for the "first tool after a sleep" histogram (waits5.js). */
|
|
341
|
+
function toolLabel(tools) {
|
|
342
|
+
if (!tools.length) return '(no tool)';
|
|
343
|
+
const labels = [...new Set(tools.map((tool) => WAIT_TOOLS.get(tool) || String(tool).replace('mcp__wall_e__walle_', '')))].sort();
|
|
344
|
+
return labels.join('+').slice(0, 60);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Streams one Codex rollout (through transcript-source: `.zst` siblings included) and reports every
|
|
349
|
+
* wait call in the window: row label, outcome, the code-mode cell yield it asked for, and the tool
|
|
350
|
+
* the lead called next after a sleep.
|
|
351
|
+
*/
|
|
352
|
+
async function scanRolloutForWaits(transcriptPath, opts, report) {
|
|
353
|
+
if (!fs.existsSync(transcriptPath)) return;
|
|
354
|
+
if (opts.commandRuns) beginCommandFile(opts.commandRuns);
|
|
355
|
+
const stream = createTranscriptReadStream(transcriptPath, {});
|
|
356
|
+
const calls = new Map();
|
|
357
|
+
const cells = new Map();
|
|
358
|
+
// WP-5: the call ids already reported, so one call seen under both rollout spellings counts once.
|
|
359
|
+
const reportedWaits = new Set();
|
|
360
|
+
let afterSleep = false;
|
|
361
|
+
const lines = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
362
|
+
for await (const line of lines) {
|
|
363
|
+
// `call` is the cheap pre-filter of every call/output record; the direct Wall-E item is spelled
|
|
364
|
+
// `McpToolCall`, which does not contain the lowercase needle, so it is named here too.
|
|
365
|
+
if (line.length > MAX_LINE || !(line.includes('call') || line.includes('McpToolCall'))) continue;
|
|
366
|
+
const record = parseJson(line);
|
|
367
|
+
if (!record) continue;
|
|
368
|
+
const ts = Date.parse(record.timestamp);
|
|
369
|
+
const inWin = ts >= opts.w0 && ts < opts.w1;
|
|
370
|
+
const payload = record.payload || {};
|
|
371
|
+
const type = payload.type;
|
|
372
|
+
if (opts.commandRuns) trackCommandRun(opts.commandRuns, payload, ts, inWin);
|
|
373
|
+
// WP-5 (§6.8, D-11): the direct Wall-E MCP spelling. Since the launch change
|
|
374
|
+
// (`features.code_mode.direct_only_tool_namespaces=["mcp__wall_e"]`) one live call appears as THREE
|
|
375
|
+
// records: `function_call` (bare `walle_worker_wait`, `call_id`), this `item_completed`
|
|
376
|
+
// McpToolCall, and their `function_call_output` carrying the same `call_id`. VERIFIED on a real
|
|
377
|
+
// rollout of 2026-10-24 (39 such waits, ~/.codex/sessions/2026/10/24/): the response_item
|
|
378
|
+
// `function_call` and `function_call_output` both carry `call_id`, and it is byte-equal to the
|
|
379
|
+
// `item_completed` item's `id` (`call_id` is NOT a field of the item). One shared id, so the
|
|
380
|
+
// id-keyed dedup below is exact and no (tool, timestamp) fallback is needed.
|
|
381
|
+
if (type === 'item_completed') {
|
|
382
|
+
const item = payload.item;
|
|
383
|
+
if (item && item.type === 'McpToolCall' && item.server === 'wall-e' && WAIT_TOOLS.has(item.tool)) {
|
|
384
|
+
const callId = item.id === undefined || item.id === null ? '' : String(item.id);
|
|
385
|
+
const alreadyReported = Boolean(callId) && reportedWaits.has(callId);
|
|
386
|
+
if (callId) reportedWaits.add(callId);
|
|
387
|
+
if (!alreadyReported) {
|
|
388
|
+
// The `function_call` for the same call carries the call's own window position, so reuse it
|
|
389
|
+
// when it has been seen; the completion's own timestamp decides otherwise.
|
|
390
|
+
const call = (callId && calls.get(callId)) || { labels: [WAIT_TOOLS.get(item.tool)], via: 'direct', inWin, yieldMs: null };
|
|
391
|
+
if (callId) calls.delete(callId);
|
|
392
|
+
emitWait(report, call, ts, mcpToolResultText(item.result));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (type === 'custom_tool_call' && payload.name === 'exec') {
|
|
398
|
+
const script = payload.input || '';
|
|
399
|
+
const tools = execToolNames(script);
|
|
400
|
+
// `labels` is null for a script that mixes waits with other work; the call is still tracked so
|
|
401
|
+
// that a later code-mode cell wait on it resolves to it (and is dropped, as `other`).
|
|
402
|
+
const labels = waitLabelsOf(tools, script);
|
|
403
|
+
calls.set(payload.call_id, { labels, via: 'exec', inWin, yieldMs: labels ? execYieldOf(script) : null });
|
|
404
|
+
if (inWin) {
|
|
405
|
+
if (tools.includes('sleep')) {
|
|
406
|
+
afterSleep = true;
|
|
407
|
+
const duration = /duration_ms["\s:]*(\d+)/.exec(script);
|
|
408
|
+
if (duration) report.sleepDuration(Number(duration[1]));
|
|
409
|
+
} else if (afterSleep) {
|
|
410
|
+
report.sleepNext(toolLabel(tools));
|
|
411
|
+
afterSleep = false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (type === 'function_call') {
|
|
417
|
+
const args = parseJson(payload.arguments || '{}') || {};
|
|
418
|
+
if (payload.name === 'wait' && args.cell_id) {
|
|
419
|
+
calls.set(payload.call_id, { labels: cells.get(String(args.cell_id)) || null, via: 'cell-wait', inWin });
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const labels = waitLabelsOf([payload.name], payload.arguments || '');
|
|
423
|
+
if (labels) calls.set(payload.call_id, { labels, via: 'direct', inWin, yieldMs: null });
|
|
424
|
+
if (inWin) {
|
|
425
|
+
if (payload.name === 'sleep') {
|
|
426
|
+
afterSleep = true;
|
|
427
|
+
const duration = /duration_ms["\s:]*(\d+)/.exec(payload.arguments || '');
|
|
428
|
+
if (duration) report.sleepDuration(Number(duration[1]));
|
|
429
|
+
} else if (afterSleep) {
|
|
430
|
+
report.sleepNext(toolLabel([payload.name]));
|
|
431
|
+
afterSleep = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (type === 'function_call_output' || type === 'custom_tool_call_output') {
|
|
437
|
+
const call = calls.get(payload.call_id);
|
|
438
|
+
if (!call) continue;
|
|
439
|
+
calls.delete(payload.call_id);
|
|
440
|
+
const out = outputText(payload.output);
|
|
441
|
+
if (call.via === 'exec') {
|
|
442
|
+
const cell = /Script running with cell ID (\S+)/.exec(out);
|
|
443
|
+
// A cell inherits the script's wait family; a script that also did other work marks `other`,
|
|
444
|
+
// and a cell wait on `other` is not a wait-type call (waits4.js's rule).
|
|
445
|
+
if (cell) cells.set(cell[1], call.labels || ['other']);
|
|
446
|
+
}
|
|
447
|
+
// WP-5 dedup: this call was already reported from its `item_completed` McpToolCall (the record
|
|
448
|
+
// that carries the result), so the bare `function_call`/`function_call_output` pair is dropped.
|
|
449
|
+
const callId = payload.call_id === undefined || payload.call_id === null ? '' : String(payload.call_id);
|
|
450
|
+
if (callId && reportedWaits.has(callId)) continue;
|
|
451
|
+
if (callId) reportedWaits.add(callId);
|
|
452
|
+
emitWait(report, call, ts, out);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (opts.commandRuns) finishCommandFile(opts.commandRuns);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** waits4.js: the cell yield the lead asked for when wrapping a wait in a code-mode script. */
|
|
459
|
+
function execYieldOf(script) {
|
|
460
|
+
const match = /@exec:\s*(\{[^}]*\})/.exec(script);
|
|
461
|
+
if (!match) return 'default';
|
|
462
|
+
const parsed = parseJson(match[1]);
|
|
463
|
+
if (parsed && parsed.yield_time_ms !== undefined) return parsed.yield_time_ms;
|
|
464
|
+
return 'default';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function runWaitPass(args, leadThreads) {
|
|
468
|
+
const sessionsRoot = path.join(os.homedir(), '.codex', 'sessions');
|
|
469
|
+
const files = codexRolloutFiles(sessionsRoot, leadThreads);
|
|
470
|
+
const rows = [];
|
|
471
|
+
const sleepDurations = [];
|
|
472
|
+
const sleepNext = new Map();
|
|
473
|
+
const yields = new Map();
|
|
474
|
+
const dropped = new Map();
|
|
475
|
+
const bump = (map, key) => map.set(key, (map.get(key) || 0) + 1);
|
|
476
|
+
const report = {
|
|
477
|
+
wait(call) {
|
|
478
|
+
rows.push(call);
|
|
479
|
+
if (call.yieldMs !== null) {
|
|
480
|
+
const key = call.labels.join('+');
|
|
481
|
+
if (!yields.has(key)) yields.set(key, new Map());
|
|
482
|
+
bump(yields.get(key), String(call.yieldMs));
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
sleepNext(label) { bump(sleepNext, label); },
|
|
486
|
+
sleepDuration(ms) { if (Number.isFinite(ms)) sleepDurations.push(ms); },
|
|
487
|
+
droppedWait(reason) { bump(dropped, reason); },
|
|
488
|
+
};
|
|
489
|
+
let unreadable = 0;
|
|
490
|
+
for (const transcriptPath of files.values()) {
|
|
491
|
+
try {
|
|
492
|
+
await scanRolloutForWaits(transcriptPath, args, report);
|
|
493
|
+
} catch {
|
|
494
|
+
// REPORTED, never silent: a rollout whose bytes cannot be decoded (a damaged `.zst` sibling)
|
|
495
|
+
// is counted and skipped so one bad file cannot abort the census.
|
|
496
|
+
unreadable += 1;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return { rows, sleepDurations, sleepNext, yields, dropped, fileCount: files.size, unreadable };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* ------------------------------------------------------------------ *
|
|
503
|
+
* WP-3 — §5 OQ-2: did the lead act on a command's partial output?
|
|
504
|
+
* ------------------------------------------------------------------ */
|
|
505
|
+
|
|
506
|
+
const SESSION_ID_JSON_RE = /"session_id"\s*:\s*(\d+)/g;
|
|
507
|
+
const SESSION_ID_TEXT_RE = /Process running with session ID (\d+)/g;
|
|
508
|
+
const EDIT_TOOLS = new Set(['apply_patch', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
509
|
+
// Waits the lead parks on that carry no new information by themselves, so they are never "the first
|
|
510
|
+
// non-wait action". `write_stdin` is deliberately absent: the rule counts a poll as an action.
|
|
511
|
+
const PASSIVE_WAIT_TOOLS = new Set([
|
|
512
|
+
'sleep', 'wait_agent', 'wait', 'walle_worker_wait', 'walle_worker_status',
|
|
513
|
+
'mcp__wall_e__walle_worker_wait', 'mcp__wall_e__walle_worker_status',
|
|
514
|
+
]);
|
|
515
|
+
const KILL_RE = /\bp?kill\b/;
|
|
516
|
+
|
|
517
|
+
/** One run's state: the command session, its origin, and the empty-`chars` polls seen on it. */
|
|
518
|
+
function blankCommandRun(sessionId, startTs, family) {
|
|
519
|
+
return { sessionId, startTs, family, polls: [], pollsCount: 0, durationMs: null, partialBytes: null, unfinished: true, acted: false, kill: false };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** The per-run accumulator, reset for every rollout file (ordering is only meaningful inside one). */
|
|
523
|
+
function createCommandRunTracker() {
|
|
524
|
+
return { runs: [], actions: [], open: new Map(), seq: 0, pendingRuns: new Map() };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function beginCommandFile(state) {
|
|
528
|
+
state.actions = [];
|
|
529
|
+
state.open = new Map();
|
|
530
|
+
state.seq = 0;
|
|
531
|
+
state.pendingRuns = new Map();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* The lead's action in one rollout call record, as the WP-3 ordering rule sees it: the command
|
|
536
|
+
* strings it starts, the `write_stdin` sites it names, whether it edits, and whether it is nothing
|
|
537
|
+
* but a passive wait. `order` is the script's tool calls in call order, which is also the order of
|
|
538
|
+
* the result texts Codex returns for them (see `outputResults`).
|
|
539
|
+
*/
|
|
540
|
+
function describeLeadAction(payload) {
|
|
541
|
+
const action = { waitOnly: false, edit: false, polls: [], commands: [], commandSites: [], order: [] };
|
|
542
|
+
if (payload.type === 'function_call') {
|
|
543
|
+
const name = String(payload.name || '');
|
|
544
|
+
const args = parseJson(payload.arguments || '{}') || {};
|
|
545
|
+
if (name === 'exec_command' || name === 'shell' || name === 'local_shell') {
|
|
546
|
+
const cmd = args.command === undefined ? args.cmd : args.command;
|
|
547
|
+
action.commands.push(Array.isArray(cmd) ? cmd.join(' ') : String(cmd === undefined || cmd === null ? '' : cmd));
|
|
548
|
+
} else if (name === 'write_stdin') {
|
|
549
|
+
const sessionId = typeof args.session_id === 'number' ? args.session_id : literalSessionId(`session_id:${args.session_id}`);
|
|
550
|
+
action.polls.push({ sessionId, chars: typeof args.chars === 'string' ? args.chars : '', site: 0 });
|
|
551
|
+
} else if (EDIT_TOOLS.has(name)) action.edit = true;
|
|
552
|
+
else if (PASSIVE_WAIT_TOOLS.has(name)) action.waitOnly = true;
|
|
553
|
+
return action;
|
|
554
|
+
}
|
|
555
|
+
if (payload.type === 'custom_tool_call') {
|
|
556
|
+
const name = String(payload.name || '');
|
|
557
|
+
if (name === 'exec') {
|
|
558
|
+
const script = String(payload.input || '');
|
|
559
|
+
const sites = execCallWindows(script);
|
|
560
|
+
action.order = sites.map((site) => {
|
|
561
|
+
if (site.name === 'exec_command' || site.name === 'shell') return { kind: 'command', cmd: commandLiteralOf(script, site.at) };
|
|
562
|
+
if (site.name === 'write_stdin') return { kind: 'poll', sessionId: literalSessionId(site.window), chars: literalChars(site.window) };
|
|
563
|
+
return { kind: 'other' };
|
|
564
|
+
});
|
|
565
|
+
action.order.forEach((call, i) => {
|
|
566
|
+
if (call.kind === 'command') { action.commands.push(call.cmd); action.commandSites.push(i); }
|
|
567
|
+
else if (call.kind === 'poll') action.polls.push({ sessionId: call.sessionId, chars: call.chars, site: i });
|
|
568
|
+
});
|
|
569
|
+
action.edit = sites.some((site) => EDIT_TOOLS.has(site.name));
|
|
570
|
+
// A script with no tool call at all is a reply to the user, not a wait.
|
|
571
|
+
action.waitOnly = Boolean(sites.length) && !action.edit && !action.commands.length && !action.polls.length
|
|
572
|
+
&& sites.every((site) => PASSIVE_WAIT_TOOLS.has(site.name));
|
|
573
|
+
return action;
|
|
574
|
+
}
|
|
575
|
+
if (EDIT_TOOLS.has(name)) action.edit = true;
|
|
576
|
+
else if (PASSIVE_WAIT_TOOLS.has(name)) action.waitOnly = true;
|
|
577
|
+
return action;
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The command output a poll returned, which is what "the partial output the lead saw" means here:
|
|
584
|
+
* Codex wraps it in its own `Script completed / Wall time … / Output:` (code-mode) or
|
|
585
|
+
* `Command: … / Chunk ID: … / Wall time: … / Output:` (CLI) header, and a poll whose command produced
|
|
586
|
+
* nothing must count as empty, so the wrapper is stripped before the byte count.
|
|
587
|
+
*/
|
|
588
|
+
function pollOutputText(output) {
|
|
589
|
+
const out = String(output || '');
|
|
590
|
+
const marker = out.lastIndexOf('Output:\n');
|
|
591
|
+
const body = marker >= 0 ? out.slice(marker + 'Output:\n'.length) : out;
|
|
592
|
+
const parsed = parseJson(body.trim());
|
|
593
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.output === 'string') return parsed.output;
|
|
594
|
+
// `Promise.allSettled(...)` keeps each result under `value`; the command's output is what counts.
|
|
595
|
+
const settled = parsed && typeof parsed === 'object' && parsed.value;
|
|
596
|
+
if (settled && typeof settled === 'object' && typeof settled.output === 'string') return settled.output;
|
|
597
|
+
return body;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** The session ids an originating call's output names, in the order they appear (code-mode JSON, then CLI text). */
|
|
601
|
+
function runSessionIds(output) {
|
|
602
|
+
const out = String(output || '');
|
|
603
|
+
const found = [];
|
|
604
|
+
for (const [re, group] of [[SESSION_ID_JSON_RE, 1], [SESSION_ID_TEXT_RE, 1]]) {
|
|
605
|
+
re.lastIndex = 0;
|
|
606
|
+
let m;
|
|
607
|
+
while ((m = re.exec(out))) found.push({ at: m.index, sessionId: Number(m[group]) });
|
|
608
|
+
}
|
|
609
|
+
found.sort((a, b) => a.at - b.at);
|
|
610
|
+
return found.map((hit) => hit.sessionId);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* One result text per tool call of the script, when Codex returned the output as its array form: the
|
|
615
|
+
* first element is Codex's own `Script completed / Wall time … / Output:` header and every following
|
|
616
|
+
* element is one call's result, in call order. `aligned` says the count matched the script's calls, so
|
|
617
|
+
* result `i` really belongs to call `i`; a lead that prints extra text per call, or a CLI-form output
|
|
618
|
+
* (a single string), leaves `texts` null and the caller falls back to scanning the whole output.
|
|
619
|
+
*/
|
|
620
|
+
function outputResults(output, callCount) {
|
|
621
|
+
if (!Array.isArray(output)) return { texts: null, aligned: false };
|
|
622
|
+
const texts = output.map((part) => (typeof part === 'string' ? part : String((part && part.text) || '')));
|
|
623
|
+
const body = texts.length && /^\s*(Script completed|Script running with cell ID)/.test(texts[0]) ? texts.slice(1) : texts;
|
|
624
|
+
return { texts: body, aligned: body.length === callCount };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** The family label `classifyCommand` gives a command string (never the command itself). */
|
|
628
|
+
function commandFamily(cmd) {
|
|
629
|
+
const rec = blankCall();
|
|
630
|
+
classifyCommand(cmd, rec);
|
|
631
|
+
const cats = new Set(Object.keys(rec.cat));
|
|
632
|
+
if (!cats.size) return 'unknown command';
|
|
633
|
+
if (cats.has('edit')) return 'edit files';
|
|
634
|
+
if (cats.has('test')) return 'run tests / gate';
|
|
635
|
+
if (cats.has('ship')) return 'commit / merge';
|
|
636
|
+
if (cats.has('review')) return 'review diff / log';
|
|
637
|
+
if (cats.has('read')) return 'read / search';
|
|
638
|
+
return 'other shell';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Feeds one rollout call record (or its output) into the run tracker. The call record creates the
|
|
643
|
+
* action entry and the run's polls; the output record resolves the run's session ids and the poll's
|
|
644
|
+
* exit, bytes and observation time.
|
|
645
|
+
*/
|
|
646
|
+
function trackCommandRun(state, payload, ts, inWin) {
|
|
647
|
+
const type = payload.type;
|
|
648
|
+
if (type === 'function_call' || type === 'custom_tool_call') {
|
|
649
|
+
const action = describeLeadAction(payload);
|
|
650
|
+
if (!action || !payload.call_id) return;
|
|
651
|
+
const entry = { seq: state.seq, ts, inWin, action };
|
|
652
|
+
state.seq += 1;
|
|
653
|
+
state.actions.push(entry);
|
|
654
|
+
state.open.set(String(payload.call_id), entry);
|
|
655
|
+
if (!inWin) return;
|
|
656
|
+
for (const poll of action.polls) {
|
|
657
|
+
if (poll.sessionId === null || poll.chars.length) continue;
|
|
658
|
+
const run = state.pendingRuns.get(poll.sessionId);
|
|
659
|
+
if (run) run.polls.push({ seq: entry.seq, ts, outTs: null, bytes: 0, exited: false });
|
|
660
|
+
}
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (type !== 'function_call_output' && type !== 'custom_tool_call_output') return;
|
|
664
|
+
const entry = state.open.get(String(payload.call_id));
|
|
665
|
+
if (!entry) return;
|
|
666
|
+
state.open.delete(String(payload.call_id));
|
|
667
|
+
const out = outputText(payload.output);
|
|
668
|
+
const { texts, aligned } = outputResults(payload.output, entry.action.order.length);
|
|
669
|
+
if (entry.action.commands.length) {
|
|
670
|
+
if (!entry.inWin) return;
|
|
671
|
+
const ids = runSessionIds(out);
|
|
672
|
+
entry.action.commands.forEach((cmd, i) => {
|
|
673
|
+
// With one result per call, the call's own result says whether it opened a session; without that
|
|
674
|
+
// alignment the ids of the whole output are paired with the commands in order.
|
|
675
|
+
const sessionId = aligned ? runSessionIds(texts[entry.action.commandSites[i]])[0] : (ids.length === 1 ? ids[0] : ids[i]);
|
|
676
|
+
if (sessionId === undefined) return;
|
|
677
|
+
if (state.pendingRuns.has(sessionId)) return;
|
|
678
|
+
const run = blankCommandRun(sessionId, entry.ts, commandFamily(cmd));
|
|
679
|
+
state.pendingRuns.set(sessionId, run);
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
// A real lead batches polls with other work in one script, so this runs even when the script also
|
|
683
|
+
// started commands or edited files.
|
|
684
|
+
for (const poll of entry.action.polls) {
|
|
685
|
+
if (poll.sessionId === null || poll.chars.length) continue;
|
|
686
|
+
const run = state.pendingRuns.get(poll.sessionId);
|
|
687
|
+
if (!run) continue;
|
|
688
|
+
const record = run.polls.find((p) => p.seq === entry.seq);
|
|
689
|
+
if (!record) continue;
|
|
690
|
+
const result = aligned ? String(texts[poll.site] || '') : out;
|
|
691
|
+
record.outTs = ts;
|
|
692
|
+
record.bytes = pollOutputText(result).length;
|
|
693
|
+
record.exited = EXIT_RE.test(result);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** What the lead's action does to an in-flight run: `acted` (case 1/2/3) and the kill sub-flag. */
|
|
698
|
+
function actionVerdict(action, sessionId) {
|
|
699
|
+
for (const poll of action.polls) {
|
|
700
|
+
if (poll.sessionId !== sessionId || !poll.chars.length) continue;
|
|
701
|
+
return { acted: true, kill: poll.chars.includes('\u0003') };
|
|
702
|
+
}
|
|
703
|
+
if (action.edit) return { acted: true, kill: false };
|
|
704
|
+
for (const cmd of action.commands) if (KILL_RE.test(cmd)) return { acted: true, kill: true };
|
|
705
|
+
return { acted: false, kill: false };
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Closes one rollout file: every run either has an exit poll (duration, partial bytes and the
|
|
710
|
+
* ordering rule) or is counted unfinished. Nothing is inferred from what the lead wrote — only from
|
|
711
|
+
* which call came first.
|
|
712
|
+
*/
|
|
713
|
+
function finishCommandFile(state) {
|
|
714
|
+
for (const run of state.pendingRuns.values()) {
|
|
715
|
+
const polls = run.polls;
|
|
716
|
+
run.pollsCount = polls.length;
|
|
717
|
+
// The brief's definition: a run is a run only when at least one empty-`chars` poll followed the
|
|
718
|
+
// originating call. A session that was started and never polled is not a polled command run.
|
|
719
|
+
if (!polls.length) continue;
|
|
720
|
+
const exitIndex = polls.findIndex((poll) => poll.exited);
|
|
721
|
+
if (exitIndex < 0) {
|
|
722
|
+
state.runs.push(run);
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
run.unfinished = false;
|
|
726
|
+
run.durationMs = polls[exitIndex].outTs - run.startTs;
|
|
727
|
+
let partialBytes = 0;
|
|
728
|
+
let lastPartial = -1;
|
|
729
|
+
for (let i = 0; i < exitIndex; i += 1) {
|
|
730
|
+
if (polls[i].bytes > 0) { partialBytes += polls[i].bytes; lastPartial = i; }
|
|
731
|
+
}
|
|
732
|
+
run.partialBytes = partialBytes;
|
|
733
|
+
if (lastPartial >= 0) {
|
|
734
|
+
for (let seq = polls[lastPartial].seq + 1; seq < polls[exitIndex].seq; seq += 1) {
|
|
735
|
+
const entry = state.actions[seq];
|
|
736
|
+
if (!entry || !entry.inWin || entry.action.waitOnly) continue;
|
|
737
|
+
const verdict = actionVerdict(entry.action, run.sessionId);
|
|
738
|
+
run.acted = verdict.acted;
|
|
739
|
+
run.kill = verdict.kill;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
state.runs.push(run);
|
|
744
|
+
}
|
|
745
|
+
state.pendingRuns = new Map();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function numberText(value) { return Number(value).toLocaleString('en-US'); }
|
|
749
|
+
|
|
750
|
+
/** §5 OQ-2's rows: per command family, plus the pooled `Total`, sorted by finished runs. */
|
|
751
|
+
function commandOutcomeRows(runs) {
|
|
752
|
+
const byFamily = new Map();
|
|
753
|
+
for (const run of runs) {
|
|
754
|
+
if (!byFamily.has(run.family)) {
|
|
755
|
+
byFamily.set(run.family, { family: run.family, n: 0, unfinished: 0, acted: 0, kill: 0, durations: [], partials: [] });
|
|
756
|
+
}
|
|
757
|
+
const entry = byFamily.get(run.family);
|
|
758
|
+
if (run.unfinished) { entry.unfinished += 1; continue; }
|
|
759
|
+
entry.n += 1;
|
|
760
|
+
if (run.acted) { entry.acted += 1; if (run.kill) entry.kill += 1; }
|
|
761
|
+
entry.durations.push(run.durationMs);
|
|
762
|
+
entry.partials.push(run.partialBytes);
|
|
763
|
+
}
|
|
764
|
+
const rows = [...byFamily.values()].sort((a, b) => b.n - a.n || a.family.localeCompare(b.family));
|
|
765
|
+
const total = { family: 'Total', n: 0, unfinished: 0, acted: 0, kill: 0, durations: [], partials: [] };
|
|
766
|
+
for (const row of rows) {
|
|
767
|
+
total.n += row.n;
|
|
768
|
+
total.unfinished += row.unfinished;
|
|
769
|
+
total.acted += row.acted;
|
|
770
|
+
total.kill += row.kill;
|
|
771
|
+
total.durations.push(...row.durations);
|
|
772
|
+
total.partials.push(...row.partials);
|
|
773
|
+
}
|
|
774
|
+
rows.push(total);
|
|
775
|
+
for (const row of rows) {
|
|
776
|
+
row.durations.sort((a, b) => a - b);
|
|
777
|
+
row.partials.sort((a, b) => a - b);
|
|
778
|
+
const p50 = percentile(row.durations, 0.5);
|
|
779
|
+
const p90 = percentile(row.durations, 0.9);
|
|
780
|
+
row.share = row.n ? (100 * row.acted) / row.n : 0;
|
|
781
|
+
row.dur_p50_s = p50 === null ? null : Math.round(p50 / 1000);
|
|
782
|
+
row.dur_p90_s = p90 === null ? null : Math.round(p90 / 1000);
|
|
783
|
+
row.partial_p50 = percentile(row.partials, 0.5);
|
|
784
|
+
}
|
|
785
|
+
return rows;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** The §5 OQ-2 table, in the brief's shape; a family label is the only thing ever printed. */
|
|
789
|
+
function commandOutcomeLines(rows) {
|
|
790
|
+
const lines = ['command outcomes (polled code-mode command runs; did the lead act on partial output before exit?)'];
|
|
791
|
+
const seconds = (v) => (v === null ? 'n/a' : numberText(v));
|
|
792
|
+
lines.push(`${pad('family', 20)}${padLeft('n', 6)}${padLeft('unfinished', 12)}${padLeft('acted-on-partial', 18)}${padLeft('kill', 6)}${padLeft('dur p50/p90 s', 16)}${padLeft('partial p50', 13)}`);
|
|
793
|
+
for (const row of rows) {
|
|
794
|
+
lines.push(`${pad(row.family, 20)}${padLeft(numberText(row.n), 6)}${padLeft(numberText(row.unfinished), 12)}`
|
|
795
|
+
+ `${padLeft(`${numberText(row.acted)} (${row.share.toFixed(1)}%)`, 18)}${padLeft(numberText(row.kill), 6)}`
|
|
796
|
+
+ `${padLeft(`${seconds(row.dur_p50_s)} / ${seconds(row.dur_p90_s)}`, 16)}${padLeft(seconds(row.partial_p50), 13)}`);
|
|
797
|
+
}
|
|
798
|
+
return lines;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/* ------------------------------------------------------------------ *
|
|
802
|
+
* §1.6 — exploration bursts
|
|
803
|
+
* ------------------------------------------------------------------ */
|
|
804
|
+
|
|
805
|
+
// The pilot's per-call classifier, ported VERBATIM from its scratch script ref/parse-tools.js
|
|
806
|
+
// (TEST_RE/COMMIT_RE/REVIEW_RE/READ_RE, DELEG, fileClass/lineDiff/patchStats, classifyCommand,
|
|
807
|
+
// addTool, addExec). It is deliberately not a reconstruction of the same intent: `read / search` is
|
|
808
|
+
// what drives a burst, so a looser read rule silently inflates every baseline this tool prints.
|
|
809
|
+
const TEST_RE = /\b(npm (run )?test|npx (playwright|jest|vitest)|node --test|node\s+\S*run-(test-suite|hermetic)|test-gate|run-hermetic|pytest|playwright test|jest\b|vitest\b|go test|cargo test|phpunit|deflake)/;
|
|
810
|
+
const COMMIT_RE = /\bgit\s+(-C\s+\S+\s+)?(commit|merge|push|cherry-pick|rebase)\b/;
|
|
811
|
+
const REVIEW_RE = /\bgit\s+(-C\s+\S+\s+)?(diff|show|log)\b/;
|
|
812
|
+
const READ_RE = /^\s*(cd\s+\S+\s*&&\s*)?(cat|sed|head|tail|rg|grep|ls|find|wc|nl|less|awk|tree|jq|sqlite3)\b/;
|
|
813
|
+
const DELEG = {
|
|
814
|
+
Agent: 'spawn_sub', Task: 'spawn_sub', spawn_agent: 'spawn_sub', TaskOutput: 'wait_sub', AgentOutputTool: 'wait_sub', wait: 'wait_sub', wait_agent: 'wait_sub',
|
|
815
|
+
send_message: 'msg_sub', send_input: 'msg_sub', SendMessage: 'msg_sub', close_agent: 'msg_sub', TaskStop: 'msg_sub',
|
|
816
|
+
walle_delegate_implementation: 'delegate', walle_worker_wait: 'worker_wait', walle_worker_status: 'worker_status',
|
|
817
|
+
walle_resume_worker: 'worker_resume', walle_stop_worker: 'worker_stop',
|
|
818
|
+
followup_task: 'msg_sub', list_agents: 'wait_sub', ListAgents: 'wait_sub', SubagentHandback: 'handback',
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
function fileClass(p) {
|
|
822
|
+
const f = String(p || '');
|
|
823
|
+
if (/\/\.claude\/briefs\/|brief\.md$|-brief\.md$|worker-reports\//.test(f)) return 'brief';
|
|
824
|
+
if (/(^|\/)(tests?|tests-[a-z]+|__tests__)\/|\.test\.|\.spec\./.test(f)) return 'test';
|
|
825
|
+
if (/\.(html|md|txt)$/.test(f)) return 'doc';
|
|
826
|
+
if (/\.(json|lock|snap|svg|csv|jsonl)$/.test(f) || /golden|baseline|fixtures\//.test(f)) return 'data';
|
|
827
|
+
return 'code';
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function lineDiff(oldS, newS) {
|
|
831
|
+
const a = String(oldS || '').split('\n'); const b = String(newS || '').split('\n');
|
|
832
|
+
if (!oldS) return { add: newS ? b.length : 0, del: 0 };
|
|
833
|
+
const m = new Map(); for (const l of a) m.set(l, (m.get(l) || 0) + 1);
|
|
834
|
+
let common = 0; for (const l of b) { const c = m.get(l); if (c) { common++; m.set(l, c - 1); } }
|
|
835
|
+
return { add: b.length - common, del: a.length - common };
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* The `tools.<name>(` calls one Codex code-mode `exec` script makes, in script order, each with the
|
|
840
|
+
* slice of script text that follows it (the window its arguments live in).
|
|
841
|
+
*/
|
|
842
|
+
function execCallWindows(script) {
|
|
843
|
+
const s = String(script || '');
|
|
844
|
+
const sites = [];
|
|
845
|
+
for (const m of s.matchAll(/tools\.([A-Za-z0-9_]+)\s*\(/g)) {
|
|
846
|
+
const next = s.indexOf('tools.', m.index + 1);
|
|
847
|
+
sites.push({ name: m[1], at: m.index, window: s.slice(m.index, next < 0 ? m.index + 600 : Math.min(next, m.index + 600)) });
|
|
848
|
+
}
|
|
849
|
+
return sites;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** The `tools.<name>(` calls one Codex code-mode `exec` script makes, in script order. */
|
|
853
|
+
function execToolNames(script) {
|
|
854
|
+
return execCallWindows(script).map((site) => site.name);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* The `cmd:`/`command:` literal of the `exec_command` whose `tools.` match starts at `fromIndex`,
|
|
859
|
+
* searched forward (parse-tools.js's window). A non-JSON literal is kept raw.
|
|
860
|
+
*/
|
|
861
|
+
function commandLiteralOf(script, fromIndex) {
|
|
862
|
+
const cm = /(?:cmd|command)\s*:\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`[^`]*`|\[[^\]]*\])/.exec(script.slice(fromIndex, fromIndex + 4000));
|
|
863
|
+
let cmd = cm ? cm[1] : '';
|
|
864
|
+
try { if (cmd.startsWith('"')) cmd = JSON.parse(cmd); } catch { /* expected: a non-JSON literal; keep the raw text */ }
|
|
865
|
+
return cmd;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** The command strings a call record starts, in script order (WP-3's run origins). */
|
|
869
|
+
function execCommandStrings(script) {
|
|
870
|
+
const s = String(script || '');
|
|
871
|
+
return execCallWindows(s)
|
|
872
|
+
.filter((site) => site.name === 'exec_command' || site.name === 'shell')
|
|
873
|
+
.map((site) => commandLiteralOf(s, site.at));
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/** The literal argument of a `"..."`/`'...'`/`` `...` `` site, decoded when it is JSON-escaped. */
|
|
877
|
+
function decodeStringLiteral(literal) {
|
|
878
|
+
const parsed = parseJson(String(literal));
|
|
879
|
+
return typeof parsed === 'string' ? parsed : String(literal).slice(1, -1);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/** The numeric session id a `write_stdin` call site names, or null when it is not a literal. */
|
|
883
|
+
function literalSessionId(text) {
|
|
884
|
+
const m = /session_id\s*:\s*(?:"(\d+)"|'(\d+)'|(\d+))/.exec(text);
|
|
885
|
+
return m ? Number(m[1] ?? m[2] ?? m[3]) : null;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** The `chars` a `write_stdin` call site carries ('' when the field is absent). */
|
|
889
|
+
function literalChars(text) {
|
|
890
|
+
const m = /chars\s*:\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`[^`]*`)/.exec(text);
|
|
891
|
+
return m ? decodeStringLiteral(m[1]) : '';
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/** The `tools.write_stdin(...)` call sites of a code-mode script, with their session id and chars. */
|
|
895
|
+
function writeStdinSites(script) {
|
|
896
|
+
return execCallWindows(script)
|
|
897
|
+
.filter((site) => site.name === 'write_stdin')
|
|
898
|
+
.map((site) => ({ sessionId: literalSessionId(site.window), chars: literalChars(site.window) }));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function unescapeJs(s) { return s.includes('\n') ? s : s.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\"/g, '"').replace(/\\\\/g, '\\'); }
|
|
902
|
+
|
|
903
|
+
function patchStats(text, rec) {
|
|
904
|
+
let i = 0;
|
|
905
|
+
while ((i = text.indexOf('*** Begin Patch', i)) >= 0) {
|
|
906
|
+
const j = text.indexOf('*** End Patch', i); const body = unescapeJs(text.slice(i, j < 0 ? undefined : j)); i = j < 0 ? text.length : j + 1;
|
|
907
|
+
let cur = null;
|
|
908
|
+
for (const line of body.split('\n')) {
|
|
909
|
+
const h = /^\*\*\* (Update|Add|Delete) File: (.+)$/.exec(line.trim());
|
|
910
|
+
if (h) { cur = h[2].trim(); rec.files.add(cur); continue; }
|
|
911
|
+
if (!cur) continue;
|
|
912
|
+
const cls = fileClass(cur);
|
|
913
|
+
if (line.startsWith('+') && !line.startsWith('+++')) rec.lines[cls] = (rec.lines[cls] || [0, 0]), rec.lines[cls][0]++;
|
|
914
|
+
else if (line.startsWith('-') && !line.startsWith('---')) rec.lines[cls] = (rec.lines[cls] || [0, 0]), rec.lines[cls][1]++;
|
|
915
|
+
}
|
|
916
|
+
rec.cat.edit = (rec.cat.edit || 0) + 1;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function classifyCommand(cmd, rec) {
|
|
921
|
+
const c = String(cmd || '');
|
|
922
|
+
if (!c) return;
|
|
923
|
+
if (/apply_patch/.test(c) && c.includes('*** Begin Patch')) { patchStats(c, rec); return; }
|
|
924
|
+
if (TEST_RE.test(c)) rec.cat.test = (rec.cat.test || 0) + 1;
|
|
925
|
+
else if (COMMIT_RE.test(c)) rec.cat.ship = (rec.cat.ship || 0) + 1;
|
|
926
|
+
else if (REVIEW_RE.test(c)) rec.cat.review = (rec.cat.review || 0) + 1;
|
|
927
|
+
else if (READ_RE.test(c)) rec.cat.read = (rec.cat.read || 0) + 1;
|
|
928
|
+
else rec.cat.bash = (rec.cat.bash || 0) + 1;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function addTool(name, input, rec) {
|
|
932
|
+
const base = String(name || '').replace(/^mcp__[a-z_-]+__/i, '');
|
|
933
|
+
if (DELEG[base]) { rec.cat[DELEG[base]] = (rec.cat[DELEG[base]] || 0) + 1; return; }
|
|
934
|
+
switch (base) {
|
|
935
|
+
case 'Edit': case 'MultiEdit': case 'Write': case 'NotebookEdit': {
|
|
936
|
+
const fp = input && (input.file_path || input.notebook_path); if (fp) rec.files.add(fp);
|
|
937
|
+
const cls = fileClass(fp);
|
|
938
|
+
const edits = base === 'MultiEdit' ? (input.edits || []) : base === 'Write' ? [{ old_string: '', new_string: input && input.content }] : [{ old_string: input && input.old_string, new_string: input && (input.new_string ?? input.new_source) }];
|
|
939
|
+
for (const e of edits) { const d = lineDiff(e.old_string, e.new_string); rec.lines[cls] = rec.lines[cls] || [0, 0]; rec.lines[cls][0] += d.add; rec.lines[cls][1] += d.del; }
|
|
940
|
+
rec.cat[cls === 'brief' ? 'brief' : 'edit'] = (rec.cat[cls === 'brief' ? 'brief' : 'edit'] || 0) + 1;
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
case 'apply_patch': patchStats(typeof input === 'string' ? input : JSON.stringify(input || ''), rec); return;
|
|
944
|
+
case 'Read': case 'Grep': case 'Glob': case 'LS': case 'NotebookRead': rec.cat.read = (rec.cat.read || 0) + 1; return;
|
|
945
|
+
case 'Bash': case 'shell': case 'exec_command': case 'local_shell': {
|
|
946
|
+
const cmd = input && (input.command ?? input.cmd);
|
|
947
|
+
classifyCommand(Array.isArray(cmd) ? cmd.join(' ') : cmd, rec); return;
|
|
948
|
+
}
|
|
949
|
+
case 'write_stdin': case 'BashOutput': case 'KillShell': case 'Monitor': case 'sleep': case 'ScheduleWakeup': case 'clock__curr_time': rec.cat.poll = (rec.cat.poll || 0) + 1; return;
|
|
950
|
+
default:
|
|
951
|
+
if (/search_graph|search_code|get_code_snippet|trace_path|query_graph|get_architecture|codebase/i.test(name)) { rec.cat.read = (rec.cat.read || 0) + 1; return; }
|
|
952
|
+
if (/^(browser_batch|computer|javascript_tool|navigate|tabs_[a-z_]+|find|get_page_text|read_page|form_input|read_console_messages|read_network_requests|gif_creator|upload_image|resize_window)$/.test(base)) { rec.cat.browser = (rec.cat.browser || 0) + 1; return; }
|
|
953
|
+
if (/^(WebFetch|WebSearch|web__run|web_search)$/.test(base)) { rec.cat.web = (rec.cat.web || 0) + 1; return; }
|
|
954
|
+
if (/^walle_|lookup_context|search_memories|context_hint/.test(base)) { rec.cat.memory = (rec.cat.memory || 0) + 1; return; }
|
|
955
|
+
rec.cat.other = (rec.cat.other || 0) + 1; rec.other[base] = (rec.other[base] || 0) + 1;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/** Codex code-mode `exec`: JS text calling tools.<name>({...}). Extract inner calls. */
|
|
960
|
+
function addExec(code, rec) {
|
|
961
|
+
const s = String(code || '');
|
|
962
|
+
const re = /tools\.([A-Za-z0-9_]+)\s*\(/g; let m;
|
|
963
|
+
let any = false;
|
|
964
|
+
while ((m = re.exec(s))) {
|
|
965
|
+
any = true;
|
|
966
|
+
const name = m[1];
|
|
967
|
+
if (name === 'exec_command' || name === 'shell') {
|
|
968
|
+
classifyCommand(commandLiteralOf(s, m.index), rec);
|
|
969
|
+
} else if (name === 'apply_patch') {
|
|
970
|
+
patchStats(s.slice(m.index), rec);
|
|
971
|
+
} else addTool(name, null, rec);
|
|
972
|
+
}
|
|
973
|
+
if (!any && s.includes('*** Begin Patch')) patchStats(s, rec);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/** The per-call tool accumulator ref/parse-tools.js fills in (`cat`, `lines`, `files`, `other`). */
|
|
977
|
+
function blankCall() {
|
|
978
|
+
return { cat: {}, lines: {}, files: new Set(), other: {} };
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// ref/calls.js `category()`, precedence included. Only `read / search` drives a burst.
|
|
982
|
+
const WAIT_FAMILIES = new Set(['worker_wait', 'worker_status', 'wait_sub', 'poll']);
|
|
983
|
+
|
|
984
|
+
function categoryOf(call, last) {
|
|
985
|
+
const own = Object.keys(call.cat || {}); const s = new Set(own); const L = call.lines || {};
|
|
986
|
+
if (!own.length) return last ? 'reply to user' : 'text only, mid-turn';
|
|
987
|
+
if (s.has('delegate') || s.has('spawn_sub') || s.has('brief')) return 'brief / launch delegate';
|
|
988
|
+
if (s.has('worker_resume') || s.has('msg_sub') || s.has('worker_stop')) return 'send-back / steer delegate';
|
|
989
|
+
if (own.every((k) => WAIT_FAMILIES.has(k))) return 'wait / poll';
|
|
990
|
+
if (s.has('edit') && (L.code || L.test)) return 'edit code/tests';
|
|
991
|
+
if (s.has('edit')) return 'edit docs/other';
|
|
992
|
+
if (s.has('ship')) return 'commit / merge';
|
|
993
|
+
if (s.has('test')) return 'run tests / gate';
|
|
994
|
+
if (s.has('review')) return 'review diff / log';
|
|
995
|
+
if (s.has('read')) return 'read / search';
|
|
996
|
+
if (s.has('bash')) return 'other shell';
|
|
997
|
+
if (s.has('browser')) return 'browser';
|
|
998
|
+
if (s.has('memory') || s.has('web')) return 'memory / web';
|
|
999
|
+
if (s.has('handback')) return 'reply to user';
|
|
1000
|
+
return 'other tool';
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Lead calls of the window: `agent_id = ''` and the session is one of the lead sessions (a
|
|
1005
|
+
* top-level provider session — not a worker, not a sub-agent thread). Sessions the ledger knows but
|
|
1006
|
+
* the session index does not are excluded, never guessed in.
|
|
1007
|
+
*/
|
|
1008
|
+
function readLeadCalls(usageDb, args, index) {
|
|
1009
|
+
const rows = [];
|
|
1010
|
+
const statement = usageDb.prepare(`SELECT call_key, provider, session_id, agent_id, ts, model, cost_micros, source_file_id, line_offset
|
|
1011
|
+
FROM usage_calls WHERE ts >= ? AND ts < ? ORDER BY session_id, ts`);
|
|
1012
|
+
for (const row of statement.iterate(args.w0, args.w1)) {
|
|
1013
|
+
if (String(row.agent_id || '') !== '') continue;
|
|
1014
|
+
const sessionId = String(row.session_id);
|
|
1015
|
+
if (index.leadSessions && !index.leadSessions.has(sessionId)) continue;
|
|
1016
|
+
if (index.threadIds.has(sessionId)) continue;
|
|
1017
|
+
rows.push({
|
|
1018
|
+
sessionId,
|
|
1019
|
+
provider: String(row.provider),
|
|
1020
|
+
model: row.model === null || row.model === undefined ? '' : String(row.model),
|
|
1021
|
+
ts: Number(row.ts),
|
|
1022
|
+
costMicros: Number(row.cost_micros) || 0,
|
|
1023
|
+
sourceFileId: Number(row.source_file_id),
|
|
1024
|
+
lineOffset: Number(row.line_offset),
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
return rows;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function sourcePathsByFileId(usageDb) {
|
|
1031
|
+
const map = new Map();
|
|
1032
|
+
for (const row of usageDb.prepare('SELECT file_id, source_path FROM usage_files').iterate()) {
|
|
1033
|
+
map.set(Number(row.file_id), String(row.source_path));
|
|
1034
|
+
}
|
|
1035
|
+
return map;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Reads the categories of the given calls out of their vendor transcripts. A call's reply is the
|
|
1040
|
+
* record at its ledger line offset (Claude: the assistant message with its tool_use blocks; Codex:
|
|
1041
|
+
* the token_usage_record, whose response items are the tool calls that preceded it).
|
|
1042
|
+
*/
|
|
1043
|
+
async function categoriesForCalls(calls, pathsByFileId) {
|
|
1044
|
+
const byPath = new Map();
|
|
1045
|
+
for (const call of calls) {
|
|
1046
|
+
const sourcePath = pathsByFileId.get(call.sourceFileId);
|
|
1047
|
+
if (!sourcePath) continue;
|
|
1048
|
+
if (!byPath.has(sourcePath)) byPath.set(sourcePath, new Map());
|
|
1049
|
+
byPath.get(sourcePath).set(call.lineOffset, call);
|
|
1050
|
+
}
|
|
1051
|
+
for (const [transcriptPath, offsets] of byPath) {
|
|
1052
|
+
if (!fs.existsSync(transcriptPath)) {
|
|
1053
|
+
for (const call of offsets.values()) call.category = null;
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
await scanTranscriptForCategories(transcriptPath, offsets);
|
|
1057
|
+
}
|
|
1058
|
+
return calls;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
async function scanTranscriptForCategories(transcriptPath, offsets) {
|
|
1062
|
+
let open = blankCall(); // the tools of the response that is still open (Codex)
|
|
1063
|
+
let byteOffset = 0;
|
|
1064
|
+
const stream = createTranscriptReadStream(transcriptPath, {});
|
|
1065
|
+
const lines = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1066
|
+
for await (const line of lines) {
|
|
1067
|
+
const lineStart = byteOffset;
|
|
1068
|
+
byteOffset += Buffer.byteLength(line, 'utf8') + 1;
|
|
1069
|
+
const wanted = offsets.get(lineStart);
|
|
1070
|
+
if (line.length > MAX_LINE) { if (wanted) wanted.category = null; continue; }
|
|
1071
|
+
const record = parseJson(line);
|
|
1072
|
+
if (!record) { if (wanted) wanted.category = null; continue; }
|
|
1073
|
+
const payload = record.payload || {};
|
|
1074
|
+
if (record.type === 'assistant') {
|
|
1075
|
+
// Claude: the tools are the assistant message's tool_use blocks (parse-tools.js groups them by
|
|
1076
|
+
// message id; the ledger points at the message line).
|
|
1077
|
+
const call = blankCall();
|
|
1078
|
+
if (record.message && Array.isArray(record.message.content)) {
|
|
1079
|
+
for (const block of record.message.content) if (block && block.type === 'tool_use') addTool(block.name, block.input, call);
|
|
1080
|
+
}
|
|
1081
|
+
if (wanted) wanted.category = categoryOf(call, false);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (wanted) {
|
|
1085
|
+
// Codex: the reply's tool calls are the response items that preceded this usage record
|
|
1086
|
+
// (`token_usage_record`, or the `token_count` event the ledger offsets point at).
|
|
1087
|
+
wanted.category = categoryOf(open, false);
|
|
1088
|
+
open = blankCall();
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
collectCodexTools(record, payload, open);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** The tool calls one Codex rollout record carries (parse-tools.js's response_item switch). */
|
|
1096
|
+
function collectCodexTools(record, payload, call) {
|
|
1097
|
+
if (record.type !== 'response_item') return;
|
|
1098
|
+
if (payload.type === 'custom_tool_call') {
|
|
1099
|
+
if (payload.name === 'exec') addExec(payload.input, call);
|
|
1100
|
+
else addTool(payload.name, payload.input, call);
|
|
1101
|
+
} else if (payload.type === 'function_call') {
|
|
1102
|
+
addTool(payload.name, parseJson(payload.arguments || '{}') || {}, call);
|
|
1103
|
+
} else if (payload.type === 'local_shell_call') {
|
|
1104
|
+
classifyCommand((payload.action && payload.action.command || []).join(' '), call);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Runs of >=3 consecutive read/search lead calls, per cmpct/scen.js: the session's calls are taken in
|
|
1110
|
+
* timestamp order, a run is a maximal stretch of `read / search` calls, it ends at any other call (a
|
|
1111
|
+
* call whose reply we could not read counts as "not read / search", never as a read) or at a gap of
|
|
1112
|
+
* 20 minutes or more, and only runs of 3 or more count.
|
|
1113
|
+
*/
|
|
1114
|
+
function findBursts(calls) {
|
|
1115
|
+
const bySession = new Map();
|
|
1116
|
+
for (const call of calls) {
|
|
1117
|
+
if (!bySession.has(call.sessionId)) bySession.set(call.sessionId, []);
|
|
1118
|
+
bySession.get(call.sessionId).push(call);
|
|
1119
|
+
}
|
|
1120
|
+
let bursts = 0;
|
|
1121
|
+
let callsAfterFirst = 0;
|
|
1122
|
+
let costMicros = 0;
|
|
1123
|
+
for (const list of bySession.values()) {
|
|
1124
|
+
list.sort((a, b) => a.ts - b.ts);
|
|
1125
|
+
let i = 0;
|
|
1126
|
+
while (i < list.length) {
|
|
1127
|
+
if (list[i].category !== 'read / search') { i += 1; continue; }
|
|
1128
|
+
let j = i;
|
|
1129
|
+
while (j + 1 < list.length && list[j + 1].category === 'read / search' && list[j + 1].ts - list[j].ts < 20 * 60e3) j += 1;
|
|
1130
|
+
if (j - i + 1 >= 3) {
|
|
1131
|
+
bursts += 1;
|
|
1132
|
+
for (let k = i + 1; k <= j; k += 1) { callsAfterFirst += 1; costMicros += list[k].costMicros; }
|
|
1133
|
+
}
|
|
1134
|
+
i = j + 1;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return { bursts, callsAfterFirst, costMicros };
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/** Review-profile hand-offs launched from lead sessions in the window, split by lane. */
|
|
1141
|
+
function reviewHandOffs(usageDb, workerLaneById, args) {
|
|
1142
|
+
const byLane = new Map([['routed', 0], ['native', 0], ['unknown', 0]]);
|
|
1143
|
+
const statement = usageDb.prepare('SELECT session_id, worker_ctm_session_id, ts FROM usage_delegations WHERE ts >= ? AND ts < ?');
|
|
1144
|
+
for (const row of statement.iterate(args.w0, args.w1)) {
|
|
1145
|
+
const worker = workerLaneById.get(String(row.worker_ctm_session_id));
|
|
1146
|
+
if (!worker || !worker.review) continue;
|
|
1147
|
+
const lane = worker.routed ? 'routed' : 'native';
|
|
1148
|
+
byLane.set(lane, byLane.get(lane) + 1);
|
|
1149
|
+
}
|
|
1150
|
+
return byLane;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/* ------------------------------------------------------------------ *
|
|
1154
|
+
* Output
|
|
1155
|
+
* ------------------------------------------------------------------ */
|
|
1156
|
+
|
|
1157
|
+
function pad(text, width) { return String(text).padEnd(width); }
|
|
1158
|
+
function padLeft(text, width) { return String(text).padStart(width); }
|
|
1159
|
+
|
|
1160
|
+
function waitTableLines(rowTally, total, nothingTotal) {
|
|
1161
|
+
const lines = [];
|
|
1162
|
+
lines.push(`${pad('what the lead waited on [how called]', 44)}${padLeft('calls', 7)} ${padLeft('wake carried nothing', 14)} outcomes`);
|
|
1163
|
+
for (const [label, entry] of sortedEntries(rowTally)) {
|
|
1164
|
+
const pct = entry.calls ? Math.round((100 * entry.nothing) / entry.calls) : 0;
|
|
1165
|
+
const outcomes = [...entry.outcomes].sort((a, b) => b[1] - a[1]).map(([name, count]) => `${name} ${count}`).join(' · ');
|
|
1166
|
+
lines.push(`${pad(label, 44)}${padLeft(entry.calls, 7)} ${padLeft(`${pct}%`, 14)} ${outcomes}`);
|
|
1167
|
+
}
|
|
1168
|
+
const totalPct = total ? Math.round((100 * nothingTotal) / total) : 0;
|
|
1169
|
+
lines.push(`${pad('Total', 44)}${padLeft(total, 7)} ${padLeft(`${totalPct}% (${nothingTotal})`, 14)}`);
|
|
1170
|
+
return lines;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function tallyWaitRows(rows) {
|
|
1174
|
+
const table = new Map();
|
|
1175
|
+
for (const row of rows) {
|
|
1176
|
+
const entry = table.get(row.rowLabel) || { calls: 0, nothing: 0, outcomes: new Map() };
|
|
1177
|
+
entry.calls += 1;
|
|
1178
|
+
if (row.outcome.startsWith('NOTHING')) entry.nothing += 1;
|
|
1179
|
+
entry.outcomes.set(row.outcome, (entry.outcomes.get(row.outcome) || 0) + 1);
|
|
1180
|
+
table.set(row.rowLabel, entry);
|
|
1181
|
+
}
|
|
1182
|
+
return table;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function sortedEntries(map) {
|
|
1186
|
+
return [...map].sort((a, b) => b[1].calls - a[1].calls);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function histogramLines(map, header, limit) {
|
|
1190
|
+
const parts = [...map].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([key, count]) => `${key} ${count}`);
|
|
1191
|
+
return `${header}: ${parts.length ? parts.join(' · ') : '(none)'}`;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
async function runCensus(args) {
|
|
1195
|
+
const dataDir = resolveDataDir(args.dataDir);
|
|
1196
|
+
const usageDb = openReadOnlyDb(path.join(dataDir, 'usage.db'));
|
|
1197
|
+
const coreDb = openReadOnlyDb(path.join(dataDir, 'core.db'));
|
|
1198
|
+
try {
|
|
1199
|
+
const { threadIds, workerLaneById, leadSessions } = readWorkerIndex(coreDb);
|
|
1200
|
+
const leadThreads = codexLeadThreads(usageDb, threadIds, args);
|
|
1201
|
+
// WP-3's tracker is created only for `--command-outcomes`, so every other run streams and
|
|
1202
|
+
// reports exactly the records it always did.
|
|
1203
|
+
const commandRuns = args.commandOutcomes ? createCommandRunTracker() : null;
|
|
1204
|
+
const waits = await runWaitPass(commandRuns ? { ...args, commandRuns } : args, leadThreads);
|
|
1205
|
+
const table = tallyWaitRows(waits.rows);
|
|
1206
|
+
let total = 0;
|
|
1207
|
+
let nothingTotal = 0;
|
|
1208
|
+
for (const entry of table.values()) { total += entry.calls; nothingTotal += entry.nothing; }
|
|
1209
|
+
return { dataDir, waits, table, total, nothingTotal, workerLaneById, workerThreadIds: threadIds, leadSessions, commandRuns, usageDb, coreDb };
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
usageDb.close();
|
|
1212
|
+
coreDb.close();
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
module.exports = {
|
|
1218
|
+
parseArgs,
|
|
1219
|
+
resolveDataDir,
|
|
1220
|
+
openReadOnlyDb,
|
|
1221
|
+
readWorkerIndex,
|
|
1222
|
+
codexLeadThreads,
|
|
1223
|
+
codexRolloutFiles,
|
|
1224
|
+
waitLabelsOf,
|
|
1225
|
+
waitRowLabel,
|
|
1226
|
+
waitOutcome,
|
|
1227
|
+
emitWait,
|
|
1228
|
+
mcpToolResultText,
|
|
1229
|
+
percentile,
|
|
1230
|
+
toolLabel,
|
|
1231
|
+
execYieldOf,
|
|
1232
|
+
scanRolloutForWaits,
|
|
1233
|
+
categoryOf,
|
|
1234
|
+
addTool,
|
|
1235
|
+
addExec,
|
|
1236
|
+
classifyCommand,
|
|
1237
|
+
fileClass,
|
|
1238
|
+
blankCall,
|
|
1239
|
+
execToolNames,
|
|
1240
|
+
execCommandStrings,
|
|
1241
|
+
writeStdinSites,
|
|
1242
|
+
commandFamily,
|
|
1243
|
+
createCommandRunTracker,
|
|
1244
|
+
describeLeadAction,
|
|
1245
|
+
trackCommandRun,
|
|
1246
|
+
finishCommandFile,
|
|
1247
|
+
commandOutcomeRows,
|
|
1248
|
+
commandOutcomeLines,
|
|
1249
|
+
findBursts,
|
|
1250
|
+
reviewHandOffs,
|
|
1251
|
+
readLeadCalls,
|
|
1252
|
+
sourcePathsByFileId,
|
|
1253
|
+
categoriesForCalls,
|
|
1254
|
+
scanTranscriptForCategories,
|
|
1255
|
+
tallyWaitRows,
|
|
1256
|
+
sortedEntries,
|
|
1257
|
+
usage,
|
|
1258
|
+
UsageError,
|
|
1259
|
+
runCensus,
|
|
1260
|
+
run,
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
async function run(argv) {
|
|
1264
|
+
const args = parseArgs(argv);
|
|
1265
|
+
if (args.help) { process.stdout.write(`${usage()}\n`); return 0; }
|
|
1266
|
+
const census = await runCensus(args);
|
|
1267
|
+
try {
|
|
1268
|
+
const out = [];
|
|
1269
|
+
out.push(`lead wait census window ${args.since} .. ${args.until} (exclusive)`);
|
|
1270
|
+
out.push(...waitTableLines(census.table, census.total, census.nothingTotal));
|
|
1271
|
+
const sleeps = [...census.waits.sleepDurations].sort((a, b) => a - b);
|
|
1272
|
+
out.push(`sleep duration_ms p10 ${percentile(sleeps, 0.1)} p50 ${percentile(sleeps, 0.5)} p90 ${percentile(sleeps, 0.9)} (n=${sleeps.length})`);
|
|
1273
|
+
out.push(histogramLines(census.waits.sleepNext, 'first tool the lead calls after a sleep', 14));
|
|
1274
|
+
for (const [kind, hist] of [...census.waits.yields].sort()) {
|
|
1275
|
+
const counts = [...hist].sort((a, b) => b[1] - a[1]).map(([v, c]) => `${v === 'default' ? 'default (30,000)' : `${v} ms`} ${c}`);
|
|
1276
|
+
out.push(`code-mode cell yield asked when wrapping ${kind} [inside exec]: ${counts.join(' · ')}`);
|
|
1277
|
+
}
|
|
1278
|
+
const commandRows = args.commandOutcomes ? commandOutcomeRows(census.commandRuns.runs) : null;
|
|
1279
|
+
if (commandRows) {
|
|
1280
|
+
out.push('');
|
|
1281
|
+
out.push(...commandOutcomeLines(commandRows));
|
|
1282
|
+
}
|
|
1283
|
+
const result = {
|
|
1284
|
+
window: { since: args.since, until: args.until },
|
|
1285
|
+
waits: { total: census.total, wake_carried_nothing: census.nothingTotal, rows: [] },
|
|
1286
|
+
sleep: { n: sleeps.length, p10: percentile(sleeps, 0.1), p50: percentile(sleeps, 0.5), p90: percentile(sleeps, 0.9) },
|
|
1287
|
+
sleep_next: Object.fromEntries(census.waits.sleepNext),
|
|
1288
|
+
cell_yield: Object.fromEntries([...census.waits.yields].map(([k, v]) => [k, Object.fromEntries(v)])),
|
|
1289
|
+
rollouts_scanned: census.waits.fileCount,
|
|
1290
|
+
};
|
|
1291
|
+
for (const [label, entry] of sortedEntries(census.table)) {
|
|
1292
|
+
result.waits.rows.push({ label, calls: entry.calls, wake_carried_nothing: entry.nothing, outcomes: Object.fromEntries(entry.outcomes) });
|
|
1293
|
+
}
|
|
1294
|
+
if (commandRows) {
|
|
1295
|
+
result.command_outcomes = commandRows.map((row) => ({
|
|
1296
|
+
family: row.family,
|
|
1297
|
+
n: row.n,
|
|
1298
|
+
unfinished: row.unfinished,
|
|
1299
|
+
acted_on_partial: row.acted,
|
|
1300
|
+
acted_on_partial_pct: Number(row.share.toFixed(1)),
|
|
1301
|
+
kill: row.kill,
|
|
1302
|
+
dur_p50_s: row.dur_p50_s,
|
|
1303
|
+
dur_p90_s: row.dur_p90_s,
|
|
1304
|
+
partial_p50: row.partial_p50,
|
|
1305
|
+
}));
|
|
1306
|
+
}
|
|
1307
|
+
if (args.bursts) {
|
|
1308
|
+
const calls = readLeadCalls(census.usageDb, args, { leadSessions: census.leadSessions, threadIds: census.workerThreadIds });
|
|
1309
|
+
const paths = sourcePathsByFileId(census.usageDb);
|
|
1310
|
+
await categoriesForCalls(calls, paths);
|
|
1311
|
+
const bursts = findBursts(calls);
|
|
1312
|
+
const handOffs = reviewHandOffs(census.usageDb, census.workerLaneById, args);
|
|
1313
|
+
const leadBurstUsd = bursts.bursts ? bursts.costMicros / 1e6 / bursts.bursts : 0;
|
|
1314
|
+
out.push('');
|
|
1315
|
+
out.push('exploration bursts (runs of >=3 consecutive read/search lead calls)');
|
|
1316
|
+
out.push(`bursts ${bursts.bursts}`);
|
|
1317
|
+
out.push(`lead calls inside bursts after the first ${bursts.callsAfterFirst}`);
|
|
1318
|
+
out.push(`lead cost of those calls $${(bursts.costMicros / 1e6).toFixed(0)}; lead_burst_usd $${leadBurstUsd.toFixed(2)}`);
|
|
1319
|
+
const routed = handOffs.get('routed');
|
|
1320
|
+
const native = handOffs.get('native');
|
|
1321
|
+
const perBurst = (n) => (bursts.bursts ? (n / bursts.bursts).toFixed(3) : 'n/a');
|
|
1322
|
+
out.push(`review-profile hand-offs in window: routed ${routed} (${perBurst(routed)} per burst) · native ${native} (${perBurst(native)} per burst)`);
|
|
1323
|
+
result.bursts = {
|
|
1324
|
+
bursts: bursts.bursts,
|
|
1325
|
+
calls_after_first: bursts.callsAfterFirst,
|
|
1326
|
+
cost_usd: bursts.costMicros / 1e6,
|
|
1327
|
+
lead_burst_usd: leadBurstUsd,
|
|
1328
|
+
handoffs: { routed, native, routed_per_burst: bursts.bursts ? routed / bursts.bursts : null, native_per_burst: bursts.bursts ? native / bursts.bursts : null },
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
1332
|
+
if (args.json) fs.writeFileSync(args.json, `${JSON.stringify(result, null, 2)}\n`);
|
|
1333
|
+
} finally {
|
|
1334
|
+
census.usageDb.close();
|
|
1335
|
+
census.coreDb.close();
|
|
1336
|
+
}
|
|
1337
|
+
return 0;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
if (require.main === module) {
|
|
1341
|
+
run(process.argv.slice(2))
|
|
1342
|
+
.then(() => { process.exitCode = 0; })
|
|
1343
|
+
.catch((error) => {
|
|
1344
|
+
if (error instanceof UsageError) process.stderr.write(`lead-wait-census: ${error.message}\n${usage()}\n`);
|
|
1345
|
+
else process.stderr.write(`lead-wait-census: ${error && error.stack ? error.stack : error}\n`);
|
|
1346
|
+
process.exitCode = error instanceof UsageError ? 1 : 2;
|
|
1347
|
+
});
|
|
1348
|
+
}
|