spexcode 0.6.2 → 0.6.5
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/spec-cli/bin/spex.mjs +76 -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 +33 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +307 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.js +1859 -0
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +111 -0
- package/node_modules/@spexcode/spec-cli/dist/client.js +476 -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 +53 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +686 -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/delivery-queue.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-queue.js +179 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +547 -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 +361 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +37 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +503 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +716 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +35 -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 +325 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.js +3026 -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 +10 -0
- package/node_modules/@spexcode/spec-cli/dist/help.js +574 -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 +7 -0
- package/{spec-cli/src/listen.ts → node_modules/@spexcode/spec-cli/dist/listen.js} +13 -17
- 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 +4 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +208 -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/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-cursors.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/session-cursors.js +82 -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 +51 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +233 -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 +412 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +4239 -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 +47 -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 +166 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +2736 -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 +183 -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/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
package/spec-cli/src/search.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { loadSpecsLite } from './specs.js'
|
|
2
|
-
import { rankDocs, type RankInput } from './ranker.js'
|
|
3
|
-
|
|
4
|
-
export type SearchResult = { id: string; title: string; path: string; score: number; snippet: string }
|
|
5
|
-
export type SearchStats = { nodes: number; tokens: number; ms: number }
|
|
6
|
-
|
|
7
|
-
// zero-result fallback: nearest node titles by per-word edit distance over title+id — the lightweight
|
|
8
|
-
// string distance that routes a typo'd query to a next step. Each query word takes its best normalised
|
|
9
|
-
// Levenshtein similarity against any title/id word (≥0.5, so unrelated words don't accumulate), then the
|
|
10
|
-
// words sum. Same loadSpecsLite read as the ranker; deliberately NOT part of rankDocs scoring (this
|
|
11
|
-
// tolerates typos, the ranker must not).
|
|
12
|
-
export function nearestTitles(query: string, n = 3): { id: string; title: string }[] {
|
|
13
|
-
const words = (s: string) => s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1)
|
|
14
|
-
const lev = (a: string, b: string) => {
|
|
15
|
-
const dp = Array.from({ length: b.length + 1 }, (_, i) => i)
|
|
16
|
-
for (let i = 1; i <= a.length; i++) {
|
|
17
|
-
let prev = dp[0]
|
|
18
|
-
dp[0] = i
|
|
19
|
-
for (let j = 1; j <= b.length; j++) {
|
|
20
|
-
const cur = dp[j]
|
|
21
|
-
dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
22
|
-
prev = cur
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return dp[b.length]
|
|
26
|
-
}
|
|
27
|
-
const qws = words(query)
|
|
28
|
-
if (!qws.length) return [] // nothing to be "near" (e.g. a pure-CJK query) — the caller still points at spex graph
|
|
29
|
-
return loadSpecsLite()
|
|
30
|
-
.map((s) => {
|
|
31
|
-
const tws = [...new Set(words(`${s.title} ${s.id}`))]
|
|
32
|
-
let score = 0
|
|
33
|
-
for (const q of qws) {
|
|
34
|
-
let best = 0
|
|
35
|
-
for (const t of tws) best = Math.max(best, 1 - lev(q, t) / Math.max(q.length, t.length))
|
|
36
|
-
if (best >= 0.5) score += best
|
|
37
|
-
}
|
|
38
|
-
return { id: s.id, title: s.title, score }
|
|
39
|
-
})
|
|
40
|
-
.filter((s) => s.score > 0)
|
|
41
|
-
.sort((a, b) => b.score - a.score)
|
|
42
|
-
.slice(0, n)
|
|
43
|
-
.map(({ id, title }) => ({ id, title }))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function searchSpecs(query: string, opts: { limit?: number; onStats?: (s: SearchStats) => void } = {}): Promise<SearchResult[]> {
|
|
47
|
-
const t0 = performance.now()
|
|
48
|
-
// pre-sort by (shorter id, then id): rankDocs sorts stably, so for equal-scored nodes this IS the tiebreak.
|
|
49
|
-
const nodes = loadSpecsLite()
|
|
50
|
-
.slice()
|
|
51
|
-
.sort((a, b) => a.id.length - b.id.length || a.id.localeCompare(b.id))
|
|
52
|
-
const inputs: RankInput<(typeof nodes)[number]>[] = nodes.map((s) => ({
|
|
53
|
-
ref: s, name: `${s.title} ${s.id}`, desc: s.desc, body: s.body,
|
|
54
|
-
}))
|
|
55
|
-
const out = rankDocs(query, inputs, { limit: opts.limit }).map((r) => ({
|
|
56
|
-
id: r.ref.id, title: r.ref.title, path: r.ref.path, score: r.score, snippet: r.snippet,
|
|
57
|
-
}))
|
|
58
|
-
if (opts.onStats) {
|
|
59
|
-
const tokens = inputs.reduce((a, i) => a + `${i.name} ${i.desc} ${i.body}`.split(/[^a-z0-9]+/i).filter(Boolean).length, 0)
|
|
60
|
-
opts.onStats({ nodes: inputs.length, tokens, ms: performance.now() - t0 })
|
|
61
|
-
}
|
|
62
|
-
return out
|
|
63
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { sessionArtifactPath, sessionStoreDir } from './layout.js'
|
|
4
|
-
import type { TimelineEvent } from './session-timeline.js'
|
|
5
|
-
|
|
6
|
-
// @@@ session-cursors - a reader's durable place in a log. One `cursors.json` per session in its global store
|
|
7
|
-
// dir: `follows` holds one entry per followed session, including the reader's own id when it watches its own
|
|
8
|
-
// log. A position is an event INDEX into timeline.ndjson (lines already consumed), so it is also the index of
|
|
9
|
-
// the next unread event. A position is NOT a work list: what a session still owes its agent is a debt, and it
|
|
10
|
-
// lives in its own queue ([[delivery-queue]]). Binding both to one counter is what made a session's own status
|
|
11
|
-
// lines get consumed as though they were mail, and made "anything outstanding?" a scan of all history.
|
|
12
|
-
|
|
13
|
-
export type Cursors = { version: 1; follows: Record<string, number> }
|
|
14
|
-
|
|
15
|
-
const cursorsPath = (id: string): string => sessionArtifactPath(id, 'cursors.json')
|
|
16
|
-
const at = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0)
|
|
17
|
-
|
|
18
|
-
// Never throws: a missing, empty, or unparseable file reads as "nothing consumed", because re-showing a
|
|
19
|
-
// message is the honest recovery for a lost position and skipping one is not. Followed entries whose target
|
|
20
|
-
// store dir is gone are dropped here — expiry is this read, and the next write persists it.
|
|
21
|
-
export function readCursors(id: string): Cursors {
|
|
22
|
-
let raw: { follows?: unknown } | null = null
|
|
23
|
-
try { raw = JSON.parse(readFileSync(cursorsPath(id), 'utf8')) } catch { /* no cursors yet */ }
|
|
24
|
-
const follows: Record<string, number> = {}
|
|
25
|
-
const stored = raw?.follows
|
|
26
|
-
if (stored && typeof stored === 'object') {
|
|
27
|
-
for (const [target, pos] of Object.entries(stored as Record<string, unknown>)) {
|
|
28
|
-
if (!target || !existsSync(sessionStoreDir(target))) continue
|
|
29
|
-
follows[target] = at(pos)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return { version: 1, follows }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Written whole and atomically, one field per line — the same shape as the session record, so a position stays
|
|
36
|
-
// readable by an exact whole-line match where a value regex would not be.
|
|
37
|
-
function writeCursors(id: string, cursors: Cursors): void {
|
|
38
|
-
const dir = sessionStoreDir(id)
|
|
39
|
-
mkdirSync(dir, { recursive: true })
|
|
40
|
-
const tmp = join(dir, `.cursors.json.${process.pid}.tmp`)
|
|
41
|
-
writeFileSync(tmp, JSON.stringify(cursors, null, 2) + '\n')
|
|
42
|
-
renameSync(tmp, cursorsPath(id))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const followCursor = (id: string, target: string): number | null => {
|
|
46
|
-
const stored = readCursors(id).follows[target]
|
|
47
|
-
return stored === undefined ? null : stored
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Start or advance a follow. Following IS this entry existing, so the first call registers the relationship.
|
|
51
|
-
// Monotonic: a stale read can leave a position too low (an event read twice), never too high (one lost).
|
|
52
|
-
export function advanceFollow(id: string, target: string, to: number): void {
|
|
53
|
-
const cursors = readCursors(id)
|
|
54
|
-
const stored = cursors.follows[target]
|
|
55
|
-
if (stored !== undefined && to <= stored) return
|
|
56
|
-
writeCursors(id, { ...cursors, follows: { ...cursors.follows, [target]: Math.max(0, Math.floor(to)) } })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const followedSessions = (id: string): string[] => Object.keys(readCursors(id).follows)
|
|
60
|
-
|
|
61
|
-
const sameStatus = (a: TimelineEvent, b: TimelineEvent): boolean =>
|
|
62
|
-
a.kind === 'status' && b.kind === 'status' && a.status === b.status
|
|
63
|
-
&& (a.proposal ?? null) === (b.proposal ?? null) && (a.note ?? null) === (b.note ?? null)
|
|
64
|
-
|
|
65
|
-
// @@@ edges, not lines - what a reader has not yet seen, with repeated status lines dropped. X→X IS NOT A
|
|
66
|
-
// TRANSITION: the log is append-only and already holds runs of identical status lines from before the
|
|
67
|
-
// timeline observer was deleted (every stray `spex serve` process ran an fs.watch over the same store and
|
|
68
|
-
// re-recorded each real move, so ONE transition landed as up to 6 lines within ~200ms). Those bytes are
|
|
69
|
-
// history and stay. Any consumer that decides "did it move?" must therefore compare VALUES, never adjacency,
|
|
70
|
-
// and must compare against the last status the reader ALREADY saw — otherwise a duplicate straddling the
|
|
71
|
-
// cursor boundary reads as a fresh move on the very next tick. `next` is where the cursor goes after consuming
|
|
72
|
-
// the WHOLE slice: the full length, because a dropped duplicate is still consumed. `at[i]` is the absolute
|
|
73
|
-
// index of `events[i]`, which is what lets a reader that STOPS on one event ([[session-follow]]'s take-one
|
|
74
|
-
// wait) advance to exactly `at[i] + 1` and leave the lines behind it unread for the next reader.
|
|
75
|
-
export function unreadSince(events: TimelineEvent[], from: number): { events: TimelineEvent[]; at: number[]; next: number } {
|
|
76
|
-
const start = Math.min(Math.max(0, Math.floor(from)), events.length)
|
|
77
|
-
let prev: TimelineEvent | null = null
|
|
78
|
-
for (let i = start - 1; i >= 0; i--) if (events[i].kind === 'status') { prev = events[i]; break }
|
|
79
|
-
const out: TimelineEvent[] = []
|
|
80
|
-
const at: number[] = []
|
|
81
|
-
for (let i = start; i < events.length; i++) {
|
|
82
|
-
const e = events[i]
|
|
83
|
-
if (e.kind === 'status') {
|
|
84
|
-
if (prev && sameStatus(prev, e)) continue
|
|
85
|
-
prev = e
|
|
86
|
-
}
|
|
87
|
-
out.push(e)
|
|
88
|
-
at.push(i)
|
|
89
|
-
}
|
|
90
|
-
return { events: out, at, next: events.length }
|
|
91
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { streamSSE } from 'hono/streaming'
|
|
2
|
-
import type { Context } from 'hono'
|
|
3
|
-
import { harnessById } from './harness.js'
|
|
4
|
-
import { readAliasedRawRecord } from './layout.js'
|
|
5
|
-
import { currentHumanTurn } from './session-timeline.js'
|
|
6
|
-
import type { ExecutionStep, ExecutionTurn } from './execution-trace.js'
|
|
7
|
-
|
|
8
|
-
export type SessionExecution = Readonly<{
|
|
9
|
-
revision: string
|
|
10
|
-
turnId: string | null
|
|
11
|
-
workingNote: string | null
|
|
12
|
-
steps: readonly ExecutionStep[]
|
|
13
|
-
}>
|
|
14
|
-
|
|
15
|
-
const absentExecution = (turn: ExecutionTurn | null = null): SessionExecution => ({ revision: `turn:${turn?.token ?? '0'}`, turnId: turn?.token ?? null, workingNote: null, steps: [] })
|
|
16
|
-
|
|
17
|
-
// This is deliberately a read projection. Native transcript bytes stay within the adapter, while the API owns
|
|
18
|
-
// only enough information for the frontend's fixed renderer to paint the current execution slice.
|
|
19
|
-
export function readSessionExecution(id: string): SessionExecution | null {
|
|
20
|
-
let record: ReturnType<typeof readAliasedRawRecord>
|
|
21
|
-
try { record = readAliasedRawRecord(id) } catch { return null }
|
|
22
|
-
if (!record?.governed) return null
|
|
23
|
-
const turn = currentHumanTurn(id)
|
|
24
|
-
const threadId = typeof record.harness_session_id === 'string' ? record.harness_session_id : ''
|
|
25
|
-
if (!threadId) return absentExecution(turn)
|
|
26
|
-
try {
|
|
27
|
-
const trace = harnessById(typeof record.harness === 'string' && record.harness ? record.harness : 'claude')
|
|
28
|
-
.executionTrace(threadId, turn)
|
|
29
|
-
return trace || absentExecution(turn)
|
|
30
|
-
} catch {
|
|
31
|
-
// A missing/unreadable native source is an absent transient trace, not a synthetic conversation failure.
|
|
32
|
-
return absentExecution(turn)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const TRACE_TICK_MS = 500
|
|
37
|
-
const HEARTBEAT_TICKS = 20
|
|
38
|
-
|
|
39
|
-
export async function sessionExecutionStream(c: Context) {
|
|
40
|
-
const id = c.req.param('id') || ''
|
|
41
|
-
if (!readSessionExecution(id)) return c.json({ error: 'no such session' }, 404)
|
|
42
|
-
return streamSSE(c, async (stream) => {
|
|
43
|
-
let aborted = false
|
|
44
|
-
let lastRevision = ''
|
|
45
|
-
let ticks = 0
|
|
46
|
-
const publish = async (): Promise<boolean> => {
|
|
47
|
-
const execution = readSessionExecution(id)
|
|
48
|
-
if (!execution) return false
|
|
49
|
-
if (execution.revision === lastRevision) return true
|
|
50
|
-
lastRevision = execution.revision
|
|
51
|
-
await stream.writeSSE({ event: 'execution', data: JSON.stringify(execution) })
|
|
52
|
-
return true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
stream.onAbort(() => { aborted = true })
|
|
56
|
-
try {
|
|
57
|
-
if (!await publish()) return
|
|
58
|
-
while (!aborted) {
|
|
59
|
-
await stream.sleep(TRACE_TICK_MS)
|
|
60
|
-
if (aborted) break
|
|
61
|
-
if (!await publish()) break
|
|
62
|
-
if (++ticks % HEARTBEAT_TICKS === 0) await stream.writeSSE({ event: 'ping', data: 'x' })
|
|
63
|
-
}
|
|
64
|
-
} catch {
|
|
65
|
-
// EventSource reconnects a dropped stream. A native trace read must never take down the session server.
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { accessSync, constants, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { basename, isAbsolute, resolve } from 'node:path'
|
|
3
|
-
import { readRecordEntry, sessionArtifactPath } from './layout.js'
|
|
4
|
-
|
|
5
|
-
export class SessionFileError extends Error {
|
|
6
|
-
constructor(readonly status: 400 | 403 | 404 | 500, message: string) {
|
|
7
|
-
super(message)
|
|
8
|
-
this.name = 'SessionFileError'
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type SessionFileLock = <T>(id: string, body: () => T) => T
|
|
13
|
-
|
|
14
|
-
export const SESSION_FILE_PREVIEW_MAX_BYTES = 2 * 1024 * 1024
|
|
15
|
-
export type SessionFilePreviewKind = 'text' | 'image'
|
|
16
|
-
const TEXT_EXTENSIONS = new Set(['.txt', '.md', '.markdown', '.json', '.yaml', '.yml', '.toml', '.ini', '.log', '.csv', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.css', '.html', '.xml', '.py', '.go', '.rs', '.java', '.sh', '.sql'])
|
|
17
|
-
const IMAGE_TYPES: Record<string, string> = {
|
|
18
|
-
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const filesPath = (id: string) => sessionArtifactPath(id, 'files.json')
|
|
22
|
-
|
|
23
|
-
function requireSession(id: string): void {
|
|
24
|
-
const record = readRecordEntry(id)
|
|
25
|
-
if (record.kind === 'absent') throw new SessionFileError(404, `session ${id} does not exist`)
|
|
26
|
-
if (record.kind === 'corrupt') throw new SessionFileError(500, `session ${id} has an unreadable record: ${record.error}`)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function readFiles(id: string): string[] {
|
|
30
|
-
const path = filesPath(id)
|
|
31
|
-
let parsed: unknown
|
|
32
|
-
try { parsed = JSON.parse(readFileSync(path, 'utf8')) }
|
|
33
|
-
catch (error) {
|
|
34
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []
|
|
35
|
-
throw new SessionFileError(500, `session file list is unreadable: ${path} — ${error instanceof Error ? error.message : String(error)}`)
|
|
36
|
-
}
|
|
37
|
-
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== 'string' || !isAbsolute(value)))
|
|
38
|
-
throw new SessionFileError(500, `session file list is invalid: ${path}`)
|
|
39
|
-
return [...new Set(parsed)]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function writeFiles(id: string, files: string[]): void {
|
|
43
|
-
const path = filesPath(id)
|
|
44
|
-
const tmp = `${path}.${process.pid}.tmp`
|
|
45
|
-
try {
|
|
46
|
-
writeFileSync(tmp, `${JSON.stringify(files, null, 2)}\n`)
|
|
47
|
-
renameSync(tmp, path)
|
|
48
|
-
} finally {
|
|
49
|
-
try { unlinkSync(tmp) } catch { /* rename already consumed the temporary file */ }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function currentFile(path: string): void {
|
|
54
|
-
try {
|
|
55
|
-
if (!statSync(path).isFile()) throw new SessionFileError(400, `not a regular file: ${path}`)
|
|
56
|
-
accessSync(path, constants.R_OK)
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (error instanceof SessionFileError) throw error
|
|
59
|
-
const code = (error as NodeJS.ErrnoException).code
|
|
60
|
-
if (code === 'ENOENT' || code === 'ENOTDIR') throw new SessionFileError(404, `file does not exist: ${path}`)
|
|
61
|
-
throw new SessionFileError(403, `file is not readable: ${path}`)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function listSessionFiles(id: string): string[] {
|
|
66
|
-
requireSession(id)
|
|
67
|
-
return readSessionFiles(id)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Session projections already hold a parsed record; re-checking session.json here would make an in-memory
|
|
71
|
-
// projection depend on the public route's existence guard. The route-facing list keeps that guard above.
|
|
72
|
-
export function readSessionFiles(id: string): string[] {
|
|
73
|
-
return readFiles(id)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function addSessionFile(id: string, input: string, lock: SessionFileLock, cwd = process.cwd()): { path: string; added: boolean } {
|
|
77
|
-
const path = resolve(cwd, input)
|
|
78
|
-
currentFile(path)
|
|
79
|
-
return lock(id, () => {
|
|
80
|
-
requireSession(id)
|
|
81
|
-
const files = readFiles(id)
|
|
82
|
-
if (files.includes(path)) return { path, added: false }
|
|
83
|
-
writeFiles(id, [...files, path])
|
|
84
|
-
return { path, added: true }
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function retractSessionFile(id: string, input: string, lock: SessionFileLock, cwd = process.cwd()): { path: string; removed: boolean } {
|
|
89
|
-
const path = resolve(cwd, input)
|
|
90
|
-
return lock(id, () => {
|
|
91
|
-
requireSession(id)
|
|
92
|
-
const files = readFiles(id)
|
|
93
|
-
if (!files.includes(path)) return { path, removed: false }
|
|
94
|
-
writeFiles(id, files.filter((file) => file !== path))
|
|
95
|
-
return { path, removed: true }
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function openSessionFile(id: string, path: string): { path: string; name: string; size: number } {
|
|
100
|
-
requireSession(id)
|
|
101
|
-
if (!readFiles(id).includes(path)) throw new SessionFileError(403, 'that path was not posted by this session')
|
|
102
|
-
try {
|
|
103
|
-
const stat = statSync(path)
|
|
104
|
-
if (!stat.isFile()) throw new SessionFileError(404, `file no longer exists: ${path}`)
|
|
105
|
-
accessSync(path, constants.R_OK)
|
|
106
|
-
return { path, name: basename(path) || 'download', size: stat.size }
|
|
107
|
-
} catch (error) {
|
|
108
|
-
if (error instanceof SessionFileError) throw error
|
|
109
|
-
throw new SessionFileError(404, `file no longer exists: ${path}`)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function sessionFilePreviewKind(path: string): { kind: SessionFilePreviewKind; contentType: string } | null {
|
|
114
|
-
const extension = basename(path).toLowerCase().match(/\.[^.]+$/)?.[0]
|
|
115
|
-
if (!extension) return null
|
|
116
|
-
if (TEXT_EXTENSIONS.has(extension)) return { kind: 'text', contentType: 'text/plain; charset=utf-8' }
|
|
117
|
-
const contentType = IMAGE_TYPES[extension]
|
|
118
|
-
return contentType ? { kind: 'image', contentType } : null
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export const sessionFilesPath = (id: string) => filesPath(id)
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { sessionStoreDir } from './layout.js'
|
|
3
|
-
import { advanceFollow, followCursor, unreadSince } from './session-cursors.js'
|
|
4
|
-
import { timelineDisplay, timelineEvents, timelineStamp } from './session-timeline.js'
|
|
5
|
-
import { sessionTitle, type DisplayStatus, type Session } from './sessions.js'
|
|
6
|
-
|
|
7
|
-
// @@@ session-follow - supervision is FOLLOWING a log past a cursor, never polling a derived board. One tick
|
|
8
|
-
// costs ONE stat per target: if timeline.ndjson has not grown, nothing is opened and nothing is parsed. No
|
|
9
|
-
// call here reaches the backend, a rendezvous socket, or tmux, which is the whole point — M followers over N
|
|
10
|
-
// sessions cost the control plane zero, where the old poll cost one board build (a connect + a tmux spawn per
|
|
11
|
-
// live session) per follower per interval.
|
|
12
|
-
|
|
13
|
-
// Actionable = a state whose arrival means "a human/supervisor must now act". `offline` is deliberately ABSENT
|
|
14
|
-
// where the old poll had it: liveness is a present-tense probe derivation, never authored, so it can never
|
|
15
|
-
// appear on a log ([[state]]). A follower learns what a session DECLARED and never that it died.
|
|
16
|
-
const ACTIONABLE = new Set<DisplayStatus>(['review', 'done', 'close-pending', 'error', 'asking'])
|
|
17
|
-
const NEXT: Record<string, string> = {
|
|
18
|
-
review: 'merge | close',
|
|
19
|
-
done: 'merge | close',
|
|
20
|
-
'close-pending': 'close',
|
|
21
|
-
error: 'resume (relaunch & retry) | show --capture | close',
|
|
22
|
-
asking: 'send "<msg>" | show --capture',
|
|
23
|
-
idle: 'send "<msg>" | show --capture',
|
|
24
|
-
queued: 'waiting for a free slot — starts automatically | close',
|
|
25
|
-
}
|
|
26
|
-
const trunc = (s: string, n: number): string => (s.length <= n ? s : `${s.slice(0, n - 1)}…`)
|
|
27
|
-
|
|
28
|
-
export function sessionEvent(s: Session): string {
|
|
29
|
-
const note = s.note ? ` — note: ${s.note}` : ''
|
|
30
|
-
const asked = s.promptPreview ? ` · asked: ${s.promptPreview}` : ''
|
|
31
|
-
return `[spex] ${s.status} · ${sessionTitle(s)} — act: ${NEXT[s.status] || '—'}${note}${asked} [id ${s.id}]`
|
|
32
|
-
}
|
|
33
|
-
// @@@ launchEvent - a session's FIRST sighting, emitted once per id whatever its status, so the stream is a
|
|
34
|
-
// complete lifecycle feed: launched → [transitions] → closed. A launch's own first line is `active` (not
|
|
35
|
-
// actionable), so without this the feed would be blind to new sessions starting.
|
|
36
|
-
export function launchEvent(s: Session): string {
|
|
37
|
-
const note = s.note ? ` — note: ${s.note}` : ''
|
|
38
|
-
const asked = s.promptPreview ? ` · asked: ${s.promptPreview}` : ''
|
|
39
|
-
return `[spex] launched · ${sessionTitle(s)} — act: capture | send "<msg>"${note}${asked} [id ${s.id}]`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// `targets` is re-read every tick so a BROAD follow picks up sessions that launch while it runs; an explicit
|
|
43
|
-
// selector resolves once and hands back a fixed list. `self` is the follower's own session: its inbox rides the
|
|
44
|
-
// same follow, and it is where the durable cursors live — a follower with no session record (a human shell)
|
|
45
|
-
// keeps them in memory for the life of the process.
|
|
46
|
-
export type FollowOpts = {
|
|
47
|
-
targets: () => string[]
|
|
48
|
-
self?: string | null
|
|
49
|
-
statuses?: string[]
|
|
50
|
-
includeIdle?: boolean
|
|
51
|
-
intervalMs?: number
|
|
52
|
-
as?: string
|
|
53
|
-
take?: boolean
|
|
54
|
-
timeoutMs?: number
|
|
55
|
-
onObserved?: (id: string, status: DisplayStatus, previous: DisplayStatus | null) => void
|
|
56
|
-
row?: (id: string, status: DisplayStatus, note: string | null) => Session | null
|
|
57
|
-
}
|
|
58
|
-
// Only `take` mode (what `spex session wait` runs) resolves; a stream follow never returns. There is no
|
|
59
|
-
// transport outcome, because there is no transport: following is reading a file, so the failure that once
|
|
60
|
-
// needed its own vocabulary — a backend that could not be reached, misread as a session verdict — cannot occur.
|
|
61
|
-
export type FollowOutcome =
|
|
62
|
-
| { reached: DisplayStatus; id: string; path: DisplayStatus[] }
|
|
63
|
-
| { mail: { from: string | null; text: string } }
|
|
64
|
-
| { timedOut: true; path: DisplayStatus[] }
|
|
65
|
-
| { gone: string }
|
|
66
|
-
|
|
67
|
-
type FollowState = { pos: number; prev: DisplayStatus | null; path: DisplayStatus[]; stamp: string | null }
|
|
68
|
-
|
|
69
|
-
export async function followSessions(emit: (line: string) => void, opts: FollowOpts): Promise<FollowOutcome> {
|
|
70
|
-
const { targets, self = null, statuses, includeIdle = false, intervalMs = 1000, as, take = false, timeoutMs = 1_200_000, onObserved, row } = opts
|
|
71
|
-
const tag = as ? `[${as}] ` : ''
|
|
72
|
-
const state = new Map<string, FollowState>()
|
|
73
|
-
const memo = new Map<string, number>() // cursors for a follower that has no record of its own to store them in
|
|
74
|
-
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
75
|
-
// the no-hang wall: a fixed deadline computed ONCE, checked before EVERY sleep below.
|
|
76
|
-
const deadline = take ? Date.now() + Math.max(1000, timeoutMs) : 0
|
|
77
|
-
const isActionable = (st: DisplayStatus) => ACTIONABLE.has(st) || (includeIdle && st === 'idle')
|
|
78
|
-
const passes = (st: DisplayStatus) => !statuses?.length || statuses.includes(st)
|
|
79
|
-
const anyPath = (): DisplayStatus[] => {
|
|
80
|
-
for (const f of state.values()) if (f.path.length) return f.path
|
|
81
|
-
return []
|
|
82
|
-
}
|
|
83
|
-
const readCursor = (id: string): number | null => (self ? followCursor(self, id) : memo.get(id) ?? null)
|
|
84
|
-
const writeCursor = (id: string, to: number): void => { if (self) advanceFollow(self, id, to); else memo.set(id, to) }
|
|
85
|
-
const line = (id: string, st: DisplayStatus, note: string | null, first: boolean): void => {
|
|
86
|
-
const s = row?.(id, st, note)
|
|
87
|
-
if (!s) return
|
|
88
|
-
if (first) emit(tag + launchEvent(s))
|
|
89
|
-
else if (passes(st) && isActionable(st)) emit(tag + sessionEvent(s))
|
|
90
|
-
}
|
|
91
|
-
let first = true
|
|
92
|
-
let shown = -1 // stream mode: the last inbox line this process printed (see the INBOX read below)
|
|
93
|
-
|
|
94
|
-
for (;;) {
|
|
95
|
-
const ids = targets()
|
|
96
|
-
const seen = new Set<string>()
|
|
97
|
-
for (const id of ids) {
|
|
98
|
-
if (id === self) continue // the follower's own log is its INBOX, followed below on its own cursor rule
|
|
99
|
-
seen.add(id)
|
|
100
|
-
if (!existsSync(sessionStoreDir(id))) {
|
|
101
|
-
if (state.delete(id)) emit(`${tag}[spex] closed · removed [id ${id}]`)
|
|
102
|
-
if (take) return { gone: id }
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
let f = state.get(id)
|
|
106
|
-
if (!f) {
|
|
107
|
-
const evs = timelineEvents(id)
|
|
108
|
-
// A stored cursor always wins — that IS the resume, losing nothing across a follower's death. With
|
|
109
|
-
// none: a target already present when the follow started predates it, so we begin at the log's END
|
|
110
|
-
// (its history is not news); a target that appears on a LATER tick genuinely just launched, so its
|
|
111
|
-
// whole log is read from 0 and its `launched` line reaches the feed.
|
|
112
|
-
const pos = readCursor(id) ?? (first ? evs.length : 0)
|
|
113
|
-
// The ARRIVAL state is the last status line BEFORE the cursor — a fact on the log, not whatever a poll
|
|
114
|
-
// happened to catch. With no such line the first status we read is the arrival instead.
|
|
115
|
-
let prev: DisplayStatus | null = null
|
|
116
|
-
for (let i = Math.min(pos, evs.length) - 1; i >= 0; i--) {
|
|
117
|
-
const e = evs[i]
|
|
118
|
-
if (e.kind === 'status') { prev = timelineDisplay(e); break }
|
|
119
|
-
}
|
|
120
|
-
f = { pos, prev, path: prev ? [prev] : [], stamp: null }
|
|
121
|
-
state.set(id, f)
|
|
122
|
-
if (prev) { onObserved?.(id, prev, null); line(id, prev, null, true) }
|
|
123
|
-
}
|
|
124
|
-
const stamp = timelineStamp(id)
|
|
125
|
-
if (stamp !== null && stamp === f.stamp) continue // THE cheap tick: nothing appended, nothing parsed
|
|
126
|
-
f.stamp = stamp
|
|
127
|
-
const evs = timelineEvents(id)
|
|
128
|
-
const slice = unreadSince(evs, f.pos)
|
|
129
|
-
let hit: FollowOutcome | null = null
|
|
130
|
-
for (let k = 0; k < slice.events.length && !hit; k++) {
|
|
131
|
-
const e = slice.events[k]
|
|
132
|
-
if (e.kind === 'sent') {
|
|
133
|
-
const s = row?.(id, f.prev ?? 'unknown', null)
|
|
134
|
-
if (s) emit(`${tag}[spex] message · ${sessionTitle(s)} — from ${e.from ?? 'human'}: ${trunc(e.text, 120)} [id ${id}]`)
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
const st = timelineDisplay(e)
|
|
138
|
-
const was = f.prev
|
|
139
|
-
f.prev = st
|
|
140
|
-
f.path.push(st)
|
|
141
|
-
onObserved?.(id, st, was)
|
|
142
|
-
line(id, st, e.note, was === null)
|
|
143
|
-
// THE edge: a previously-observed NON-actionable status moving INTO an actionable one. An actionable
|
|
144
|
-
// ARRIVAL (was === null) is not an edge — that standing level is what a level-triggered wait falsely
|
|
145
|
-
// returned on; nor is an actionable→actionable hop (review→done). The rise OUT of non-actionable is the
|
|
146
|
-
// one signal that means "the target needs you AGAIN". The previous state is the previous LINE, so two
|
|
147
|
-
// moves inside one tick are two edges rather than one collapsed sample.
|
|
148
|
-
if (take && was !== null && !isActionable(was) && isActionable(st)) hit = { reached: st, id, path: f.path }
|
|
149
|
-
// Consume exactly up to the event we stopped on, so the lines behind it stay unread for the next wait.
|
|
150
|
-
if (hit) { f.pos = slice.at[k] + 1; writeCursor(id, f.pos) }
|
|
151
|
-
}
|
|
152
|
-
if (hit) return hit
|
|
153
|
-
f.pos = slice.next
|
|
154
|
-
writeCursor(id, f.pos)
|
|
155
|
-
}
|
|
156
|
-
for (const id of [...state.keys()]) {
|
|
157
|
-
if (seen.has(id)) continue
|
|
158
|
-
state.delete(id)
|
|
159
|
-
emit(`${tag}[spex] closed · removed [id ${id}]`)
|
|
160
|
-
}
|
|
161
|
-
// THE FOLLOWER'S OWN LOG — watched exactly like any other target, on its own entry in `cursors.json`. It is
|
|
162
|
-
// a WATCH, not a delivery: a message reaches the agent as a prompt through the adapter ([[delivery-queue]]),
|
|
163
|
-
// so this position only decides what THIS process has already reported and can never make an agent miss
|
|
164
|
-
// mail. Never advanced here in take mode — the waiter stops on the event and the next wait resumes on it.
|
|
165
|
-
if (self && existsSync(sessionStoreDir(self))) {
|
|
166
|
-
const mine = unreadSince(timelineEvents(self), followCursor(self, self) ?? 0)
|
|
167
|
-
for (let k = 0; k < mine.events.length; k++) {
|
|
168
|
-
const e = mine.events[k]
|
|
169
|
-
if (e.kind !== 'sent') continue
|
|
170
|
-
if (take) return { mail: { from: e.from, text: e.text } }
|
|
171
|
-
// a stream has no turn boundary to advance that cursor, so its own high-water mark — process-local,
|
|
172
|
-
// never written — is what keeps an unread line from being re-printed on every tick.
|
|
173
|
-
if (mine.at[k] <= shown) continue
|
|
174
|
-
shown = mine.at[k]
|
|
175
|
-
emit(`${tag}[spex] message · you — from ${e.from ?? 'human'}: ${trunc(e.text, 120)} [id ${self}]`)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
first = false
|
|
179
|
-
if (take && Date.now() >= deadline) return { timedOut: true, path: anyPath() }
|
|
180
|
-
await sleep(intervalMs)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { apiBaseInfo, reparentSessionRecords, type SessionReparentResult } from './sessions.js'
|
|
2
|
-
import { BackendError, backendConnectionRefused, clientReparent } from './client.js'
|
|
3
|
-
|
|
4
|
-
export class SessionReparentRequestError extends Error {
|
|
5
|
-
constructor(message: string) {
|
|
6
|
-
super(message)
|
|
7
|
-
this.name = 'SessionReparentRequestError'
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function parseReparentRequest(body: unknown): { children: string[]; parent: string } {
|
|
12
|
-
if (!body || typeof body !== 'object') throw new SessionReparentRequestError('reparent body must be JSON')
|
|
13
|
-
const raw = body as { children?: unknown; parent?: unknown }
|
|
14
|
-
if (!Array.isArray(raw.children) || !raw.children.length || raw.children.some((id) => typeof id !== 'string' || !id.trim()))
|
|
15
|
-
throw new SessionReparentRequestError('reparent children must be one or more session ids')
|
|
16
|
-
if (typeof raw.parent !== 'string' || !raw.parent.trim()) throw new SessionReparentRequestError('reparent parent must be a session id')
|
|
17
|
-
return { children: raw.children.map((id) => id.trim()), parent: raw.parent.trim() }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function reparentRequest(body: unknown): Promise<SessionReparentResult> {
|
|
21
|
-
const request = parseReparentRequest(body)
|
|
22
|
-
return reparentSessionRecords(request.children, request.parent)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function reparentSessions(children: string[], parent: string): Promise<SessionReparentResult> {
|
|
26
|
-
try {
|
|
27
|
-
return await clientReparent(children, parent)
|
|
28
|
-
} catch (error) {
|
|
29
|
-
if (!(error instanceof BackendError) || (await apiBaseInfo()).source === 'flag' || !backendConnectionRefused(error)) throw error
|
|
30
|
-
console.error('spex: no backend reachable — reparenting in-process under the session record locks')
|
|
31
|
-
return reparentSessionRecords(children, parent)
|
|
32
|
-
}
|
|
33
|
-
}
|