spexcode 0.6.2 → 0.6.6
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/bin/spex.mjs +4 -0
- package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +23 -0
- package/node_modules/@spexcode/session-core/dist/delivery-queue.js +195 -0
- package/node_modules/@spexcode/session-core/dist/index.d.ts +5 -0
- package/node_modules/@spexcode/session-core/dist/index.js +5 -0
- package/node_modules/@spexcode/session-core/dist/internal.d.ts +3 -0
- package/node_modules/@spexcode/session-core/dist/internal.js +3 -0
- package/node_modules/@spexcode/session-core/dist/message.d.ts +22 -0
- package/node_modules/@spexcode/session-core/dist/message.js +52 -0
- package/node_modules/@spexcode/session-core/dist/record-lock.d.ts +7 -0
- package/node_modules/@spexcode/session-core/dist/record-lock.js +152 -0
- package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +50 -0
- package/node_modules/@spexcode/session-core/dist/runtime-session.js +286 -0
- package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +14 -0
- package/node_modules/@spexcode/session-core/dist/session-cursors.js +82 -0
- package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +46 -0
- package/node_modules/@spexcode/session-core/dist/session-timeline.js +216 -0
- package/node_modules/@spexcode/session-core/package.json +33 -0
- package/node_modules/@spexcode/spec-cli/bin/spex.mjs +77 -0
- package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +36 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +316 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.js +1908 -0
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +112 -0
- package/node_modules/@spexcode/spec-cli/dist/client.js +481 -0
- package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
- package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +58 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +798 -0
- package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
- package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
- package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +553 -0
- package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
- package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
- package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
- package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +363 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +38 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +527 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +717 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +36 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
- package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
- package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
- package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
- package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +36 -32
- package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +338 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.js +3146 -0
- package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
- package/node_modules/@spexcode/spec-cli/dist/help.d.ts +15 -0
- package/node_modules/@spexcode/spec-cli/dist/help.js +594 -0
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
- package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
- package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
- package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
- package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
- package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
- package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
- package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
- package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
- package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
- package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
- package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
- package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
- package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
- package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +8 -0
- package/node_modules/@spexcode/spec-cli/dist/listen.js +24 -0
- package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
- package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
- package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
- package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
- package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
- package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
- package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
- package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
- package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +5 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +215 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
- package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
- package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
- package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
- package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
- package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
- package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
- package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
- package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
- package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
- package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
- package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
- package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.js +58 -0
- package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
- package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
- package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
- package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
- package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
- package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
- package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +11 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +20 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +433 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +4667 -0
- package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
- package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
- package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
- package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
- package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
- package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
- package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
- package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
- package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
- package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
- package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
- package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
- package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
- package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
- package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +1 -1
- package/node_modules/@spexcode/spec-cli/package.json +49 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
- package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
- package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
- package/node_modules/@spexcode/spec-core/dist/git.d.ts +170 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +2768 -0
- package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
- package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
- package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
- package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
- package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
- package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
- package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
- package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
- package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
- package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
- package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
- package/node_modules/@spexcode/spec-core/dist/layout.d.ts +187 -0
- package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
- package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
- package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
- package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
- package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
- package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
- package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
- package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
- package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
- package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
- package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
- package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
- package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
- package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
- package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
- package/node_modules/@spexcode/spec-core/package.json +28 -0
- package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
- package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
- package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
- package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
- package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
- package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
- package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
- package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
- package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
- package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
- package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
- package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
- package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
- package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
- package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
- package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
- package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
- package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
- package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
- package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
- package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
- package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
- package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
- package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
- package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
- package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
- package/node_modules/@spexcode/spec-eval/package.json +36 -0
- package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
- package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
- package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
- package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
- package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
- package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
- package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
- package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
- package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
- package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
- package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
- package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
- package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
- package/node_modules/@spexcode/spec-forge/package.json +32 -0
- package/package.json +33 -19
- package/spec-cli/bin/spex.mjs +0 -69
- package/spec-cli/src/anchors.ts +0 -728
- package/spec-cli/src/attach.ts +0 -43
- package/spec-cli/src/claude-headless.ts +0 -321
- package/spec-cli/src/cli.ts +0 -1258
- package/spec-cli/src/client.ts +0 -414
- package/spec-cli/src/cockpit.ts +0 -43
- package/spec-cli/src/codex-runtime-generations.ts +0 -646
- package/spec-cli/src/commit-surgery.ts +0 -68
- package/spec-cli/src/contract-filter.ts +0 -202
- package/spec-cli/src/delivery-queue.ts +0 -165
- package/spec-cli/src/doctor.ts +0 -516
- package/spec-cli/src/execution-trace.ts +0 -444
- package/spec-cli/src/file-write.ts +0 -22
- package/spec-cli/src/gateway-auth.ts +0 -181
- package/spec-cli/src/gateway-hub.ts +0 -358
- package/spec-cli/src/gateway.ts +0 -461
- package/spec-cli/src/git.ts +0 -2556
- package/spec-cli/src/graph.ts +0 -271
- package/spec-cli/src/graphCache.ts +0 -710
- package/spec-cli/src/graphDelta.ts +0 -77
- package/spec-cli/src/graphStream.ts +0 -1019
- package/spec-cli/src/harness-select.ts +0 -77
- package/spec-cli/src/harness.ts +0 -3025
- package/spec-cli/src/headless-controller.ts +0 -63
- package/spec-cli/src/help.ts +0 -492
- package/spec-cli/src/host-resources.ts +0 -762
- package/spec-cli/src/host.ts +0 -611
- package/spec-cli/src/identity-presets.d.ts +0 -13
- package/spec-cli/src/identity-presets.js +0 -138
- package/spec-cli/src/index.ts +0 -818
- package/spec-cli/src/init.ts +0 -258
- package/spec-cli/src/issues-cli.ts +0 -276
- package/spec-cli/src/issues.ts +0 -237
- package/spec-cli/src/layout.ts +0 -640
- package/spec-cli/src/lint.ts +0 -488
- package/spec-cli/src/listen.ts +0 -28
- package/spec-cli/src/localIssues.ts +0 -538
- package/spec-cli/src/materialize.ts +0 -417
- package/spec-cli/src/mentions.ts +0 -81
- package/spec-cli/src/opencode-headless.ts +0 -205
- package/spec-cli/src/pi-headless.ts +0 -153
- package/spec-cli/src/plugin-harness.ts +0 -157
- package/spec-cli/src/process-identity.ts +0 -207
- package/spec-cli/src/project-identity.ts +0 -73
- package/spec-cli/src/project-store.ts +0 -17
- package/spec-cli/src/pty-bridge.ts +0 -396
- package/spec-cli/src/pty-helper.mjs +0 -96
- package/spec-cli/src/pty-native-helper.mjs +0 -22
- package/spec-cli/src/ranker.ts +0 -191
- package/spec-cli/src/reaper.ts +0 -125
- package/spec-cli/src/resilience.ts +0 -41
- package/spec-cli/src/reviewSnapshot.ts +0 -28
- package/spec-cli/src/reviews.ts +0 -434
- package/spec-cli/src/runtime-guard.ts +0 -35
- package/spec-cli/src/runtime-ownership.ts +0 -88
- package/spec-cli/src/search.bench.mjs +0 -80
- package/spec-cli/src/search.ts +0 -63
- package/spec-cli/src/session-cursors.ts +0 -91
- package/spec-cli/src/session-execution.ts +0 -68
- package/spec-cli/src/session-files.ts +0 -121
- package/spec-cli/src/session-follow.ts +0 -182
- package/spec-cli/src/session-reparent.ts +0 -33
- package/spec-cli/src/session-timeline.ts +0 -247
- package/spec-cli/src/session-web.ts +0 -135
- package/spec-cli/src/sessions.ts +0 -3883
- package/spec-cli/src/sh.ts +0 -3
- package/spec-cli/src/slash-commands.ts +0 -318
- package/spec-cli/src/source-files.ts +0 -104
- package/spec-cli/src/specs.ts +0 -498
- package/spec-cli/src/supervise.ts +0 -245
- package/spec-cli/src/tree.ts +0 -118
- package/spec-cli/src/tsx-bin.ts +0 -18
- package/spec-cli/src/uninstall.ts +0 -171
- package/spec-cli/src/uploads.ts +0 -253
- package/spec-cli/src/worktree-sources.ts +0 -42
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
- package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
- package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
- package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
- package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
- package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
- package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
- package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
- package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
- package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
- package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
- package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
- package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
- package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
- package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
- package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
- package/spec-dashboard/dist/index.html +0 -29
- package/spec-dashboard/src/reviewFilters.js +0 -322
- package/spec-dashboard/src/reviewQuery.js +0 -174
- package/spec-dashboard/src/session.js +0 -199
- package/spec-eval/src/cache.ts +0 -57
- package/spec-eval/src/cli.ts +0 -894
- package/spec-eval/src/evaltab.ts +0 -264
- package/spec-eval/src/filing.ts +0 -39
- package/spec-eval/src/freshness.ts +0 -836
- package/spec-eval/src/humanok.ts +0 -28
- package/spec-eval/src/scenariofresh.ts +0 -244
- package/spec-eval/src/scenarios.ts +0 -672
- package/spec-eval/src/sessioneval.ts +0 -2220
- package/spec-eval/src/sidecar.ts +0 -93
- package/spec-eval/src/timeline.ts +0 -54
- package/spec-forge/src/__fixtures__/github-forge.json +0 -63
- package/spec-forge/src/cache.ts +0 -66
- package/spec-forge/src/cli.ts +0 -99
- package/spec-forge/src/drivers/github.ts +0 -132
- package/spec-forge/src/drivers/gitlab.ts +0 -153
- package/spec-forge/src/drivers.ts +0 -73
- package/spec-forge/src/links.ts +0 -72
- package/spec-forge/src/needs-eval.ts +0 -29
- package/spec-forge/src/port.ts +0 -36
- package/spec-forge/src/resident.ts +0 -46
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
import { cpus, platform } from 'node:os';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { defaultHarness, HARNESSES, harnessById, harnessByIdOrNull, sessionIdentityEnvVars } from './harness.js';
|
|
7
|
+
import { listSessionIds, readConfig, readJsonConfig, readPublicRecordEntry, readRawRecord, runtimeRoot } from '@spexcode/spec-core';
|
|
8
|
+
import { repoRoot } from '@spexcode/spec-core';
|
|
9
|
+
import { endpointRecordPath } from './host.js';
|
|
10
|
+
import { detachedRuntimeGenerationToken, parseProcStat, processStartToken, verifyDetachedRuntime } from '@spexcode/spec-core';
|
|
11
|
+
import { readBackendInstanceRecords } from './runtime-ownership.js';
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
sessionRssMiB: 1024,
|
|
14
|
+
backendRssMiB: 2048,
|
|
15
|
+
idleCpuPercent: 2,
|
|
16
|
+
sampleMs: 1000,
|
|
17
|
+
reportIntervalMs: 60_000,
|
|
18
|
+
};
|
|
19
|
+
export class ResourceConflict extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(message, code = 'resource-conflict') {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = 'ResourceConflict';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const finitePositive = (value, fallback, name) => {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return fallback;
|
|
30
|
+
const n = Number(value);
|
|
31
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
32
|
+
throw new ResourceConflict(`resources.${name} must be a positive number`);
|
|
33
|
+
return n;
|
|
34
|
+
};
|
|
35
|
+
export function resourceBudgets(root = repoRoot()) {
|
|
36
|
+
const r = readConfig(root).resources ?? {};
|
|
37
|
+
return {
|
|
38
|
+
sessionRssMiB: finitePositive(r.sessionRssMiB, DEFAULTS.sessionRssMiB, 'sessionRssMiB'),
|
|
39
|
+
backendRssMiB: finitePositive(r.backendRssMiB, DEFAULTS.backendRssMiB, 'backendRssMiB'),
|
|
40
|
+
idleCpuPercent: finitePositive(r.idleCpuPercent, DEFAULTS.idleCpuPercent, 'idleCpuPercent'),
|
|
41
|
+
sampleMs: Math.max(50, Math.floor(finitePositive(r.sampleMs, DEFAULTS.sampleMs, 'sampleMs'))),
|
|
42
|
+
reportIntervalMs: Math.max(5000, Math.floor(finitePositive(r.reportIntervalMs, DEFAULTS.reportIntervalMs, 'reportIntervalMs'))),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const readSelectedEnv = (path) => {
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = readFileSync(path, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
const wanted = new Set([...sessionIdentityEnvVars(), 'SPEXCODE_PROJECT_ROOT', 'SPEXCODE_INSTANCE_ID']);
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const entry of raw.split('\0')) {
|
|
56
|
+
const at = entry.indexOf('=');
|
|
57
|
+
if (at > 0 && wanted.has(entry.slice(0, at)))
|
|
58
|
+
out[entry.slice(0, at)] = entry.slice(at + 1);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
};
|
|
62
|
+
const readPss = (path) => {
|
|
63
|
+
try {
|
|
64
|
+
const hit = readFileSync(path, 'utf8').match(/^Pss:\s+(\d+)\s+kB$/m);
|
|
65
|
+
return hit ? Number(hit[1]) : null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
let pageKiBCache = null;
|
|
72
|
+
const pageKiB = () => {
|
|
73
|
+
if (pageKiBCache !== null)
|
|
74
|
+
return pageKiBCache;
|
|
75
|
+
try {
|
|
76
|
+
pageKiBCache = Number(execFileSync('getconf', ['PAGESIZE'], { encoding: 'utf8' }).trim()) / 1024;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
pageKiBCache = 4;
|
|
80
|
+
}
|
|
81
|
+
return pageKiBCache;
|
|
82
|
+
};
|
|
83
|
+
const procSnapshot = (procRoot = '/proc', withDetails = true) => {
|
|
84
|
+
const out = new Map();
|
|
85
|
+
let dirs;
|
|
86
|
+
try {
|
|
87
|
+
dirs = readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
for (const name of dirs) {
|
|
93
|
+
const pid = Number(name);
|
|
94
|
+
try {
|
|
95
|
+
const stat = parseProcStat(readFileSync(join(procRoot, name, 'stat'), 'utf8'));
|
|
96
|
+
const command = withDetails
|
|
97
|
+
? readFileSync(join(procRoot, name, 'comm'), 'utf8').trim()
|
|
98
|
+
: '';
|
|
99
|
+
out.set(pid, {
|
|
100
|
+
pid,
|
|
101
|
+
ppid: stat.ppid,
|
|
102
|
+
ticks: stat.ticks,
|
|
103
|
+
startToken: stat.startToken,
|
|
104
|
+
rssKiB: stat.rssPages * pageKiB(),
|
|
105
|
+
pssKiB: withDetails ? readPss(join(procRoot, name, 'smaps_rollup')) : null,
|
|
106
|
+
cpuPercent: 0,
|
|
107
|
+
command,
|
|
108
|
+
env: withDetails ? readSelectedEnv(join(procRoot, name, 'environ')) : {},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch { /* process exited or is unreadable during the snapshot */ }
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
};
|
|
115
|
+
const totalCpuTicks = (procRoot = '/proc') => {
|
|
116
|
+
try {
|
|
117
|
+
const line = readFileSync(join(procRoot, 'stat'), 'utf8').split('\n')[0];
|
|
118
|
+
return line.split(/\s+/).slice(1).reduce((n, value) => n + Number(value || 0), 0);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const hostCpuSnapshot = (procRoot = '/proc') => {
|
|
125
|
+
try {
|
|
126
|
+
const fields = readFileSync(join(procRoot, 'stat'), 'utf8').split('\n')[0].split(/\s+/).slice(1).map(Number);
|
|
127
|
+
return { total: fields.reduce((n, value) => n + (value || 0), 0), idle: (fields[3] || 0) + (fields[4] || 0) };
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
134
|
+
const descendants = (root, procs) => {
|
|
135
|
+
const ids = new Set([root]);
|
|
136
|
+
let changed = true;
|
|
137
|
+
while (changed) {
|
|
138
|
+
changed = false;
|
|
139
|
+
for (const p of procs.values())
|
|
140
|
+
if (!ids.has(p.pid) && ids.has(p.ppid)) {
|
|
141
|
+
ids.add(p.pid);
|
|
142
|
+
changed = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return ids;
|
|
146
|
+
};
|
|
147
|
+
const publicRecordInventory = () => {
|
|
148
|
+
const entries = listSessionIds().map(readPublicRecordEntry);
|
|
149
|
+
const byId = new Map();
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (entry.kind === 'ok')
|
|
152
|
+
byId.set(entry.raw.session_id, entry);
|
|
153
|
+
else if (entry.kind === 'corrupt')
|
|
154
|
+
byId.set(entry.sessionId, entry);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
entries,
|
|
158
|
+
records: entries.flatMap((entry) => entry.kind === 'ok' ? [entry.raw] : []),
|
|
159
|
+
byId,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const rawRecords = () => listSessionIds().map(readRawRecord).filter((r) => !!r);
|
|
163
|
+
const runtimePid = (file) => {
|
|
164
|
+
try {
|
|
165
|
+
const pid = Number(readFileSync(file, 'utf8').trim());
|
|
166
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
// A session record outlives the config that created it, so a removed plugin harness or a renamed harness id
|
|
173
|
+
// leaves a governed record this registry cannot resolve. That is a normal long-lived state for a host-wide
|
|
174
|
+
// sweep, not a request naming a bad id: the sweep reports the record and keeps every other owner's findings.
|
|
175
|
+
const unresolvableHarness = (rec) => {
|
|
176
|
+
const id = rec.harness || defaultHarness.id;
|
|
177
|
+
return harnessByIdOrNull(id) ? null : id;
|
|
178
|
+
};
|
|
179
|
+
// Mirrors the corrupt-record owner: a record the sweep could not fully read stays a public owner so its
|
|
180
|
+
// absence from a process-derived group cannot read as health. Here the unknown half is the harness, so
|
|
181
|
+
// lifecycle is unknown and reclaim is closed rather than guessed from the record's own status field.
|
|
182
|
+
const unresolvedHarnessOwners = (recs, budgets, have) => {
|
|
183
|
+
const out = [];
|
|
184
|
+
for (const rec of recs) {
|
|
185
|
+
const unresolved = unresolvableHarness(rec);
|
|
186
|
+
if (!unresolved || !rec.governed || rec.stopped || have(rec.session_id))
|
|
187
|
+
continue;
|
|
188
|
+
out.push({
|
|
189
|
+
kind: 'session', id: rec.session_id, label: `session ${rec.session_id.slice(0, 8)}`, status: rec.status, liveness: 'unknown',
|
|
190
|
+
processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0,
|
|
191
|
+
budget: { rssMiB: budgets.sessionRssMiB, idleCpuPercent: budgets.idleCpuPercent },
|
|
192
|
+
findings: [`harness-unresolved:${unresolved}`],
|
|
193
|
+
reclaim: { eligible: false, reason: `harness '${unresolved}' is not resolvable by this registry; shared-runtime stop safety cannot be proven` },
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
};
|
|
198
|
+
const sharedDescriptors = (recs, retainRegistry = false) => {
|
|
199
|
+
const out = new Map();
|
|
200
|
+
for (const harness of HARNESSES)
|
|
201
|
+
for (const descriptor of harness.sharedRuntimes?.(runtimeRoot()) ?? []) {
|
|
202
|
+
if (!out.has(descriptor.key))
|
|
203
|
+
out.set(descriptor.key, { descriptor, recs: [] });
|
|
204
|
+
}
|
|
205
|
+
for (const rec of recs) {
|
|
206
|
+
// Keep archived records in the adapter join so a stale loaded thread is attributed to its real record and
|
|
207
|
+
// reported as an archive hazard. Clean archived records carry stopped:true and no loaded reference, so they
|
|
208
|
+
// contribute nothing to the active set while still preserving exact ownership if the invariant is violated.
|
|
209
|
+
if (!rec.governed)
|
|
210
|
+
continue;
|
|
211
|
+
const harness = harnessByIdOrNull(rec.harness || defaultHarness.id);
|
|
212
|
+
if (!harness)
|
|
213
|
+
continue; // joined to no descriptor; surfaced as a finding on that record's own session owner
|
|
214
|
+
const exactKey = harness.targetDescriptorKey?.({ session: rec.session_id, harnessSessionId: rec.harness_session_id }) ?? null;
|
|
215
|
+
for (const descriptor of harness.sharedRuntimes?.(runtimeRoot()) ?? []) {
|
|
216
|
+
if (exactKey && descriptor.key !== exactKey)
|
|
217
|
+
continue;
|
|
218
|
+
const entry = out.get(descriptor.key) ?? { descriptor, recs: [] };
|
|
219
|
+
entry.recs.push(rec);
|
|
220
|
+
out.set(descriptor.key, entry);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!retainRegistry)
|
|
224
|
+
for (const [key, entry] of out)
|
|
225
|
+
if (!entry.recs.length && !runtimePid(entry.descriptor.pidFile))
|
|
226
|
+
out.delete(key);
|
|
227
|
+
return out;
|
|
228
|
+
};
|
|
229
|
+
const projectReferences = (entry, probe, publicById) => {
|
|
230
|
+
const byThread = new Map();
|
|
231
|
+
for (const rec of entry.recs)
|
|
232
|
+
if (rec.harness_session_id)
|
|
233
|
+
byThread.set(rec.harness_session_id, [...(byThread.get(rec.harness_session_id) ?? []), rec]);
|
|
234
|
+
const observed = probe.healthy ? probe.references : [];
|
|
235
|
+
const live = observed.map((reference) => {
|
|
236
|
+
const owners = byThread.get(reference.referenceId) ?? [];
|
|
237
|
+
if (owners.length !== 1)
|
|
238
|
+
return {
|
|
239
|
+
sessionId: null,
|
|
240
|
+
threadId: reference.referenceId,
|
|
241
|
+
status: null,
|
|
242
|
+
ownerState: owners.length ? 'ambiguous' : 'unowned',
|
|
243
|
+
referenceState: 'loaded',
|
|
244
|
+
turnPresence: reference.turnPresence,
|
|
245
|
+
protectsControlPlane: true,
|
|
246
|
+
};
|
|
247
|
+
const rec = owners[0];
|
|
248
|
+
const publicEntry = publicById.get(rec.session_id);
|
|
249
|
+
return {
|
|
250
|
+
sessionId: rec.session_id,
|
|
251
|
+
threadId: reference.referenceId,
|
|
252
|
+
status: rec.status,
|
|
253
|
+
ownerState: 'governed',
|
|
254
|
+
referenceState: 'loaded',
|
|
255
|
+
turnPresence: reference.turnPresence,
|
|
256
|
+
protectsControlPlane: true,
|
|
257
|
+
...(publicEntry?.kind === 'ok' && publicEntry.liveness ? { liveness: publicEntry.liveness } : {}),
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
const loaded = new Set(observed.map((reference) => reference.referenceId));
|
|
261
|
+
for (const rec of entry.recs) {
|
|
262
|
+
if (rec.stopped || (rec.harness_session_id && loaded.has(rec.harness_session_id)))
|
|
263
|
+
continue;
|
|
264
|
+
const publicEntry = publicById.get(rec.session_id);
|
|
265
|
+
live.push({
|
|
266
|
+
sessionId: rec.session_id,
|
|
267
|
+
threadId: rec.harness_session_id ?? null,
|
|
268
|
+
status: rec.status,
|
|
269
|
+
ownerState: 'governed',
|
|
270
|
+
referenceState: rec.harness_session_id ? 'record-only' : 'queued-no-thread',
|
|
271
|
+
turnPresence: 'none',
|
|
272
|
+
protectsControlPlane: false,
|
|
273
|
+
...(publicEntry?.kind === 'ok' && publicEntry.liveness ? { liveness: publicEntry.liveness } : {}),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return live;
|
|
277
|
+
};
|
|
278
|
+
const buildInventory = (procs, publicRecords = publicRecordInventory()) => {
|
|
279
|
+
const recs = publicRecords.records;
|
|
280
|
+
const activeRecs = recs.filter((rec) => {
|
|
281
|
+
if (!rec.archived)
|
|
282
|
+
return true;
|
|
283
|
+
// A legacy archived row with a resident leaf is a visible hazard, not clean cold storage. Keep charging
|
|
284
|
+
// that exact process until an explicit archive repair succeeds; truly cold records stay out of the active set.
|
|
285
|
+
const pid = runtimePid(join(runtimeRoot(), 'sessions', rec.session_id, 'agent.pid'));
|
|
286
|
+
return !!pid && procs.has(pid);
|
|
287
|
+
});
|
|
288
|
+
const byId = new Map(activeRecs.map((rec) => [rec.session_id, rec.session_id]));
|
|
289
|
+
for (const entry of publicRecords.entries)
|
|
290
|
+
if (entry.kind === 'corrupt')
|
|
291
|
+
byId.set(entry.sessionId, entry.sessionId);
|
|
292
|
+
for (const rec of activeRecs)
|
|
293
|
+
if (rec.harness_session_id)
|
|
294
|
+
byId.set(rec.harness_session_id, rec.session_id);
|
|
295
|
+
const ownership = new Map();
|
|
296
|
+
const adapterVars = sessionIdentityEnvVars().filter((v) => v !== 'SPEXCODE_SESSION_ID');
|
|
297
|
+
const actingSession = (p) => {
|
|
298
|
+
const acting = adapterVars.map((key) => p.env[key]).find((value) => value && byId.has(value));
|
|
299
|
+
return acting ? byId.get(acting) : undefined;
|
|
300
|
+
};
|
|
301
|
+
for (const p of procs.values()) {
|
|
302
|
+
const acting = actingSession(p);
|
|
303
|
+
const fallback = p.env.SPEXCODE_SESSION_ID;
|
|
304
|
+
const sid = acting ?? (fallback ? byId.get(fallback) : undefined);
|
|
305
|
+
if (sid)
|
|
306
|
+
ownership.set(p.pid, `session:${sid}`);
|
|
307
|
+
}
|
|
308
|
+
for (const rec of activeRecs) {
|
|
309
|
+
const root = runtimePid(join(runtimeRoot(), 'sessions', rec.session_id, 'agent.pid'));
|
|
310
|
+
if (root && procs.has(root))
|
|
311
|
+
for (const pid of descendants(root, procs))
|
|
312
|
+
if (!ownership.has(pid))
|
|
313
|
+
ownership.set(pid, `session:${rec.session_id}`);
|
|
314
|
+
}
|
|
315
|
+
const shared = sharedDescriptors(recs);
|
|
316
|
+
for (const [key, entry] of shared) {
|
|
317
|
+
const root = runtimePid(entry.descriptor.pidFile);
|
|
318
|
+
if (root && procs.has(root))
|
|
319
|
+
for (const pid of descendants(root, procs)) {
|
|
320
|
+
const proc = procs.get(pid);
|
|
321
|
+
// A legacy shared daemon may carry its first launcher's fallback session id. The registered root and
|
|
322
|
+
// descendants without a known acting thread still belong to the control plane; real per-thread tools
|
|
323
|
+
// carry the adapter's thread id and retain their session charge.
|
|
324
|
+
if (pid === root || !actingSession(proc))
|
|
325
|
+
ownership.set(pid, `shared:${key}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Endpoint records are served-tree slots, while sessions/shared runtimes/instance history use the Git
|
|
329
|
+
// common-dir project slot. Reuse the endpoint seam instead of assuming both identities have one path.
|
|
330
|
+
const backend = readJsonConfig(endpointRecordPath(repoRoot()));
|
|
331
|
+
const backendPid = Number.isFinite(Number(backend.pid)) ? Number(backend.pid) : null;
|
|
332
|
+
const backendInstance = typeof backend.instanceId === 'string' ? backend.instanceId : null;
|
|
333
|
+
if (backendPid && procs.has(backendPid))
|
|
334
|
+
for (const pid of descendants(backendPid, procs)) {
|
|
335
|
+
if (!ownership.has(pid))
|
|
336
|
+
ownership.set(pid, `backend:${backendInstance || backendPid}`);
|
|
337
|
+
}
|
|
338
|
+
const backendInstances = readBackendInstanceRecords(repoRoot());
|
|
339
|
+
for (const instance of backendInstances) {
|
|
340
|
+
if (processStartToken(instance.pid) !== instance.startToken || !procs.has(instance.pid))
|
|
341
|
+
continue;
|
|
342
|
+
const current = instance.pid === backendPid && instance.instanceId === backendInstance;
|
|
343
|
+
const key = current ? `backend:${instance.instanceId}` : `orphan:backend:${instance.instanceId}`;
|
|
344
|
+
for (const pid of descendants(instance.pid, procs))
|
|
345
|
+
if (!ownership.has(pid))
|
|
346
|
+
ownership.set(pid, key);
|
|
347
|
+
}
|
|
348
|
+
const root = repoRoot();
|
|
349
|
+
for (const p of procs.values()) {
|
|
350
|
+
if (ownership.has(p.pid) || p.env.SPEXCODE_PROJECT_ROOT !== root)
|
|
351
|
+
continue;
|
|
352
|
+
const claimed = p.env.SPEXCODE_SESSION_ID;
|
|
353
|
+
if (claimed && !byId.has(claimed))
|
|
354
|
+
ownership.set(p.pid, `orphan:session:${claimed}`);
|
|
355
|
+
else if (p.env.SPEXCODE_INSTANCE_ID && p.env.SPEXCODE_INSTANCE_ID !== backendInstance)
|
|
356
|
+
ownership.set(p.pid, `orphan:backend:${p.env.SPEXCODE_INSTANCE_ID}`);
|
|
357
|
+
else
|
|
358
|
+
ownership.set(p.pid, 'unattributed:project');
|
|
359
|
+
}
|
|
360
|
+
return { procs, recs, publicById: publicRecords.byId, ownership, shared, backendPid, backendInstance, backendInstances };
|
|
361
|
+
};
|
|
362
|
+
const mib = (kib) => Math.round((kib / 1024) * 10) / 10;
|
|
363
|
+
const ownerProcesses = (ids, procs) => ids.map((pid) => procs.get(pid)).filter(Boolean).map((p) => ({
|
|
364
|
+
pid: p.pid,
|
|
365
|
+
startToken: p.startToken,
|
|
366
|
+
command: p.command,
|
|
367
|
+
rssMiB: mib(p.rssKiB),
|
|
368
|
+
pssMiB: p.pssKiB === null ? null : mib(p.pssKiB),
|
|
369
|
+
cpuPercent: Math.round(p.cpuPercent * 10) / 10,
|
|
370
|
+
}));
|
|
371
|
+
const ownerTotals = (processes) => ({
|
|
372
|
+
rssMiB: Math.round(processes.reduce((n, p) => n + p.rssMiB, 0) * 10) / 10,
|
|
373
|
+
pssMiB: processes.every((p) => p.pssMiB !== null) ? Math.round(processes.reduce((n, p) => n + (p.pssMiB ?? 0), 0) * 10) / 10 : null,
|
|
374
|
+
cpuPercent: Math.round(processes.reduce((n, p) => n + p.cpuPercent, 0) * 10) / 10,
|
|
375
|
+
});
|
|
376
|
+
const terminal = (rec) => !!rec && rec.status === 'awaiting' && (rec.proposal === 'nothing' || rec.proposal === 'close');
|
|
377
|
+
const probeRuntime = async (descriptor) => {
|
|
378
|
+
try {
|
|
379
|
+
return await descriptor.probe();
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
return { healthy: false, references: [], error: error.message };
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const sessionOwnerBlocker = (id, harnessId, recs) => {
|
|
386
|
+
const targetRecord = recs.find((rec) => rec.session_id === id);
|
|
387
|
+
const targetHarness = harnessId ? harnessById(harnessId) : null;
|
|
388
|
+
const targetThread = targetRecord?.harness_session_id;
|
|
389
|
+
if (!targetHarness || !targetThread)
|
|
390
|
+
return null;
|
|
391
|
+
const ownerCount = recs.filter((rec) => rec.governed && (rec.harness || defaultHarness.id) === targetHarness.id && rec.harness_session_id === targetThread).length;
|
|
392
|
+
if (ownerCount === 1)
|
|
393
|
+
return null;
|
|
394
|
+
const label = targetHarness.sharedRuntimes?.(runtimeRoot())[0]?.label ?? targetHarness.id;
|
|
395
|
+
return `${label} target thread ${targetThread} has no one exact governed session owner`;
|
|
396
|
+
};
|
|
397
|
+
export function assertSessionOwnerSafe(id, harnessId) {
|
|
398
|
+
const blocker = sessionOwnerBlocker(id, harnessId, rawRecords());
|
|
399
|
+
if (blocker)
|
|
400
|
+
throw new ResourceConflict(blocker);
|
|
401
|
+
}
|
|
402
|
+
const sessionStopBlocker = async (id, harnessId, recs = rawRecords(), knownProbes, opts = {}) => {
|
|
403
|
+
const targetRecord = recs.find((rec) => rec.session_id === id);
|
|
404
|
+
const targetHarness = harnessId ? harnessById(harnessId) : null;
|
|
405
|
+
const targetThread = targetRecord?.harness_session_id;
|
|
406
|
+
const ownerBlocker = sessionOwnerBlocker(id, harnessId, recs);
|
|
407
|
+
if (ownerBlocker)
|
|
408
|
+
return ownerBlocker;
|
|
409
|
+
const exactKey = targetHarness?.targetDescriptorKey?.({ session: id, harnessSessionId: targetRecord?.harness_session_id }) ?? null;
|
|
410
|
+
const targetDescriptors = targetHarness?.sharedRuntimes?.(runtimeRoot()) ?? [];
|
|
411
|
+
if (targetHarness && targetDescriptors.length === 0)
|
|
412
|
+
return null;
|
|
413
|
+
if (targetRecord?.harness_session_id && targetHarness?.targetDescriptorKey && targetDescriptors.length && !exactKey)
|
|
414
|
+
return `${targetHarness.id} target has no exact shared-runtime generation binding`;
|
|
415
|
+
const allowed = targetHarness
|
|
416
|
+
? new Set(exactKey ? [exactKey] : targetDescriptors.map((descriptor) => descriptor.key))
|
|
417
|
+
: null;
|
|
418
|
+
for (const [key, entry] of sharedDescriptors(recs, true)) {
|
|
419
|
+
if (allowed && !allowed.has(key))
|
|
420
|
+
continue;
|
|
421
|
+
const descriptor = entry.descriptor;
|
|
422
|
+
const pid = runtimePid(descriptor.pidFile);
|
|
423
|
+
const ownerCounts = new Map();
|
|
424
|
+
for (const rec of entry.recs)
|
|
425
|
+
if (rec.harness_session_id)
|
|
426
|
+
ownerCounts.set(rec.harness_session_id, (ownerCounts.get(rec.harness_session_id) ?? 0) + 1);
|
|
427
|
+
const entryTargetThread = entry.recs.find((rec) => rec.session_id === id)?.harness_session_id;
|
|
428
|
+
if (entryTargetThread && ownerCounts.get(entryTargetThread) !== 1)
|
|
429
|
+
return `${descriptor.label} target thread ${entryTargetThread} has no one exact governed session owner`;
|
|
430
|
+
if (!knownProbes && descriptor.mutationGuard) {
|
|
431
|
+
if (!targetThread)
|
|
432
|
+
return `${descriptor.label} target has no exact governed thread identity`;
|
|
433
|
+
if (!pid)
|
|
434
|
+
return `${descriptor.label} target-scoped mutation guard has no readable owner PID`;
|
|
435
|
+
const identityBefore = verifyDetachedRuntime(pid, descriptor.receiptFile);
|
|
436
|
+
if (!identityBefore.ok)
|
|
437
|
+
return `${descriptor.label} PID ${pid} has no matching live detached process-boundary record: ${identityBefore.reason}`;
|
|
438
|
+
let guard;
|
|
439
|
+
try {
|
|
440
|
+
guard = await descriptor.mutationGuard(targetThread, opts);
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return `${descriptor.label} target-scoped mutation guard failed: ${error.message}`;
|
|
444
|
+
}
|
|
445
|
+
const identityAfter = verifyDetachedRuntime(pid, descriptor.receiptFile);
|
|
446
|
+
if (!identityAfter.ok || detachedRuntimeGenerationToken(identityAfter.identity) !== detachedRuntimeGenerationToken(identityBefore.identity))
|
|
447
|
+
return `${descriptor.label} PID/start/detached-receipt identity changed during target-scoped mutation guard${identityAfter.ok ? '' : `: ${identityAfter.reason}`}`;
|
|
448
|
+
if (guard.descendantIds.length && guard.coldTeardownAuthorized !== true)
|
|
449
|
+
return `${descriptor.label} target thread ${targetThread} has owned descendants (${guard.descendantIds.join(', ')})`;
|
|
450
|
+
if (!guard.healthy)
|
|
451
|
+
return `${descriptor.label} target thread ${targetThread} is unknown: ${guard.error || 'target-scoped mutation guard failed'}`;
|
|
452
|
+
if (guard.targetTurnPresence === 'active')
|
|
453
|
+
return `${descriptor.label} target thread ${targetThread} has an active turn`;
|
|
454
|
+
if (guard.targetTurnPresence === 'unknown')
|
|
455
|
+
return `${descriptor.label} target thread ${targetThread} turn state is unknown`;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const probe = knownProbes?.get(descriptor.key) ?? await probeRuntime(descriptor);
|
|
459
|
+
const targetRef = probe.healthy && targetThread ? probe.references.find((reference) => reference.referenceId === targetThread) : undefined;
|
|
460
|
+
const siblings = probe.healthy ? probe.references.filter((reference) => !targetThread || reference.referenceId !== targetThread) : [];
|
|
461
|
+
const liveReason = siblings.length
|
|
462
|
+
? `${siblings.length} live sibling thread(s)`
|
|
463
|
+
: probe.healthy ? `${probe.references.length} live thread reference(s)` : 'an unproven live reference set';
|
|
464
|
+
if (!pid)
|
|
465
|
+
return `${descriptor.label} has ${liveReason} but no readable owner PID`;
|
|
466
|
+
const identity = verifyDetachedRuntime(pid, descriptor.receiptFile);
|
|
467
|
+
if (!identity.ok) {
|
|
468
|
+
const refs = probe.references.map((reference) => reference.referenceId).join(', ') || 'no loaded threads';
|
|
469
|
+
return `${descriptor.label} PID ${pid} serves ${refs} and has no matching live detached process-boundary record: ${identity.reason}`;
|
|
470
|
+
}
|
|
471
|
+
if (!probe.healthy)
|
|
472
|
+
return `${descriptor.label} PID ${pid}@${identity.identity.startToken} has an unproven live reference set: ${probe.error || 'unknown probe failure'}`;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
};
|
|
476
|
+
const probeShared = async (shared) => new Map(await Promise.all([...shared].map(async ([key, entry]) => [key, await probeRuntime(entry.descriptor)])));
|
|
477
|
+
const sharedFindings = (references, probe) => {
|
|
478
|
+
const findings = [];
|
|
479
|
+
if (!probe.healthy)
|
|
480
|
+
findings.push(`control-plane-probe-failed:${probe.error || 'unknown'}`);
|
|
481
|
+
if (references.some((ref) => ref.ownerState === 'unowned'))
|
|
482
|
+
findings.push('unowned-loaded-thread');
|
|
483
|
+
if (references.some((ref) => ref.ownerState === 'ambiguous'))
|
|
484
|
+
findings.push('ambiguous-loaded-thread-owner');
|
|
485
|
+
if (references.some((ref) => ref.turnPresence === 'unknown'))
|
|
486
|
+
findings.push('turn-presence-unknown');
|
|
487
|
+
if (references.some((ref) => ref.referenceState !== 'loaded'))
|
|
488
|
+
findings.push('record-without-live-thread');
|
|
489
|
+
return findings;
|
|
490
|
+
};
|
|
491
|
+
const atomicJson = (path, value) => {
|
|
492
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
493
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
494
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
495
|
+
renameSync(tmp, path);
|
|
496
|
+
};
|
|
497
|
+
const memInfo = (procRoot = '/proc') => {
|
|
498
|
+
const values = {};
|
|
499
|
+
try {
|
|
500
|
+
for (const line of readFileSync(join(procRoot, 'meminfo'), 'utf8').split('\n')) {
|
|
501
|
+
const m = line.match(/^(\w+):\s+(\d+)/);
|
|
502
|
+
if (m)
|
|
503
|
+
values[m[1]] = Number(m[2]);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch { /* unavailable below */ }
|
|
507
|
+
const total = values.MemTotal;
|
|
508
|
+
const available = values.MemAvailable;
|
|
509
|
+
const swapTotal = values.SwapTotal;
|
|
510
|
+
const swapFree = values.SwapFree;
|
|
511
|
+
return {
|
|
512
|
+
memoryTotalMiB: total ? mib(total) : null,
|
|
513
|
+
memoryUsedMiB: total && available !== undefined ? mib(total - available) : null,
|
|
514
|
+
memoryAvailableMiB: available !== undefined ? mib(available) : null,
|
|
515
|
+
swapTotalMiB: swapTotal !== undefined ? mib(swapTotal) : null,
|
|
516
|
+
swapUsedMiB: swapTotal !== undefined && swapFree !== undefined ? mib(swapTotal - swapFree) : null,
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
export async function collectResourceReport(opts = {}) {
|
|
520
|
+
const root = repoRoot();
|
|
521
|
+
const budgets = resourceBudgets(root);
|
|
522
|
+
if (platform() !== 'linux' && !opts.procRoot) {
|
|
523
|
+
const publicRecords = publicRecordInventory();
|
|
524
|
+
const recs = publicRecords.records;
|
|
525
|
+
const owners = [];
|
|
526
|
+
const shared = sharedDescriptors(recs);
|
|
527
|
+
const probes = await probeShared(shared);
|
|
528
|
+
for (const [key, entry] of shared) {
|
|
529
|
+
const probe = probes.get(key);
|
|
530
|
+
const refs = projectReferences(entry, probe, publicRecords.byId);
|
|
531
|
+
owners.push({ kind: 'shared-runtime', id: key, label: entry.descriptor.label, processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0, budget: { rssMiB: null, idleCpuPercent: null }, controlPlane: { healthy: probe.healthy, refCount: probe.healthy ? probe.references.length : null, ...(probe.error ? { error: probe.error } : {}) }, references: refs, findings: sharedFindings(refs, probe) });
|
|
532
|
+
}
|
|
533
|
+
for (const entry of publicRecords.entries)
|
|
534
|
+
if (entry.kind === 'corrupt')
|
|
535
|
+
owners.push({
|
|
536
|
+
kind: 'session', id: entry.sessionId, label: `session ${entry.sessionId.slice(0, 8)}`, status: 'corrupt', liveness: 'unknown',
|
|
537
|
+
processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0,
|
|
538
|
+
budget: { rssMiB: budgets.sessionRssMiB, idleCpuPercent: budgets.idleCpuPercent },
|
|
539
|
+
findings: [`session-record-corrupt:${entry.error}`], reclaim: { eligible: false, reason: 'session record is corrupt; ownership and lifecycle are unknown' },
|
|
540
|
+
});
|
|
541
|
+
owners.push(...unresolvedHarnessOwners(recs, budgets, (sessionId) => owners.some((owner) => owner.kind === 'session' && owner.id === sessionId)));
|
|
542
|
+
const report = { version: 1, measuredAt: new Date().toISOString(), projectRoot: root, platform: platform(), available: false, unavailableReason: `host process metrics are unavailable on ${platform()}; shared runtime references remain visible`, host: { memoryTotalMiB: null, memoryUsedMiB: null, memoryAvailableMiB: null, swapTotalMiB: null, swapUsedMiB: null, cpuPercent: null }, budgets, owners, totals: { rssMiB: 0, pssMiB: null, cpuPercent: 0 }, findings: owners.reduce((n, o) => n + o.findings.length, 0) };
|
|
543
|
+
if (opts.persist !== false)
|
|
544
|
+
atomicJson(join(runtimeRoot(), 'resource-report.json'), report);
|
|
545
|
+
return report;
|
|
546
|
+
}
|
|
547
|
+
const procRoot = opts.procRoot ?? '/proc';
|
|
548
|
+
const first = procSnapshot(procRoot);
|
|
549
|
+
const hostBefore = hostCpuSnapshot(procRoot);
|
|
550
|
+
const cpuBefore = hostBefore?.total ?? totalCpuTicks(procRoot);
|
|
551
|
+
await sleep(budgets.sampleMs);
|
|
552
|
+
const second = procSnapshot(procRoot, false);
|
|
553
|
+
const hostAfter = hostCpuSnapshot(procRoot);
|
|
554
|
+
const cpuAfter = hostAfter?.total ?? totalCpuTicks(procRoot);
|
|
555
|
+
const totalDelta = Math.max(1, cpuAfter - cpuBefore);
|
|
556
|
+
for (const p of first.values()) {
|
|
557
|
+
const next = second.get(p.pid);
|
|
558
|
+
if (next && next.startToken === p.startToken)
|
|
559
|
+
p.cpuPercent = Math.max(0, (next.ticks - p.ticks) / totalDelta * cpus().length * 100);
|
|
560
|
+
}
|
|
561
|
+
const inv = buildInventory(first);
|
|
562
|
+
const sharedProbes = await probeShared(inv.shared);
|
|
563
|
+
const grouped = new Map();
|
|
564
|
+
for (const [pid, owner] of inv.ownership)
|
|
565
|
+
grouped.set(owner, [...(grouped.get(owner) ?? []), pid]);
|
|
566
|
+
const bySession = new Map(inv.recs.map((rec) => [rec.session_id, rec]));
|
|
567
|
+
const owners = [];
|
|
568
|
+
for (const [owner, ids] of grouped) {
|
|
569
|
+
const processes = ownerProcesses(ids, first);
|
|
570
|
+
const totals = ownerTotals(processes);
|
|
571
|
+
let kind = 'unattributed';
|
|
572
|
+
let id = owner;
|
|
573
|
+
let label = owner;
|
|
574
|
+
let status;
|
|
575
|
+
let lifecycle;
|
|
576
|
+
let rssBudget = null;
|
|
577
|
+
let idleBudget = null;
|
|
578
|
+
let references;
|
|
579
|
+
let controlPlane;
|
|
580
|
+
const findings = [];
|
|
581
|
+
let reclaim;
|
|
582
|
+
if (owner.startsWith('session:')) {
|
|
583
|
+
kind = 'session';
|
|
584
|
+
id = owner.slice(8);
|
|
585
|
+
label = `session ${id.slice(0, 8)}`;
|
|
586
|
+
const publicEntry = inv.publicById.get(id);
|
|
587
|
+
const rec = bySession.get(id);
|
|
588
|
+
status = publicEntry?.kind === 'corrupt' ? 'corrupt' : rec?.status;
|
|
589
|
+
rssBudget = budgets.sessionRssMiB;
|
|
590
|
+
idleBudget = budgets.idleCpuPercent;
|
|
591
|
+
if (rec && publicEntry?.kind === 'ok')
|
|
592
|
+
lifecycle = {
|
|
593
|
+
...(publicEntry.liveness ? { liveness: publicEntry.liveness } : {}),
|
|
594
|
+
proposal: rec.proposal, note: rec.note, worktreePath: rec.worktree_path, branch: rec.branch, stopped: rec.stopped, archived: rec.archived,
|
|
595
|
+
};
|
|
596
|
+
else if (publicEntry?.kind === 'corrupt') {
|
|
597
|
+
lifecycle = { liveness: 'unknown' };
|
|
598
|
+
findings.push(`session-record-corrupt:${publicEntry.error}`);
|
|
599
|
+
}
|
|
600
|
+
if (rec?.archived)
|
|
601
|
+
findings.push('archived-runtime-hazard:leaf-still-resident');
|
|
602
|
+
if (totals.rssMiB > rssBudget)
|
|
603
|
+
findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`);
|
|
604
|
+
if (status !== 'active' && status !== 'queued' && totals.cpuPercent > idleBudget)
|
|
605
|
+
findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`);
|
|
606
|
+
// The stop guard resolves the record's harness fail-loud, which is right for a stop REQUEST and wrong
|
|
607
|
+
// here: this sweep reads rows nobody named. An unresolvable harness makes stop safety unprovable, so it
|
|
608
|
+
// is reported and reclaim stays closed — never a resolvable-looking zero, never a lost report.
|
|
609
|
+
const unresolved = rec ? unresolvableHarness(rec) : null;
|
|
610
|
+
if (unresolved)
|
|
611
|
+
findings.push(`harness-unresolved:${unresolved}`);
|
|
612
|
+
const stopBlocker = rec && !unresolved ? await sessionStopBlocker(id, rec.harness || defaultHarness.id, inv.recs, sharedProbes) : null;
|
|
613
|
+
if (unresolved) {
|
|
614
|
+
reclaim = { eligible: false, reason: `harness '${unresolved}' is not resolvable by this registry; shared-runtime stop safety cannot be proven` };
|
|
615
|
+
}
|
|
616
|
+
else if (terminal(rec) && totals.cpuPercent <= idleBudget && !stopBlocker) {
|
|
617
|
+
reclaim = { eligible: true, reason: `terminal lifecycle ${rec.status}/${rec.proposal}; a future exact action must revalidate every fact` };
|
|
618
|
+
}
|
|
619
|
+
else
|
|
620
|
+
reclaim = { eligible: false, reason: stopBlocker ? `shared runtime unsafe: ${stopBlocker}` : terminal(rec) ? 'terminal record is still consuming CPU; liveness contradicts safe retirement' : 'owner is not terminal; budget age/status alone never authorizes stop' };
|
|
621
|
+
}
|
|
622
|
+
else if (owner.startsWith('shared:')) {
|
|
623
|
+
kind = 'shared-runtime';
|
|
624
|
+
id = owner.slice(7);
|
|
625
|
+
const shared = inv.shared.get(id);
|
|
626
|
+
label = shared.descriptor.label;
|
|
627
|
+
rssBudget = budgets.backendRssMiB;
|
|
628
|
+
idleBudget = budgets.idleCpuPercent;
|
|
629
|
+
const probe = sharedProbes.get(id);
|
|
630
|
+
references = projectReferences(shared, probe, inv.publicById);
|
|
631
|
+
if (references.some((reference) => reference.sessionId && bySession.get(reference.sessionId)?.archived))
|
|
632
|
+
findings.push('archived-runtime-hazard:loaded-thread');
|
|
633
|
+
controlPlane = { healthy: probe.healthy, refCount: probe.healthy ? probe.references.length : null, ...(probe.error ? { error: probe.error } : {}) };
|
|
634
|
+
if (totals.rssMiB > rssBudget)
|
|
635
|
+
findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`);
|
|
636
|
+
const protectedReferences = references.filter((reference) => reference.protectsControlPlane);
|
|
637
|
+
const turnPresenceKnown = !references.some((reference) => reference.turnPresence === 'unknown');
|
|
638
|
+
if (probe.healthy && turnPresenceKnown && !references.some((reference) => reference.turnPresence === 'active') && totals.cpuPercent > idleBudget)
|
|
639
|
+
findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`);
|
|
640
|
+
if (probe.healthy && protectedReferences.length === 0)
|
|
641
|
+
findings.push('orphan-shared-runtime:no-live-references');
|
|
642
|
+
findings.push(...sharedFindings(references, probe));
|
|
643
|
+
if (ids.some((pid) => sessionIdentityEnvVars().some((key) => !!first.get(pid)?.env[key])))
|
|
644
|
+
findings.push('identity-leak:project-control-plane-carries-session-id');
|
|
645
|
+
reclaim = { eligible: false, reason: !probe.healthy ? 'live reference count unknown; adapter/project teardown only' : protectedReferences.length ? `protected by ${protectedReferences.length} loaded/active thread reference(s)` : 'shared runtime teardown belongs to its adapter/project owner, never session stop' };
|
|
646
|
+
}
|
|
647
|
+
else if (owner.startsWith('backend:')) {
|
|
648
|
+
kind = 'backend';
|
|
649
|
+
id = owner.slice(8);
|
|
650
|
+
label = `backend ${id}`;
|
|
651
|
+
rssBudget = budgets.backendRssMiB;
|
|
652
|
+
idleBudget = budgets.idleCpuPercent;
|
|
653
|
+
if (totals.rssMiB > rssBudget)
|
|
654
|
+
findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`);
|
|
655
|
+
if (totals.cpuPercent > idleBudget)
|
|
656
|
+
findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`);
|
|
657
|
+
if (ids.some((pid) => sessionIdentityEnvVars().some((key) => !!first.get(pid)?.env[key])))
|
|
658
|
+
findings.push('identity-leak:project-control-plane-carries-session-id');
|
|
659
|
+
reclaim = { eligible: false, reason: 'current endpoint owner; backend teardown is not a session stop' };
|
|
660
|
+
}
|
|
661
|
+
else if (owner.startsWith('orphan:')) {
|
|
662
|
+
kind = 'orphan';
|
|
663
|
+
id = owner.slice(7);
|
|
664
|
+
label = owner;
|
|
665
|
+
findings.push('orphan:owner-record-absent');
|
|
666
|
+
if (id.startsWith('session:')) {
|
|
667
|
+
const exactOwner = id.slice(8);
|
|
668
|
+
id = exactOwner;
|
|
669
|
+
label = `orphan session ${exactOwner}`;
|
|
670
|
+
reclaim = { eligible: true, reason: 'process carries this project and session identity, but the owner record is absent' };
|
|
671
|
+
}
|
|
672
|
+
else if (id.startsWith('backend:')) {
|
|
673
|
+
const exactOwner = id.slice(8);
|
|
674
|
+
id = exactOwner;
|
|
675
|
+
label = `orphan backend ${exactOwner}`;
|
|
676
|
+
rssBudget = budgets.backendRssMiB;
|
|
677
|
+
idleBudget = budgets.idleCpuPercent;
|
|
678
|
+
if (totals.rssMiB > rssBudget)
|
|
679
|
+
findings.push(`rss-over-budget:${Math.round((totals.rssMiB - rssBudget) * 10) / 10}MiB`);
|
|
680
|
+
if (totals.cpuPercent > idleBudget)
|
|
681
|
+
findings.push(`idle-cpu-over-budget:${Math.round((totals.cpuPercent - idleBudget) * 10) / 10}%`);
|
|
682
|
+
reclaim = { eligible: false, reason: 'superseded backend generation remains owned by backend supervisor teardown, never session stop' };
|
|
683
|
+
}
|
|
684
|
+
else
|
|
685
|
+
reclaim = { eligible: false, reason: 'runtime teardown belongs to its registered owner' };
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
findings.push('unattributed:project-process-without-owner');
|
|
689
|
+
reclaim = { eligible: false, reason: 'ownership is insufficient; reporting stays read-only' };
|
|
690
|
+
}
|
|
691
|
+
owners.push({ kind, id, label, ...(status ? { status } : {}), ...(lifecycle ?? {}), processes, ...totals, budget: { rssMiB: rssBudget, idleCpuPercent: idleBudget }, ...(controlPlane ? { controlPlane } : {}), ...(references ? { references } : {}), findings, ...(reclaim ? { reclaim } : {}) });
|
|
692
|
+
}
|
|
693
|
+
// A corrupt record stays a public owner even without a currently attributable process. Its absence from a
|
|
694
|
+
// process-derived group cannot turn unknown lifecycle/ownership into disappearance.
|
|
695
|
+
for (const entry of inv.publicById.values())
|
|
696
|
+
if (entry.kind === 'corrupt'
|
|
697
|
+
&& !owners.some((owner) => owner.kind === 'session' && owner.id === entry.sessionId))
|
|
698
|
+
owners.push({
|
|
699
|
+
kind: 'session', id: entry.sessionId, label: `session ${entry.sessionId.slice(0, 8)}`, status: 'corrupt', liveness: 'unknown',
|
|
700
|
+
processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0,
|
|
701
|
+
budget: { rssMiB: budgets.sessionRssMiB, idleCpuPercent: budgets.idleCpuPercent },
|
|
702
|
+
findings: [`session-record-corrupt:${entry.error}`], reclaim: { eligible: false, reason: 'session record is corrupt; ownership and lifecycle are unknown' },
|
|
703
|
+
});
|
|
704
|
+
owners.push(...unresolvedHarnessOwners(inv.recs, budgets, (sessionId) => owners.some((owner) => owner.kind === 'session' && owner.id === sessionId)));
|
|
705
|
+
// A referenced shared runtime with no readable process is still operationally important: keep its live or
|
|
706
|
+
// unknown refcount visible instead of silently omitting it from a process-derived report.
|
|
707
|
+
for (const [key, shared] of inv.shared)
|
|
708
|
+
if (!owners.some((o) => o.kind === 'shared-runtime' && o.id === key)) {
|
|
709
|
+
const probe = sharedProbes.get(key);
|
|
710
|
+
const references = projectReferences(shared, probe, inv.publicById);
|
|
711
|
+
const protectedReferences = references.filter((reference) => reference.protectsControlPlane);
|
|
712
|
+
owners.push({ kind: 'shared-runtime', id: key, label: shared.descriptor.label, processes: [], rssMiB: 0, pssMiB: null, cpuPercent: 0, budget: { rssMiB: budgets.backendRssMiB, idleCpuPercent: null }, controlPlane: { healthy: probe.healthy, refCount: probe.healthy ? probe.references.length : null, ...(probe.error ? { error: probe.error } : {}) }, references, findings: ['runtime-process-unavailable', ...sharedFindings(references, probe)], reclaim: { eligible: false, reason: !probe.healthy ? 'live reference count unknown; adapter/project teardown only' : protectedReferences.length ? `protected by ${protectedReferences.length} loaded/active thread reference(s)` : 'adapter/project teardown only' } });
|
|
713
|
+
}
|
|
714
|
+
owners.sort((a, b) => b.rssMiB - a.rssMiB || a.id.localeCompare(b.id));
|
|
715
|
+
const rssMiB = Math.round(owners.reduce((n, o) => n + o.rssMiB, 0) * 10) / 10;
|
|
716
|
+
const pssMiB = owners.every((o) => o.pssMiB !== null) ? Math.round(owners.reduce((n, o) => n + (o.pssMiB ?? 0), 0) * 10) / 10 : null;
|
|
717
|
+
const cpuPercent = Math.round(owners.reduce((n, o) => n + o.cpuPercent, 0) * 10) / 10;
|
|
718
|
+
const report = {
|
|
719
|
+
version: 1,
|
|
720
|
+
measuredAt: new Date().toISOString(),
|
|
721
|
+
projectRoot: root,
|
|
722
|
+
platform: platform(),
|
|
723
|
+
available: true,
|
|
724
|
+
host: {
|
|
725
|
+
...memInfo(procRoot),
|
|
726
|
+
cpuPercent: hostBefore && hostAfter && hostAfter.total > hostBefore.total
|
|
727
|
+
? Math.round((1 - (hostAfter.idle - hostBefore.idle) / (hostAfter.total - hostBefore.total)) * 1000) / 10
|
|
728
|
+
: null,
|
|
729
|
+
},
|
|
730
|
+
budgets,
|
|
731
|
+
owners,
|
|
732
|
+
totals: { rssMiB, pssMiB, cpuPercent },
|
|
733
|
+
findings: owners.reduce((n, owner) => n + owner.findings.length, 0),
|
|
734
|
+
};
|
|
735
|
+
if (opts.persist !== false)
|
|
736
|
+
atomicJson(join(runtimeRoot(), 'resource-report.json'), report);
|
|
737
|
+
return report;
|
|
738
|
+
}
|
|
739
|
+
export async function assertSessionStopSafe(id, rec, opts = {}) {
|
|
740
|
+
if (!rec)
|
|
741
|
+
throw new ResourceConflict(`refusing to stop ${id}: no readable session record proves the adapter or leaf owner`);
|
|
742
|
+
const blocker = await sessionStopBlocker(id, rec.harness || null, rawRecords(), undefined, opts);
|
|
743
|
+
if (blocker)
|
|
744
|
+
throw new ResourceConflict(`refusing to stop ${id}: ${blocker}`);
|
|
745
|
+
}
|
|
746
|
+
export function formatResourceReport(report) {
|
|
747
|
+
const host = report.available
|
|
748
|
+
? `host memory ${report.host.memoryUsedMiB}/${report.host.memoryTotalMiB} MiB, swap ${report.host.swapUsedMiB}/${report.host.swapTotalMiB} MiB`
|
|
749
|
+
: `host metrics unavailable: ${report.unavailableReason}`;
|
|
750
|
+
const lines = [`resources @ ${report.measuredAt}`, ` ${host}`, ` attributed PSS ${report.totals.pssMiB ?? 'unavailable'} MiB (RSS ${report.totals.rssMiB} MiB), findings ${report.findings}`];
|
|
751
|
+
for (const owner of report.owners) {
|
|
752
|
+
const lifecycle = owner.status ? ` state=${owner.status}/${owner.proposal ?? '-'} branch=${owner.branch ?? '-'}` : '';
|
|
753
|
+
const refs = owner.references
|
|
754
|
+
? ` refs=${owner.controlPlane?.refCount ?? '?'}/${owner.references.length} active=${owner.references.filter((r) => r.turnPresence === 'active').length} unknown=${owner.references.filter((r) => r.turnPresence === 'unknown').length}`
|
|
755
|
+
: '';
|
|
756
|
+
lines.push(` ${owner.kind.padEnd(14)} ${owner.id} rss=${owner.rssMiB}MiB pss=${owner.pssMiB ?? '?'}MiB cpu=${owner.cpuPercent}%${lifecycle}${refs}${owner.findings.length ? ` ! ${owner.findings.join(',')}` : ''}`);
|
|
757
|
+
}
|
|
758
|
+
return lines.join('\n');
|
|
759
|
+
}
|
|
760
|
+
let monitorStarted = false;
|
|
761
|
+
export function startResourceMonitor() {
|
|
762
|
+
if (monitorStarted)
|
|
763
|
+
return;
|
|
764
|
+
monitorStarted = true;
|
|
765
|
+
const interval = resourceBudgets().reportIntervalMs;
|
|
766
|
+
let last = new Set();
|
|
767
|
+
let running = false;
|
|
768
|
+
const sample = async () => {
|
|
769
|
+
if (running)
|
|
770
|
+
return;
|
|
771
|
+
running = true;
|
|
772
|
+
try {
|
|
773
|
+
const report = await collectResourceReport();
|
|
774
|
+
const stableFinding = (finding) => finding.startsWith('rss-over-budget:') ? 'rss-over-budget'
|
|
775
|
+
: finding.startsWith('idle-cpu-over-budget:') ? 'idle-cpu-over-budget'
|
|
776
|
+
: finding;
|
|
777
|
+
const next = new Set(report.owners.flatMap((owner) => owner.findings.map((finding) => `${owner.kind}:${owner.id}:${stableFinding(finding)}`)));
|
|
778
|
+
for (const finding of next)
|
|
779
|
+
if (!last.has(finding))
|
|
780
|
+
console.warn(`[resources] entered ${finding}`);
|
|
781
|
+
for (const finding of last)
|
|
782
|
+
if (!next.has(finding))
|
|
783
|
+
console.warn(`[resources] cleared ${finding}`);
|
|
784
|
+
last = next;
|
|
785
|
+
}
|
|
786
|
+
catch (e) {
|
|
787
|
+
console.error(`[resources] sample failed: ${e.message}`);
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
running = false;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
setTimeout(() => void sample(), 5000).unref();
|
|
794
|
+
setInterval(() => void sample(), interval).unref();
|
|
795
|
+
}
|