gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216
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/bundled-resource-path.d.ts +8 -0
- package/dist/bundled-resource-path.js +14 -0
- package/dist/headless-query.js +6 -6
- package/dist/resources/extensions/gsd/auto/session.js +27 -32
- package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
- package/dist/resources/extensions/gsd/auto-loop.js +956 -0
- package/dist/resources/extensions/gsd/auto-observability.js +4 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
- package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
- package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
- package/dist/resources/extensions/gsd/auto-start.js +330 -309
- package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
- package/dist/resources/extensions/gsd/auto-timers.js +3 -4
- package/dist/resources/extensions/gsd/auto-verification.js +35 -73
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
- package/dist/resources/extensions/gsd/auto.js +283 -1013
- package/dist/resources/extensions/gsd/captures.js +10 -4
- package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +296 -151
- package/dist/resources/extensions/gsd/index.js +92 -228
- package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
- package/dist/resources/extensions/gsd/progress-score.js +61 -156
- package/dist/resources/extensions/gsd/quick.js +98 -122
- package/dist/resources/extensions/gsd/session-lock.js +13 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/undo.js +43 -48
- package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
- package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
- package/dist/resources/extensions/gsd/verification-gate.js +6 -35
- package/dist/resources/extensions/gsd/worktree-command.js +30 -24
- package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
- package/dist/resources/extensions/gsd/worktree.js +7 -44
- package/dist/tool-bootstrap.js +59 -11
- package/dist/worktree-cli.js +7 -7
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +735 -2588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/src/models.generated.ts +1039 -2892
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +47 -30
- package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
- package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
- package/src/resources/extensions/gsd/auto-observability.ts +4 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
- package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
- package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
- package/src/resources/extensions/gsd/auto-start.ts +440 -354
- package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
- package/src/resources/extensions/gsd/auto-timers.ts +3 -4
- package/src/resources/extensions/gsd/auto-verification.ts +76 -90
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
- package/src/resources/extensions/gsd/auto.ts +515 -1199
- package/src/resources/extensions/gsd/captures.ts +10 -4
- package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +8 -1
- package/src/resources/extensions/gsd/gitignore.ts +4 -2
- package/src/resources/extensions/gsd/gsd-db.ts +375 -180
- package/src/resources/extensions/gsd/index.ts +104 -263
- package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
- package/src/resources/extensions/gsd/progress-score.ts +65 -200
- package/src/resources/extensions/gsd/quick.ts +121 -125
- package/src/resources/extensions/gsd/session-lock.ts +11 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
- package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
- package/src/resources/extensions/gsd/types.ts +90 -81
- package/src/resources/extensions/gsd/undo.ts +42 -46
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
- package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
- package/src/resources/extensions/gsd/verification-gate.ts +6 -39
- package/src/resources/extensions/gsd/worktree-command.ts +36 -24
- package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
- package/src/resources/extensions/gsd/worktree.ts +7 -44
- package/dist/resources/extensions/gsd/auto-constants.js +0 -5
- package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
- package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
- package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
- package/src/resources/extensions/gsd/auto-constants.ts +0 -6
- package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
- package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* auto-reentrancy-guard.test.ts — Tests for the unconditional reentrancy guard.
|
|
3
|
-
*
|
|
4
|
-
* Regression for #1272: auto-mode stuck-loop where gap watchdog or
|
|
5
|
-
* pendingAgentEndRetry could enter dispatchNextUnit concurrently during
|
|
6
|
-
* recursive skip chains because the reentrancy guard was bypassed when
|
|
7
|
-
* skipDepth > 0.
|
|
8
|
-
*
|
|
9
|
-
* The fix makes the guard unconditional (`if (s.dispatching)` without
|
|
10
|
-
* `&& s.skipDepth === 0`), and defers recursive re-dispatch via
|
|
11
|
-
* setImmediate/setTimeout so s.dispatching is released first.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
_getDispatching,
|
|
16
|
-
_setDispatching,
|
|
17
|
-
_getSkipDepth,
|
|
18
|
-
_setSkipDepth,
|
|
19
|
-
} from "../auto.ts";
|
|
20
|
-
import { createTestContext } from "./test-helpers.ts";
|
|
21
|
-
|
|
22
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
23
|
-
|
|
24
|
-
async function main(): Promise<void> {
|
|
25
|
-
// ─── Test-only accessors work ───────────────────────────────────────────
|
|
26
|
-
console.log("\n=== reentrancy guard: test accessors round-trip ===");
|
|
27
|
-
{
|
|
28
|
-
_setDispatching(false);
|
|
29
|
-
assertEq(_getDispatching(), false, "dispatching starts false");
|
|
30
|
-
|
|
31
|
-
_setDispatching(true);
|
|
32
|
-
assertEq(_getDispatching(), true, "dispatching set to true");
|
|
33
|
-
|
|
34
|
-
_setDispatching(false);
|
|
35
|
-
assertEq(_getDispatching(), false, "dispatching reset to false");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ─── skipDepth accessors ────────────────────────────────────────────────
|
|
39
|
-
console.log("\n=== reentrancy guard: skipDepth accessors round-trip ===");
|
|
40
|
-
{
|
|
41
|
-
_setSkipDepth(0);
|
|
42
|
-
assertEq(_getSkipDepth(), 0, "skipDepth starts at 0");
|
|
43
|
-
|
|
44
|
-
_setSkipDepth(3);
|
|
45
|
-
assertEq(_getSkipDepth(), 3, "skipDepth set to 3");
|
|
46
|
-
|
|
47
|
-
_setSkipDepth(0);
|
|
48
|
-
assertEq(_getSkipDepth(), 0, "skipDepth reset to 0");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── Guard blocks even when skipDepth > 0 (#1272 regression) ───────────
|
|
52
|
-
console.log("\n=== reentrancy guard: blocks when dispatching=true regardless of skipDepth ===");
|
|
53
|
-
{
|
|
54
|
-
// Simulate the scenario from #1272: dispatching=true + skipDepth>0
|
|
55
|
-
// The old guard (`if (s.dispatching && s.skipDepth === 0)`) would allow
|
|
56
|
-
// concurrent entry when skipDepth > 0. The fix makes the check
|
|
57
|
-
// unconditional on skipDepth.
|
|
58
|
-
_setDispatching(true);
|
|
59
|
-
_setSkipDepth(2);
|
|
60
|
-
|
|
61
|
-
// Verify dispatching is true — guard should block regardless of skipDepth
|
|
62
|
-
assertTrue(
|
|
63
|
-
_getDispatching() === true,
|
|
64
|
-
"dispatching flag is true during skip chain"
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// The actual reentrancy guard in dispatchNextUnit checks:
|
|
68
|
-
// if (s.dispatching) { return; }
|
|
69
|
-
// We verify the state that would trigger the guard:
|
|
70
|
-
const wouldBlock = _getDispatching(); // unconditional check
|
|
71
|
-
const wouldBlockOld = _getDispatching() && _getSkipDepth() === 0; // old check
|
|
72
|
-
|
|
73
|
-
assertTrue(wouldBlock === true, "new guard blocks when dispatching=true, skipDepth=2");
|
|
74
|
-
assertTrue(wouldBlockOld === false, "old guard WOULD NOT block when dispatching=true, skipDepth=2 (the bug)");
|
|
75
|
-
|
|
76
|
-
// Clean up
|
|
77
|
-
_setDispatching(false);
|
|
78
|
-
_setSkipDepth(0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─── Guard allows entry when dispatching=false ──────────────────────────
|
|
82
|
-
console.log("\n=== reentrancy guard: allows entry when dispatching=false ===");
|
|
83
|
-
{
|
|
84
|
-
_setDispatching(false);
|
|
85
|
-
_setSkipDepth(0);
|
|
86
|
-
assertTrue(!_getDispatching(), "guard allows entry when dispatching=false, skipDepth=0");
|
|
87
|
-
|
|
88
|
-
_setDispatching(false);
|
|
89
|
-
_setSkipDepth(3);
|
|
90
|
-
assertTrue(!_getDispatching(), "guard allows entry when dispatching=false, skipDepth=3");
|
|
91
|
-
|
|
92
|
-
_setSkipDepth(0);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ─── skipDepth does not affect guard decision (the fix) ─────────────────
|
|
96
|
-
console.log("\n=== reentrancy guard: skipDepth is irrelevant to guard decision ===");
|
|
97
|
-
{
|
|
98
|
-
for (const depth of [0, 1, 2, 5]) {
|
|
99
|
-
_setDispatching(true);
|
|
100
|
-
_setSkipDepth(depth);
|
|
101
|
-
assertTrue(
|
|
102
|
-
_getDispatching() === true,
|
|
103
|
-
`guard blocks at skipDepth=${depth} when dispatching=true`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
for (const depth of [0, 1, 2, 5]) {
|
|
108
|
-
_setDispatching(false);
|
|
109
|
-
_setSkipDepth(depth);
|
|
110
|
-
assertTrue(
|
|
111
|
-
_getDispatching() === false,
|
|
112
|
-
`guard allows at skipDepth=${depth} when dispatching=false`
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Clean up
|
|
117
|
-
_setDispatching(false);
|
|
118
|
-
_setSkipDepth(0);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
report();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
main().catch((err) => {
|
|
125
|
-
console.error(err);
|
|
126
|
-
process.exit(1);
|
|
127
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* auto-skip-loop.test.ts — Tests for the consecutive-skip loop breaker.
|
|
3
|
-
*
|
|
4
|
-
* Regression for #728: auto-mode infinite skip loop on previously completed
|
|
5
|
-
* plan-slice units when deriveState keeps returning the same unit.
|
|
6
|
-
*
|
|
7
|
-
* The skip paths in dispatchNextUnit track consecutive skips per unit via
|
|
8
|
-
* unitConsecutiveSkips. When the same unit is skipped > MAX_CONSECUTIVE_SKIPS
|
|
9
|
-
* times without a real dispatch in between, the completion record is evicted
|
|
10
|
-
* so deriveState can reconcile.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { tmpdir } from "node:os";
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
_getUnitConsecutiveSkips,
|
|
19
|
-
_resetUnitConsecutiveSkips,
|
|
20
|
-
} from "../auto.ts";
|
|
21
|
-
import { MAX_CONSECUTIVE_SKIPS } from "../auto/session.ts";
|
|
22
|
-
import { persistCompletedKey, removePersistedKey, loadPersistedKeys } from "../auto-recovery.ts";
|
|
23
|
-
import { createTestContext } from "./test-helpers.ts";
|
|
24
|
-
|
|
25
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
26
|
-
|
|
27
|
-
function makeTmpBase(): string {
|
|
28
|
-
const dir = mkdtempSync(join(tmpdir(), "gsd-skip-loop-test-"));
|
|
29
|
-
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
30
|
-
return dir;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function main(): Promise<void> {
|
|
34
|
-
// ─── Counter starts at zero ────────────────────────────────────────────
|
|
35
|
-
console.log("\n=== skip loop counter: initial state ===");
|
|
36
|
-
{
|
|
37
|
-
_resetUnitConsecutiveSkips();
|
|
38
|
-
const map = _getUnitConsecutiveSkips();
|
|
39
|
-
assertEq(map.size, 0, "counter map starts empty after reset");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ─── Counter increments correctly ────────────────────────────────────
|
|
43
|
-
console.log("\n=== skip loop counter: increments on repeated calls ===");
|
|
44
|
-
{
|
|
45
|
-
_resetUnitConsecutiveSkips();
|
|
46
|
-
const map = _getUnitConsecutiveSkips();
|
|
47
|
-
const key = "plan-slice/M001/S04";
|
|
48
|
-
|
|
49
|
-
for (let i = 1; i <= MAX_CONSECUTIVE_SKIPS; i++) {
|
|
50
|
-
const prev = map.get(key) ?? 0;
|
|
51
|
-
map.set(key, prev + 1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
assertEq(map.get(key), MAX_CONSECUTIVE_SKIPS, `counter reaches MAX_CONSECUTIVE_SKIPS (${MAX_CONSECUTIVE_SKIPS})`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ─── Threshold constant is sane ──────────────────────────────────────
|
|
58
|
-
console.log("\n=== skip loop counter: threshold is reasonable ===");
|
|
59
|
-
{
|
|
60
|
-
assertTrue(MAX_CONSECUTIVE_SKIPS >= 3, "threshold allows a few legitimate skips");
|
|
61
|
-
assertTrue(MAX_CONSECUTIVE_SKIPS <= 10, "threshold catches loops quickly");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ─── Reset clears all keys ────────────────────────────────────────────
|
|
65
|
-
console.log("\n=== skip loop counter: reset clears all keys ===");
|
|
66
|
-
{
|
|
67
|
-
_resetUnitConsecutiveSkips();
|
|
68
|
-
const map = _getUnitConsecutiveSkips();
|
|
69
|
-
map.set("plan-slice/M001/S01", 2);
|
|
70
|
-
map.set("plan-slice/M001/S02", 1);
|
|
71
|
-
assertEq(map.size, 2, "map has 2 entries before reset");
|
|
72
|
-
|
|
73
|
-
_resetUnitConsecutiveSkips();
|
|
74
|
-
assertEq(_getUnitConsecutiveSkips().size, 0, "map empty after reset");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ─── Eviction path: persistCompletedKey + removePersistedKey round-trip
|
|
78
|
-
// (simulates what the loop-breaker does) ───────────────────────────
|
|
79
|
-
console.log("\n=== skip loop counter: eviction removes persisted key ===");
|
|
80
|
-
{
|
|
81
|
-
_resetUnitConsecutiveSkips();
|
|
82
|
-
const base = makeTmpBase();
|
|
83
|
-
try {
|
|
84
|
-
const key = "plan-slice/M001/S04";
|
|
85
|
-
const keySet = new Set<string>();
|
|
86
|
-
|
|
87
|
-
persistCompletedKey(base, key);
|
|
88
|
-
loadPersistedKeys(base, keySet);
|
|
89
|
-
assertTrue(keySet.has(key), "key persisted before eviction");
|
|
90
|
-
|
|
91
|
-
// Simulate loop-breaker eviction
|
|
92
|
-
keySet.delete(key);
|
|
93
|
-
removePersistedKey(base, key);
|
|
94
|
-
const keySet2 = new Set<string>();
|
|
95
|
-
loadPersistedKeys(base, keySet2);
|
|
96
|
-
assertTrue(!keySet2.has(key), "key absent after eviction");
|
|
97
|
-
} finally {
|
|
98
|
-
rmSync(base, { recursive: true, force: true });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── Counter resets per-key, not globally ─────────────────────────────
|
|
103
|
-
console.log("\n=== skip loop counter: per-key isolation ===");
|
|
104
|
-
{
|
|
105
|
-
_resetUnitConsecutiveSkips();
|
|
106
|
-
const map = _getUnitConsecutiveSkips();
|
|
107
|
-
map.set("plan-slice/M001/S04", MAX_CONSECUTIVE_SKIPS + 1);
|
|
108
|
-
map.set("plan-slice/M001/S05", 1);
|
|
109
|
-
|
|
110
|
-
// Deleting S04 (eviction) should not affect S05
|
|
111
|
-
map.delete("plan-slice/M001/S04");
|
|
112
|
-
assertTrue(!map.has("plan-slice/M001/S04"), "S04 evicted");
|
|
113
|
-
assertEq(map.get("plan-slice/M001/S05"), 1, "S05 counter unaffected");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
_resetUnitConsecutiveSkips();
|
|
117
|
-
report();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
main().catch((err) => {
|
|
121
|
-
console.error(err);
|
|
122
|
-
process.exit(1);
|
|
123
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dispatch-stall-guard.test.ts — Verifies defensive guards against dispatch stalls (#1073).
|
|
3
|
-
*
|
|
4
|
-
* After a slice completes, dispatchNextUnit must reliably dispatch the next unit.
|
|
5
|
-
* These tests verify:
|
|
6
|
-
* 1. newSession() has timeout protection (prevents permanent hang if session creation stalls)
|
|
7
|
-
* 2. handleAgentEnd has a dispatch hang guard (catches dispatchNextUnit itself hanging)
|
|
8
|
-
* 3. Session timeout constants are exported for configurability
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import test from "node:test";
|
|
12
|
-
import assert from "node:assert/strict";
|
|
13
|
-
import { readFileSync } from "node:fs";
|
|
14
|
-
import { join, dirname } from "node:path";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
-
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const AUTO_TS_PATH = join(__dirname, "..", "auto.ts");
|
|
19
|
-
const SESSION_TS_PATH = join(__dirname, "..", "auto", "session.ts");
|
|
20
|
-
|
|
21
|
-
function getAutoTsSource(): string {
|
|
22
|
-
return readFileSync(AUTO_TS_PATH, "utf-8");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getSessionTsSource(): string {
|
|
26
|
-
return readFileSync(SESSION_TS_PATH, "utf-8");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ── Session timeout constants ───────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
test("AutoSession exports NEW_SESSION_TIMEOUT_MS constant", () => {
|
|
32
|
-
const source = getSessionTsSource();
|
|
33
|
-
assert.ok(
|
|
34
|
-
source.includes("NEW_SESSION_TIMEOUT_MS"),
|
|
35
|
-
"auto/session.ts must export NEW_SESSION_TIMEOUT_MS for newSession() timeout",
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("AutoSession exports DISPATCH_HANG_TIMEOUT_MS constant", () => {
|
|
40
|
-
const source = getSessionTsSource();
|
|
41
|
-
assert.ok(
|
|
42
|
-
source.includes("DISPATCH_HANG_TIMEOUT_MS"),
|
|
43
|
-
"auto/session.ts must export DISPATCH_HANG_TIMEOUT_MS for dispatch hang detection",
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("NEW_SESSION_TIMEOUT_MS is a reasonable value (15-120 seconds)", () => {
|
|
48
|
-
const source = getSessionTsSource();
|
|
49
|
-
const match = source.match(/NEW_SESSION_TIMEOUT_MS\s*=\s*(\d[\d_]*)/);
|
|
50
|
-
assert.ok(match, "NEW_SESSION_TIMEOUT_MS must have a numeric value");
|
|
51
|
-
const value = parseInt(match![1]!.replace(/_/g, ""), 10);
|
|
52
|
-
assert.ok(value >= 15_000 && value <= 120_000,
|
|
53
|
-
`NEW_SESSION_TIMEOUT_MS must be 15-120s, got ${value}ms`,
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("DISPATCH_HANG_TIMEOUT_MS is greater than NEW_SESSION_TIMEOUT_MS", () => {
|
|
58
|
-
const source = getSessionTsSource();
|
|
59
|
-
const sessionMatch = source.match(/NEW_SESSION_TIMEOUT_MS\s*=\s*(\d[\d_]*)/);
|
|
60
|
-
const dispatchMatch = source.match(/DISPATCH_HANG_TIMEOUT_MS\s*=\s*(\d[\d_]*)/);
|
|
61
|
-
assert.ok(sessionMatch && dispatchMatch, "Both timeout constants must exist");
|
|
62
|
-
const sessionTimeout = parseInt(sessionMatch![1]!.replace(/_/g, ""), 10);
|
|
63
|
-
const dispatchTimeout = parseInt(dispatchMatch![1]!.replace(/_/g, ""), 10);
|
|
64
|
-
assert.ok(dispatchTimeout > sessionTimeout,
|
|
65
|
-
`DISPATCH_HANG_TIMEOUT_MS (${dispatchTimeout}) must be > NEW_SESSION_TIMEOUT_MS (${sessionTimeout})`,
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ── newSession() timeout in dispatchNextUnit ─────────────────────────────────
|
|
70
|
-
|
|
71
|
-
test("dispatchNextUnit wraps newSession() with Promise.race timeout", () => {
|
|
72
|
-
const source = getAutoTsSource();
|
|
73
|
-
// Search the full file — dispatchNextUnit is very large
|
|
74
|
-
assert.ok(
|
|
75
|
-
source.includes("Promise.race") && source.includes("NEW_SESSION_TIMEOUT_MS"),
|
|
76
|
-
"dispatchNextUnit must use Promise.race with NEW_SESSION_TIMEOUT_MS to timeout newSession() (#1073)",
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("dispatchNextUnit handles newSession() timeout gracefully", () => {
|
|
81
|
-
const source = getAutoTsSource();
|
|
82
|
-
// Must notify user when session times out
|
|
83
|
-
assert.ok(
|
|
84
|
-
source.includes("Session creation timed out") || source.includes("Session creation failed"),
|
|
85
|
-
"dispatchNextUnit must notify user when newSession() times out or fails (#1073)",
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ── Dispatch hang guard in handleAgentEnd ────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
test("handleAgentEnd has a dispatch hang guard before dispatchNextUnit", () => {
|
|
92
|
-
const source = getAutoTsSource();
|
|
93
|
-
const fnIdx = source.indexOf("export async function handleAgentEnd");
|
|
94
|
-
assert.ok(fnIdx > -1, "handleAgentEnd must exist");
|
|
95
|
-
|
|
96
|
-
// Find the section between step mode check and dispatchNextUnit call
|
|
97
|
-
const fnBlock = source.slice(fnIdx, source.indexOf("\n// ─── Step Mode", fnIdx + 100));
|
|
98
|
-
assert.ok(
|
|
99
|
-
fnBlock.includes("DISPATCH_HANG_TIMEOUT_MS") || fnBlock.includes("dispatchHangGuard"),
|
|
100
|
-
"handleAgentEnd must have a dispatch hang guard before calling dispatchNextUnit (#1073)",
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("dispatch hang guard is cleared in finally block", () => {
|
|
105
|
-
const source = getAutoTsSource();
|
|
106
|
-
const fnIdx = source.indexOf("export async function handleAgentEnd");
|
|
107
|
-
const fnBlock = source.slice(fnIdx, source.indexOf("\n// ─── Step Mode", fnIdx + 100));
|
|
108
|
-
assert.ok(
|
|
109
|
-
fnBlock.includes("clearTimeout(dispatchHangGuard)"),
|
|
110
|
-
"dispatch hang guard must be cleared in finally block to prevent false alarms (#1073)",
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// ── Constants are imported in auto.ts ────────────────────────────────────────
|
|
115
|
-
|
|
116
|
-
test("auto.ts imports NEW_SESSION_TIMEOUT_MS and DISPATCH_HANG_TIMEOUT_MS", () => {
|
|
117
|
-
const source = getAutoTsSource();
|
|
118
|
-
assert.ok(
|
|
119
|
-
source.includes("NEW_SESSION_TIMEOUT_MS"),
|
|
120
|
-
"auto.ts must import NEW_SESSION_TIMEOUT_MS from session.ts",
|
|
121
|
-
);
|
|
122
|
-
assert.ok(
|
|
123
|
-
source.includes("DISPATCH_HANG_TIMEOUT_MS"),
|
|
124
|
-
"auto.ts must import DISPATCH_HANG_TIMEOUT_MS from session.ts",
|
|
125
|
-
);
|
|
126
|
-
});
|