spexcode 0.6.1 → 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 +99 -149
- 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} +115 -22
- 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 +27 -7
- 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/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +9 -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/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +22 -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-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +15 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +10 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +13 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +12 -0
- 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 -706
- package/spec-cli/src/attach.ts +0 -43
- package/spec-cli/src/claude-headless.ts +0 -321
- package/spec-cli/src/cli.ts +0 -1115
- package/spec-cli/src/client.ts +0 -315
- 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 -107
- package/spec-cli/src/doctor.ts +0 -516
- 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 -356
- package/spec-cli/src/gateway.ts +0 -368
- package/spec-cli/src/git.ts +0 -2180
- package/spec-cli/src/graph.ts +0 -221
- package/spec-cli/src/graphCache.ts +0 -641
- 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 -2747
- package/spec-cli/src/headless-controller.ts +0 -63
- package/spec-cli/src/help.ts +0 -480
- package/spec-cli/src/host-resources.ts +0 -707
- 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 -128
- package/spec-cli/src/index.ts +0 -730
- 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 -557
- package/spec-cli/src/lint.ts +0 -458
- package/spec-cli/src/localIssues.ts +0 -538
- package/spec-cli/src/materialize.ts +0 -412
- 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 -405
- 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-follow.ts +0 -182
- package/spec-cli/src/session-timeline.ts +0 -98
- package/spec-cli/src/sessions.ts +0 -3403
- 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-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -28
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +0 -23
- package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -32
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -9
- package/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -15
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -18
- package/spec-dashboard/dist/assets/App-F9uaAVcH.js +0 -2
- package/spec-dashboard/dist/assets/Dashboard-Ba_jhxp1.js +0 -27
- package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
- package/spec-dashboard/dist/assets/EvalsPage-FixoOg_n.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-CuKLFhH3.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-CHgEHORJ.js +0 -2
- package/spec-dashboard/dist/assets/Modal-CQgYymmr.js +0 -1
- package/spec-dashboard/dist/assets/PageScroll-hT7UTLvD.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-CtXxakF9.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
- package/spec-dashboard/dist/assets/SessionInterface-Bpie-9fs.js +0 -39
- package/spec-dashboard/dist/assets/SessionWindow-CixDi4PI.js +0 -1
- package/spec-dashboard/dist/assets/Settings-C2MsucfE.js +0 -1
- package/spec-dashboard/dist/assets/Thread-C6Go8HRh.js +0 -13
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
- package/spec-dashboard/dist/assets/data-B-RQmit6.js +0 -1
- package/spec-dashboard/dist/assets/index-CsI8DElI.css +0 -1
- package/spec-dashboard/dist/assets/index-DrVao0Ep.js +0 -41
- package/spec-dashboard/dist/assets/launch-BBH02b1v.js +0 -276
- package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
- package/spec-dashboard/dist/assets/terminalFont-C6oGNKrf.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 -889
- package/spec-eval/src/evaltab.ts +0 -262
- package/spec-eval/src/filing.ts +0 -39
- package/spec-eval/src/freshness.ts +0 -557
- 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 -2226
- 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/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/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,208 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir, userInfo } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { headlessTurnFailureShell } from './harness.js';
|
|
7
|
+
import { shQuote } from './sh.js';
|
|
8
|
+
const pexec = promisify(execFile);
|
|
9
|
+
const WAKE_EARLY_EXIT_MS = 5_000;
|
|
10
|
+
const OUTCOME_POLL_MS = 25;
|
|
11
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
function accountLoginShell() {
|
|
13
|
+
try {
|
|
14
|
+
return userInfo().shell || '';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// A headless turn owns the pane only while `opencode run` is alive. Returning to a shell keeps the tmux
|
|
21
|
+
// window as the session's durable home without adding a resident controller or an stdin bridge.
|
|
22
|
+
function turnHome(command, outcomePath) {
|
|
23
|
+
const outcomeSetup = outcomePath ? [
|
|
24
|
+
`__spex_outcome_path=${shQuote(outcomePath)}`,
|
|
25
|
+
`__spex_outcome_tmp=${shQuote(`${outcomePath}.tmp`)}`,
|
|
26
|
+
'__spex_outcome() { printf \'%s\\n\' "$1" > "$__spex_outcome_tmp" && mv -f "$__spex_outcome_tmp" "$__spex_outcome_path"; }',
|
|
27
|
+
'__spex_outcome "running:$$" || { printf "[spex opencode-headless] could not create turn outcome marker\\n" >&2; exit 125; }',
|
|
28
|
+
] : [];
|
|
29
|
+
const failure = outcomePath ? [
|
|
30
|
+
'__spex_cas_rc=0',
|
|
31
|
+
'if [ "$__spex_rc" -ne 0 ]; then',
|
|
32
|
+
' __spex_outcome "reporting:$$:$__spex_rc" || true',
|
|
33
|
+
` ${headlessTurnFailureShell('opencode-headless', false)}`,
|
|
34
|
+
' __spex_cas_rc=$?',
|
|
35
|
+
'fi',
|
|
36
|
+
'__spex_outcome "exit:$__spex_rc:cas:$__spex_cas_rc" || printf "[spex opencode-headless] could not finalize turn outcome marker\\n" >&2',
|
|
37
|
+
] : [
|
|
38
|
+
`if [ "$__spex_rc" -ne 0 ]; then ${headlessTurnFailureShell('opencode-headless')}; fi`,
|
|
39
|
+
];
|
|
40
|
+
const script = [
|
|
41
|
+
...outcomeSetup,
|
|
42
|
+
command,
|
|
43
|
+
'__spex_rc=$?',
|
|
44
|
+
...failure,
|
|
45
|
+
'[ "$__spex_rc" -eq 0 ] || printf "[spex opencode-headless] turn exited rc=%s\\n" "$__spex_rc" >&2',
|
|
46
|
+
'exec "${SHELL:-/bin/sh}"',
|
|
47
|
+
].join('\n');
|
|
48
|
+
return `bash -lc ${shQuote(script)} spexcode-opencode-headless`;
|
|
49
|
+
}
|
|
50
|
+
// Launcher profiles carry a base executable plus its configured flags (`opencode --auto`). OpenCode parses
|
|
51
|
+
// `run` as a subcommand, so it must sit between those two halves (`opencode run --auto`), not at the tail.
|
|
52
|
+
function runPrelude(opencodeCmd) {
|
|
53
|
+
const accountShell = accountLoginShell();
|
|
54
|
+
return [
|
|
55
|
+
'__spex_login_shell="${SHELL:-}"',
|
|
56
|
+
`[ -n "$__spex_login_shell" ] || __spex_login_shell=${shQuote(accountShell)}`,
|
|
57
|
+
`__spex_cmd=(${opencodeCmd})`,
|
|
58
|
+
'__spex_env=()',
|
|
59
|
+
'while [ "${#__spex_cmd[@]}" -gt 0 ] && [[ "${__spex_cmd[0]}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; do',
|
|
60
|
+
' __spex_env+=("${__spex_cmd[0]}")',
|
|
61
|
+
' __spex_cmd=("${__spex_cmd[@]:1}")',
|
|
62
|
+
'done',
|
|
63
|
+
// Resolve OpenCode in the same login + interactive environment the user relies on. The launcher-leading
|
|
64
|
+
// assignments are applied after shell startup, so its explicit per-session config still wins.
|
|
65
|
+
'__spex_run() { [ -n "$__spex_login_shell" ] || { printf "[spex opencode-headless] no login shell could be resolved - turn NOT started\\n" >&2; return 126; }; "$__spex_login_shell" -ilc \'exec env "$@"\' spexcode-opencode-headless "${__spex_env[@]}" "${__spex_cmd[0]}" run "${__spex_cmd[@]:1}" "$@"; }',
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
export function opencodeHeadlessLaunchCommand(opencodeCmd = 'opencode') {
|
|
69
|
+
const script = [
|
|
70
|
+
...runPrelude(opencodeCmd),
|
|
71
|
+
'if [ "${1:-}" = "--resume" ]; then',
|
|
72
|
+
' export SPEXCODE_OPENCODE_RESUME_ID="$2"',
|
|
73
|
+
' unset SPEXCODE_OPENCODE_CONTINUE',
|
|
74
|
+
' __spex_run --session "$2"',
|
|
75
|
+
'elif [ "${1:-}" = "--continue" ]; then',
|
|
76
|
+
' unset SPEXCODE_OPENCODE_RESUME_ID',
|
|
77
|
+
' export SPEXCODE_OPENCODE_CONTINUE=1',
|
|
78
|
+
' __spex_run --continue',
|
|
79
|
+
'elif [ -n "${1:-}" ]; then',
|
|
80
|
+
' unset SPEXCODE_OPENCODE_RESUME_ID SPEXCODE_OPENCODE_CONTINUE',
|
|
81
|
+
' __spex_run "$1"',
|
|
82
|
+
'else',
|
|
83
|
+
' __spex_run',
|
|
84
|
+
'fi',
|
|
85
|
+
].join('\n');
|
|
86
|
+
return turnHome(script);
|
|
87
|
+
}
|
|
88
|
+
export function opencodeHeadlessWakeCommand(opencodeCmd, harnessSessionId, text, outcomePath) {
|
|
89
|
+
const resume = harnessSessionId ? [
|
|
90
|
+
`export SPEXCODE_OPENCODE_RESUME_ID=${shQuote(harnessSessionId)}`,
|
|
91
|
+
'unset SPEXCODE_OPENCODE_CONTINUE',
|
|
92
|
+
`__spex_run --session ${shQuote(harnessSessionId)} ${shQuote(text)}`,
|
|
93
|
+
] : [
|
|
94
|
+
'unset SPEXCODE_OPENCODE_RESUME_ID',
|
|
95
|
+
'export SPEXCODE_OPENCODE_CONTINUE=1',
|
|
96
|
+
`__spex_run --continue ${shQuote(text)}`,
|
|
97
|
+
];
|
|
98
|
+
return turnHome([...runPrelude(opencodeCmd), ...resume].join('\n'), outcomePath);
|
|
99
|
+
}
|
|
100
|
+
function readTurnOutcome(path) {
|
|
101
|
+
let value;
|
|
102
|
+
try {
|
|
103
|
+
value = readFileSync(path, 'utf8').trim();
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error.code === 'ENOENT')
|
|
107
|
+
return undefined;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
let match = /^running:(\d+)$/.exec(value);
|
|
111
|
+
if (match)
|
|
112
|
+
return { state: 'running', pid: Number(match[1]) };
|
|
113
|
+
match = /^reporting:(\d+):(-?\d+)$/.exec(value);
|
|
114
|
+
if (match)
|
|
115
|
+
return { state: 'reporting', pid: Number(match[1]), code: Number(match[2]) };
|
|
116
|
+
match = /^exit:(-?\d+):cas:(\d+)$/.exec(value);
|
|
117
|
+
if (match)
|
|
118
|
+
return { state: 'exit', code: Number(match[1]), casCode: Number(match[2]) };
|
|
119
|
+
return { state: 'invalid' };
|
|
120
|
+
}
|
|
121
|
+
function turnExited(rec, outcome) {
|
|
122
|
+
if (outcome.code === 0)
|
|
123
|
+
return { ok: true };
|
|
124
|
+
const cas = outcome.casCode === 0 ? '' : `; error CAS reporter also exited with code ${outcome.casCode}`;
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: `opencode-headless turn exited with code ${outcome.code} during startup for session ${rec.session}${cas} - immediate wake failed`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function pidAlive(pid) {
|
|
131
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
132
|
+
return false;
|
|
133
|
+
try {
|
|
134
|
+
process.kill(pid, 0);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return error.code === 'EPERM';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function spawnOpenCodeHeadlessTurn(rec, text, opencodeCmd, socketPath) {
|
|
142
|
+
if (!rec.worktreePath)
|
|
143
|
+
return { ok: false, error: `opencode-headless session ${rec.session} has no worktree path - turn NOT started` };
|
|
144
|
+
let outcomeDir;
|
|
145
|
+
try {
|
|
146
|
+
outcomeDir = mkdtempSync(join(tmpdir(), 'spexcode-oh-turn-'));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
return { ok: false, error: `opencode-headless could not prepare turn confirmation for session ${rec.session}: ${error.message}` };
|
|
150
|
+
}
|
|
151
|
+
const outcomePath = join(outcomeDir, 'exit-code');
|
|
152
|
+
const tmuxSock = process.env.SPEXCODE_TMUX || 'spexcode';
|
|
153
|
+
const args = [
|
|
154
|
+
'-L', tmuxSock, 'respawn-pane', '-k', '-t', rec.session, '-c', rec.worktreePath,
|
|
155
|
+
'-e', `SPEXCODE_SESSION_ID=${rec.session}`,
|
|
156
|
+
'-e', 'CLAUDE_BG_BACKEND=daemon',
|
|
157
|
+
'-e', `CLAUDE_BG_RENDEZVOUS_SOCK=${socketPath}`,
|
|
158
|
+
];
|
|
159
|
+
for (const name of ['SPEXCODE_HOME', 'CODEX_HOME']) {
|
|
160
|
+
const value = process.env[name];
|
|
161
|
+
if (value)
|
|
162
|
+
args.push('-e', `${name}=${value}`);
|
|
163
|
+
}
|
|
164
|
+
args.push(opencodeHeadlessWakeCommand(opencodeCmd, rec.harnessSessionId, text, outcomePath));
|
|
165
|
+
try {
|
|
166
|
+
await pexec('tmux', args, { timeout: 5_000 });
|
|
167
|
+
const deadline = Date.now() + WAKE_EARLY_EXIT_MS;
|
|
168
|
+
for (;;) {
|
|
169
|
+
const outcome = readTurnOutcome(outcomePath);
|
|
170
|
+
if (outcome?.state === 'exit')
|
|
171
|
+
return turnExited(rec, outcome);
|
|
172
|
+
if (outcome?.state === 'invalid') {
|
|
173
|
+
return { ok: false, error: `opencode-headless turn for session ${rec.session} wrote an invalid exit outcome - immediate wake not confirmed` };
|
|
174
|
+
}
|
|
175
|
+
if (Date.now() >= deadline)
|
|
176
|
+
break;
|
|
177
|
+
await sleep(Math.min(OUTCOME_POLL_MS, deadline - Date.now()));
|
|
178
|
+
}
|
|
179
|
+
const first = readTurnOutcome(outcomePath);
|
|
180
|
+
if (first?.state === 'exit')
|
|
181
|
+
return turnExited(rec, first);
|
|
182
|
+
if (!first)
|
|
183
|
+
return { ok: false, error: `opencode-headless turn for session ${rec.session} never confirmed startup - immediate wake failed` };
|
|
184
|
+
if (first.state === 'invalid')
|
|
185
|
+
return { ok: false, error: `opencode-headless turn for session ${rec.session} wrote an invalid exit outcome - immediate wake not confirmed` };
|
|
186
|
+
if (first.state === 'reporting') {
|
|
187
|
+
return { ok: false, error: `opencode-headless turn exited with code ${first.code} but its error CAS reporter did not finish for session ${rec.session} - immediate wake failed` };
|
|
188
|
+
}
|
|
189
|
+
if (!pidAlive(first.pid)) {
|
|
190
|
+
return { ok: false, error: `opencode-headless turn wrapper died before reporting an outcome for session ${rec.session} - immediate wake failed` };
|
|
191
|
+
}
|
|
192
|
+
await sleep(OUTCOME_POLL_MS);
|
|
193
|
+
const settled = readTurnOutcome(outcomePath);
|
|
194
|
+
if (settled?.state === 'exit')
|
|
195
|
+
return turnExited(rec, settled);
|
|
196
|
+
if (settled?.state !== 'running' || settled.pid !== first.pid || !pidAlive(settled.pid)) {
|
|
197
|
+
return { ok: false, error: `opencode-headless turn did not remain live through startup for session ${rec.session} - immediate wake failed` };
|
|
198
|
+
}
|
|
199
|
+
return { ok: true };
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
203
|
+
return { ok: false, error: `opencode-headless could not start a turn for session ${rec.session}: ${detail}` };
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
rmSync(outcomeDir, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const OPENCODE_EVENTS: readonly ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
|
|
2
|
+
export declare const OPENCODE_TOOL_NAMES: Record<string, string>;
|
|
3
|
+
export declare function opencodePluginSource(dispatch: string, spex: string): string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { shimRuntimeSource } from './shim-runtime.js'
|
|
2
|
-
|
|
1
|
+
import { shimRuntimeSource } from './shim-runtime.js';
|
|
3
2
|
// @@@ opencode plugin generator - the opencode adapter's "shim" is not a settings JSON but a generated
|
|
4
3
|
// opencode PLUGIN (.opencode/plugins/spexcode.ts, auto-loaded by opencode from the project tree) that runs
|
|
5
4
|
// inside the agent process. It is a THIN HOST over the shared shim runtime ([[shim-runtime]], embedded
|
|
@@ -15,26 +14,23 @@ import { shimRuntimeSource } from './shim-runtime.js'
|
|
|
15
14
|
// agent_id stamp), the minted-id capture, and the two resume seeds.
|
|
16
15
|
// This module is a PURE content producer (no imports from harness.ts — the adapter object lives there, in the
|
|
17
16
|
// one HARNESSES seam) so there is no import cycle.
|
|
18
|
-
|
|
19
17
|
// the lifecycle events the generated plugin can emit (drives the shim + hooks-manifest binding). opencode's
|
|
20
18
|
// bus has no failed-stop and no idle/attention notification event, so StopFailure/Notification are genuinely
|
|
21
19
|
// absent — the same five codex binds.
|
|
22
|
-
export const OPENCODE_EVENTS = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop']
|
|
23
|
-
|
|
20
|
+
export const OPENCODE_EVENTS = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop'];
|
|
24
21
|
// opencode tool name → Claude tool name, so [[inject-spec-first]] / [[inject-spec-of-file]] and every other
|
|
25
22
|
// claude-family handler read the payload with zero opencode knowledge. Unknown tools pass through untouched
|
|
26
23
|
// (opencode's names are lowercase). Exported for tests.
|
|
27
|
-
export const OPENCODE_TOOL_NAMES
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
24
|
+
export const OPENCODE_TOOL_NAMES = {
|
|
25
|
+
bash: 'Bash', edit: 'Edit', write: 'Write', read: 'Read', grep: 'Grep', glob: 'Glob', list: 'LS',
|
|
26
|
+
patch: 'Edit', todowrite: 'TodoWrite', todoread: 'TodoRead', webfetch: 'WebFetch', task: 'Task',
|
|
27
|
+
};
|
|
32
28
|
// the generated plugin source. `dispatch` and `spex` are absolute paths baked at materialize time (same as
|
|
33
29
|
// every shim); the 'dispatch.sh' substring doubles as the identity stamp cleanHarness gates removal on. The
|
|
34
30
|
// file is plain TS with no @opencode-ai/plugin type import, so it loads regardless of the host project's
|
|
35
31
|
// node_modules; opencode calls every exported function with its plugin context.
|
|
36
|
-
export function opencodePluginSource(dispatch
|
|
37
|
-
|
|
32
|
+
export function opencodePluginSource(dispatch, spex) {
|
|
33
|
+
return `// spexcode: GENERATED by spex materialize — do not edit (regenerated per clone; see [[opencode-harness]])
|
|
38
34
|
// Bridges opencode's event bus into SpexCode's dispatch.sh (claude-shaped payloads) and serves the
|
|
39
35
|
// per-session rendezvous socket for prompt delivery + liveness.
|
|
40
36
|
import { spawn } from "node:child_process"
|
|
@@ -142,5 +138,5 @@ export const SpexcodePlugin = async (ctx) => {
|
|
|
142
138
|
},
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
|
-
|
|
141
|
+
`;
|
|
146
142
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, realpathSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
|
-
import { shimRuntimeSource } from './shim-runtime.js'
|
|
5
|
-
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { shimRuntimeSource } from './shim-runtime.js';
|
|
6
5
|
// @@@ pi-harness helpers - the pi-ONLY machinery behind harness.ts's piHarness adapter: the generated
|
|
7
6
|
// extension source (pi's shim is a TypeScript extension, not a hooks JSON — pi has no external hook binding;
|
|
8
7
|
// its lifecycle surface IS the extension API) and the trust writer (pi's project-trust store,
|
|
9
8
|
// ~/.pi/agent/trust.json). Kept out of harness.ts so the adapter table stays scannable; imports NOTHING from
|
|
10
9
|
// harness.ts at runtime (no cycle — harness.ts imports us).
|
|
11
|
-
|
|
12
10
|
// ---------------------------------------------------------------------------------------------------------
|
|
13
11
|
// the pi shim = a generated extension at .pi/extensions/spexcode.ts (pi runs .ts extensions natively). It is
|
|
14
12
|
// a THIN HOST over the shared shim runtime ([[shim-runtime]], embedded verbatim): this generator declares
|
|
@@ -25,8 +23,8 @@ import { shimRuntimeSource } from './shim-runtime.js'
|
|
|
25
23
|
// message; a no-longer-injectable host is reported loud — never silently dropped (a dropped rejection is
|
|
26
24
|
// a session stuck `active` forever).
|
|
27
25
|
// The rendezvous inject is pi.sendUserMessage({deliverAs: steer}), always able → no canInject gate.
|
|
28
|
-
export function piExtensionSource(dispatch
|
|
29
|
-
|
|
26
|
+
export function piExtensionSource(dispatch, spex) {
|
|
27
|
+
return `// generated by spexcode materialize — DO NOT EDIT (regenerated per clone; identity: dispatch.sh)
|
|
30
28
|
// SpexCode's pi shim: forwards pi lifecycle events to dispatch.sh as Claude-shaped hook payloads, and serves
|
|
31
29
|
// this session's rendezvous socket for prompt delivery + liveness.
|
|
32
30
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
@@ -75,9 +73,8 @@ export default function spexcode(pi: ExtensionAPI) {
|
|
|
75
73
|
pi.on("agent_settled", async () => { if (rt.stopPending()) await stop() })
|
|
76
74
|
pi.on("session_shutdown", async () => { rv?.close() })
|
|
77
75
|
}
|
|
78
|
-
|
|
76
|
+
`;
|
|
79
77
|
}
|
|
80
|
-
|
|
81
78
|
// ---------------------------------------------------------------------------------------------------------
|
|
82
79
|
// pi trust — make a dispatched/self-launched pi load our project-local extension with zero prompts. pi gates
|
|
83
80
|
// EVERY project-local resource (.pi/extensions, .pi/skills, .pi/prompts, .pi/settings.json) behind project
|
|
@@ -87,38 +84,46 @@ export default function spexcode(pi: ExtensionAPI) {
|
|
|
87
84
|
// carries `--approve` (one-run trust) as defence for a worktree living OUTSIDE the checkout. Writes are
|
|
88
85
|
// idempotent and surgical: other projects' decisions are untouched; a corrupt trust.json FAILS LOUD rather
|
|
89
86
|
// than being clobbered; remove only deletes a `true` we could have written, never a user's saved "false".
|
|
90
|
-
|
|
91
87
|
// pi has no config-dir env of its own (hardcoded ~/.pi/agent) — SPEXCODE_PI_AGENT_DIR is OUR test seam only.
|
|
92
|
-
const piAgentDir = () => process.env.SPEXCODE_PI_AGENT_DIR || join(homedir(), '.pi', 'agent')
|
|
88
|
+
const piAgentDir = () => process.env.SPEXCODE_PI_AGENT_DIR || join(homedir(), '.pi', 'agent');
|
|
93
89
|
// pi canonicalizes trust keys (realpath); mirror it so our key matches its lookup.
|
|
94
|
-
const canonical = (p
|
|
95
|
-
|
|
96
|
-
function readTrust(file: string): Record<string, unknown> {
|
|
97
|
-
if (!existsSync(file)) return {}
|
|
98
|
-
const raw = readFileSync(file, 'utf8')
|
|
99
|
-
try { return JSON.parse(raw) as Record<string, unknown> } catch (e) {
|
|
100
|
-
throw new Error(`${file} is not valid JSON — refusing to rewrite pi's trust store: ${String(e)}`)
|
|
101
|
-
}
|
|
90
|
+
const canonical = (p) => { try {
|
|
91
|
+
return realpathSync(p);
|
|
102
92
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
93
|
+
catch {
|
|
94
|
+
return p;
|
|
95
|
+
} };
|
|
96
|
+
function readTrust(file) {
|
|
97
|
+
if (!existsSync(file))
|
|
98
|
+
return {};
|
|
99
|
+
const raw = readFileSync(file, 'utf8');
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw new Error(`${file} is not valid JSON — refusing to rewrite pi's trust store: ${String(e)}`);
|
|
105
|
+
}
|
|
114
106
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
107
|
+
export function writePiTrust(proj) {
|
|
108
|
+
const dir = piAgentDir();
|
|
109
|
+
const file = join(dir, 'trust.json');
|
|
110
|
+
const key = canonical(proj);
|
|
111
|
+
const data = readTrust(file);
|
|
112
|
+
if (data[key] === true)
|
|
113
|
+
return file; // already trusted — no churn
|
|
114
|
+
data[key] = true;
|
|
115
|
+
mkdirSync(dir, { recursive: true });
|
|
116
|
+
writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
|
|
117
|
+
return file;
|
|
118
|
+
}
|
|
119
|
+
export function removePiTrust(proj) {
|
|
120
|
+
const file = join(piAgentDir(), 'trust.json');
|
|
121
|
+
if (!existsSync(file))
|
|
122
|
+
return;
|
|
123
|
+
const key = canonical(proj);
|
|
124
|
+
const data = readTrust(file);
|
|
125
|
+
if (data[key] !== true)
|
|
126
|
+
return; // absent, or a user's own "do not trust" — never touch it
|
|
127
|
+
delete data[key];
|
|
128
|
+
writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
|
|
124
129
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DispatchResult, HarnessDeliveryRecord } from './harness.js';
|
|
2
|
+
/** The resident controller socket is distinct from pi's per-turn rendezvous socket. */
|
|
3
|
+
export declare const piHeadlessSock: (id: string) => string;
|
|
4
|
+
export declare function piHeadlessLaunchCommand(id: string, runtimeDir: string, piCmd: string): string;
|
|
5
|
+
export declare const deliverViaPiHeadless: (rec: HarnessDeliveryRecord, text: string) => Promise<DispatchResult>;
|
|
6
|
+
export declare function piHeadlessColdRuntime(rec: Pick<HarnessDeliveryRecord, 'session'>): Promise<DispatchResult>;
|
|
7
|
+
export declare class PiHeadlessController {
|
|
8
|
+
private readonly id;
|
|
9
|
+
private readonly piCmd;
|
|
10
|
+
private readonly cwd;
|
|
11
|
+
private server;
|
|
12
|
+
private child;
|
|
13
|
+
private controlQueue;
|
|
14
|
+
private closing;
|
|
15
|
+
private readonly socketPath;
|
|
16
|
+
constructor(id: string, _runtimeDir: string, piCmd: string, cwd?: string);
|
|
17
|
+
start(initialPrompt?: string): Promise<void>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
private terminateTurn;
|
|
20
|
+
private waitForExit;
|
|
21
|
+
private accept;
|
|
22
|
+
private handle;
|
|
23
|
+
private spawnTurn;
|
|
24
|
+
}
|
|
25
|
+
export declare function runPiHeadlessController(id: string, runtimeDir: string, piCmd: string, tail: string[]): Promise<void>;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { controlRequest, withTimeout } from './headless-controller.js';
|
|
8
|
+
import { shQuote } from './sh.js';
|
|
9
|
+
const PKG = fileURLToPath(new URL('..', import.meta.url));
|
|
10
|
+
const SPEX = join(PKG, 'bin', 'spex.mjs');
|
|
11
|
+
const CONTROL_TIMEOUT_MS = 30_000;
|
|
12
|
+
const START_TIMEOUT_MS = 30_000;
|
|
13
|
+
const TERM_EXIT_GRACE_MS = 500;
|
|
14
|
+
const KILL_EXIT_GRACE_MS = 2_000;
|
|
15
|
+
/** The resident controller socket is distinct from pi's per-turn rendezvous socket. */
|
|
16
|
+
export const piHeadlessSock = (id) => join(tmpdir(), `spexcode-ph-${id}.sock`);
|
|
17
|
+
export function piHeadlessLaunchCommand(id, runtimeDir, piCmd) {
|
|
18
|
+
return [shQuote(SPEX), 'internal', 'pi-headless-run', shQuote(id), shQuote(runtimeDir), shQuote(piCmd), '--'].join(' ');
|
|
19
|
+
}
|
|
20
|
+
export const deliverViaPiHeadless = (rec, text) => controlRequest(piHeadlessSock(rec.session), { type: 'deliver', text, mid: rec.mid }, {
|
|
21
|
+
name: 'pi-headless', session: rec.session, timeoutMs: CONTROL_TIMEOUT_MS,
|
|
22
|
+
rejected: 'pi-headless controller rejected the request',
|
|
23
|
+
});
|
|
24
|
+
// The resident controller and a running pi turn own two per-session listeners. The generic lifecycle
|
|
25
|
+
// teardown has already proved and removed the exact controller leaf before this runs; cold filing is valid
|
|
26
|
+
// only once neither listener can still accept work for this session.
|
|
27
|
+
export async function piHeadlessColdRuntime(rec) {
|
|
28
|
+
const { listenerAt, rvSock } = await import('./harness.js');
|
|
29
|
+
const paths = [piHeadlessSock(rec.session), rvSock(rec.session)];
|
|
30
|
+
const probes = await Promise.all(paths.map((path) => listenerAt(path)));
|
|
31
|
+
const pending = paths.filter((_, index) => probes[index] !== 'dead');
|
|
32
|
+
return pending.length
|
|
33
|
+
? { ok: false, error: `pi-headless runtime is still ${probes.some((probe) => probe === 'live') ? 'live' : 'unproven'} (${pending.join(', ')})` }
|
|
34
|
+
: { ok: true };
|
|
35
|
+
}
|
|
36
|
+
export class PiHeadlessController {
|
|
37
|
+
id;
|
|
38
|
+
piCmd;
|
|
39
|
+
cwd;
|
|
40
|
+
server = null;
|
|
41
|
+
child = null;
|
|
42
|
+
controlQueue = Promise.resolve();
|
|
43
|
+
closing = false;
|
|
44
|
+
socketPath;
|
|
45
|
+
constructor(id, _runtimeDir, piCmd, cwd = process.cwd()) {
|
|
46
|
+
this.id = id;
|
|
47
|
+
this.piCmd = piCmd;
|
|
48
|
+
this.cwd = cwd;
|
|
49
|
+
this.socketPath = piHeadlessSock(id);
|
|
50
|
+
}
|
|
51
|
+
async start(initialPrompt) {
|
|
52
|
+
try {
|
|
53
|
+
rmSync(this.socketPath, { force: true });
|
|
54
|
+
}
|
|
55
|
+
catch { /* stale control socket is replaced at startup */ }
|
|
56
|
+
this.server = createServer((socket) => this.accept(socket));
|
|
57
|
+
await new Promise((resolve, reject) => {
|
|
58
|
+
const onError = (error) => { this.server?.off('listening', onListening); reject(error); };
|
|
59
|
+
const onListening = () => { this.server?.off('error', onError); resolve(); };
|
|
60
|
+
this.server.once('error', onError);
|
|
61
|
+
this.server.once('listening', onListening);
|
|
62
|
+
this.server.listen(this.socketPath);
|
|
63
|
+
});
|
|
64
|
+
if (initialPrompt)
|
|
65
|
+
void this.spawnTurn(initialPrompt, false).catch((error) => {
|
|
66
|
+
console.error(`[spex pi-headless] initial turn failed: ${error.message}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async close() {
|
|
70
|
+
if (this.closing)
|
|
71
|
+
return;
|
|
72
|
+
this.closing = true;
|
|
73
|
+
const child = this.child;
|
|
74
|
+
try {
|
|
75
|
+
if (child)
|
|
76
|
+
await this.terminateTurn(child);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
if (!this.server)
|
|
81
|
+
return resolve();
|
|
82
|
+
this.server.close(() => resolve());
|
|
83
|
+
});
|
|
84
|
+
// same proof-before-removal rule as every other teardown (harness.ts unlinkSocks): a socket path is keyed
|
|
85
|
+
// by session id alone, so only a listener PROVEN dead is ours to unlink.
|
|
86
|
+
const { rvSock, unlinkSocks } = await import('./harness.js');
|
|
87
|
+
await unlinkSocks(this.socketPath, rvSock(this.id));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async terminateTurn(turn) {
|
|
91
|
+
if (turn.process.exitCode !== null)
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
turn.process.kill('SIGTERM');
|
|
95
|
+
}
|
|
96
|
+
catch { /* the child can leave between the exit check and signal */ }
|
|
97
|
+
if (await this.waitForExit(turn, TERM_EXIT_GRACE_MS))
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
turn.process.kill('SIGKILL');
|
|
101
|
+
}
|
|
102
|
+
catch { /* already gone */ }
|
|
103
|
+
await withTimeout(turn.exited, KILL_EXIT_GRACE_MS, `pi-headless turn did not exit for session ${this.id}`);
|
|
104
|
+
}
|
|
105
|
+
async waitForExit(turn, timeoutMs) {
|
|
106
|
+
try {
|
|
107
|
+
await withTimeout(turn.exited, timeoutMs, 'turn exit grace elapsed');
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
accept(socket) {
|
|
115
|
+
socket.setEncoding('utf8');
|
|
116
|
+
let buffer = '';
|
|
117
|
+
let handled = false;
|
|
118
|
+
socket.on('data', (chunk) => {
|
|
119
|
+
if (handled)
|
|
120
|
+
return;
|
|
121
|
+
buffer += chunk;
|
|
122
|
+
const nl = buffer.indexOf('\n');
|
|
123
|
+
if (nl < 0)
|
|
124
|
+
return;
|
|
125
|
+
handled = true;
|
|
126
|
+
let request;
|
|
127
|
+
try {
|
|
128
|
+
request = JSON.parse(buffer.slice(0, nl));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
socket.end(`${JSON.stringify({ ok: false, error: `invalid control request: ${error.message}` })}\n`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.controlQueue = this.controlQueue.then(async () => {
|
|
135
|
+
const result = await this.handle(request).catch((error) => ({ ok: false, error: error.message }));
|
|
136
|
+
socket.end(`${JSON.stringify(result)}\n`);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async handle(request) {
|
|
141
|
+
if (request.type !== 'deliver')
|
|
142
|
+
return { ok: false, error: 'unknown pi-headless control request' };
|
|
143
|
+
if (!request.text)
|
|
144
|
+
return { ok: false, error: 'empty prompt - nothing to deliver' };
|
|
145
|
+
// A live extension listener is an in-flight pi turn. Only a proven absent listener may cold-wake a saved
|
|
146
|
+
// session; an inconclusive probe must not start a duplicate turn.
|
|
147
|
+
const { deliverViaSocketOrWake } = await import('./harness.js');
|
|
148
|
+
return deliverViaSocketOrWake(this.id, request.text, request.mid, async () => {
|
|
149
|
+
if (this.child)
|
|
150
|
+
await withTimeout(this.child.exited, 5_000, `previous pi-headless turn did not exit for session ${this.id}`);
|
|
151
|
+
await this.spawnTurn(request.text, true);
|
|
152
|
+
return { ok: true };
|
|
153
|
+
}, `could not determine whether pi turn ${this.id} is live — prompt NOT delivered`);
|
|
154
|
+
}
|
|
155
|
+
async spawnTurn(text, resume) {
|
|
156
|
+
if (this.closing)
|
|
157
|
+
throw new Error('pi-headless controller is closing');
|
|
158
|
+
const mode = resume ? ['--session', this.id] : ['--session-id', this.id];
|
|
159
|
+
// Keep pi's default text mode. `--mode json` is intentionally omitted: it can hang in this runtime.
|
|
160
|
+
const args = ['-p', ...mode, text];
|
|
161
|
+
const command = `exec ${this.piCmd} ${args.map(shQuote).join(' ')}`;
|
|
162
|
+
const childProcess = spawn('/bin/sh', ['-lc', command], { cwd: this.cwd, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
163
|
+
let resolveExit;
|
|
164
|
+
const exited = new Promise((resolve) => { resolveExit = resolve; });
|
|
165
|
+
const turn = { process: childProcess, exited };
|
|
166
|
+
this.child = turn;
|
|
167
|
+
childProcess.stdout?.pipe(process.stdout);
|
|
168
|
+
childProcess.stderr?.pipe(process.stderr);
|
|
169
|
+
childProcess.once('error', (error) => console.error(`[spex pi-headless] child spawn failed: ${error.message}`));
|
|
170
|
+
childProcess.once('close', (code) => {
|
|
171
|
+
if (this.child === turn)
|
|
172
|
+
this.child = null;
|
|
173
|
+
resolveExit(code);
|
|
174
|
+
if (code !== 0 && !this.closing)
|
|
175
|
+
void import('./harness.js').then(({ reportHeadlessTurnExit }) => reportHeadlessTurnExit(this.id, 'pi-headless', code, this.cwd));
|
|
176
|
+
});
|
|
177
|
+
await withTimeout(new Promise((resolve, reject) => {
|
|
178
|
+
childProcess.once('spawn', () => resolve());
|
|
179
|
+
childProcess.once('error', reject);
|
|
180
|
+
}), START_TIMEOUT_MS, `pi-headless child did not start for session ${this.id}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export async function runPiHeadlessController(id, runtimeDir, piCmd, tail) {
|
|
184
|
+
// runtimeDir is retained in the command shape for parity with claude-headless and future per-session output.
|
|
185
|
+
mkdirSync(join(runtimeDir, 'sessions', id), { recursive: true });
|
|
186
|
+
const controller = new PiHeadlessController(id, runtimeDir, piCmd);
|
|
187
|
+
const resume = tail[0] === '--session';
|
|
188
|
+
const prompt = resume ? undefined : tail[0] === '--session-id' ? tail.slice(2).join(' ') : tail.join(' ');
|
|
189
|
+
await controller.start(prompt);
|
|
190
|
+
await new Promise((resolve) => {
|
|
191
|
+
const stop = () => void controller.close().finally(resolve);
|
|
192
|
+
process.once('SIGINT', stop);
|
|
193
|
+
process.once('SIGTERM', stop);
|
|
194
|
+
process.once('SIGHUP', stop);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type PluginBundle = {
|
|
2
|
+
contract: string;
|
|
3
|
+
skills: {
|
|
4
|
+
name: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}[];
|
|
7
|
+
agents: {
|
|
8
|
+
name: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}[];
|
|
11
|
+
commands: {
|
|
12
|
+
name: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}[];
|
|
15
|
+
spex: string;
|
|
16
|
+
version: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function pluginBundleDir(proj: string, folder: string): string;
|
|
19
|
+
export declare function emitPlugin(proj: string, folder: string, r: PluginBundle): void;
|
|
20
|
+
export declare function cleanPlugin(proj: string, folder: string): void;
|
|
21
|
+
export declare function pluginVersion(): string;
|