@mjasnikovs/pi-task 0.38.28 → 0.38.30
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/dist/config/config.d.ts +70 -70
- package/dist/config/config.js +26 -35
- package/dist/config/extension-list.d.ts +6 -5
- package/dist/config/extension-list.js +3 -2
- package/dist/config/reasoning-args.d.ts +9 -7
- package/dist/config/reasoning-args.js +12 -10
- package/dist/config/reasoning.d.ts +44 -105
- package/dist/config/reasoning.js +27 -704
- package/dist/config/register.d.ts +34 -48
- package/dist/config/register.js +41 -51
- package/dist/config/tool-list.d.ts +16 -16
- package/dist/config/tool-list.js +1 -1
- package/dist/remote/bridge.d.ts +19 -10
- package/dist/remote/bridge.js +3 -2
- package/dist/remote/broadcast.js +3 -1
- package/dist/remote/events.js +12 -11
- package/dist/remote/history.d.ts +1 -1
- package/dist/remote/protocol.d.ts +6 -3
- package/dist/remote/protocol.js +2 -1
- package/dist/remote/push.d.ts +16 -16
- package/dist/remote/push.js +27 -27
- package/dist/remote/register.d.ts +3 -3
- package/dist/remote/register.js +17 -19
- package/dist/remote/server.d.ts +9 -8
- package/dist/remote/server.js +15 -14
- package/dist/remote/session-state.d.ts +5 -4
- package/dist/remote/session-state.js +8 -5
- package/dist/remote/sw.d.ts +7 -6
- package/dist/remote/sw.js +7 -6
- package/dist/remote/tailscale.d.ts +4 -2
- package/dist/remote/tailscale.js +4 -2
- package/dist/remote/ui-highlight.js +6 -5
- package/dist/remote/ui-render.js +4 -4
- package/dist/remote/ui-script.js +24 -24
- package/dist/remote/ui-styles.d.ts +1 -1
- package/dist/remote/ui-styles.js +10 -13
- package/dist/remote/ui-tools.js +9 -6
- package/dist/shared/child-extensions.d.ts +29 -17
- package/dist/shared/child-extensions.js +29 -17
- package/dist/shared/child-output.d.ts +30 -24
- package/dist/shared/child-output.js +25 -17
- package/dist/shared/child-process.d.ts +47 -40
- package/dist/shared/child-process.js +50 -59
- package/dist/shared/command-watchdog.d.ts +22 -16
- package/dist/shared/command-watchdog.js +28 -21
- package/dist/shared/fs-text.d.ts +16 -10
- package/dist/shared/fs-text.js +16 -10
- package/dist/shared/git-runner.d.ts +25 -25
- package/dist/shared/git-runner.js +25 -25
- package/dist/shared/leaked-tool-call.d.ts +17 -11
- package/dist/shared/leaked-tool-call.js +23 -15
- package/dist/shared/model-endpoint.d.ts +29 -16
- package/dist/shared/model-endpoint.js +33 -21
- package/dist/shared/pi-invocation.d.ts +7 -4
- package/dist/shared/pi-invocation.js +12 -7
- package/dist/shared/pkg-version.d.ts +13 -5
- package/dist/shared/pkg-version.js +13 -5
- package/dist/shared/reasoning-capability.d.ts +35 -24
- package/dist/shared/reasoning-capability.js +35 -24
- package/dist/shared/stream-watchdog.d.ts +60 -44
- package/dist/shared/stream-watchdog.js +62 -45
- package/dist/task/accept-debt.d.ts +41 -43
- package/dist/task/accept-debt.js +73 -65
- package/dist/task/api-synthesis.d.ts +24 -21
- package/dist/task/api-synthesis.js +32 -26
- package/dist/task/apis-contract.d.ts +32 -64
- package/dist/task/apis-contract.js +32 -64
- package/dist/task/artifact-closure.d.ts +27 -13
- package/dist/task/artifact-closure.js +95 -67
- package/dist/task/auto-commit.d.ts +46 -35
- package/dist/task/auto-commit.js +51 -38
- package/dist/task/auto-io.d.ts +45 -25
- package/dist/task/auto-io.js +57 -29
- package/dist/task/auto-orchestrator.d.ts +26 -24
- package/dist/task/auto-orchestrator.js +178 -162
- package/dist/task/auto-prompts.d.ts +36 -24
- package/dist/task/auto-prompts.js +40 -26
- package/dist/task/autofix-ledger.d.ts +27 -25
- package/dist/task/autofix-ledger.js +29 -26
- package/dist/task/batch-test-task.d.ts +20 -12
- package/dist/task/batch-test-task.js +67 -60
- package/dist/task/boot-probe.d.ts +60 -44
- package/dist/task/boot-probe.js +91 -72
- package/dist/task/cancel-input.d.ts +30 -16
- package/dist/task/cancel-input.js +20 -11
- package/dist/task/cancel-points.d.ts +27 -20
- package/dist/task/cancel-points.js +30 -22
- package/dist/task/child-runner.d.ts +46 -51
- package/dist/task/child-runner.js +48 -49
- package/dist/task/child-status.d.ts +23 -16
- package/dist/task/child-status.js +23 -16
- package/dist/task/clamp-output.js +12 -5
- package/dist/task/command-run.d.ts +31 -28
- package/dist/task/command-run.js +44 -35
- package/dist/task/command-shrink.d.ts +25 -18
- package/dist/task/command-shrink.js +37 -31
- package/dist/task/command-watchdog.d.ts +9 -6
- package/dist/task/command-watchdog.js +21 -15
- package/dist/task/context-attribution.d.ts +34 -26
- package/dist/task/context-attribution.js +34 -26
- package/dist/task/context-silence.d.ts +39 -29
- package/dist/task/context-silence.js +35 -25
- package/dist/task/context-usage.d.ts +25 -7
- package/dist/task/context-usage.js +21 -6
- package/dist/task/contracts.d.ts +8 -4
- package/dist/task/contracts.js +25 -17
- package/dist/task/coverage-loop.d.ts +22 -18
- package/dist/task/coverage-loop.js +35 -30
- package/dist/task/critique-probes.d.ts +13 -14
- package/dist/task/critique-probes.js +50 -39
- package/dist/task/debug-log.d.ts +13 -5
- package/dist/task/debug-log.js +32 -20
- package/dist/task/decompose-fidelity.d.ts +11 -9
- package/dist/task/decompose-fidelity.js +38 -33
- package/dist/task/decompose-granularity.d.ts +41 -38
- package/dist/task/decompose-granularity.js +41 -38
- package/dist/task/deep-render-check.d.ts +22 -14
- package/dist/task/deep-render-check.js +40 -31
- package/dist/task/dropped-input.d.ts +12 -7
- package/dist/task/dropped-input.js +5 -2
- package/dist/task/enforce-attribution.d.ts +38 -47
- package/dist/task/enforce-attribution.js +46 -52
- package/dist/task/enforce-guidelines.d.ts +31 -20
- package/dist/task/enforce-guidelines.js +32 -21
- package/dist/task/enrichment.d.ts +7 -2
- package/dist/task/enrichment.js +26 -14
- package/dist/task/env-notes.d.ts +16 -7
- package/dist/task/env-notes.js +48 -31
- package/dist/task/env-template-closure.d.ts +4 -4
- package/dist/task/env-template-closure.js +42 -34
- package/dist/task/external-context.d.ts +28 -21
- package/dist/task/external-context.js +17 -12
- package/dist/task/failure-classifier.d.ts +4 -5
- package/dist/task/failure-classifier.js +6 -7
- package/dist/task/file-inventory.d.ts +15 -11
- package/dist/task/file-inventory.js +25 -22
- package/dist/task/final-gate-fix.d.ts +74 -86
- package/dist/task/final-gate-fix.js +97 -116
- package/dist/task/final-gate-progress.d.ts +29 -46
- package/dist/task/final-gate-progress.js +40 -51
- package/dist/task/final-gate.d.ts +64 -97
- package/dist/task/final-gate.js +192 -199
- package/dist/task/fix-child.d.ts +21 -27
- package/dist/task/fix-child.js +21 -27
- package/dist/task/foreign-path.d.ts +6 -5
- package/dist/task/foreign-path.js +0 -0
- package/dist/task/frozen-conflict.d.ts +9 -10
- package/dist/task/frozen-conflict.js +61 -64
- package/dist/task/frozen-path-guard.d.ts +35 -14
- package/dist/task/frozen-path-guard.js +56 -39
- package/dist/task/gate-child.d.ts +27 -28
- package/dist/task/gate-child.js +37 -35
- package/dist/task/gate-deps.d.ts +34 -27
- package/dist/task/gate-deps.js +169 -159
- package/dist/task/gate-tally.d.ts +77 -80
- package/dist/task/gate-tally.js +65 -68
- package/dist/task/git-state-guard.d.ts +15 -11
- package/dist/task/git-state-guard.js +76 -66
- package/dist/task/impl-widget.d.ts +25 -16
- package/dist/task/impl-widget.js +27 -17
- package/dist/task/implementation-thinking.d.ts +33 -31
- package/dist/task/implementation-thinking.js +5 -6
- package/dist/task/implementation-turn.d.ts +34 -31
- package/dist/task/implementation-turn.js +29 -27
- package/dist/task/inline-markdown.d.ts +20 -7
- package/dist/task/inline-markdown.js +15 -6
- package/dist/task/launch-config-gap.js +25 -39
- package/dist/task/launch-contract.d.ts +18 -21
- package/dist/task/launch-contract.js +28 -30
- package/dist/task/launch-manifest.d.ts +6 -2
- package/dist/task/launch-manifest.js +35 -34
- package/dist/task/ledger.js +16 -14
- package/dist/task/lint-fix.d.ts +6 -8
- package/dist/task/lint-fix.js +67 -69
- package/dist/task/loop-detector.d.ts +9 -8
- package/dist/task/loop-detector.js +16 -12
- package/dist/task/mid-run-input.d.ts +17 -15
- package/dist/task/mid-run-input.js +17 -15
- package/dist/task/orchestrator.d.ts +24 -28
- package/dist/task/orchestrator.js +62 -64
- package/dist/task/orientation.d.ts +18 -23
- package/dist/task/orientation.js +24 -31
- package/dist/task/owned-freeze-conflict.d.ts +21 -20
- package/dist/task/owned-freeze-conflict.js +52 -85
- package/dist/task/owned-freeze-reassign.d.ts +40 -60
- package/dist/task/owned-freeze-reassign.js +41 -61
- package/dist/task/parsers.d.ts +4 -2
- package/dist/task/parsers.js +4 -4
- package/dist/task/phases.d.ts +41 -48
- package/dist/task/phases.js +180 -248
- package/dist/task/plan-io.d.ts +6 -7
- package/dist/task/plan-io.js +6 -7
- package/dist/task/plan-orchestrator.d.ts +10 -8
- package/dist/task/plan-orchestrator.js +14 -10
- package/dist/task/plan-prompts.d.ts +6 -5
- package/dist/task/plan-prompts.js +6 -5
- package/dist/task/plan-readonly.d.ts +4 -5
- package/dist/task/plan-readonly.js +4 -5
- package/dist/task/plan-rounds.d.ts +17 -29
- package/dist/task/plan-rounds.js +21 -34
- package/dist/task/plan-session.d.ts +58 -72
- package/dist/task/plan-session.js +61 -83
- package/dist/task/probe-gaming.d.ts +28 -27
- package/dist/task/probe-gaming.js +0 -0
- package/dist/task/prohibition-probe.d.ts +14 -16
- package/dist/task/prompts.d.ts +3 -4
- package/dist/task/prompts.js +17 -26
- package/dist/task/qa-transcript.d.ts +15 -22
- package/dist/task/qa-transcript.js +15 -21
- package/dist/task/question-box.d.ts +17 -13
- package/dist/task/question-box.js +19 -15
- package/dist/task/question-dedup.d.ts +6 -7
- package/dist/task/question-dedup.js +13 -14
- package/dist/task/question-dialog.d.ts +22 -32
- package/dist/task/question-dialog.js +22 -32
- package/dist/task/question-source.d.ts +18 -44
- package/dist/task/question-source.js +22 -51
- package/dist/task/refuted-constraint.d.ts +11 -31
- package/dist/task/refuted-constraint.js +27 -51
- package/dist/task/regenerable-artifacts.d.ts +12 -31
- package/dist/task/regenerable-artifacts.js +12 -31
- package/dist/task/render-check.d.ts +11 -22
- package/dist/task/render-check.js +33 -46
- package/dist/task/repo-health-check.d.ts +10 -14
- package/dist/task/repo-health-check.js +17 -23
- package/dist/task/requirements.d.ts +38 -71
- package/dist/task/requirements.js +78 -126
- package/dist/task/research-fanout-budget.d.ts +51 -88
- package/dist/task/research-fanout-budget.js +51 -88
- package/dist/task/research-worker.d.ts +33 -36
- package/dist/task/research-worker.js +39 -61
- package/dist/task/resume-gap.d.ts +14 -15
- package/dist/task/root-cause-repair.d.ts +9 -9
- package/dist/task/root-cause-repair.js +28 -40
- package/dist/task/run-bracket.d.ts +10 -13
- package/dist/task/run-end.d.ts +12 -22
- package/dist/task/run-end.js +8 -16
- package/dist/task/run-final-gate.d.ts +19 -21
- package/dist/task/run-final-gate.js +62 -80
- package/dist/task/runner-globs.d.ts +12 -13
- package/dist/task/runner-globs.js +12 -13
- package/dist/task/runner-resolve.d.ts +9 -9
- package/dist/task/runner-resolve.js +22 -23
- package/dist/task/script-escape.d.ts +10 -12
- package/dist/task/script-escape.js +13 -14
- package/dist/task/serve-entry.d.ts +1 -1
- package/dist/task/serve-entry.js +22 -25
- package/dist/task/service-blocks.js +4 -2
- package/dist/task/shipped-source.d.ts +11 -29
- package/dist/task/shipped-source.js +11 -29
- package/dist/task/skip-escape.js +10 -14
- package/dist/task/spec-urls.d.ts +26 -65
- package/dist/task/spec-urls.js +26 -65
- package/dist/task/spec-validation.d.ts +17 -20
- package/dist/task/spec-validation.js +17 -20
- package/dist/task/stall-detector.d.ts +23 -30
- package/dist/task/stall-detector.js +23 -30
- package/dist/task/stream-watchdog.d.ts +14 -12
- package/dist/task/stream-watchdog.js +14 -12
- package/dist/task/substitution-probe.d.ts +17 -20
- package/dist/task/substitution-probe.js +17 -20
- package/dist/task/task-gates.d.ts +36 -41
- package/dist/task/task-gates.js +95 -106
- package/dist/task/task-io.d.ts +4 -4
- package/dist/task/task-io.js +4 -4
- package/dist/task/task-parsers.js +4 -3
- package/dist/task/task-provenance.d.ts +2 -2
- package/dist/task/task-provenance.js +11 -13
- package/dist/task/task-types.d.ts +4 -3
- package/dist/task/terminal-outcome.d.ts +14 -16
- package/dist/task/terminal-outcome.js +12 -14
- package/dist/task/test-assembly.d.ts +13 -20
- package/dist/task/test-assembly.js +13 -20
- package/dist/task/timings.d.ts +5 -3
- package/dist/task/timings.js +5 -3
- package/dist/task/title-label.d.ts +9 -4
- package/dist/task/title-label.js +9 -4
- package/dist/task/type-only-answer.d.ts +44 -52
- package/dist/task/type-only-answer.js +44 -52
- package/dist/task/unfailable-command.d.ts +18 -24
- package/dist/task/unfailable-command.js +21 -27
- package/dist/task/unknown-routing.d.ts +10 -4
- package/dist/task/unknown-routing.js +10 -4
- package/dist/task/user-directives.d.ts +5 -8
- package/dist/task/user-directives.js +5 -8
- package/dist/task/verify-quality.d.ts +18 -22
- package/dist/task/verify-quality.js +45 -46
- package/dist/task/verify-reconcile.d.ts +15 -10
- package/dist/task/verify-reconcile.js +45 -43
- package/dist/task/verify-resolution.d.ts +24 -20
- package/dist/task/verify-resolution.js +51 -50
- package/dist/task/verify-work.d.ts +59 -66
- package/dist/task/verify-work.js +101 -138
- package/dist/task/widget.d.ts +15 -14
- package/dist/task/widget.js +22 -17
- package/dist/task/wiring-claims.d.ts +25 -32
- package/dist/task/wiring-claims.js +30 -35
- package/dist/task/write-guard.d.ts +39 -39
- package/dist/task/write-guard.js +48 -51
- package/dist/task/yolo.d.ts +34 -30
- package/dist/task/yolo.js +42 -37
- package/dist/workers/abstention.d.ts +21 -41
- package/dist/workers/abstention.js +27 -48
- package/dist/workers/brave-search.d.ts +4 -3
- package/dist/workers/brave-search.js +5 -2
- package/dist/workers/brave-warning.d.ts +7 -4
- package/dist/workers/brave-warning.js +19 -7
- package/dist/workers/ddg-search.d.ts +6 -6
- package/dist/workers/ddg-search.js +18 -12
- package/dist/workers/docs-cache.js +5 -2
- package/dist/workers/docs-chunk.d.ts +30 -37
- package/dist/workers/docs-chunk.js +37 -41
- package/dist/workers/docs-core.d.ts +28 -44
- package/dist/workers/docs-core.js +25 -44
- package/dist/workers/docs-index.js +4 -3
- package/dist/workers/docs-lookup.d.ts +15 -22
- package/dist/workers/docs-lookup.js +12 -21
- package/dist/workers/docs-project.d.ts +15 -9
- package/dist/workers/docs-project.js +17 -10
- package/dist/workers/docs-resolve.d.ts +19 -20
- package/dist/workers/docs-resolve.js +35 -32
- package/dist/workers/docs-retrieve.d.ts +5 -6
- package/dist/workers/docs-retrieve.js +18 -15
- package/dist/workers/exa-search.d.ts +9 -6
- package/dist/workers/exa-search.js +23 -12
- package/dist/workers/fetch-core.d.ts +13 -16
- package/dist/workers/fetch-core.js +23 -23
- package/dist/workers/focused-extractor.d.ts +12 -12
- package/dist/workers/focused-extractor.js +16 -19
- package/dist/workers/html-clean.js +24 -14
- package/dist/workers/http-request.d.ts +28 -20
- package/dist/workers/http-request.js +22 -17
- package/dist/workers/npm-version.d.ts +28 -11
- package/dist/workers/npm-version.js +24 -15
- package/dist/workers/phantom-imports.d.ts +15 -12
- package/dist/workers/phantom-imports.js +30 -24
- package/dist/workers/pi-worker-core.d.ts +86 -54
- package/dist/workers/pi-worker-core.js +112 -112
- package/dist/workers/pi-worker-docs.d.ts +24 -19
- package/dist/workers/pi-worker-docs.js +67 -76
- package/dist/workers/pi-worker-fetch.d.ts +7 -3
- package/dist/workers/pi-worker-fetch.js +27 -19
- package/dist/workers/pi-worker-search.js +12 -8
- package/dist/workers/pi-worker.d.ts +9 -4
- package/dist/workers/pi-worker.js +23 -10
- package/dist/workers/reasoning-warning.d.ts +18 -17
- package/dist/workers/reasoning-warning.js +22 -20
- package/dist/workers/research-cache.js +50 -78
- package/dist/workers/search-core.js +7 -5
- package/dist/workers/search-types.d.ts +10 -9
- package/dist/workers/search-types.js +9 -8
- package/dist/workers/session-hint.d.ts +13 -14
- package/dist/workers/session-hint.js +8 -9
- package/dist/workers/shared.d.ts +21 -25
- package/dist/workers/shared.js +0 -0
- package/dist/workers/single-read-extension.d.ts +14 -7
- package/dist/workers/single-read-extension.js +14 -7
- package/dist/workers/single-read-guard.d.ts +25 -28
- package/dist/workers/single-read-guard.js +32 -32
- package/dist/workers/typeonly-log.d.ts +12 -9
- package/dist/workers/typeonly-log.js +29 -33
- package/dist/workers/worker-channels.d.ts +15 -23
- package/dist/workers/worker-channels.js +15 -23
- package/dist/workers/worker-failure.d.ts +38 -46
- package/dist/workers/worker-failure.js +31 -39
- package/dist/workers/worker-kill.d.ts +25 -26
- package/dist/workers/worker-kill.js +16 -19
- package/dist/workers/worker-profiles.d.ts +43 -53
- package/dist/workers/worker-profiles.js +30 -38
- package/package.json +10 -8
package/dist/task/boot-probe.js
CHANGED
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
* boot-probe — does the assembled product actually START, and does the page it
|
|
3
3
|
* serves actually render?
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* existed in the CONSUMERS — seven validation harnesses under `scripts/` import
|
|
9
|
-
* exactly this surface and nothing else from the gate.
|
|
5
|
+
* Its one consumer is final-gate.ts, which imports twelve names from here, calls
|
|
6
|
+
* `discoverBootCommand` at three sites and `runBootSection` at one, and re-exports
|
|
7
|
+
* six of them plus `BootSectionVerdict` for its own callers.
|
|
10
8
|
*
|
|
11
|
-
* The
|
|
12
|
-
* `
|
|
13
|
-
* listener parsers
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
9
|
+
* The entry points are `discoverBootCommand`, `detectsServedApp`, `runBootCheck`,
|
|
10
|
+
* `runBootSection` and `bootSkipVerdict`. Much else is exported too — the three
|
|
11
|
+
* listener parsers, the port helpers (`pickFreePort`, `isPortFree`,
|
|
12
|
+
* `preferredDeclaredPort`), `canEnumerateListeners`, `defaultFindPortHolder`,
|
|
13
|
+
* `recoverOrphanPort`, `rejectedLaunchScript` — because the gate or their own
|
|
14
|
+
* tests reach for them. Only the pgid helpers, the HTTP evidence probe, the reap
|
|
15
|
+
* and the spawn/teardown defaults are private.
|
|
17
16
|
*
|
|
18
|
-
* The boot check is deliberately NOT a CLOSURE_SCANS row: it is an
|
|
19
|
-
* port-binding exercise, and every row would need its own escape
|
|
20
|
-
*
|
|
17
|
+
* The boot check is deliberately NOT a CLOSURE_SCANS row (final-gate.ts): it is an
|
|
18
|
+
* async, stateful, port-binding exercise, and every row would need its own escape
|
|
19
|
+
* hatch.
|
|
21
20
|
*/
|
|
22
21
|
import { spawn, spawnSync } from 'node:child_process';
|
|
23
22
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -98,9 +97,9 @@ function isWatcherOnlyMultiplexer(member, scripts) {
|
|
|
98
97
|
}
|
|
99
98
|
/**
|
|
100
99
|
* Why this script is NOT a launch of the shipped app, or null when it plausibly
|
|
101
|
-
* is one
|
|
100
|
+
* is one.
|
|
102
101
|
*
|
|
103
|
-
*
|
|
102
|
+
* A boot command can resolve to `bun run dev`, whose body is
|
|
104
103
|
* `docker compose -f docker-compose.dev.yml up -d && until docker compose … pg_isready
|
|
105
104
|
* … && concurrently "bun run dev:css" "bun run dev:js" "bun run --watch
|
|
106
105
|
* src/server/index.ts"`. The gate sandbox has no docker, so the chain died at 127 and
|
|
@@ -112,15 +111,22 @@ function isWatcherOnlyMultiplexer(member, scripts) {
|
|
|
112
111
|
* producing an unfalsifiable skip.
|
|
113
112
|
*
|
|
114
113
|
* CONSERVATIVE AND LEXICAL BY CONSTRUCTION. Only two shapes are rejected, both
|
|
115
|
-
* decidable from the script text alone
|
|
114
|
+
* decidable from the script text alone, and both run against every case named
|
|
115
|
+
* below:
|
|
116
116
|
* 1. the chain OPENS with container orchestration (docker/podman/nerdctl … up|start|run);
|
|
117
117
|
* 2. the whole body is a multiplexer (concurrently/npm-run-all/run-p/run-s/turbo)
|
|
118
118
|
* whose every child is an ASSET watcher in watch mode (tailwind/tsc/esbuild/…),
|
|
119
119
|
* i.e. nothing in it can ever listen.
|
|
120
|
-
* Anything else — `vite`, `next dev`, `node dist/index.js`, `nodemon`, `bun
|
|
121
|
-
* src/index.ts`, and any multiplexer with one non-asset child — is accepted
|
|
122
|
-
* unchanged.
|
|
123
|
-
*
|
|
120
|
+
* Anything else — `vite`, `next dev`, `node dist/index.js`, `nodemon`, `bun run
|
|
121
|
+
* --watch src/index.ts`, and any multiplexer with one non-asset child — is accepted
|
|
122
|
+
* unchanged. All of those were run and accepted; so was a bare
|
|
123
|
+
* `tailwindcss … --watch`, which is only rejected INSIDE a multiplexer. A chain
|
|
124
|
+
* opening `docker compose … up` is rejected even when a real launch follows it,
|
|
125
|
+
* while `bun run docker-compose.dev.yml` is accepted, since the verb must be a
|
|
126
|
+
* bare token.
|
|
127
|
+
*
|
|
128
|
+
* Deciding whether a watcher actually SERVES is not attempted here; that is exactly
|
|
129
|
+
* what the static serve-entry check is for.
|
|
124
130
|
*/
|
|
125
131
|
export function nonLaunchScriptReason(body, scripts = {}) {
|
|
126
132
|
const members = chainMembers(body);
|
|
@@ -139,7 +145,7 @@ export function nonLaunchScriptReason(body, scripts = {}) {
|
|
|
139
145
|
* else `dev`; Makefile `run`). null means the project has nothing to boot —
|
|
140
146
|
* the boot check degrades to nothing-to-run.
|
|
141
147
|
*
|
|
142
|
-
* A script that is not a LAUNCH at all (nonLaunchScriptReason —
|
|
148
|
+
* A script that is not a LAUNCH at all (nonLaunchScriptReason — an
|
|
143
149
|
* `docker compose up` orchestrator) is rejected here and falls through to the
|
|
144
150
|
* next candidate, then to null. Discovering nothing is strictly better than
|
|
145
151
|
* discovering something unfalsifiable: an env-gap skip of an orchestration script
|
|
@@ -161,7 +167,7 @@ export function discoverBootCommand(cwd) {
|
|
|
161
167
|
}
|
|
162
168
|
/**
|
|
163
169
|
* The launch script that EXISTS but was rejected as not-a-launch, if any. Without
|
|
164
|
-
* this the rejection would trade
|
|
170
|
+
* this the rejection would trade an unfalsifiable skip for pure silence: no
|
|
165
171
|
* boot command means bootSkipVerdict has no label to name, and a project whose test
|
|
166
172
|
* suite ran still reports `observed > 0`, so unobservedVerdict stays quiet too. A
|
|
167
173
|
* served app whose only declared launch script cannot start it was not observed to
|
|
@@ -245,11 +251,14 @@ export function parseSsListeners(stdout) {
|
|
|
245
251
|
return out;
|
|
246
252
|
}
|
|
247
253
|
/**
|
|
248
|
-
* `netstat -tlnp` rows → {pid, port}
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
254
|
+
* `netstat -tlnp` rows → {pid, port}. Three parsers exist because no single
|
|
255
|
+
* enumerator is present everywhere — a box may ship netstat and no ss, or ss and
|
|
256
|
+
* no netstat — and without SOME enumerator the served-app check can never observe
|
|
257
|
+
* a listener and fails unfalsifiably.
|
|
258
|
+
*
|
|
259
|
+
* The pid rides in the trailing "PID/Program name" column ("1234/bun"); rows the
|
|
260
|
+
* kernel will not attribute to us print "-" there and are skipped. Both run as
|
|
261
|
+
* described.
|
|
253
262
|
*/
|
|
254
263
|
export function parseNetstatListeners(stdout) {
|
|
255
264
|
const out = [];
|
|
@@ -309,7 +318,7 @@ function listeningSockets() {
|
|
|
309
318
|
return [];
|
|
310
319
|
}
|
|
311
320
|
/**
|
|
312
|
-
* Can ANY socket-enumeration tool run here at all? (
|
|
321
|
+
* Can ANY socket-enumeration tool run here at all? (a sandbox may have
|
|
313
322
|
* none, so `groupHasListener` returned false forever and the boot check emitted
|
|
314
323
|
* "never opened a listening socket" no matter what the app did — an unfalsifiable
|
|
315
324
|
* FAIL that failed a run whose app demonstrably served.) This is a CAPABILITY
|
|
@@ -318,8 +327,10 @@ function listeningSockets() {
|
|
|
318
327
|
* must degrade to the survival rule exactly like win32 — never a false FAIL on a
|
|
319
328
|
* platform we cannot probe.
|
|
320
329
|
*
|
|
321
|
-
* "Ran" = spawned without ENOENT and either exited 0 or printed something
|
|
322
|
-
*
|
|
330
|
+
* "Ran" = spawned without ENOENT and either exited 0 or printed something. Both
|
|
331
|
+
* halves of that disjunction are load-bearing: `lsof` really does exit 1 on an
|
|
332
|
+
* empty match set (checked), and a netstat that rejects `-p` prints nothing, so
|
|
333
|
+
* neither an exit code nor output alone would answer the question.
|
|
323
334
|
* Memoised: the answer is a property of the box, not of the run.
|
|
324
335
|
*/
|
|
325
336
|
let listenerToolCapability = null;
|
|
@@ -341,10 +352,10 @@ export function canEnumerateListeners() {
|
|
|
341
352
|
}
|
|
342
353
|
/**
|
|
343
354
|
* A free TCP port on the loopback interface, or null if one cannot be reserved.
|
|
344
|
-
* The boot check hands this to the child as PORT so that a successful HTTP
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
355
|
+
* The boot check hands this to the child as PORT so that a successful HTTP request
|
|
356
|
+
* to it is OWNERSHIP evidence: nobody else knows the number, whereas an orphaned
|
|
357
|
+
* server left by an earlier check still answers on a conventional port and would
|
|
358
|
+
* pass a check the app had not earned.
|
|
348
359
|
*/
|
|
349
360
|
export function pickFreePort() {
|
|
350
361
|
return new Promise(resolve => {
|
|
@@ -362,7 +373,8 @@ export function pickFreePort() {
|
|
|
362
373
|
}
|
|
363
374
|
});
|
|
364
375
|
}
|
|
365
|
-
/** Can we bind 127.0.0.1:`port` right now?
|
|
376
|
+
/** Can we bind 127.0.0.1:`port` right now? Free ⇒ the boot child can have it.
|
|
377
|
+
* Run: true for a just-reserved port, false while anything holds it. */
|
|
366
378
|
export function isPortFree(port) {
|
|
367
379
|
return new Promise(resolve => {
|
|
368
380
|
try {
|
|
@@ -388,8 +400,9 @@ export async function preferredDeclaredPort(cwd) {
|
|
|
388
400
|
return (await isPortFree(port)) ? port : null;
|
|
389
401
|
}
|
|
390
402
|
/**
|
|
391
|
-
* Does anything answer HTTP on 127.0.0.1:`port`? Any response at all
|
|
392
|
-
*
|
|
403
|
+
* Does anything answer HTTP on 127.0.0.1:`port`? Any response at all counts — a
|
|
404
|
+
* status IS a listener, and a live server answering 404 on an unknown path is the
|
|
405
|
+
* ordinary case — so only a connection error or a timeout is a no.
|
|
393
406
|
* Runs in a throwaway child of our own runtime so it needs no curl on PATH and
|
|
394
407
|
* stays synchronous inside the boot poll.
|
|
395
408
|
*/
|
|
@@ -493,9 +506,10 @@ function defaultSpawnBoot(bin, args, o) {
|
|
|
493
506
|
/**
|
|
494
507
|
* The real group teardown, best-effort. A group already gone is not an error.
|
|
495
508
|
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
509
|
+
* On POSIX the negative pid signals the whole group, which is what makes a
|
|
510
|
+
* `detached` spawn reapable together with anything it backgrounded. Windows has
|
|
511
|
+
* neither process groups nor a negative-pid kill, so that branch shells out to
|
|
512
|
+
* `taskkill /T /F` as a single forced tree teardown instead of escalating.
|
|
499
513
|
*/
|
|
500
514
|
function defaultKillGroup(pid, sig) {
|
|
501
515
|
try {
|
|
@@ -511,8 +525,11 @@ function defaultKillGroup(pid, sig) {
|
|
|
511
525
|
}
|
|
512
526
|
}
|
|
513
527
|
/**
|
|
514
|
-
* Exercise the start command ONCE.
|
|
515
|
-
*
|
|
528
|
+
* Exercise the start command ONCE. All four outcomes below were run against real
|
|
529
|
+
* child processes in throwaway projects.
|
|
530
|
+
*
|
|
531
|
+
* For a CLI project (`expectServer` false) the command's own fate within the grace
|
|
532
|
+
* window decides:
|
|
516
533
|
*
|
|
517
534
|
* - non-zero exit (or signal death) before the window closes → FAIL, output tail;
|
|
518
535
|
* - exit 0 before the window closes → PASS (a CLI-style "run" that finished);
|
|
@@ -522,22 +539,21 @@ function defaultKillGroup(pid, sig) {
|
|
|
522
539
|
* For a SERVED app (`expectServer` true — the spec/plan promised an HTTP server) mere
|
|
523
540
|
* survival is not enough: a watcher (`dev` = tailwind/bundler --watch) stays alive
|
|
524
541
|
* forever without ever listening, and a type-only entrypoint exits 0 in <1s having
|
|
525
|
-
* served nothing
|
|
542
|
+
* served nothing. The boot then
|
|
526
543
|
* PASSes only once a LISTENing socket owned by our process group is observed; if the
|
|
527
544
|
* command exits, or the grace window closes, with no listener ever seen → FAIL naming
|
|
528
545
|
* that a listening server was expected.
|
|
529
546
|
*
|
|
530
|
-
* OBSERVABILITY is a precondition of that FAIL
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
* recorded failed. Two defences, in order:
|
|
547
|
+
* OBSERVABILITY is a precondition of that FAIL. The listener requirement needs
|
|
548
|
+
* pgid-attributed socket enumeration; win32 has none, and neither does a Linux
|
|
549
|
+
* image shipping no ss, netstat or lsof. Without a defence the check would emit
|
|
550
|
+
* "never opened a listening socket" against an app that demonstrably serves, and
|
|
551
|
+
* no amount of fixing could falsify it. Two defences, in order:
|
|
536
552
|
*
|
|
537
553
|
* - the child is spawned with a freshly reserved, otherwise-unused PORT, and an
|
|
538
554
|
* HTTP answer on THAT port proves a listener regardless of tooling. The private
|
|
539
555
|
* port is what makes the HTTP probe trustworthy: an orphaned server from an
|
|
540
|
-
* earlier check answers on
|
|
556
|
+
* earlier check answers on:3000, but nobody else knows this number.
|
|
541
557
|
* - if nothing can enumerate listeners AND the assigned port never answered, the
|
|
542
558
|
* served-app requirement is unobservable here, so `expectServer` collapses to
|
|
543
559
|
* the survival rule and the PASS is stamped UNOBSERVED. An app that ignores PORT
|
|
@@ -545,8 +561,10 @@ function defaultKillGroup(pid, sig) {
|
|
|
545
561
|
* not an app defect, and it may not be reported as one.
|
|
546
562
|
*
|
|
547
563
|
* A child that EXITS non-zero still FAILs in every environment: "the process died"
|
|
548
|
-
* needs no socket probe
|
|
549
|
-
*
|
|
564
|
+
* needs no socket probe. Confirmed — a start script exiting 3 comes back as
|
|
565
|
+
* `exited 3` with the output tail attached, with no listener question asked. That
|
|
566
|
+
* is what keeps a crashed app reportable even where a hot-reloading runtime would
|
|
567
|
+
* otherwise hold the process open.
|
|
550
568
|
*
|
|
551
569
|
* Env-gap contract as everywhere: spawn error (ENOENT) or a command-not-found
|
|
552
570
|
* inside the chain (exit 127, or the runner's own wording where the platform
|
|
@@ -563,11 +581,11 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
563
581
|
// a client whose base URL was baked in at build time calls that origin and no
|
|
564
582
|
// other, so serving it anywhere else makes the whole authenticated half
|
|
565
583
|
// unobservable. Anything else — no declaration, a port already held — falls back
|
|
566
|
-
// to the freshly reserved private port that
|
|
584
|
+
// to the freshly reserved private port that the ownership evidence needs.
|
|
567
585
|
const noPreference = () => Promise.resolve(null);
|
|
568
586
|
const preferred = expectServer ? await (opts.deps?.preferredPort ?? noPreference)() : null;
|
|
569
587
|
const assignedPort = preferred ?? (expectServer ? await (opts.deps?.pickPort ?? pickFreePort)() : null);
|
|
570
|
-
// Runner resolution
|
|
588
|
+
// Runner resolution: same contract as runGateCommand — resolve
|
|
571
589
|
// the runner and carry its directory on PATH so the boot script's own chain
|
|
572
590
|
// can re-invoke it.
|
|
573
591
|
const runner = resolveRunner(bin);
|
|
@@ -610,7 +628,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
610
628
|
};
|
|
611
629
|
const reapGroup = opts.deps?.killGroup ?? defaultKillGroup;
|
|
612
630
|
const killGroup = (sig) => {
|
|
613
|
-
// Truthiness, deliberately: `process.kill(
|
|
631
|
+
// Truthiness, deliberately: `process.kill(0, sig)` signals the
|
|
614
632
|
// CALLER's own process group, so a pid of 0 turns a best-effort
|
|
615
633
|
// teardown into self-termination. Node's spawn never yields 0, but
|
|
616
634
|
// `spawnBoot` is a seam now and a fake or future child could.
|
|
@@ -630,7 +648,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
630
648
|
};
|
|
631
649
|
// Served apps only: poll for a listening socket owned by our process group.
|
|
632
650
|
// As soon as one appears the boot has demonstrably served → run the render
|
|
633
|
-
// check against the LIVE listener (
|
|
651
|
+
// check against the LIVE listener (a listener that serves a
|
|
634
652
|
// permanently blank page passed every curl-shaped check), then PASS/FAIL.
|
|
635
653
|
// The probe is spawnSync, so the interval cannot re-enter mid-check.
|
|
636
654
|
// The deep probe is asynchronous (it drives a browser session), so the
|
|
@@ -671,7 +689,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
671
689
|
: undefined);
|
|
672
690
|
}
|
|
673
691
|
// The page renders. Now sign in and prove the AUTHENTICATED half
|
|
674
|
-
// is alive
|
|
692
|
+
// is alive: the server accepted the login and the
|
|
675
693
|
// client never used it. Async, so the interval is held off by
|
|
676
694
|
// `probing` until this settles.
|
|
677
695
|
probing = true;
|
|
@@ -705,7 +723,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
705
723
|
// Blind here (no enumeration tool, and the assigned port never
|
|
706
724
|
// answered) ⇒ we cannot tell "never listened" from "ignores PORT".
|
|
707
725
|
// Survival rule, stamped UNOBSERVED — an observer limitation is not
|
|
708
|
-
// an app defect
|
|
726
|
+
// an app defect.
|
|
709
727
|
if (!canEnumerate)
|
|
710
728
|
return passAndKill(UNOBSERVED_LISTENER_NOTE);
|
|
711
729
|
settle({
|
|
@@ -745,7 +763,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
745
763
|
const tail = outputTail(out, err);
|
|
746
764
|
// A bind collision is an environment condition, not an app defect — hand
|
|
747
765
|
// it back distinctly so the gate can reap our own orphan and retry rather
|
|
748
|
-
// than reporting the app "crashed"
|
|
766
|
+
// than reporting the app "crashed".
|
|
749
767
|
if (isAddressInUse(`${out}\n${err}`)) {
|
|
750
768
|
settle({
|
|
751
769
|
outcome: 'orphan-port',
|
|
@@ -760,9 +778,9 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
760
778
|
}
|
|
761
779
|
/**
|
|
762
780
|
* The SAME third verdict, at the door unobservedVerdict cannot reach: the boot
|
|
763
|
-
* check specifically
|
|
781
|
+
* check specifically.
|
|
764
782
|
*
|
|
765
|
-
*
|
|
783
|
+
* A run can ship an app with no HTTP server behind a converged final gate. Its
|
|
766
784
|
* `src/server/index.ts` ends at `export {app}` — no `Bun.serve`, no
|
|
767
785
|
* `export default app`, no `start` script — so `bun run src/server/index.ts` exits
|
|
768
786
|
* 0 immediately and the product cannot be started at all. The gate's boot command
|
|
@@ -771,26 +789,27 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
|
|
|
771
789
|
* contribute nothing to `dynObserved`, and `bun run test`, `test:ct`, `build`,
|
|
772
790
|
* `lint`, `seed` and `migrate` all ran — so `dynObserved > 0`, the full-skip
|
|
773
791
|
* blindness guard (observabilityGapFailure) stayed correctly quiet, and the trail
|
|
774
|
-
*
|
|
792
|
+
* report then reads `final-gate: autofix converged — statics + … passed`, with
|
|
793
|
+
* every task green.
|
|
775
794
|
*
|
|
776
795
|
* The defect is that "the app was never observed to boot" and "the app booted
|
|
777
|
-
* fine" produced BYTE-IDENTICAL gate output. That is the class
|
|
796
|
+
* fine" produced BYTE-IDENTICAL gate output. That is the class the verdict check
|
|
778
797
|
* exists to kill one layer up: absence of evidence rendered in the shape of
|
|
779
798
|
* evidence. So a discovered-but-skipped boot now names itself, and — unlike every
|
|
780
799
|
* other skip — it CANNOT be cancelled by observations from other commands.
|
|
781
|
-
* Component tests are the trap here, not the alibi:
|
|
800
|
+
* Component tests are the trap here, not the alibi: a suite of green Playwright
|
|
782
801
|
* CT tests, and CT mounts components in a browser without ever assembling or
|
|
783
802
|
* starting the server.
|
|
784
803
|
*
|
|
785
804
|
* DECIDED, do not silently re-open:
|
|
786
805
|
* - NOT a FAIL. A boot skip on a docker-less box is a genuine environment gap, and
|
|
787
|
-
* failing it re-creates
|
|
806
|
+
* failing it re-creates the unfalsifiable-FAIL mistake pointing the other
|
|
788
807
|
* way. UNOBSERVED blocks nothing while being loud and durable (the caller records
|
|
789
808
|
* it as final-gate debt the next run re-surfaces), and it keeps "boot never ran"
|
|
790
809
|
* out of the autofix child's seed — a child cannot fix a missing docker, so the
|
|
791
810
|
* highest-probability response would be to FABRICATE a bootable command, the
|
|
792
811
|
* class that refuted the `## verified tooling` harvest.
|
|
793
|
-
* - BOTH skip flavours count.
|
|
812
|
+
* - BOTH skip flavours count. A skip can carry `spawnFailed: false` (127 inside
|
|
794
813
|
* the script chain, not an ENOENT on the runner), so keying off spawnFailed would
|
|
795
814
|
* have missed the actual defect.
|
|
796
815
|
* - SERVED APPS ONLY. `expectServer === false` (a CLI/library project) is fenced off
|
|
@@ -850,13 +869,13 @@ export async function runBootSection(cwd, opts = {}) {
|
|
|
850
869
|
return { observed: false, warnings };
|
|
851
870
|
}
|
|
852
871
|
const label = `${boot[0]} ${boot[1].join(' ')}`;
|
|
853
|
-
// Render check
|
|
872
|
+
// Render check: for a served app, load the live page in a
|
|
854
873
|
// headless browser and judge the RENDERED DOM — curl can't run JS, so a
|
|
855
874
|
// blank-mount app passed every prior "renders" check. runRenderCheck
|
|
856
875
|
// env-gap-SKIPs when no browser exists, so a box without one never gets a
|
|
857
876
|
// false FAIL.
|
|
858
877
|
//
|
|
859
|
-
// Authenticated deep-render check
|
|
878
|
+
// Authenticated deep-render check: the page above renders, so now
|
|
860
879
|
// sign in with the account the project's own dotenv declares (the same
|
|
861
880
|
// ADMIN_PHONE/ADMIN_PASSWORD the launch contract's seed step consumes) and
|
|
862
881
|
// require the session to actually work. WEB-ONLY by construction — it hangs off
|
|
@@ -887,18 +906,18 @@ export async function runBootSection(cwd, opts = {}) {
|
|
|
887
906
|
if (unobserved !== null)
|
|
888
907
|
verdict.unobservedNote = unobserved;
|
|
889
908
|
if (b.outcome === 'fail') {
|
|
890
|
-
// OBSERVED
|
|
909
|
+
// OBSERVED. Every path that produces `fail` here is a probe
|
|
891
910
|
// that looked: the render judge saw an empty body, the deep session saw the
|
|
892
911
|
// authenticated half dead, the enumerator saw no listener, or the launch
|
|
893
912
|
// command itself exited non-zero. The one condition that means "we could not
|
|
894
|
-
// look" — no ss/netstat/lsof
|
|
913
|
+
// look" — no ss/netstat/lsof — returns PASS stamped UNOBSERVED
|
|
895
914
|
// and never reaches here.
|
|
896
915
|
verdict.failure = { detail: `boot check: \`${label}\` ${b.detail}`, rank: 0, observed: true };
|
|
897
916
|
}
|
|
898
917
|
else if (b.outcome === 'orphan-port') {
|
|
899
918
|
// Could not clear the port. Distinct HARNESS diagnosis, never a bare app
|
|
900
919
|
// FAIL: name the port and (when known) the process squatting on it. The
|
|
901
|
-
// holder lookup reads the SAME deps the boot ran under
|
|
920
|
+
// holder lookup reads the SAME deps the boot ran under. A
|
|
902
921
|
// second reach into `BootDeps` from the gate, one layer away from the run.
|
|
903
922
|
const holder = b.port !== null ? (deps.findPortHolder ?? defaultFindPortHolder)(b.port) : null;
|
|
904
923
|
const who = holder ? ` — held by an orphaned process (pid ${holder.pid}: ${holder.command})`
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* THE PROBLEM. While a /task-auto run is in flight, the host's interactive main
|
|
5
5
|
* loop is parked at `await session.prompt("/task-auto …")` and never loops back
|
|
6
|
-
* to read input. pi's editor submit handler
|
|
6
|
+
* to read input. pi's editor submit handler — `modes/interactive/interactive-mode.js`
|
|
7
|
+
* — has two paths, and both are verbatim in the installed package:
|
|
7
8
|
*
|
|
8
|
-
* - streaming → session.prompt(text, {streamingBehavior:"steer"})
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - streaming → `session.prompt(text, {streamingBehavior: "steer"})`. pi's own
|
|
10
|
+
* comment on that branch says it "handles extension commands
|
|
11
|
+
* (execute immediately)".
|
|
12
|
+
* - not streaming → `pendingUserInputs.push(text)`, reached only when no
|
|
13
|
+
* `onInputCallback` is registered — i.e. exactly when the main
|
|
14
|
+
* loop is parked. `getUserInput()` is the sole drain.
|
|
12
15
|
*
|
|
13
16
|
* The host session is NOT streaming for most of a run: the spec phases and every
|
|
14
17
|
* gate are child `pi` processes, not host turns. So a /task-auto-cancel typed
|
|
@@ -16,12 +19,17 @@
|
|
|
16
19
|
* already finished — by which point `autoRunning` is false and it answers "No
|
|
17
20
|
* /task-auto loop is running." The command was, for most of a run, inert.
|
|
18
21
|
*
|
|
19
|
-
* THE FIX. `ctx.ui.onTerminalInput` (→ TUI.addInputListener) is a raw stdin
|
|
20
|
-
* listener
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* THE FIX. `ctx.ui.onTerminalInput` (→ `TUI.addInputListener`) is a raw stdin
|
|
23
|
+
* listener, and the ordering it depends on is real: `handleTerminalInput` runs
|
|
24
|
+
* every registered input listener FIRST, returning immediately on the first one
|
|
25
|
+
* that answers `{consume: true}`, before any focused-component dispatch below it.
|
|
26
|
+
* That happens independently of the parked main loop.
|
|
27
|
+
*
|
|
28
|
+
* So we watch for the submit key, read what the editor is holding, and if it is
|
|
29
|
+
* the cancel command we raise the request ourselves and `consume` the keystroke —
|
|
30
|
+
* otherwise the line sits in the queue and replays after the run as the confusing
|
|
31
|
+
* "No /task-auto loop is running." auto-orchestrator emits when nothing is in
|
|
32
|
+
* flight.
|
|
25
33
|
*
|
|
26
34
|
* WHAT ELSE IT NOW CARRIES. The same interception makes the terminal behave like
|
|
27
35
|
* the browser for everything else typed mid-run, instead of feeding pi's queue:
|
|
@@ -32,7 +40,9 @@
|
|
|
32
40
|
* task turn, rather than starting a competing turn or being replayed after
|
|
33
41
|
* the run.
|
|
34
42
|
*
|
|
35
|
-
* Three things are deliberately NOT intercepted, and each would be a regression
|
|
43
|
+
* Three things are deliberately NOT intercepted, and each would be a regression
|
|
44
|
+
* (the key test: ESC against a full editor is not a submission, and neither is any
|
|
45
|
+
* ordinary character):
|
|
36
46
|
* - a keystroke that is not a submit, so typing and history are untouched;
|
|
37
47
|
* - anything typed while a prompt/dialog is open (the raw listener sees keys
|
|
38
48
|
* BEFORE the focused component, so swallowing here would eat the user's
|
|
@@ -65,10 +75,14 @@ export declare function installCancelListener(ctx: ExtensionCommandContext, onCa
|
|
|
65
75
|
* Begin intercepting mid-run terminal input. Refcounted, because runs nest:
|
|
66
76
|
* /task-auto arms for its loop and every task inside it arms again, so a plain
|
|
67
77
|
* "replace" would let the first inner run's end silently un-arm the rest of the
|
|
68
|
-
* loop.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
78
|
+
* loop. Run: three nested arms produce ONE install and take three disarms to
|
|
79
|
+
* release.
|
|
80
|
+
*
|
|
81
|
+
* The first `onCancel` wins — /task-auto passes one so a typed /task-auto-cancel
|
|
82
|
+
* can post its acknowledgement through the live ctx; a plain /task passes none
|
|
83
|
+
* and lets the generic bridge dispatch handle that command like any other. An arm
|
|
84
|
+
* that adds a callback where there was none upgrades in place, reinstalling
|
|
85
|
+
* against the same refcount rather than starting a second one.
|
|
72
86
|
*/
|
|
73
87
|
export declare function armCancelListener(ctx: ExtensionCommandContext, onCancel?: (live: ExtensionCommandContext) => void): void;
|
|
74
88
|
/**
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { holdInput, isRunActive } from './mid-run-input.js';
|
|
2
2
|
import { getBridge } from '../remote/bridge.js';
|
|
3
3
|
import { getState } from '../remote/session-state.js';
|
|
4
|
-
/** The command this listener delivers.
|
|
5
|
-
* bare "task-auto-cancel"
|
|
4
|
+
/** The command this listener delivers. Anchored at both ends and requiring the
|
|
5
|
+
* leading slash, so bare "task-auto-cancel", "/task-auto-cancel now" and
|
|
6
|
+
* "please run /task-auto-cancel" all fail to match — prose about the command must
|
|
7
|
+
* never fire it. Surrounding whitespace is tolerated. */
|
|
6
8
|
const CANCEL_RE = /^\/task-auto-cancel\s*$/;
|
|
7
9
|
/** Enter, in the encodings a terminal actually sends. A bare "\n" is what many
|
|
8
|
-
* terminals emit in raw mode; "\r" is the usual CR.
|
|
10
|
+
* terminals emit in raw mode; "\r" is the usual CR; "\r\n" covers the pair. All
|
|
11
|
+
* three submit; nothing else does, ESC included. */
|
|
9
12
|
function isSubmitKey(data) {
|
|
10
13
|
return data === '\r' || data === '\n' || data === '\r\n';
|
|
11
14
|
}
|
|
@@ -132,10 +135,12 @@ export function installCancelListener(ctx, onCancel) {
|
|
|
132
135
|
}
|
|
133
136
|
// ─── Armed listener (survives session replacement) ────────────────────────────
|
|
134
137
|
/**
|
|
135
|
-
* The listener does NOT survive `ctx.newSession()
|
|
136
|
-
*
|
|
137
|
-
* `
|
|
138
|
-
*
|
|
138
|
+
* The listener does NOT survive `ctx.newSession()`, and the chain is all three
|
|
139
|
+
* links, each present in the installed package: `InteractiveMode` registers
|
|
140
|
+
* `setBeforeSessionInvalidate(() => this.resetExtensionUI())`, `resetExtensionUI`
|
|
141
|
+
* calls `clearExtensionTerminalInputListeners()`, and that clears the very set
|
|
142
|
+
* this listener lives in — so every per-task session replacement silently drops
|
|
143
|
+
* it. That replacement happens at
|
|
139
144
|
* the START of each task, which is exactly the window this listener exists to
|
|
140
145
|
* cover, so the run must re-arm against the fresh ctx as soon as one appears
|
|
141
146
|
* (runSingleTask, where the new ctx is also handed to the remote bridge).
|
|
@@ -148,10 +153,14 @@ let armed = null;
|
|
|
148
153
|
* Begin intercepting mid-run terminal input. Refcounted, because runs nest:
|
|
149
154
|
* /task-auto arms for its loop and every task inside it arms again, so a plain
|
|
150
155
|
* "replace" would let the first inner run's end silently un-arm the rest of the
|
|
151
|
-
* loop.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
156
|
+
* loop. Run: three nested arms produce ONE install and take three disarms to
|
|
157
|
+
* release.
|
|
158
|
+
*
|
|
159
|
+
* The first `onCancel` wins — /task-auto passes one so a typed /task-auto-cancel
|
|
160
|
+
* can post its acknowledgement through the live ctx; a plain /task passes none
|
|
161
|
+
* and lets the generic bridge dispatch handle that command like any other. An arm
|
|
162
|
+
* that adds a callback where there was none upgrades in place, reinstalling
|
|
163
|
+
* against the same refcount rather than starting a second one.
|
|
155
164
|
*/
|
|
156
165
|
export function armCancelListener(ctx, onCancel) {
|
|
157
166
|
if (armed) {
|
|
@@ -3,24 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A cancel is a *request*, never a kill. It is honoured only where the on-disk
|
|
5
5
|
* state is durable and /task-auto-resume provably continues from it. Killing a
|
|
6
|
-
* child mid-phase would also stop the run
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* child mid-phase would also stop the run — the abort signal reaches every
|
|
7
|
+
* child's process group — but it throws away that phase's work and can leave a
|
|
8
|
+
* half-written tree, so the flag is polled at boundaries instead.
|
|
9
9
|
*
|
|
10
|
-
* The checkpoint set
|
|
10
|
+
* The checkpoint set is exactly these four, one call site each:
|
|
11
11
|
*
|
|
12
|
-
* loop-top the previous task is checked off
|
|
13
|
-
*
|
|
14
|
-
* pre-task after the pre-task checkpoint commit,
|
|
15
|
-
* constructed: tree committed, no inner id
|
|
16
|
-
* entry is simply still unchecked and a
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
12
|
+
* loop-top (auto-orchestrator) the previous task is checked off and
|
|
13
|
+
* committed and the next one has not started.
|
|
14
|
+
* pre-task (auto-orchestrator) after the pre-task checkpoint commit,
|
|
15
|
+
* before the runner is constructed: tree committed, no inner id
|
|
16
|
+
* stamped yet, so the entry is simply still unchecked and a
|
|
17
|
+
* resume restarts it.
|
|
18
|
+
* phase:<name> (orchestrator, inside the phase loop) after setTaskSection and
|
|
19
|
+
* postCommitPhase have persisted this phase's output. Front-matter
|
|
20
|
+
* `phase` still names the phase that JUST FINISHED, not the next
|
|
21
|
+
* one: `advance()` writes it at the TOP of each iteration and
|
|
22
|
+
* nothing moves it afterwards — postCommitPhase writes only
|
|
23
|
+
* `title` and `label`. Since the resume skip rule is
|
|
24
|
+
* `idx < resumeIdx`, a resume therefore RE-ENTERS this phase and
|
|
25
|
+
* runs it again. Nothing is lost, because its section is already
|
|
26
|
+
* on disk; the cost is repeating one phase, not zero.
|
|
27
|
+
* pre-final-gate (run-final-gate) every task is checked off and committed; the
|
|
28
|
+
* whole-repo gate has not started. A resume re-enters the same
|
|
29
|
+
* branch.
|
|
24
30
|
*
|
|
25
31
|
* DELIBERATELY NOT checkpoints — stopping here is not safe:
|
|
26
32
|
* - mid implementation turn: uncommitted, half-applied edits. The user's ESC
|
|
@@ -29,8 +35,9 @@
|
|
|
29
35
|
* work is written but unverified and uncommitted; the gates are what make it
|
|
30
36
|
* durable. Cancel is observed on the far side, at loop-top.
|
|
31
37
|
*/
|
|
32
|
-
/** Every place the cancel flag is polled.
|
|
33
|
-
*
|
|
38
|
+
/** Every place the cancel flag is polled. A closed union so the tests enumerate
|
|
39
|
+
* the same set the loop does — and they do: cancel-points.test.ts asserts on the
|
|
40
|
+
* recorded trail rather than on the loop's own bookkeeping. */
|
|
34
41
|
export type CancelCheckpoint = 'loop-top' | 'pre-task' | 'pre-final-gate' | `phase:${string}`;
|
|
35
42
|
export declare function requestCancel(): void;
|
|
36
43
|
export declare function isCancelRequested(): boolean;
|
|
@@ -44,7 +51,7 @@ export declare function isCancelRequested(): boolean;
|
|
|
44
51
|
* of a trial to get a clean slate.
|
|
45
52
|
*/
|
|
46
53
|
export declare function resetCancel(): void;
|
|
47
|
-
/** Drop the recorded checkpoint trail
|
|
54
|
+
/** Drop the recorded checkpoint trail. */
|
|
48
55
|
export declare function resetCheckpointTrail(): void;
|
|
49
56
|
/**
|
|
50
57
|
* Poll the cancel flag at a safe checkpoint. Records the visit either way, so a
|
|
@@ -54,5 +61,5 @@ export declare function resetCheckpointTrail(): void;
|
|
|
54
61
|
* @returns true when the caller must stop here.
|
|
55
62
|
*/
|
|
56
63
|
export declare function cancelCheckpoint(where: CancelCheckpoint): boolean;
|
|
57
|
-
/** Checkpoints crossed since the last reset
|
|
64
|
+
/** Checkpoints crossed since the last reset. */
|
|
58
65
|
export declare function checkpointsCrossed(): readonly CancelCheckpoint[];
|