create-walle 0.10.2 → 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 +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- 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/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/db.js +156 -32
- 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 -11
- 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 +84 -1
- 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/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -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 +77 -51
- 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/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- 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 +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -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 +96 -12
- 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-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -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 +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- 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 +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- 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-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.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- 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 +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -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 +36 -10
- 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/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 +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -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 +24 -6
- 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/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- 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/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/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/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 +4 -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 +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- 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/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/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- 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 +1 -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/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -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-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- 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/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/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- 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/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/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- 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 +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- 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 +1 -1
- 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 +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -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-status-ledger.js +106 -4
- 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/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/storage/coding-session-model-store.js +4 -1
- 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/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-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-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- 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/wake-skip-reasons.js +1 -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 +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- 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 +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -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/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- 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 +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- 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 +180 -14
- 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 +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- 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/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- 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 +10 -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-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 +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- 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 +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -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.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- 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 +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -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/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 +5 -0
- 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/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- 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/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- 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/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- 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 +174 -1
- 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/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- 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/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- 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 +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- 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-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -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 +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- 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 +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- 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/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-3a (design doc docs/background-gate-watchers-bounded-observation-2026-10-26.html §5 R7–R10,
|
|
4
|
+
// §6.4, §8 WP-3): the SERVER half of the operator stop for exactly ONE hook-declared background
|
|
5
|
+
// shell of a local session. The UI button is WP-3b.
|
|
6
|
+
//
|
|
7
|
+
// The operator's click is the decision (R8: report by default, stop on a decision). What this
|
|
8
|
+
// module never does is guess: the target is a process group leader (pid === pgid) that is a
|
|
9
|
+
// STRICT descendant of the session's agent pid AND whose argv carries the ledger entry's declared
|
|
10
|
+
// label in the CLI's own `eval '…'` encoding. Zero or several matches, an identity change between
|
|
11
|
+
// two reads, or a target that is the agent's own group all refuse with nothing signalled.
|
|
12
|
+
//
|
|
13
|
+
// Identity is the triple (pid, pgid, startedAt) — never a process name (R7, P6): pgrep-style
|
|
14
|
+
// matching matches its own observer and would signal a second session's identically-worded shell.
|
|
15
|
+
//
|
|
16
|
+
// Escalation is §6.4's ladder and ONLY the ladder: SIGTERM the group, wait the lifecycle's park
|
|
17
|
+
// grace, re-read, SIGKILL the group if any row of that pgid remains, re-read, report survivors.
|
|
18
|
+
// There is no caller-chosen signal. Nothing here removes the ledger entry (R9: the CLI's next
|
|
19
|
+
// Stop/task notification does that) and nothing retries.
|
|
20
|
+
|
|
21
|
+
const { execFile } = require('node:child_process');
|
|
22
|
+
const { lifecycleConfigFromEnv } = require('./session-lifecycle');
|
|
23
|
+
const { requesterFromRequest } = require('./restart-ledger');
|
|
24
|
+
|
|
25
|
+
// §6.5's read, verbatim: one `ps` read carrying the identity triple (lstart = process start time)
|
|
26
|
+
// plus the argv the label is matched against. `-axo` is macOS/BSD; the GNU spelling is the same.
|
|
27
|
+
const PS_STOP_ARGS = Object.freeze(['-axo', 'pid=,ppid=,pgid=,lstart=,command=']);
|
|
28
|
+
const PS_TIMEOUT_MS = 10_000;
|
|
29
|
+
const PS_MAX_BUFFER = 8 * 1024 * 1024;
|
|
30
|
+
|
|
31
|
+
// The response carries a head, not the whole argv: a Claude Code background shell's command line
|
|
32
|
+
// embeds the entire snapshot preamble and can be tens of kilobytes.
|
|
33
|
+
const ARGV_HEAD_MAX = 200;
|
|
34
|
+
|
|
35
|
+
// Inside the CLI's `eval '…'` a single quote is encoded as '"'"' (observed in a live argv,
|
|
36
|
+
// §"Measured facts"). The needle deliberately has no closing quote: the ledger label is the
|
|
37
|
+
// command truncated to 160 chars, so the real argv is normally LONGER than the needle.
|
|
38
|
+
const EVAL_QUOTE_ESCAPE = `'"'"'`;
|
|
39
|
+
|
|
40
|
+
const STOP_BODY_LIMIT_BYTES = 1024;
|
|
41
|
+
const AUDIT_KIND = 'status-background-stopped';
|
|
42
|
+
|
|
43
|
+
// One stop per (session, task) at a time. Postgres-free and process-free on purpose: the key is
|
|
44
|
+
// only ever held across an awaited resolve + ladder, and it is released in `finally`.
|
|
45
|
+
const inFlightStops = new Set();
|
|
46
|
+
|
|
47
|
+
// ` 44398 51583 44398 Mon Oct 26 17:58:01 2026 /bin/zsh -c source …`
|
|
48
|
+
// lstart is FIVE whitespace-separated tokens; the argv is everything after them, verbatim except
|
|
49
|
+
// for the whitespace run ps uses to align the column. Day-of-month is space-padded by ps
|
|
50
|
+
// ("Mon Oct 6 …"), which is why lstart is token-matched and then normalised rather than sliced.
|
|
51
|
+
const STOP_ROW_RE = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\S+\s+\S+\s+\S+)(?:\s+(.*))?$/;
|
|
52
|
+
|
|
53
|
+
function parseStopRows(text) {
|
|
54
|
+
const rows = [];
|
|
55
|
+
for (const line of String(text == null ? '' : text).split(/\r?\n/)) {
|
|
56
|
+
if (!line.trim()) continue;
|
|
57
|
+
const match = STOP_ROW_RE.exec(line);
|
|
58
|
+
if (!match) continue;
|
|
59
|
+
const pid = Number(match[1]);
|
|
60
|
+
const ppid = Number(match[2]);
|
|
61
|
+
const pgid = Number(match[3]);
|
|
62
|
+
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(ppid) || !Number.isSafeInteger(pgid)) continue;
|
|
63
|
+
rows.push({
|
|
64
|
+
pid,
|
|
65
|
+
ppid,
|
|
66
|
+
pgid,
|
|
67
|
+
startedAt: match[4].trim().replace(/\s+/g, ' '),
|
|
68
|
+
command: String(match[5] == null ? '' : match[5]).trim(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return rows;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The substring every Claude Code background shell's argv carries: the wrapper's `eval '<command>'`
|
|
75
|
+
// with the command's own single quotes in the shell's close/double/open encoding.
|
|
76
|
+
function evalNeedle(label) {
|
|
77
|
+
return `eval '${String(label == null ? '' : label).replace(/'/g, EVAL_QUOTE_ESCAPE)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function strictDescendants(byParent, agentPid) {
|
|
81
|
+
const seen = new Set([agentPid]);
|
|
82
|
+
const queue = [...(byParent.get(agentPid) || [])];
|
|
83
|
+
const out = [];
|
|
84
|
+
while (queue.length) {
|
|
85
|
+
const pid = queue.shift();
|
|
86
|
+
// A cyclic or duplicated ppid edge must not turn the walk into an infinite loop.
|
|
87
|
+
if (seen.has(pid)) continue;
|
|
88
|
+
seen.add(pid);
|
|
89
|
+
out.push(pid);
|
|
90
|
+
queue.push(...(byParent.get(pid) || []));
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// §6.4's candidate/target view of a row — what the 409 and the 200 both carry.
|
|
96
|
+
function candidateOf(row) {
|
|
97
|
+
return {
|
|
98
|
+
pid: row.pid,
|
|
99
|
+
pgid: row.pgid,
|
|
100
|
+
startedAt: row.startedAt,
|
|
101
|
+
argvHead: String(row.command || '').slice(0, ARGV_HEAD_MAX),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// rows → the single group leader that is this session's declared shell, or a typed refusal.
|
|
106
|
+
function resolveBackgroundTarget(rows, { agentPid, label } = {}) {
|
|
107
|
+
const agent = Number(agentPid);
|
|
108
|
+
const byPid = new Map();
|
|
109
|
+
const byParent = new Map();
|
|
110
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
111
|
+
if (!Number.isSafeInteger(row && row.pid)) continue;
|
|
112
|
+
if (!byPid.has(row.pid)) byPid.set(row.pid, row);
|
|
113
|
+
const kids = byParent.get(row.ppid);
|
|
114
|
+
if (kids) kids.push(row.pid);
|
|
115
|
+
else byParent.set(row.ppid, [row.pid]);
|
|
116
|
+
}
|
|
117
|
+
// The agent's own group is never a target (R7 refusal): its row when present, the pid itself
|
|
118
|
+
// otherwise — the CLI is a group leader of its own, so the fallback is the same value.
|
|
119
|
+
const agentRow = byPid.get(agent);
|
|
120
|
+
const agentPgid = agentRow ? Number(agentRow.pgid) : agent;
|
|
121
|
+
const needle = evalNeedle(label);
|
|
122
|
+
|
|
123
|
+
const candidates = [];
|
|
124
|
+
for (const pid of strictDescendants(byParent, agent)) {
|
|
125
|
+
const row = byPid.get(pid);
|
|
126
|
+
if (!row) continue;
|
|
127
|
+
// A group LEADER only: signalling a non-leader would signal whichever group it happened to
|
|
128
|
+
// be in, i.e. someone else's (a `sleep` under bounded.js leads its own group, E4).
|
|
129
|
+
if (Number(row.pid) !== Number(row.pgid)) continue;
|
|
130
|
+
if (!String(row.command || '').includes(needle)) continue;
|
|
131
|
+
candidates.push(row);
|
|
132
|
+
}
|
|
133
|
+
if (!candidates.length) return { error: 'task_process_not_found' };
|
|
134
|
+
// Refusal outranks ambiguity: a candidate inside the agent's own group means the label matched
|
|
135
|
+
// something that is not a background task at all, and the agent's group is exactly what park
|
|
136
|
+
// and the freeze machinery own.
|
|
137
|
+
if (candidates.some((row) => Number(row.pgid) === agentPgid)) return { error: 'target_is_agent' };
|
|
138
|
+
if (candidates.length > 1) {
|
|
139
|
+
return { error: 'ambiguous', candidates: candidates.map(candidateOf).sort((a, b) => a.pid - b.pid) };
|
|
140
|
+
}
|
|
141
|
+
return { target: candidateOf(candidates[0]) };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function targetIdentity(target) {
|
|
145
|
+
return { pid: target.pid, pgid: target.pgid, startedAt: target.startedAt };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The same triple compared across two reads, or against the chip's `expect`. pid/pgid arrive as
|
|
149
|
+
// JSON numbers or strings depending on the client, so they are compared numerically; startedAt is
|
|
150
|
+
// ps's own text and is compared verbatim.
|
|
151
|
+
function sameIdentity(left, right) {
|
|
152
|
+
return Number(left && left.pid) === Number(right && right.pid)
|
|
153
|
+
&& Number(left && left.pgid) === Number(right && right.pgid)
|
|
154
|
+
&& String(left && left.startedAt) === String(right && right.startedAt);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function groupMembers(rows, pgid) {
|
|
158
|
+
return (Array.isArray(rows) ? rows : []).filter((row) => Number(row && row.pgid) === Number(pgid));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Production sleep. `deps.sleep` is the seam the ladder tests use so no test waits 8 s.
|
|
162
|
+
function defaultSleep(ms) {
|
|
163
|
+
/* deadline-exempt: the ladder's one-shot TERM grace (parkTermGraceMs), awaited inside a single operator stop request and held at most once per (session, task) by the 423 lock; not a standing recovery timer */
|
|
164
|
+
return new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// true = the signal was delivered; false = the group was already gone (ESRCH, a success of a
|
|
168
|
+
// kind: nothing to escalate to). Anything else is a real failure the operator must see.
|
|
169
|
+
function signalGroup(kill, pgid, signal) {
|
|
170
|
+
try {
|
|
171
|
+
kill(-pgid, signal);
|
|
172
|
+
return true;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error && error.code === 'ESRCH') return false;
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// §6.4's ladder, in order: resolve, re-resolve and require the SAME identity triple, SIGTERM the
|
|
180
|
+
// group, wait `graceMs`, re-read, SIGKILL the group if any row of that pgid is left, re-read,
|
|
181
|
+
// report survivors. `readRows` / `kill` / `graceMs` / `sleep` are seams (production: defaultReadRows
|
|
182
|
+
// / process.kill / lifecycleConfigFromEnv().parkTermGraceMs / defaultSleep).
|
|
183
|
+
async function stopBackgroundTask({
|
|
184
|
+
readRows,
|
|
185
|
+
kill = process.kill,
|
|
186
|
+
agentPid,
|
|
187
|
+
label,
|
|
188
|
+
graceMs,
|
|
189
|
+
sleep = defaultSleep,
|
|
190
|
+
expect = null,
|
|
191
|
+
} = {}) {
|
|
192
|
+
const first = resolveBackgroundTarget(await readRows(), { agentPid, label });
|
|
193
|
+
if (first.error) return first;
|
|
194
|
+
// The chip's last view, checked before anything is signalled: a caller naming a different
|
|
195
|
+
// process than the one we just resolved is looking at a stale row.
|
|
196
|
+
if (expect && !sameIdentity(expect, first.target)) {
|
|
197
|
+
return { error: 'identity_changed', expected: expect, observed: targetIdentity(first.target) };
|
|
198
|
+
}
|
|
199
|
+
const second = resolveBackgroundTarget(await readRows(), { agentPid, label });
|
|
200
|
+
if (second.error) return second;
|
|
201
|
+
if (!sameIdentity(second.target, first.target)) {
|
|
202
|
+
return { error: 'identity_changed', expected: targetIdentity(first.target), observed: targetIdentity(second.target) };
|
|
203
|
+
}
|
|
204
|
+
const target = second.target;
|
|
205
|
+
const grace = Number.isFinite(Number(graceMs)) ? Number(graceMs) : lifecycleConfigFromEnv().parkTermGraceMs;
|
|
206
|
+
const signalled = [];
|
|
207
|
+
let delivered;
|
|
208
|
+
try {
|
|
209
|
+
delivered = signalGroup(kill, target.pgid, 'SIGTERM');
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return { error: 'signal_failed', message: String((error && error.message) || error), target, signalled };
|
|
212
|
+
}
|
|
213
|
+
// ESRCH on TERM: the group left between the read and the signal, so there is nothing to
|
|
214
|
+
// escalate to — no KILL, no survivors.
|
|
215
|
+
if (!delivered) return { target, signalled, survivors: [], absent: true };
|
|
216
|
+
signalled.push('SIGTERM');
|
|
217
|
+
await sleep(grace);
|
|
218
|
+
let members = groupMembers(await readRows(), target.pgid);
|
|
219
|
+
if (members.length) {
|
|
220
|
+
try {
|
|
221
|
+
if (signalGroup(kill, target.pgid, 'SIGKILL')) signalled.push('SIGKILL');
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return { error: 'signal_failed', message: String((error && error.message) || error), target, signalled };
|
|
224
|
+
}
|
|
225
|
+
members = groupMembers(await readRows(), target.pgid);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
target,
|
|
229
|
+
signalled,
|
|
230
|
+
// §6.4's survivor row: `comm` is the basename of the first argv token (`/bin/zsh -c …` → zsh).
|
|
231
|
+
survivors: members.map((row) => ({
|
|
232
|
+
pid: row.pid,
|
|
233
|
+
pgid: row.pgid,
|
|
234
|
+
comm: (String(row.command || '').trim().split(/\s+/)[0] || '').split('/').pop(),
|
|
235
|
+
})),
|
|
236
|
+
absent: false,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function defaultReadRows() {
|
|
241
|
+
const binary = process.platform === 'darwin' ? '/bin/ps' : 'ps';
|
|
242
|
+
// The CALLBACK contract, deliberately not `promisify(execFile)`: a wrapped `child_process`
|
|
243
|
+
// (the hermetic test runner preloads exactly such a wrapper) keeps the callback but loses
|
|
244
|
+
// util.promisify.custom, and promisify then resolves with the raw stdout STRING — `{ stdout }`
|
|
245
|
+
// is undefined and every read comes back empty.
|
|
246
|
+
const stdout = await new Promise((resolve, reject) => {
|
|
247
|
+
execFile(binary, PS_STOP_ARGS, { timeout: PS_TIMEOUT_MS, maxBuffer: PS_MAX_BUFFER }, (error, out) => {
|
|
248
|
+
if (error) { reject(error); return; }
|
|
249
|
+
resolve(String(out));
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
return parseStopRows(stdout);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function decodeParam(value) {
|
|
256
|
+
const raw = value == null ? '' : String(value);
|
|
257
|
+
return decodeURIComponent(raw);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// POST /api/sessions/:id/background/:taskId/stop (§6.4)
|
|
261
|
+
//
|
|
262
|
+
// deps are lib/api/sessions.js's `_deps`: getSession, sendJsonResponse, readJsonRequestBody,
|
|
263
|
+
// recordSessionDiagnostic. `deps.readRows` / `deps.kill` / `deps.graceMs` / `deps.sleep` are TEST
|
|
264
|
+
// SEAMS only — production passes none of them.
|
|
265
|
+
async function handleStopRequest(req, res, params, deps = {}) {
|
|
266
|
+
const send = (status, body) => deps.sendJsonResponse(res, status, body);
|
|
267
|
+
let sessionId = '';
|
|
268
|
+
let taskId = '';
|
|
269
|
+
try {
|
|
270
|
+
sessionId = decodeParam(params && params['1']);
|
|
271
|
+
taskId = decodeParam(params && params['2']);
|
|
272
|
+
} catch {
|
|
273
|
+
/* expected: malformed percent-encoding is the client's error, not a stop decision */
|
|
274
|
+
return send(400, { error: 'invalid_id' });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const session = deps.getSession(sessionId);
|
|
278
|
+
if (!session) return send(404, { error: 'session_not_found' });
|
|
279
|
+
|
|
280
|
+
const entry = session._statusLedger && session._statusLedger.background
|
|
281
|
+
? session._statusLedger.background.get(taskId)
|
|
282
|
+
: undefined;
|
|
283
|
+
if (!entry) return send(404, { error: 'task_not_declared' });
|
|
284
|
+
// R4's declared set also carries non-shell work (subagents). Only a shell is a process tree.
|
|
285
|
+
if (entry.type !== 'shell') return send(409, { error: 'not_a_shell' });
|
|
286
|
+
|
|
287
|
+
// server.js's `_lifecycleProcessGroupId` derivation, re-stated here because server.js is frozen
|
|
288
|
+
// and this route must not reach into it.
|
|
289
|
+
const agentPid = Number((session.ptyProcess && session.ptyProcess.pid) || session.pid);
|
|
290
|
+
if (!Number.isSafeInteger(agentPid) || agentPid <= 1) return send(409, { error: 'no_agent_process' });
|
|
291
|
+
|
|
292
|
+
// The body is optional (`expect` = the chip's last view). An EMPTY body is the normal click;
|
|
293
|
+
// a non-empty body that does not parse is a request we could not read, so nothing is signalled.
|
|
294
|
+
let expect = null;
|
|
295
|
+
if (deps.readJsonRequestBody) {
|
|
296
|
+
try {
|
|
297
|
+
const body = await deps.readJsonRequestBody(req, { limitBytes: STOP_BODY_LIMIT_BYTES });
|
|
298
|
+
expect = body && typeof body === 'object' && body.expect ? body.expect : null;
|
|
299
|
+
} catch {
|
|
300
|
+
/* expected: an unreadable body never defaults to a target */
|
|
301
|
+
return send(400, { error: 'invalid_body' });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const key = `${sessionId}:${taskId}`;
|
|
306
|
+
if (inFlightStops.has(key)) return send(423, { error: 'stop_in_progress' });
|
|
307
|
+
inFlightStops.add(key);
|
|
308
|
+
try {
|
|
309
|
+
const result = await stopBackgroundTask({
|
|
310
|
+
readRows: deps.readRows || defaultReadRows,
|
|
311
|
+
kill: deps.kill,
|
|
312
|
+
graceMs: deps.graceMs,
|
|
313
|
+
sleep: deps.sleep,
|
|
314
|
+
agentPid,
|
|
315
|
+
label: entry.label,
|
|
316
|
+
expect,
|
|
317
|
+
});
|
|
318
|
+
const target = result.target || null;
|
|
319
|
+
// One audit row per DECIDED request (refusals included). Its writer's return value is the
|
|
320
|
+
// response's auditId (null when the writer coalesced the row); a failure to write it must
|
|
321
|
+
// never change the outcome the operator is about to see.
|
|
322
|
+
let auditId = null;
|
|
323
|
+
try {
|
|
324
|
+
auditId = deps.recordSessionDiagnostic(sessionId, AUDIT_KIND, {
|
|
325
|
+
taskId,
|
|
326
|
+
label: String(entry.label || '').slice(0, 160),
|
|
327
|
+
pid: target ? target.pid : null,
|
|
328
|
+
pgid: target ? target.pgid : null,
|
|
329
|
+
startedAt: target ? target.startedAt : null,
|
|
330
|
+
signalled: result.signalled || [],
|
|
331
|
+
survivors: result.survivors || [],
|
|
332
|
+
error: result.error || null,
|
|
333
|
+
requester: requesterFromRequest(req),
|
|
334
|
+
}) ?? null;
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.error(`[background-stop] ${AUDIT_KIND} diagnostic failed:`, err && err.message);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (result.error === 'signal_failed') return send(500, { error: 'signal_failed', message: result.message });
|
|
340
|
+
if (result.error) {
|
|
341
|
+
return send(409, {
|
|
342
|
+
error: result.error,
|
|
343
|
+
...(result.candidates ? { candidates: result.candidates } : {}),
|
|
344
|
+
...(result.expected !== undefined ? { expected: result.expected, observed: result.observed } : {}),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return send(200, {
|
|
348
|
+
ok: true,
|
|
349
|
+
taskId,
|
|
350
|
+
target: result.target,
|
|
351
|
+
signalled: result.signalled,
|
|
352
|
+
survivors: result.survivors,
|
|
353
|
+
absent: !!result.absent,
|
|
354
|
+
auditId,
|
|
355
|
+
});
|
|
356
|
+
} finally {
|
|
357
|
+
inFlightStops.delete(key);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = {
|
|
362
|
+
PS_STOP_ARGS,
|
|
363
|
+
parseStopRows,
|
|
364
|
+
evalNeedle,
|
|
365
|
+
resolveBackgroundTarget,
|
|
366
|
+
stopBackgroundTask,
|
|
367
|
+
defaultReadRows,
|
|
368
|
+
handleStopRequest,
|
|
369
|
+
};
|
|
@@ -114,7 +114,7 @@ function freshAgentArgs(agentType, args, context = {}) {
|
|
|
114
114
|
return result;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function classifyAttemptRuntime({ live, hasOpenTurn, queue } = {}) {
|
|
117
|
+
function classifyAttemptRuntime({ live, hasOpenTurn, everTurn, queue } = {}) {
|
|
118
118
|
const queueStatus = String(queue?.status || '');
|
|
119
119
|
const queueError = String(queue?.error || '');
|
|
120
120
|
if (!live) {
|
|
@@ -125,6 +125,9 @@ function classifyAttemptRuntime({ live, hasOpenTurn, queue } = {}) {
|
|
|
125
125
|
}
|
|
126
126
|
if (queueStatus === 'paused' && queueError) return { status: 'unavailable', error: queueError };
|
|
127
127
|
if (queue && queueStatus !== 'done') return { status: 'running', error: '' };
|
|
128
|
+
// A prompt-operand launch (no queue) has no open turn until the agent's first turn arrives; an explicit
|
|
129
|
+
// everTurn === false means that turn has never been observed, so the attempt is still starting, not ready.
|
|
130
|
+
if (!queue && !hasOpenTurn && everTurn === false) return { status: 'running', error: '' };
|
|
128
131
|
return { status: hasOpenTurn ? 'running' : 'ready', error: '' };
|
|
129
132
|
}
|
|
130
133
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// WP-15 (docs/fleet-origin-main-sync-skill-2026-10-31.html §15.4 R-15.2/R-15.3/R-15.4, §15.5 API):
|
|
3
|
+
// the LAUNCH DECISION for a best-of attempt, as a pure function.
|
|
4
|
+
//
|
|
5
|
+
// It is a module of its own because the decision is the part worth testing and server.js cannot be
|
|
6
|
+
// required from a test (it opens the primary DB). `_launchBestOfAttempt` asks this for `{ mode, args }`
|
|
7
|
+
// BEFORE it prepares the worktree, so a refusal leaves no orphan worktree and no session behind.
|
|
8
|
+
//
|
|
9
|
+
// mode 'positional' the prompt rides the launch argv, LAST: `[...freshAgentArgs(...), prompt]`.
|
|
10
|
+
// No queue is created for the attempt.
|
|
11
|
+
// mode 'queue' the agent has no launch operand (walle); the caller keeps the existing
|
|
12
|
+
// `_bestOfQueuePrompt` lane.
|
|
13
|
+
// mode null no measured way to start this agent working -> refuse
|
|
14
|
+
// (`BEST_OF_AGENT_UNSUPPORTED`, R-15.3).
|
|
15
|
+
//
|
|
16
|
+
// R-15.4: an unsafe prompt (empty, leading `-`, or a single bare word) is refused as
|
|
17
|
+
// `BEST_OF_PROMPT_UNSAFE` with the helper's own message, and never repaired with a `--`.
|
|
18
|
+
|
|
19
|
+
const { normalizeAgentType, launchPromptMode, launchPromptArgv } = require('./agent-capabilities');
|
|
20
|
+
const { freshAgentArgs } = require('./best-of-attempts');
|
|
21
|
+
|
|
22
|
+
function typedError(code, message) {
|
|
23
|
+
const error = new Error(message);
|
|
24
|
+
error.code = code;
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{ agentType?: string, sourceArgs?: string[], prompt?: string, approvalHookToken?: string }} input
|
|
30
|
+
* @returns {{ mode: 'positional'|'queue', args: string[] }}
|
|
31
|
+
*/
|
|
32
|
+
function bestOfLaunchPlan(input = {}) {
|
|
33
|
+
const { agentType, sourceArgs, prompt, approvalHookToken = '' } = input || {};
|
|
34
|
+
const type = normalizeAgentType(agentType) || String(agentType == null ? '' : agentType).toLowerCase();
|
|
35
|
+
const mode = launchPromptMode(agentType);
|
|
36
|
+
if (mode === null) {
|
|
37
|
+
throw typedError('BEST_OF_AGENT_UNSUPPORTED', `Best-of attempts cannot start a ${type || 'unknown'} session: it has no measured way to take its prompt at launch`);
|
|
38
|
+
}
|
|
39
|
+
const args = freshAgentArgs(type, sourceArgs, { approvalHookToken });
|
|
40
|
+
if (mode === 'queue') return { mode, args };
|
|
41
|
+
let promptArgv;
|
|
42
|
+
try {
|
|
43
|
+
promptArgv = launchPromptArgv(agentType, prompt);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw typedError('BEST_OF_PROMPT_UNSAFE', error.message);
|
|
46
|
+
}
|
|
47
|
+
return { mode, args: [...args, ...promptArgv] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
bestOfLaunchPlan,
|
|
52
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 (docs/box-idle-sleep-remote-typing-freezes-2026-11-01.html §5 D-1..D-5, §6, §9 S-4): the
|
|
4
|
+
// BOX holds its own power assertion so the box Mac stops idle-sleeping while a hub is attached
|
|
5
|
+
// (451 sleeps on 2026-11-01, each freezing remote typing for seconds).
|
|
6
|
+
//
|
|
7
|
+
// D-1 a darwin CTM that is its machine's REAL install (`isPrimaryInstance()`) and has seen at
|
|
8
|
+
// least one `hub-bridge-hello` holds exactly one `/usr/bin/caffeinate -s -w <ctm pid>`.
|
|
9
|
+
// D-2 no hello, no assertion: the hub laptop, a never-paired install, Linux and every other
|
|
10
|
+
// platform spawn nothing. (Dev instances are excluded by `isPrimaryInstance()` itself —
|
|
11
|
+
// lib/ctm-boot-lifecycle.js is false under NODE_TEST_CONTEXT / DEV_CTM_PORT, so a test or
|
|
12
|
+
// dev run can NEVER spawn a real caffeinate.)
|
|
13
|
+
// D-3 latched for the process lifetime: a hub disconnect does NOT release it (PolyPilot #512 —
|
|
14
|
+
// a sleeping box cannot accept the hub's re-dial), and there is no timer and no polling.
|
|
15
|
+
// `-w <pid>` releases it when CTM exits; if the child dies early, the next hello respawns.
|
|
16
|
+
// D-4 `-s` only (PreventSystemSleep, valid on AC): on battery the Mac idle-sleeps as before.
|
|
17
|
+
// D-5 kill switch `CTM_BOX_KEEP_AWAKE` — the string '0' disables; default ON in source.
|
|
18
|
+
//
|
|
19
|
+
// Best-effort by construction: nothing here throws into the caller, and every failure is REPORTED
|
|
20
|
+
// as one `[box-keep-awake] …` line (lib/hub-bridge-box.js wraps the call in a try/catch too).
|
|
21
|
+
|
|
22
|
+
const { spawn: _spawn } = require('node:child_process');
|
|
23
|
+
const { isPrimaryInstance } = require('./ctm-boot-lifecycle');
|
|
24
|
+
|
|
25
|
+
const CAFFEINATE_PATH = '/usr/bin/caffeinate';
|
|
26
|
+
|
|
27
|
+
function _defaultDeps() {
|
|
28
|
+
return {
|
|
29
|
+
platform: process.platform,
|
|
30
|
+
env: process.env,
|
|
31
|
+
pid: process.pid,
|
|
32
|
+
isPrimaryInstance: () => isPrimaryInstance({ env: process.env }),
|
|
33
|
+
spawn: _spawn,
|
|
34
|
+
log: (line) => console.log(line),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let _deps = _defaultDeps();
|
|
39
|
+
let _child = null; // the live caffeinate ChildProcess, or null
|
|
40
|
+
let _spawnCount = 0; // for status() / tests
|
|
41
|
+
|
|
42
|
+
/** Tests only. Replaces deps (merged over the defaults); resets state. */
|
|
43
|
+
function configure(deps = {}) {
|
|
44
|
+
_deps = { ..._defaultDeps(), ...(deps || {}) };
|
|
45
|
+
_child = null;
|
|
46
|
+
_spawnCount = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* D-1..D-5. Idempotent; called on every hub-bridge-hello. Never throws.
|
|
51
|
+
*
|
|
52
|
+
* @returns {{ held: boolean, reason: 'held'|'spawned'|'disabled'|'not-darwin'|'not-primary'|'spawn-failed' }}
|
|
53
|
+
*/
|
|
54
|
+
function noteHubLinked() {
|
|
55
|
+
if (String(_deps.env.CTM_BOX_KEEP_AWAKE == null ? '' : _deps.env.CTM_BOX_KEEP_AWAKE) === '0') {
|
|
56
|
+
return { held: false, reason: 'disabled' };
|
|
57
|
+
}
|
|
58
|
+
if (_deps.platform !== 'darwin') return { held: false, reason: 'not-darwin' };
|
|
59
|
+
if (!_deps.isPrimaryInstance()) return { held: false, reason: 'not-primary' };
|
|
60
|
+
// D-3: a live child is the whole latch — repeated hellos never spawn a second one.
|
|
61
|
+
if (_child && _child.exitCode === null && !_child.killed) return { held: true, reason: 'held' };
|
|
62
|
+
try {
|
|
63
|
+
const child = _deps.spawn(CAFFEINATE_PATH, ['-s', '-w', String(_deps.pid)], { stdio: 'ignore', detached: false });
|
|
64
|
+
child.on('exit', (code, signal) => {
|
|
65
|
+
if (_child === child) _child = null; // D-3: the next noteHubLinked() respawns
|
|
66
|
+
_deps.log(`[box-keep-awake] caffeinate exited code=${code} signal=${signal || ''}`);
|
|
67
|
+
});
|
|
68
|
+
child.on('error', (err) => {
|
|
69
|
+
if (_child === child) _child = null;
|
|
70
|
+
_deps.log(`[box-keep-awake] caffeinate error: ${err && err.message}`); // REPORT disposition
|
|
71
|
+
});
|
|
72
|
+
child.unref(); // never keeps CTM's event loop alive
|
|
73
|
+
_child = child;
|
|
74
|
+
_spawnCount += 1;
|
|
75
|
+
_deps.log(`[box-keep-awake] holding PreventSystemSleep (AC only) via caffeinate pid=${child.pid} for ctm pid=${_deps.pid}`);
|
|
76
|
+
return { held: true, reason: 'spawned' };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
_deps.log(`[box-keep-awake] spawn failed: ${err && err.message}`); // REPORT disposition
|
|
79
|
+
return { held: false, reason: 'spawn-failed' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @returns {{ held: boolean, pid: number|null, spawnCount: number }} */
|
|
84
|
+
function status() {
|
|
85
|
+
const held = !!(_child && _child.exitCode === null && !_child.killed);
|
|
86
|
+
return { held, pid: held ? _child.pid : null, spawnCount: _spawnCount };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { noteHubLinked, status, configure, CAFFEINATE_PATH };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-0 M-2 (docs/hub-local-cpu-memory-burn-2026-10-31.html §3.2 "who calls ~3 per second",
|
|
4
|
+
// §7 M-2): per-reason counters for the session-list broadcast. MEASUREMENT ONLY — this module
|
|
5
|
+
// holds counters and nothing else. It never decides whether to broadcast, never touches the
|
|
6
|
+
// serializer, and adds no cap/timeout/retry/early-exit.
|
|
7
|
+
//
|
|
8
|
+
// Why: after a hub boot the whole session list was serialized 1,951× (2.65 GB) in 666 s
|
|
9
|
+
// (~2.9/s) and no caller recorded why (`broadcastSessionList` took no reason). Q-3 in the design
|
|
10
|
+
// doc is exactly "which callers drive the rate", and it cannot be answered by code reading
|
|
11
|
+
// because ~74 call sites reach the function through `deps.broadcastSessionList(...)` too.
|
|
12
|
+
//
|
|
13
|
+
// Contract (asserted by tests/broadcast-reason-stats.test.js, per reason):
|
|
14
|
+
// calls === suppressed + coalesced + flushes
|
|
15
|
+
// flushes === serialized + elided
|
|
16
|
+
// where
|
|
17
|
+
// calls every invocation of `broadcastSessionList(immediate, { reason })`.
|
|
18
|
+
// suppressed the invocation produced no output: restore in progress, or a deferred-restore
|
|
19
|
+
// depth > 0 (an intermediate broadcast folded into the post-restore flush), or an
|
|
20
|
+
// internal flush that found restore in progress / no connected client.
|
|
21
|
+
// coalesced the invocation rode an already-armed flush instead of producing its own — a
|
|
22
|
+
// debounced call while the 200 ms timer was armed (or whose timer an immediate
|
|
23
|
+
// flush pre-empted), or a request folded into the in-flight sliced build.
|
|
24
|
+
// flushes a flush reached the send tail (`_doBroadcastSessionList` → `_finishBroadcast`),
|
|
25
|
+
// attributed to the reason that armed it (or `rerun` for the sliced build's
|
|
26
|
+
// self-re-run).
|
|
27
|
+
// serialized that flush actually handed the payload to the serializer.
|
|
28
|
+
// elided that flush was dropped by the serializer's no-change elision.
|
|
29
|
+
//
|
|
30
|
+
// Unknown/missing reason strings count under `unspecified`; the reason-key set is bounded
|
|
31
|
+
// (MAX_REASON_KEYS) and anything past the bound folds into `_other`, so the map is fixed-size
|
|
32
|
+
// and the per-event cost stays O(1).
|
|
33
|
+
//
|
|
34
|
+
// Gated by CTM_PERF_ATTRIBUTION (read through lib/env-flags.js, like every other WP-0 counter):
|
|
35
|
+
// '0' (also 'false'/'no'/'off') records nothing and exposes `{ byReason: {} }`.
|
|
36
|
+
|
|
37
|
+
const { boolFlag } = require('./env-flags');
|
|
38
|
+
|
|
39
|
+
const MAX_REASON_KEYS = 128;
|
|
40
|
+
const OTHER_REASON = '_other';
|
|
41
|
+
const UNSPECIFIED_REASON = 'unspecified';
|
|
42
|
+
// `rerun` is the reason the sliced build's own self-re-run is counted under (server.js
|
|
43
|
+
// `_doBroadcastSessionList`'s `.finally`); it is a call AND a flush so the invariant holds.
|
|
44
|
+
const RERUN_REASON = 'rerun';
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
function _newCounts() {
|
|
48
|
+
return { calls: 0, suppressed: 0, coalesced: 0, flushes: 0, serialized: 0, elided: 0 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeReason(reason) {
|
|
52
|
+
return typeof reason === 'string' && reason.length > 0 ? reason : UNSPECIFIED_REASON;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createBroadcastReasonStats(options = {}) {
|
|
56
|
+
const enabled = typeof options.enabled === 'function'
|
|
57
|
+
? options.enabled
|
|
58
|
+
: () => boolFlag('CTM_PERF_ATTRIBUTION', true);
|
|
59
|
+
const maxKeys = Number.isInteger(options.maxKeys) && options.maxKeys > 1
|
|
60
|
+
? options.maxKeys
|
|
61
|
+
: MAX_REASON_KEYS;
|
|
62
|
+
// One slot is reserved for `_other` so the total key count can never exceed maxKeys.
|
|
63
|
+
const namedKeyCap = maxKeys - 1;
|
|
64
|
+
const byReason = new Map();
|
|
65
|
+
|
|
66
|
+
function slot(reason) {
|
|
67
|
+
const key = normalizeReason(reason);
|
|
68
|
+
const existing = byReason.get(key);
|
|
69
|
+
if (existing) return existing;
|
|
70
|
+
if (byReason.size >= namedKeyCap) {
|
|
71
|
+
const other = byReason.get(OTHER_REASON);
|
|
72
|
+
if (other) return other;
|
|
73
|
+
const folded = _newCounts();
|
|
74
|
+
byReason.set(OTHER_REASON, folded);
|
|
75
|
+
return folded;
|
|
76
|
+
}
|
|
77
|
+
const fresh = _newCounts();
|
|
78
|
+
byReason.set(key, fresh);
|
|
79
|
+
return fresh;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isOn() {
|
|
83
|
+
return enabled() !== false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function noteCall(reason) {
|
|
87
|
+
if (!isOn()) return;
|
|
88
|
+
slot(reason).calls += 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function noteSuppressed(reason) {
|
|
92
|
+
if (!isOn()) return;
|
|
93
|
+
slot(reason).suppressed += 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function noteCoalesced(reason) {
|
|
97
|
+
if (!isOn()) return;
|
|
98
|
+
slot(reason).coalesced += 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// A flush is one trip through the send tail. `elided` says which way its no-change decision
|
|
102
|
+
// went; every flush is exactly one of serialized/elided, which is what makes
|
|
103
|
+
// `flushes === serialized + elided` true by construction.
|
|
104
|
+
function noteFlush(reason, elided) {
|
|
105
|
+
if (!isOn()) return;
|
|
106
|
+
const entry = slot(reason);
|
|
107
|
+
entry.flushes += 1;
|
|
108
|
+
if (elided) entry.elided += 1;
|
|
109
|
+
else entry.serialized += 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function snapshot() {
|
|
113
|
+
if (!isOn()) return { byReason: {} };
|
|
114
|
+
const byReasonOut = {};
|
|
115
|
+
for (const [key, counts] of byReason) {
|
|
116
|
+
byReasonOut[key] = {
|
|
117
|
+
calls: counts.calls,
|
|
118
|
+
suppressed: counts.suppressed,
|
|
119
|
+
coalesced: counts.coalesced,
|
|
120
|
+
flushes: counts.flushes,
|
|
121
|
+
serialized: counts.serialized,
|
|
122
|
+
elided: counts.elided,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return { byReason: byReasonOut };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function reset() {
|
|
129
|
+
byReason.clear();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { noteCall, noteSuppressed, noteCoalesced, noteFlush, snapshot, reset, normalizeReason, isOn };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Process-wide instance used by server.js. Tests construct their own via the factory so they
|
|
136
|
+
// never depend on import order.
|
|
137
|
+
const _default = createBroadcastReasonStats();
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
createBroadcastReasonStats,
|
|
141
|
+
normalizeReason,
|
|
142
|
+
getBroadcastReasonStats: () => _default.snapshot(),
|
|
143
|
+
resetBroadcastReasonStats: () => _default.reset(),
|
|
144
|
+
noteCall: (reason) => _default.noteCall(reason),
|
|
145
|
+
noteSuppressed: (reason) => _default.noteSuppressed(reason),
|
|
146
|
+
noteCoalesced: (reason) => _default.noteCoalesced(reason),
|
|
147
|
+
noteFlush: (reason, elided) => _default.noteFlush(reason, elided),
|
|
148
|
+
MAX_REASON_KEYS,
|
|
149
|
+
OTHER_REASON,
|
|
150
|
+
UNSPECIFIED_REASON,
|
|
151
|
+
RERUN_REASON,
|
|
152
|
+
};
|