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
|
@@ -125,6 +125,12 @@ const { decodeFramedJson: _decodeFramedJson } = require('./framed-json-codec');
|
|
|
125
125
|
// by omission.
|
|
126
126
|
const ISOLATE_INTERACTIVE = process.env.CTM_READPOOL_ISOLATE_INTERACTIVE !== '0';
|
|
127
127
|
const REQUEST_CLASSES = new Set(['interactive', 'service', 'batch']);
|
|
128
|
+
|
|
129
|
+
// WP-0 M-1/M-4 (docs/hub-local-cpu-memory-burn-2026-10-31.html §7): per-op CPU and heap
|
|
130
|
+
// attribution, aggregated pool-side for perf-summary. See _attachOpPerfTap/_recordOpPerf.
|
|
131
|
+
const PERF_HEAP_TOP_LIMIT = 20; // fixed ring: the 20 largest |heap+external| deltas seen
|
|
132
|
+
const _PERF_TAP = Symbol('ctmReadPoolPerfTap');
|
|
133
|
+
const _perfAttributionOn = () => boolFlag('CTM_PERF_ATTRIBUTION', true);
|
|
128
134
|
// Counts are a WAITING backstop (WP-3 D-5): a class refuses with QUEUE_FULL only when its waiter list is full.
|
|
129
135
|
const DEFAULT_QUEUE_CAPACITIES = Object.freeze({ interactive: 256, service: 256, batch: 128 });
|
|
130
136
|
// D-7a/D-8 (idle-wake freeze design §5, §8 WP-1b): the service-class coalesce line is a
|
|
@@ -306,6 +312,12 @@ class CtmReadPoolClient {
|
|
|
306
312
|
// ever gated by it; the map itself is maintained for every op so getStatus().perOp can
|
|
307
313
|
// report a non-zero count for an uncapped op too.
|
|
308
314
|
this._inFlightByOp = new Map();
|
|
315
|
+
// WP-0 M-1/M-4: per-op CPU totals (µs, converted to ms at read time so repeated reads
|
|
316
|
+
// cannot drift) and the bounded top-20 heap-delta ring. Both bounded: _cpuByOp is keyed by
|
|
317
|
+
// op name (the registry declares a fixed set, plus 'unknown'), _heapTopDeltas by
|
|
318
|
+
// PERF_HEAP_TOP_LIMIT. Unused (and empty) when CTM_PERF_ATTRIBUTION=0.
|
|
319
|
+
this._cpuByOp = new Map();
|
|
320
|
+
this._heapTopDeltas = [];
|
|
309
321
|
// D-7a (WP-1b): one coalesce line per service singleflight key per COALESCE_LOG_INTERVAL_MS.
|
|
310
322
|
this._lastCoalesceLogAt = new Map();
|
|
311
323
|
this._closed = false;
|
|
@@ -334,8 +346,8 @@ class CtmReadPoolClient {
|
|
|
334
346
|
historyReaderContext: this.#historyReaderContext,
|
|
335
347
|
onMemoryLimitDeath: (occupiedEntries) => this._recordMemoryLimitDeath(occupiedEntries),
|
|
336
348
|
onRefusal: (refusal) => this._recordRefusal(refusal),
|
|
337
|
-
onRespawn: (worker) => this._recordRespawn(worker),
|
|
338
|
-
onReady: () => this._drainClassWaiters(),
|
|
349
|
+
onRespawn: (worker) => { this._attachOpPerfTap(worker); this._recordRespawn(worker); },
|
|
350
|
+
onReady: (worker) => { this._attachOpPerfTap(worker); this._drainClassWaiters(); },
|
|
339
351
|
onTiming: (kind, requestClass, ms) => this._recordTiming(kind, requestClass, ms),
|
|
340
352
|
onGhostRetire: (worker, ghost, ranMs, spanMs) => this._recordGhostRetire(worker, ghost, ranMs, spanMs),
|
|
341
353
|
onGhostTerminate: (worker, ghost, ranMs, spanMs) => this._recordGhostTerminate(worker, ghost, ranMs, spanMs),
|
|
@@ -1190,6 +1202,76 @@ class CtmReadPoolClient {
|
|
|
1190
1202
|
return perOp;
|
|
1191
1203
|
}
|
|
1192
1204
|
|
|
1205
|
+
// WP-0 M-1/M-4 (hub-local-cpu-memory-burn-2026-10-31.html §7): read-only observation of the
|
|
1206
|
+
// response messages the worker handle already consumes. The worker stamps each response with
|
|
1207
|
+
// `perf = { op, cpuUserUs, cpuSystemUs, heapDeltaBytes, externalDeltaBytes }`; this listener
|
|
1208
|
+
// keeps the per-op CPU totals and the top-20 heap deltas. It runs on the SAME event as the
|
|
1209
|
+
// handle's own response accounting (so counts track responseByOp, modulo a response that
|
|
1210
|
+
// arrives after its caller gave up -- the handle has already dropped that requestId, and this
|
|
1211
|
+
// listener counts it because the op really did run), touches nothing the handle owns, and
|
|
1212
|
+
// adds one symbol property per native worker. Attached from the ready/respawn hooks, since the
|
|
1213
|
+
// handle creates (and replaces, on respawn) the native worker internally.
|
|
1214
|
+
_attachOpPerfTap(handle) {
|
|
1215
|
+
if (!_perfAttributionOn()) return;
|
|
1216
|
+
const native = handle?.worker;
|
|
1217
|
+
if (!native || typeof native.on !== 'function' || native[_PERF_TAP] === true) return;
|
|
1218
|
+
try {
|
|
1219
|
+
native[_PERF_TAP] = true;
|
|
1220
|
+
native.on('message', (msg) => this._recordOpPerf(msg));
|
|
1221
|
+
} catch { /* expected: a worker torn down mid-attach has no responses left to attribute; the respawn re-attaches */ }
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
_recordOpPerf(msg) {
|
|
1225
|
+
if (!_perfAttributionOn()) return;
|
|
1226
|
+
const perf = msg && msg.type === 'response' ? msg.perf : null;
|
|
1227
|
+
if (!perf || typeof perf !== 'object') return;
|
|
1228
|
+
const op = String(perf.op || 'unknown');
|
|
1229
|
+
const totals = this._cpuByOp.get(op) || { count: 0, userUs: 0, systemUs: 0 };
|
|
1230
|
+
totals.count += 1;
|
|
1231
|
+
// Clamp negatives away: a µs delta cannot be negative, and a clock read that somehow is
|
|
1232
|
+
// must not subtract from the op's own total.
|
|
1233
|
+
totals.userUs += Math.max(0, Number(perf.cpuUserUs) || 0);
|
|
1234
|
+
totals.systemUs += Math.max(0, Number(perf.cpuSystemUs) || 0);
|
|
1235
|
+
this._cpuByOp.set(op, totals);
|
|
1236
|
+
const heapDeltaBytes = Number(perf.heapDeltaBytes) || 0;
|
|
1237
|
+
const externalDeltaBytes = Number(perf.externalDeltaBytes) || 0;
|
|
1238
|
+
if (heapDeltaBytes === 0 && externalDeltaBytes === 0) return;
|
|
1239
|
+
this._pushHeapDelta({ op, heapDeltaBytes, externalDeltaBytes, at: Date.now() });
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Bounded top-20 by heapDeltaBytes+externalDeltaBytes descending; while full, an entry that
|
|
1243
|
+
// does not beat the smallest is dropped (no allocation, no sort). Keeps the sign of both
|
|
1244
|
+
// deltas -- a GC-heavy or buffer-releasing op is a negative row, which is information too.
|
|
1245
|
+
_pushHeapDelta(entry) {
|
|
1246
|
+
const weight = (row) => row.heapDeltaBytes + row.externalDeltaBytes;
|
|
1247
|
+
if (this._heapTopDeltas.length < PERF_HEAP_TOP_LIMIT) {
|
|
1248
|
+
this._heapTopDeltas.push(entry);
|
|
1249
|
+
this._heapTopDeltas.sort((a, b) => weight(b) - weight(a));
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (weight(entry) <= weight(this._heapTopDeltas[this._heapTopDeltas.length - 1])) return;
|
|
1253
|
+
this._heapTopDeltas[this._heapTopDeltas.length - 1] = entry;
|
|
1254
|
+
this._heapTopDeltas.sort((a, b) => weight(b) - weight(a));
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
_cpuByOpStatus() {
|
|
1258
|
+
if (!_perfAttributionOn()) return {};
|
|
1259
|
+
const out = {};
|
|
1260
|
+
for (const [op, totals] of this._cpuByOp) {
|
|
1261
|
+
out[op] = {
|
|
1262
|
+
count: totals.count,
|
|
1263
|
+
userMs: Math.round((totals.userUs / 1000) * 10) / 10,
|
|
1264
|
+
systemMs: Math.round((totals.systemUs / 1000) * 10) / 10,
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
return out;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
_heapTopDeltasStatus() {
|
|
1271
|
+
if (!_perfAttributionOn()) return [];
|
|
1272
|
+
return this._heapTopDeltas.map((row) => ({ ...row }));
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1193
1275
|
getStatus() {
|
|
1194
1276
|
const responseByOp = {};
|
|
1195
1277
|
const responseByReason = {};
|
|
@@ -1317,6 +1399,11 @@ class CtmReadPoolClient {
|
|
|
1317
1399
|
peakInputBytes: this._workers.reduce((peak, w) => Math.max(peak, w.peakInputBytes), 0),
|
|
1318
1400
|
responseByOp,
|
|
1319
1401
|
responseByReason,
|
|
1402
|
+
// WP-0 M-1/M-4 (§7): per-op CPU (ms, 0.1 precision) beside responseByOp -- same op key,
|
|
1403
|
+
// same response event -- and the bounded top-20 heap deltas. Empty when
|
|
1404
|
+
// CTM_PERF_ATTRIBUTION=0.
|
|
1405
|
+
cpuByOp: this._cpuByOpStatus(),
|
|
1406
|
+
heapTopDeltas: this._heapTopDeltasStatus(),
|
|
1320
1407
|
};
|
|
1321
1408
|
}
|
|
1322
1409
|
|
|
@@ -194,13 +194,18 @@ function createPoolWorkerClass({
|
|
|
194
194
|
workerData: { historyReaderContext: this.#historyReaderContext },
|
|
195
195
|
});
|
|
196
196
|
this.worker = worker;
|
|
197
|
+
// D-2 (docs/temp-file-leaks-disk-full-2026-10-25.html §8): captured at spawn, because a dead
|
|
198
|
+
// worker's own isolate can no longer report it. Threads share this process's pid, so pairing it
|
|
199
|
+
// with `_terminateIssued` below is the only evidence that a stopped thread left decode temps
|
|
200
|
+
// stranded mid-write (the write completed, its rename never ran).
|
|
201
|
+
const workerThreadId = worker.threadId;
|
|
197
202
|
|
|
198
203
|
const failStart = (err) => {
|
|
199
204
|
if (this.worker !== worker) return;
|
|
200
205
|
if (this.ready) return;
|
|
201
206
|
this.startPromise = null;
|
|
202
207
|
this.worker = null;
|
|
203
|
-
try {
|
|
208
|
+
try { this._terminateWorker(worker); } catch { /* expected: best-effort termination of a worker that failed to start */ }
|
|
204
209
|
reject(err instanceof Error ? err : new Error(String(err || 'read-pool worker failed to start')));
|
|
205
210
|
};
|
|
206
211
|
|
|
@@ -357,6 +362,17 @@ function createPoolWorkerClass({
|
|
|
357
362
|
this._rejectAll(err);
|
|
358
363
|
});
|
|
359
364
|
worker.on('exit', (code) => {
|
|
365
|
+
// D-2: a thread stopped mid-op (crash, or our own terminate) can never run its own cleanup,
|
|
366
|
+
// so the temps IT left are reclaimed here by ownership. Deliberately before the `this.worker
|
|
367
|
+
// !== worker` staleness guard: a replaced handle is exactly the case where nothing else would
|
|
368
|
+
// ever look at these files. Never throws -- a hygiene sweep must not disturb exit handling.
|
|
369
|
+
if (code !== 0 || worker._terminateIssued) {
|
|
370
|
+
// Fire-and-forget: the reclaim walks the whole cache, so it must not run synchronously on
|
|
371
|
+
// this (main) loop. The listener stays synchronous; a failure is only ever logged.
|
|
372
|
+
const tid = Number.isInteger(workerThreadId) ? workerThreadId : worker.threadId;
|
|
373
|
+
require('./transcript-source').removeCacheTempsForThreadAsync(process.pid, tid)
|
|
374
|
+
.catch((error) => this.logger?.warn?.(`[read-pool] cache temp cleanup failed: ${error?.message || error}`));
|
|
375
|
+
}
|
|
360
376
|
if (this.worker !== worker) return;
|
|
361
377
|
const wasReady = this.ready;
|
|
362
378
|
this.ready = false;
|
|
@@ -377,6 +393,15 @@ function createPoolWorkerClass({
|
|
|
377
393
|
return this.startPromise;
|
|
378
394
|
}
|
|
379
395
|
|
|
396
|
+
// D-2: every termination goes through here so the Worker object itself records that this thread was
|
|
397
|
+
// STOPPED (rather than closing itself), which is what makes its stranded decode temps reclaimable
|
|
398
|
+
// in the 'exit' listener. Returns worker.terminate()'s promise for the close() timeout path.
|
|
399
|
+
_terminateWorker(worker) {
|
|
400
|
+
if (!worker) return Promise.resolve();
|
|
401
|
+
worker._terminateIssued = true;
|
|
402
|
+
return worker.terminate();
|
|
403
|
+
}
|
|
404
|
+
|
|
380
405
|
async request(op, payload, timeoutMs, lifecycle = {}) {
|
|
381
406
|
let lifecycleDone = false;
|
|
382
407
|
const completeLifecycle = () => {
|
|
@@ -624,7 +649,7 @@ function createPoolWorkerClass({
|
|
|
624
649
|
// terminate immediately, dropping whatever this worker was carrying. The lazy
|
|
625
650
|
// drain above is the default; this only exists as an escape hatch.
|
|
626
651
|
this.retiring = true;
|
|
627
|
-
try { this.worker
|
|
652
|
+
try { this._terminateWorker(this.worker); } catch { /* expected: worker already exited or never started — the rollback path's only goal is that it is not running */ }
|
|
628
653
|
return;
|
|
629
654
|
}
|
|
630
655
|
this.retiring = true;
|
|
@@ -693,7 +718,7 @@ function createPoolWorkerClass({
|
|
|
693
718
|
const finish = () => { if (done) return; done = true; resolve(); };
|
|
694
719
|
let timer = null;
|
|
695
720
|
if (timeoutMs > 0) {
|
|
696
|
-
timer = setTimeout(() => {
|
|
721
|
+
timer = setTimeout(() => { this._terminateWorker(worker).finally(finish); }, timeoutMs);
|
|
697
722
|
if (typeof timer.unref === 'function') timer.unref();
|
|
698
723
|
}
|
|
699
724
|
const onMessage = (msg) => {
|
|
@@ -704,7 +729,7 @@ function createPoolWorkerClass({
|
|
|
704
729
|
};
|
|
705
730
|
worker.on('message', onMessage);
|
|
706
731
|
try { worker.postMessage({ type: 'close' }); }
|
|
707
|
-
catch { /* expected: close post race with an exiting worker; termination completes the lifecycle */ if (timer) clearTimeout(timer); worker.off('message', onMessage);
|
|
732
|
+
catch { /* expected: close post race with an exiting worker; termination completes the lifecycle */ if (timer) clearTimeout(timer); worker.off('message', onMessage); this._terminateWorker(worker).finally(finish); }
|
|
708
733
|
});
|
|
709
734
|
if (this.worker === worker) {
|
|
710
735
|
this.ready = false;
|
|
@@ -110,6 +110,17 @@ function mapAgent(agent, parentRow) {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* WP-0 (S-1, fleet-session-lists-hub-search-2026-11-01.html §6.1): the OWNER of a catalog row — the
|
|
115
|
+
* instance that executes it, falling back to its current owner. Unlike `machineFactsForRecentRow`
|
|
116
|
+
* this is unconditional: a box has no registered machines at all, so the tag path yields nothing
|
|
117
|
+
* there, and owner truth is exactly what a forwarded `owner=self` request filters on. Exported so
|
|
118
|
+
* the search projection (lib/api/session-messages.js) reads the same expression.
|
|
119
|
+
*/
|
|
120
|
+
function recentRowOwnerInstanceId(row) {
|
|
121
|
+
return String(row?.execution_host_instance_id || row?.current_owner_instance_id || '').trim();
|
|
122
|
+
}
|
|
123
|
+
|
|
113
124
|
/**
|
|
114
125
|
* WP-3 (remote-hub-followups D-10): the machine tag for a recent row — the row's EXECUTION host
|
|
115
126
|
* (falling back to its current owner) when that id names a REGISTERED machine and is not this host
|
|
@@ -202,6 +213,9 @@ function mapRecentSessionRow(row, options = {}) {
|
|
|
202
213
|
parentAgentSessionId: row.parent_agent_session_id || primary?.parentAgentSessionId || '',
|
|
203
214
|
agentNickname: row.agent_nickname || primary?.agentNickname || '',
|
|
204
215
|
agentRole: row.agent_role || primary?.agentRole || '',
|
|
216
|
+
// WP-0 (S-1): owner truth, unconditionally — a box registers no machines, so the machine tag
|
|
217
|
+
// above never fires there and `owner=self` filters on THIS key. Empty string = unknown owner.
|
|
218
|
+
ownerInstanceId: recentRowOwnerInstanceId(row),
|
|
205
219
|
// WP-3 (D-10): the machine tag, only when this row's execution host is a registered box.
|
|
206
220
|
...(machineFactsForRecentRow(row, options) || {}),
|
|
207
221
|
};
|
|
@@ -259,5 +273,6 @@ module.exports = {
|
|
|
259
273
|
mapRecentSessionRowsCooperatively,
|
|
260
274
|
pickDisplayTitle,
|
|
261
275
|
queryRecentCatalogSessions,
|
|
276
|
+
recentRowOwnerInstanceId,
|
|
262
277
|
rowAgentSessionIds,
|
|
263
278
|
};
|
|
@@ -92,22 +92,53 @@ function activityMs(session) {
|
|
|
92
92
|
// session lives. Machine labeling MUST therefore use the machine's minted instance id
|
|
93
93
|
// (`machine`/`machineName`, filled by lib/remote-machine-catalog.js) and NEVER a hostname: any
|
|
94
94
|
// hostname-based label would silently claim a box's session is on this host (D-3).
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
/**
|
|
96
|
+
* WP-0 (S-2, fleet-session-lists-hub-search-2026-11-01.html §5 D-3, §6.1): the limit + starred rule,
|
|
97
|
+
* extracted pure so the owner filter can run BEFORE the limit.
|
|
98
|
+
*
|
|
99
|
+
* @param {object[]} sessions mapped recent rows (each carries `ownerInstanceId`, S-1)
|
|
100
|
+
* @param {{limit:number, pinnedIds?:string[], ownerLocalIds?:string[]|null}} opts
|
|
101
|
+
* `ownerLocalIds` null/undefined = keep every row (today's behaviour). Otherwise a row is kept
|
|
102
|
+
* iff it names no owner at all (`''` — an unknown/legacy owner) or names one of these ids. This
|
|
103
|
+
* filter runs BEFORE the activityMs sort + slice, so foreign copies can never crowd out a box's
|
|
104
|
+
* own sessions (the 3,775 Spark copies in the design doc's §1.1).
|
|
105
|
+
* @returns {object[]} activityMs desc, sliced to `limit`, then starred/pinned rows past it
|
|
106
|
+
*/
|
|
107
|
+
function selectRecentCatalogRows(sessions, { limit, pinnedIds = [], ownerLocalIds = null } = {}) {
|
|
108
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
109
|
+
const localIds = Array.isArray(ownerLocalIds) ? ownerLocalIds : null;
|
|
110
|
+
const cap = Math.max(0, Number(limit) || 0);
|
|
111
|
+
const filtered = localIds
|
|
112
|
+
? list.filter((session) => {
|
|
113
|
+
const owner = String(session?.ownerInstanceId || '').trim();
|
|
114
|
+
return !owner || localIds.includes(owner);
|
|
115
|
+
})
|
|
116
|
+
: list.slice();
|
|
117
|
+
const sorted = filtered.sort((a, b) => activityMs(b) - activityMs(a));
|
|
118
|
+
const selected = sorted.slice(0, cap);
|
|
103
119
|
const selectedIds = new Set(selected.map((session) => String(session.sessionId || session.id || '')));
|
|
104
|
-
for (const session of
|
|
120
|
+
for (const session of sorted) {
|
|
105
121
|
const id = String(session.sessionId || session.id || '');
|
|
106
|
-
if (!selectedIds.has(id) && (session.starred ||
|
|
122
|
+
if (!selectedIds.has(id) && (session.starred || pinnedIds.includes(id))) {
|
|
107
123
|
selected.push(session);
|
|
108
124
|
selectedIds.add(id);
|
|
109
125
|
}
|
|
110
126
|
}
|
|
127
|
+
return selected;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function buildRecentSessionCatalogResponse(input = {}) {
|
|
131
|
+
const shape = normalizeRecentCatalogShape(input.query);
|
|
132
|
+
const hostname = String(input.hostname || '');
|
|
133
|
+
const sessions = (Array.isArray(input.sessions) ? input.sessions : [])
|
|
134
|
+
.map((session) => ({ ...session, hostname }));
|
|
135
|
+
// WP-0 (S-2): only `owner=self` filters; every other value (including absent) keeps today's
|
|
136
|
+
// output byte for byte, apart from the `ownerInstanceId` key each mapped row now carries.
|
|
137
|
+
const selected = selectRecentCatalogRows(sessions, {
|
|
138
|
+
limit: shape.limit,
|
|
139
|
+
pinnedIds: (input.pinnedIds || []).map(String),
|
|
140
|
+
ownerLocalIds: input.ownerFilter === 'self' ? input.ownerLocalIds : null,
|
|
141
|
+
});
|
|
111
142
|
const buffer = Buffer.from(JSON.stringify({
|
|
112
143
|
sessions: selected,
|
|
113
144
|
lastActiveProject: input.lastActiveProject || null,
|
|
@@ -126,6 +157,19 @@ function buildRecentSessionCatalogVariantBundle(input = {}) {
|
|
|
126
157
|
const variants = [];
|
|
127
158
|
const limits = [...new Set((input.limits || [1, 50, 100, 200, 500])
|
|
128
159
|
.map((limit) => normalizeRecentCatalogShape({ limit }).limit))];
|
|
160
|
+
// WP-0 (S-3): the owner dimension of the key is emitted ONLY when this host actually holds a
|
|
161
|
+
// row it does not own — then `|self` is a genuinely different answer and the hub can ask for it.
|
|
162
|
+
// With no foreign row the all-variant IS the self answer, so no `|self` key exists (and the
|
|
163
|
+
// lookup falls back), keeping every existing key byte-identical.
|
|
164
|
+
// Fail OPEN: an empty lineage (identity not minted yet) cannot tell owned rows from copies, so no
|
|
165
|
+
// `|self` variant is built and an owner=self request falls back to the all-variant. The hub's
|
|
166
|
+
// merge still drops rows owned by its own lineage (fleet-session-lists D-4).
|
|
167
|
+
const ownerLocalIds = Array.isArray(input.ownerLocalIds) && input.ownerLocalIds.length ? input.ownerLocalIds : null;
|
|
168
|
+
const hasForeignRow = !!ownerLocalIds && (Array.isArray(input.sessions) ? input.sessions : [])
|
|
169
|
+
.some((session) => {
|
|
170
|
+
const owner = String(session?.ownerInstanceId || '').trim();
|
|
171
|
+
return !!owner && !ownerLocalIds.includes(owner);
|
|
172
|
+
});
|
|
129
173
|
for (const limit of limits) {
|
|
130
174
|
for (const stale of [false, true]) {
|
|
131
175
|
const key = `${limit}|${stale ? 1 : 0}`;
|
|
@@ -133,6 +177,18 @@ function buildRecentSessionCatalogVariantBundle(input = {}) {
|
|
|
133
177
|
...input,
|
|
134
178
|
query: { limit },
|
|
135
179
|
stale,
|
|
180
|
+
ownerFilter: '',
|
|
181
|
+
});
|
|
182
|
+
variants.push({ key: Buffer.from(key, 'utf8'), body: response.buffer });
|
|
183
|
+
}
|
|
184
|
+
if (!hasForeignRow) continue;
|
|
185
|
+
for (const stale of [false, true]) {
|
|
186
|
+
const key = `${limit}|${stale ? 1 : 0}|self`;
|
|
187
|
+
const response = buildRecentSessionCatalogResponse({
|
|
188
|
+
...input,
|
|
189
|
+
query: { limit },
|
|
190
|
+
stale,
|
|
191
|
+
ownerFilter: 'self',
|
|
136
192
|
});
|
|
137
193
|
variants.push({ key: Buffer.from(key, 'utf8'), body: response.buffer });
|
|
138
194
|
}
|
|
@@ -184,5 +240,6 @@ module.exports = {
|
|
|
184
240
|
buildRecentSessionCatalogVariantBundle,
|
|
185
241
|
decodeRecentSessionCatalogVariantBundle,
|
|
186
242
|
normalizeRecentCatalogShape,
|
|
243
|
+
selectRecentCatalogRows,
|
|
187
244
|
snapshotRecentCatalogSessions,
|
|
188
245
|
};
|
|
@@ -6,6 +6,14 @@ const crypto = require('node:crypto');
|
|
|
6
6
|
const { execFile } = require('node:child_process');
|
|
7
7
|
const { readPrivateJson } = require('./recovery-private-file');
|
|
8
8
|
|
|
9
|
+
// The writer witness is read with the system `lsof`, from fixed absolute paths only: a
|
|
10
|
+
// PATH-resolved binary is caller-controlled and must not be able to answer (or refuse) a
|
|
11
|
+
// piece of recovery evidence. macOS installs lsof under /usr/sbin, Debian-family Linux
|
|
12
|
+
// under /usr/bin, so the probe walks both in that order -- same rule as
|
|
13
|
+
// bin/deflake-source-snapshot.js's `lsofCwdUsers` (ENOENT moves on, any other refusal is
|
|
14
|
+
// unknown), minus its leading bare-`lsof` PATH candidate.
|
|
15
|
+
const LSOF_CANDIDATES = Object.freeze(['/usr/sbin/lsof', '/usr/bin/lsof']);
|
|
16
|
+
|
|
9
17
|
// Controller ownership is rewritten by normal adoption; provider binding,
|
|
10
18
|
// workspace, origin, model and all other stable columns remain in the witness.
|
|
11
19
|
const VOLATILE_RECIPE_FIELDS = new Set(['pid', 'ctm_pid', 'ctm_instance_id', 'current_owner_instance_id', 'updated_at', 'last_heartbeat', 'last_activity', 'last_user_prompt_input_at', 'last_meaningful_activity_at', 'attention_dismissed_at', 'attention_dismissed_input_at']);
|
|
@@ -51,17 +59,24 @@ async function verifyWriterWitness(operation, { execFileImpl = execFile } = {})
|
|
|
51
59
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== operation.original.uid || String(stat.ino) !== String(witness.lockFile.inode) || String(stat.dev) !== String(witness.lockFile.dev)) return false;
|
|
52
60
|
// This is continuity of an explicitly selected owner's descriptor, not a
|
|
53
61
|
// generic claim that an open fd proves advisory-lock ownership.
|
|
54
|
-
return new Promise((resolve, reject) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (error
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const probe = (index) => execFileImpl(LSOF_CANDIDATES[index], ['-nP', '-Fpf', '--', lockPath], { timeout: 3000, maxBuffer: 64 * 1024 }, (error, stdout) => {
|
|
64
|
+
// Only a candidate that is not installed at this location (ENOENT) falls through to
|
|
65
|
+
// the next one: a sampler that ran and refused must not be bypassed by a second
|
|
66
|
+
// binary, so every other failure is decided by this candidate alone.
|
|
67
|
+
if (error?.code === 'ENOENT' && index + 1 < LSOF_CANDIDATES.length) return probe(index + 1);
|
|
68
|
+
// Evidence is tri-state: exit 1 with no holder is a real change, but any
|
|
69
|
+
// other sampler failure (timeout, signal, spawn error) is `unknown`, which
|
|
70
|
+
// callers must treat as a retryable hold rather than "the writer changed".
|
|
71
|
+
if (error) {
|
|
72
|
+
if (error.code === 1 && !String(stdout).trim()) return resolve(false);
|
|
73
|
+
return reject(Object.assign(new Error(`writer witness unavailable: lsof code=${error.code ?? ''} signal=${error.signal ?? ''} killed=${!!error.killed}`), { code: 'RECOVERY_EVIDENCE_UNKNOWN' }));
|
|
74
|
+
}
|
|
75
|
+
const holders = String(stdout).split('\n').filter((line) => /^p\d+$/.test(line)).map((line) => Number(line.slice(1)));
|
|
76
|
+
resolve(holders.length === 1 && holders[0] === operation.original.pid);
|
|
77
|
+
});
|
|
78
|
+
probe(0);
|
|
79
|
+
});
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
async function verifyPreservedEvidence(operation, tasks) {
|
|
@@ -22,7 +22,7 @@ const path = require('node:path');
|
|
|
22
22
|
const { WebSocket } = require('ws');
|
|
23
23
|
|
|
24
24
|
const { atomicWriteFileSync } = require('../atomic-write');
|
|
25
|
-
const { createMux, TYPE, CONTROL_SUBPROTOCOL } = require('../../ctm-relay/lib/protocol.js');
|
|
25
|
+
const { createMux, TYPE, CONTROL_SUBPROTOCOL, OPEN_KINDS } = require('../../ctm-relay/lib/protocol.js');
|
|
26
26
|
|
|
27
27
|
const DEFAULT_LOCAL_TARGET = Object.freeze({ host: '127.0.0.1', port: 3456 });
|
|
28
28
|
const DEFAULT_PING_INTERVAL_MS = 25_000;
|
|
@@ -55,6 +55,35 @@ function hostFor(relayUrl, relayHost) {
|
|
|
55
55
|
return new URL(relayUrl).host;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// SNI names a host, not an endpoint: `r.walle.sh:8443` becomes `r.walle.sh`, and
|
|
59
|
+
// a bracketed IPv6 literal loses its brackets.
|
|
60
|
+
function hostnameOnly(host) {
|
|
61
|
+
const text = String(host);
|
|
62
|
+
if (text.startsWith('[')) {
|
|
63
|
+
const end = text.indexOf(']');
|
|
64
|
+
return end === -1 ? text : text.slice(1, end);
|
|
65
|
+
}
|
|
66
|
+
const colon = text.lastIndexOf(':');
|
|
67
|
+
return colon === -1 ? text : text.slice(0, colon);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The TLS seam (WP-11b): the rev 2 edge routes on SNI, so a client that reaches
|
|
71
|
+
// the relay by address (`https://127.0.0.1:<port>`) still has to name the host it
|
|
72
|
+
// speaks for, and a test CA has to ride along in `ca`. The seam is `options.tls`,
|
|
73
|
+
// spread into the WebSocket options verbatim.
|
|
74
|
+
//
|
|
75
|
+
// Production passes neither: `relayUrl = https://r.walle.sh` with no explicit
|
|
76
|
+
// relayHost means the requested host IS the URL's host, so nothing is added and
|
|
77
|
+
// `ws` derives everything from the URL exactly as it did before.
|
|
78
|
+
function tlsOptionsFor(relayUrl, relayHost, tls) {
|
|
79
|
+
const options = tls && typeof tls === 'object' ? Object.assign({}, tls) : {};
|
|
80
|
+
if (typeof relayHost !== 'string' || relayHost.length === 0) return options;
|
|
81
|
+
if (options.servername) return options;
|
|
82
|
+
if (relayHost === new URL(relayUrl).host) return options;
|
|
83
|
+
options.servername = hostnameOnly(relayHost);
|
|
84
|
+
return options;
|
|
85
|
+
}
|
|
86
|
+
|
|
58
87
|
function forwardHeaders(raw, { keepUpgrade = false } = {}) {
|
|
59
88
|
const out = {};
|
|
60
89
|
for (const [key, value] of Object.entries(raw || {})) {
|
|
@@ -120,6 +149,7 @@ function createRelayClient(options = {}) {
|
|
|
120
149
|
|
|
121
150
|
const connectUrl = connectUrlFor(relayUrl); // throws on a malformed relayUrl, at construction
|
|
122
151
|
const relayHost = hostFor(relayUrl, options.relayHost);
|
|
152
|
+
const tlsOptions = tlsOptionsFor(relayUrl, options.relayHost, options.tls);
|
|
123
153
|
const localTarget = Object.assign({}, DEFAULT_LOCAL_TARGET, options.localTarget || {});
|
|
124
154
|
const stateFile = options.stateFile || null;
|
|
125
155
|
const clientKind = options.clientKind || 'node';
|
|
@@ -351,6 +381,9 @@ function createRelayClient(options = {}) {
|
|
|
351
381
|
if (stopping) return;
|
|
352
382
|
transition('connecting');
|
|
353
383
|
const ws = new WebSocket(connectUrl, CONTROL_SUBPROTOCOL, {
|
|
384
|
+
// `ca` / `servername` / anything else the caller supplied for TLS. Empty in
|
|
385
|
+
// production, so the options object is unchanged there.
|
|
386
|
+
...tlsOptions,
|
|
354
387
|
headers: {
|
|
355
388
|
// The control plane is routable on the base domain only; tests dial
|
|
356
389
|
// 127.0.0.1 while presenting the real Host.
|
|
@@ -407,11 +440,32 @@ function createRelayClient(options = {}) {
|
|
|
407
440
|
|
|
408
441
|
// --- OPEN handling -------------------------------------------------------
|
|
409
442
|
|
|
443
|
+
// Dispatch by kind, explicitly. Rev 1 was `kind === 'upgrade' ? serveUpgrade :
|
|
444
|
+
// serveHttp`, so a missing or unknown kind was silently served as an HTTP
|
|
445
|
+
// request to the user's workbench. Never guess: anything outside OPEN_KINDS
|
|
446
|
+
// (rev 2 has one kind, 'tls') is a protocol violation and is RST with
|
|
447
|
+
// 'client_error', with no request made to the local target.
|
|
410
448
|
function handleStream(stream) {
|
|
411
449
|
const meta = stream.meta || {};
|
|
412
450
|
stream.on('error', (err) => warn('relay-client stream error', err));
|
|
413
|
-
|
|
414
|
-
|
|
451
|
+
const kind = meta.kind;
|
|
452
|
+
if (!OPEN_KINDS.includes(kind)) {
|
|
453
|
+
// Missing, non-string, or a kind this client does not know.
|
|
454
|
+
stream.rst('client_error');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
switch (kind) {
|
|
458
|
+
case 'upgrade':
|
|
459
|
+
serveUpgrade(stream, meta);
|
|
460
|
+
return;
|
|
461
|
+
case 'http':
|
|
462
|
+
serveHttp(stream, meta);
|
|
463
|
+
return;
|
|
464
|
+
default:
|
|
465
|
+
// 'tls': known to the protocol, not served by this client yet.
|
|
466
|
+
// WP-14 terminates TLS here.
|
|
467
|
+
stream.rst('client_error');
|
|
468
|
+
}
|
|
415
469
|
}
|
|
416
470
|
|
|
417
471
|
function serveHttp(stream, meta) {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const transcriptSource = require('./transcript-source');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const { NOOP_LAP, createPerfTrace, perfPhasesJson } = require('./perf-phase-trace');
|
|
16
17
|
const { readJsonlHeader, normalizeCwd, readFirstUserMessage, wordOverlap } = require('./session-verify.js');
|
|
17
18
|
const { detectAgentType, normalizeAgentType } = require('./agent-capabilities.js');
|
|
18
19
|
const {
|
|
@@ -96,15 +97,21 @@ function readAgentFirstUserMessage(filePath, agentType) {
|
|
|
96
97
|
* @param {string} [opts.projectsDir] — defaults to ~/.claude/projects
|
|
97
98
|
* @returns {{ tabs: Array, summary: Object }}
|
|
98
99
|
*/
|
|
99
|
-
function
|
|
100
|
+
function _auditRelinkInner({ db, projectsDir }, trace) {
|
|
101
|
+
const lap = trace ? trace.lap : NOOP_LAP;
|
|
102
|
+
// a nested call already inside a lap: reported in counters, not phases
|
|
103
|
+
const countCall = (name, fn) => (trace ? trace.timed(name, fn) : fn());
|
|
100
104
|
const claudeProjectsDir = projectsDir || path.join(process.env.HOME || '', '.claude', 'projects');
|
|
101
105
|
const hasStartupTasks = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='startup_tasks'").get();
|
|
102
106
|
if (!hasStartupTasks) {
|
|
107
|
+
lap('startup_tasks_probe', 0);
|
|
103
108
|
return { tabs: [], summary: { total: 0, clean: 0, cross_linked: 0, stale: 0, orphan: 0, unclassified: 0, model_mismatch: 0 } };
|
|
104
109
|
}
|
|
110
|
+
lap('startup_tasks_probe', 1);
|
|
105
111
|
|
|
106
112
|
const stCols = new Set(db.prepare('PRAGMA table_info(startup_tasks)').all().map(c => c.name));
|
|
107
113
|
const ctmCols = new Set(db.prepare('PRAGMA table_info(ctm_sessions)').all().map(c => c.name));
|
|
114
|
+
lap('schema_probe', 2);
|
|
108
115
|
const stSelect = (name, alias = name, fallback = "''") => stCols.has(name)
|
|
109
116
|
? `st.${name} AS ${alias}`
|
|
110
117
|
: `${fallback} AS ${alias}`;
|
|
@@ -135,6 +142,7 @@ function auditRelink({ db, projectsDir }) {
|
|
|
135
142
|
LEFT JOIN ctm_sessions cs ON cs.id = st.ctm_session_id
|
|
136
143
|
ORDER BY created_at
|
|
137
144
|
`).all();
|
|
145
|
+
lap('load_startup_rows', rows.length);
|
|
138
146
|
|
|
139
147
|
const agentCols = hasAgentSessions
|
|
140
148
|
? new Set(db.prepare('PRAGMA table_info(agent_sessions)').all().map(c => c.name))
|
|
@@ -162,6 +170,7 @@ function auditRelink({ db, projectsDir }) {
|
|
|
162
170
|
agentsByCtm.get(agent.ctm_session_id).push(agent);
|
|
163
171
|
ownerByAgent.set(agent.agent_session_id, agent);
|
|
164
172
|
}
|
|
173
|
+
lap('load_agent_rows', agentRows.length);
|
|
165
174
|
|
|
166
175
|
// Build a set of all ctm_session_ids — used to detect classic cross-link signature:
|
|
167
176
|
// tab A's agent_session_id == tab B's ctm_session_id (means A got linked to B's original JSONL).
|
|
@@ -178,6 +187,7 @@ function auditRelink({ db, projectsDir }) {
|
|
|
178
187
|
argsResumeId: extractResumeTarget(agentType, args)?.sessionId || null,
|
|
179
188
|
});
|
|
180
189
|
}
|
|
190
|
+
lap('build_startup_index', rows.length);
|
|
181
191
|
|
|
182
192
|
const results = [];
|
|
183
193
|
for (const row of rows) {
|
|
@@ -200,7 +210,7 @@ function auditRelink({ db, projectsDir }) {
|
|
|
200
210
|
for (const ag of agentList) {
|
|
201
211
|
const agType = normalizeAgentType(ag.provider || '') || agentType;
|
|
202
212
|
const filePath = ag.jsonl_path || (agType === 'codex' ? '' : path.join(projectDir, `${ag.agent_session_id}.jsonl`));
|
|
203
|
-
const fileView = transcriptSource.statTranscriptSync(filePath);
|
|
213
|
+
const fileView = countCall('stat_transcript', () => transcriptSource.statTranscriptSync(filePath));
|
|
204
214
|
const exists = fileView.exists;
|
|
205
215
|
let header = null;
|
|
206
216
|
let stat = null;
|
|
@@ -208,10 +218,10 @@ function auditRelink({ db, projectsDir }) {
|
|
|
208
218
|
let contentOverlap = null;
|
|
209
219
|
if (exists) {
|
|
210
220
|
stat = fileView;
|
|
211
|
-
header = readAgentHeader(filePath, agType);
|
|
221
|
+
header = countCall('read_header', () => readAgentHeader(filePath, agType));
|
|
212
222
|
// Only bother reading first user message if we have a tab message to compare
|
|
213
223
|
if (tabFirstMsg && tabFirstMsg.length >= 10) {
|
|
214
|
-
fileFirstUserMsg = readAgentFirstUserMessage(filePath, agType);
|
|
224
|
+
fileFirstUserMsg = countCall('read_first_user_message', () => readAgentFirstUserMessage(filePath, agType));
|
|
215
225
|
if (fileFirstUserMsg) {
|
|
216
226
|
contentOverlap = wordOverlap(tabFirstMsg, fileFirstUserMsg);
|
|
217
227
|
}
|
|
@@ -239,7 +249,7 @@ function auditRelink({ db, projectsDir }) {
|
|
|
239
249
|
let codexStateModel = '';
|
|
240
250
|
if (agType === 'codex') {
|
|
241
251
|
try {
|
|
242
|
-
codexStateModel = String(getCodexStateThreadById(ag.agent_session_id, process.env.HOME)?.model || '');
|
|
252
|
+
codexStateModel = String(countCall('codex_state_read', () => getCodexStateThreadById(ag.agent_session_id, process.env.HOME))?.model || '');
|
|
243
253
|
} catch { codexStateModel = ''; /* expected: best-effort audit column */ }
|
|
244
254
|
}
|
|
245
255
|
agents.push({
|
|
@@ -382,6 +392,7 @@ function auditRelink({ db, projectsDir }) {
|
|
|
382
392
|
details,
|
|
383
393
|
});
|
|
384
394
|
}
|
|
395
|
+
lap('tab_loop', rows.length);
|
|
385
396
|
|
|
386
397
|
const summary = {
|
|
387
398
|
total: results.length,
|
|
@@ -392,8 +403,26 @@ function auditRelink({ db, projectsDir }) {
|
|
|
392
403
|
unclassified: results.filter(r => r.verdict === 'unclassified').length,
|
|
393
404
|
model_mismatch: results.filter(r => r.model_mismatch === true).length,
|
|
394
405
|
};
|
|
406
|
+
lap('build_summary', results.length);
|
|
395
407
|
|
|
396
408
|
return { tabs: results, summary };
|
|
397
409
|
}
|
|
398
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Perf-attribution wrapper (WP-0 / M-3): runs the audit with a phase trace, attaches it as
|
|
413
|
+
* `result.phases` (additive field — tabs/summary are unchanged) and logs one
|
|
414
|
+
* `[session-integrity] phases {...}` line for this run. Measurement only: with
|
|
415
|
+
* CTM_PERF_ATTRIBUTION off the trace is null, so nothing is attached or logged.
|
|
416
|
+
*/
|
|
417
|
+
function auditRelink(opts) {
|
|
418
|
+
const trace = createPerfTrace();
|
|
419
|
+
const result = _auditRelinkInner(opts, trace);
|
|
420
|
+
if (trace) {
|
|
421
|
+
trace.lap('finalize', 1);
|
|
422
|
+
result.phases = trace.phases;
|
|
423
|
+
console.log(`[session-integrity] phases ${perfPhasesJson('relink', trace)}`);
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
|
|
399
428
|
module.exports = { auditRelink };
|