gsd-pi 2.41.0-dev.b832948 → 2.41.0-dev.cac69f9
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/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +100 -118
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +195 -105
- /package/dist/web/standalone/.next/static/{43Aw72Fdw8k1aAHQOYOXr → EnGUNqHeGbE0tuuUkTJVA}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{43Aw72Fdw8k1aAHQOYOXr → EnGUNqHeGbE0tuuUkTJVA}/_ssgManifest.js +0 -0
|
@@ -2321,6 +2321,11 @@ export class InteractiveMode {
|
|
|
2321
2321
|
}
|
|
2322
2322
|
|
|
2323
2323
|
private handleCtrlZ(): void {
|
|
2324
|
+
// On Windows, SIGTSTP doesn't exist - Ctrl+Z is not supported
|
|
2325
|
+
if (process.platform === "win32") {
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2324
2329
|
// Ignore SIGINT while suspended so Ctrl+C in the terminal does not
|
|
2325
2330
|
// kill the backgrounded process. The handler is removed on resume.
|
|
2326
2331
|
const ignoreSigint = () => {};
|
|
@@ -595,7 +595,6 @@ test("autoLoop calls deriveState → resolveDispatch → runUnit in sequence", a
|
|
|
595
595
|
ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
|
|
596
596
|
const pi = makeMockPi();
|
|
597
597
|
|
|
598
|
-
let loopCount = 0;
|
|
599
598
|
const s = makeLoopSession();
|
|
600
599
|
|
|
601
600
|
const deps = makeMockDeps({
|
|
@@ -621,11 +620,8 @@ test("autoLoop calls deriveState → resolveDispatch → runUnit in sequence", a
|
|
|
621
620
|
},
|
|
622
621
|
postUnitPostVerification: async () => {
|
|
623
622
|
deps.callLog.push("postUnitPostVerification");
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (loopCount >= 1) {
|
|
627
|
-
s.active = false;
|
|
628
|
-
}
|
|
623
|
+
// Deactivate after first iteration to exit the loop
|
|
624
|
+
s.active = false;
|
|
629
625
|
return "continue" as const;
|
|
630
626
|
},
|
|
631
627
|
});
|
|
@@ -683,7 +679,6 @@ test("crash lock records session file from AFTER newSession, not before (#1710)"
|
|
|
683
679
|
};
|
|
684
680
|
const pi = makeMockPi();
|
|
685
681
|
|
|
686
|
-
let loopCount = 0;
|
|
687
682
|
const s = makeLoopSession({
|
|
688
683
|
cmdCtx: {
|
|
689
684
|
newSession: () => {
|
|
@@ -731,10 +726,8 @@ test("crash lock records session file from AFTER newSession, not before (#1710)"
|
|
|
731
726
|
},
|
|
732
727
|
postUnitPostVerification: async () => {
|
|
733
728
|
deps.callLog.push("postUnitPostVerification");
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
s.active = false;
|
|
737
|
-
}
|
|
729
|
+
// Deactivate after first iteration to exit the loop
|
|
730
|
+
s.active = false;
|
|
738
731
|
return "continue" as const;
|
|
739
732
|
},
|
|
740
733
|
});
|
|
@@ -791,6 +784,23 @@ test("autoLoop handles verification retry by continuing loop", async (t) => {
|
|
|
791
784
|
let deriveCallCount = 0;
|
|
792
785
|
const s = makeLoopSession();
|
|
793
786
|
|
|
787
|
+
// Pre-queued verification actions: each entry provides a side-effect + return value
|
|
788
|
+
type VerifyAction = { sideEffect?: () => void; response: "retry" | "continue" };
|
|
789
|
+
const verificationActions: VerifyAction[] = [
|
|
790
|
+
{
|
|
791
|
+
sideEffect: () => {
|
|
792
|
+
// Simulate retry — set pendingVerificationRetry on session
|
|
793
|
+
s.pendingVerificationRetry = {
|
|
794
|
+
unitId: "M001/S01/T01",
|
|
795
|
+
failureContext: "test failed: expected X got Y",
|
|
796
|
+
attempt: 1,
|
|
797
|
+
};
|
|
798
|
+
},
|
|
799
|
+
response: "retry",
|
|
800
|
+
},
|
|
801
|
+
{ response: "continue" },
|
|
802
|
+
];
|
|
803
|
+
|
|
794
804
|
const deps = makeMockDeps({
|
|
795
805
|
deriveState: async () => {
|
|
796
806
|
deriveCallCount++;
|
|
@@ -805,19 +815,11 @@ test("autoLoop handles verification retry by continuing loop", async (t) => {
|
|
|
805
815
|
} as any;
|
|
806
816
|
},
|
|
807
817
|
runPostUnitVerification: async () => {
|
|
818
|
+
const action = verificationActions[verifyCallCount] ?? { response: "continue" as const };
|
|
808
819
|
verifyCallCount++;
|
|
809
820
|
deps.callLog.push("runPostUnitVerification");
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
s.pendingVerificationRetry = {
|
|
813
|
-
unitId: "M001/S01/T01",
|
|
814
|
-
failureContext: "test failed: expected X got Y",
|
|
815
|
-
attempt: 1,
|
|
816
|
-
};
|
|
817
|
-
return "retry" as const;
|
|
818
|
-
}
|
|
819
|
-
// Second call: pass
|
|
820
|
-
return "continue" as const;
|
|
821
|
+
action.sideEffect?.();
|
|
822
|
+
return action.response;
|
|
821
823
|
},
|
|
822
824
|
postUnitPostVerification: async () => {
|
|
823
825
|
deps.callLog.push("postUnitPostVerification");
|
|
@@ -894,19 +896,17 @@ test("autoLoop handles dispatch skip action by continuing", async (t) => {
|
|
|
894
896
|
const s = makeLoopSession();
|
|
895
897
|
|
|
896
898
|
let dispatchCallCount = 0;
|
|
899
|
+
// Pre-queued dispatch responses: first call returns "skip", second returns "stop"
|
|
900
|
+
const dispatchResponses = [
|
|
901
|
+
{ action: "skip" as const },
|
|
902
|
+
{ action: "stop" as const, reason: "done", level: "info" as const },
|
|
903
|
+
];
|
|
897
904
|
const deps = makeMockDeps({
|
|
898
905
|
resolveDispatch: async () => {
|
|
906
|
+
const response = dispatchResponses[dispatchCallCount] ?? dispatchResponses[dispatchResponses.length - 1];
|
|
899
907
|
dispatchCallCount++;
|
|
900
908
|
deps.callLog.push("resolveDispatch");
|
|
901
|
-
|
|
902
|
-
return { action: "skip" as const };
|
|
903
|
-
}
|
|
904
|
-
// Second time: stop to exit the loop
|
|
905
|
-
return {
|
|
906
|
-
action: "stop" as const,
|
|
907
|
-
reason: "done",
|
|
908
|
-
level: "info" as const,
|
|
909
|
-
};
|
|
909
|
+
return response;
|
|
910
910
|
},
|
|
911
911
|
});
|
|
912
912
|
|
|
@@ -936,22 +936,26 @@ test("autoLoop drains sidecar queue after postUnitPostVerification enqueues item
|
|
|
936
936
|
const s = makeLoopSession();
|
|
937
937
|
|
|
938
938
|
let postVerCallCount = 0;
|
|
939
|
+
const postVerActions: Array<() => void> = [
|
|
940
|
+
() => {
|
|
941
|
+
// First call (main unit): enqueue a sidecar item
|
|
942
|
+
s.sidecarQueue.push({
|
|
943
|
+
kind: "hook" as const,
|
|
944
|
+
unitType: "hook/review",
|
|
945
|
+
unitId: "M001/S01/T01/review",
|
|
946
|
+
prompt: "review the code",
|
|
947
|
+
});
|
|
948
|
+
},
|
|
949
|
+
() => {
|
|
950
|
+
// Second call (sidecar unit completed): deactivate
|
|
951
|
+
s.active = false;
|
|
952
|
+
},
|
|
953
|
+
];
|
|
939
954
|
const deps = makeMockDeps({
|
|
940
955
|
postUnitPostVerification: async () => {
|
|
956
|
+
postVerActions[postVerCallCount]?.();
|
|
941
957
|
postVerCallCount++;
|
|
942
958
|
deps.callLog.push("postUnitPostVerification");
|
|
943
|
-
if (postVerCallCount === 1) {
|
|
944
|
-
// First call (main unit): enqueue a sidecar item
|
|
945
|
-
s.sidecarQueue.push({
|
|
946
|
-
kind: "hook" as const,
|
|
947
|
-
unitType: "hook/review",
|
|
948
|
-
unitId: "M001/S01/T01/review",
|
|
949
|
-
prompt: "review the code",
|
|
950
|
-
});
|
|
951
|
-
return "continue" as const;
|
|
952
|
-
}
|
|
953
|
-
// Second call (sidecar unit completed): done
|
|
954
|
-
s.active = false;
|
|
955
959
|
return "continue" as const;
|
|
956
960
|
},
|
|
957
961
|
});
|
|
@@ -1119,13 +1123,13 @@ test("startAuto calls selfHealRuntimeRecords before autoLoop (#1727)", () => {
|
|
|
1119
1123
|
assert.ok(healIdx > -1, "startAuto must call selfHealRuntimeRecords");
|
|
1120
1124
|
assert.ok(healIdx < loopIdx, "selfHealRuntimeRecords must be called before autoLoop");
|
|
1121
1125
|
|
|
1122
|
-
// Verify the second autoLoop call site also has selfHeal before it
|
|
1126
|
+
// Verify the second autoLoop call site also has selfHeal before it (if present)
|
|
1123
1127
|
const secondLoopIdx = fnBlock.indexOf("autoLoop(", loopIdx + 1);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1128
|
+
const secondHealIdx = fnBlock.indexOf("selfHealRuntimeRecords", healIdx + 1);
|
|
1129
|
+
assert.ok(
|
|
1130
|
+
secondLoopIdx === -1 || (secondHealIdx > -1 && secondHealIdx < secondLoopIdx),
|
|
1131
|
+
"if a second autoLoop call exists, it must also be preceded by selfHealRuntimeRecords",
|
|
1132
|
+
);
|
|
1129
1133
|
});
|
|
1130
1134
|
|
|
1131
1135
|
test("agent_end handler calls resolveAgentEnd (not handleAgentEnd)", () => {
|
|
@@ -1287,25 +1291,29 @@ test("stuck detection: window resets recovery when deriveState returns a differe
|
|
|
1287
1291
|
const s = makeLoopSession();
|
|
1288
1292
|
|
|
1289
1293
|
let deriveCallCount = 0;
|
|
1294
|
+
let postVerCallCount = 0;
|
|
1290
1295
|
let stopCalled = false;
|
|
1291
1296
|
|
|
1297
|
+
// First 3 derives return T01, 4th returns T02; dispatch follows the derived task
|
|
1298
|
+
const derivedTaskIds = ["T01", "T01", "T01", "T02"];
|
|
1299
|
+
|
|
1292
1300
|
const deps = makeMockDeps({
|
|
1293
1301
|
deriveState: async () => {
|
|
1302
|
+
const taskId = derivedTaskIds[Math.min(deriveCallCount, derivedTaskIds.length - 1)];
|
|
1294
1303
|
deriveCallCount++;
|
|
1295
1304
|
deps.callLog.push("deriveState");
|
|
1296
1305
|
return {
|
|
1297
1306
|
phase: "executing",
|
|
1298
1307
|
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
1299
1308
|
activeSlice: { id: "S01", title: "Slice 1" },
|
|
1300
|
-
activeTask: { id:
|
|
1309
|
+
activeTask: { id: taskId },
|
|
1301
1310
|
registry: [{ id: "M001", status: "active" }],
|
|
1302
1311
|
blockers: [],
|
|
1303
1312
|
} as any;
|
|
1304
1313
|
},
|
|
1305
1314
|
resolveDispatch: async () => {
|
|
1315
|
+
const taskId = derivedTaskIds[Math.min(deriveCallCount - 1, derivedTaskIds.length - 1)];
|
|
1306
1316
|
deps.callLog.push("resolveDispatch");
|
|
1307
|
-
// Return dispatch matching the task from deriveState
|
|
1308
|
-
const taskId = deriveCallCount <= 3 ? "T01" : "T02";
|
|
1309
1317
|
return {
|
|
1310
1318
|
action: "dispatch" as const,
|
|
1311
1319
|
unitType: "execute-task",
|
|
@@ -1319,11 +1327,11 @@ test("stuck detection: window resets recovery when deriveState returns a differe
|
|
|
1319
1327
|
s.active = false;
|
|
1320
1328
|
},
|
|
1321
1329
|
postUnitPostVerification: async () => {
|
|
1330
|
+
postVerCallCount++;
|
|
1322
1331
|
deps.callLog.push("postUnitPostVerification");
|
|
1323
|
-
//
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
}
|
|
1332
|
+
// Exit on the 4th call (after T02 unit completes)
|
|
1333
|
+
const shouldExit = postVerCallCount >= 4;
|
|
1334
|
+
s.active = !shouldExit;
|
|
1327
1335
|
return "continue" as const;
|
|
1328
1336
|
},
|
|
1329
1337
|
});
|
|
@@ -1362,6 +1370,14 @@ test("stuck detection: does not push to window during verification retry", async
|
|
|
1362
1370
|
let verifyCallCount = 0;
|
|
1363
1371
|
let stopReason = "";
|
|
1364
1372
|
|
|
1373
|
+
// Pre-queued responses: 3 retries then a continue (exit)
|
|
1374
|
+
const verifyActions: Array<() => "retry" | "continue"> = [
|
|
1375
|
+
() => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed", attempt: 1 }; return "retry"; },
|
|
1376
|
+
() => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed", attempt: 2 }; return "retry"; },
|
|
1377
|
+
() => { s.pendingVerificationRetry = { unitId: "M001/S01/T01", failureContext: "test failed", attempt: 3 }; return "retry"; },
|
|
1378
|
+
() => { s.active = false; return "continue"; },
|
|
1379
|
+
];
|
|
1380
|
+
|
|
1365
1381
|
const deps = makeMockDeps({
|
|
1366
1382
|
deriveState: async () =>
|
|
1367
1383
|
({
|
|
@@ -1379,20 +1395,10 @@ test("stuck detection: does not push to window during verification retry", async
|
|
|
1379
1395
|
prompt: "do the thing",
|
|
1380
1396
|
}),
|
|
1381
1397
|
runPostUnitVerification: async () => {
|
|
1398
|
+
const action = verifyActions[verifyCallCount] ?? (() => { s.active = false; return "continue" as const; });
|
|
1382
1399
|
verifyCallCount++;
|
|
1383
1400
|
deps.callLog.push("runPostUnitVerification");
|
|
1384
|
-
|
|
1385
|
-
// Set pendingVerificationRetry — should prevent stuck counter increment
|
|
1386
|
-
s.pendingVerificationRetry = {
|
|
1387
|
-
unitId: "M001/S01/T01",
|
|
1388
|
-
failureContext: "test failed",
|
|
1389
|
-
attempt: verifyCallCount,
|
|
1390
|
-
};
|
|
1391
|
-
return "retry" as const;
|
|
1392
|
-
}
|
|
1393
|
-
// After 3 retries, exit gracefully
|
|
1394
|
-
s.active = false;
|
|
1395
|
-
return "continue" as const;
|
|
1401
|
+
return action();
|
|
1396
1402
|
},
|
|
1397
1403
|
stopAuto: async (_ctx?: any, _pi?: any, reason?: string) => {
|
|
1398
1404
|
deps.callLog.push("stopAuto");
|
|
@@ -1544,7 +1550,7 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
|
|
|
1544
1550
|
const dispatchedUnitTypes: string[] = [];
|
|
1545
1551
|
|
|
1546
1552
|
// Phase sequence: each deriveState call returns a different phase.
|
|
1547
|
-
//
|
|
1553
|
+
// The 6th entry (index 5) is the terminal "complete" phase that stops the loop.
|
|
1548
1554
|
const phases = [
|
|
1549
1555
|
// Call 1: researching → dispatches research-slice
|
|
1550
1556
|
{
|
|
@@ -1576,6 +1582,12 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
|
|
|
1576
1582
|
activeSlice: { id: "S01", title: "Complete Slice" },
|
|
1577
1583
|
activeTask: null,
|
|
1578
1584
|
},
|
|
1585
|
+
// Call 6: terminal — deactivate to exit the loop
|
|
1586
|
+
{
|
|
1587
|
+
phase: "complete",
|
|
1588
|
+
activeSlice: null,
|
|
1589
|
+
activeTask: null,
|
|
1590
|
+
},
|
|
1579
1591
|
];
|
|
1580
1592
|
|
|
1581
1593
|
const dispatches = [
|
|
@@ -1588,46 +1600,26 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
|
|
|
1588
1600
|
|
|
1589
1601
|
const deps = makeMockDeps({
|
|
1590
1602
|
deriveState: async () => {
|
|
1603
|
+
const p = phases[Math.min(deriveCallCount, phases.length - 1)];
|
|
1591
1604
|
deriveCallCount++;
|
|
1592
1605
|
deps.callLog.push("deriveState");
|
|
1593
1606
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
return {
|
|
1598
|
-
phase: "complete",
|
|
1599
|
-
activeMilestone: { id: "M001", title: "Test", status: "complete" },
|
|
1600
|
-
activeSlice: null,
|
|
1601
|
-
activeTask: null,
|
|
1602
|
-
registry: [{ id: "M001", status: "complete" }],
|
|
1603
|
-
blockers: [],
|
|
1604
|
-
} as any;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
const p = phases[deriveCallCount - 1];
|
|
1607
|
+
const terminalPhases: Record<string, string> = { complete: "complete" };
|
|
1608
|
+
s.active = p.phase !== "complete";
|
|
1609
|
+
const milestoneStatus = terminalPhases[p.phase] ?? "active";
|
|
1608
1610
|
return {
|
|
1609
1611
|
phase: p.phase,
|
|
1610
|
-
activeMilestone: { id: "M001", title: "Test", status:
|
|
1611
|
-
activeSlice: p.activeSlice,
|
|
1612
|
-
activeTask: p.activeTask,
|
|
1613
|
-
registry: [{ id: "M001", status:
|
|
1612
|
+
activeMilestone: { id: "M001", title: "Test", status: milestoneStatus },
|
|
1613
|
+
activeSlice: p.activeSlice ?? null,
|
|
1614
|
+
activeTask: p.activeTask ?? null,
|
|
1615
|
+
registry: [{ id: "M001", status: milestoneStatus }],
|
|
1614
1616
|
blockers: [],
|
|
1615
1617
|
} as any;
|
|
1616
1618
|
},
|
|
1617
1619
|
resolveDispatch: async () => {
|
|
1620
|
+
const d = dispatches[Math.min(dispatchCallCount, dispatches.length - 1)];
|
|
1618
1621
|
dispatchCallCount++;
|
|
1619
1622
|
deps.callLog.push("resolveDispatch");
|
|
1620
|
-
|
|
1621
|
-
if (dispatchCallCount > dispatches.length) {
|
|
1622
|
-
// Safety: shouldn't reach here, but stop if it does
|
|
1623
|
-
return {
|
|
1624
|
-
action: "stop" as const,
|
|
1625
|
-
reason: "done",
|
|
1626
|
-
level: "info" as const,
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
const d = dispatches[dispatchCallCount - 1];
|
|
1631
1623
|
dispatchedUnitTypes.push(d.unitType);
|
|
1632
1624
|
return {
|
|
1633
1625
|
action: "dispatch" as const,
|
|
@@ -1692,18 +1684,11 @@ test("autoLoop lifecycle: advances through research → plan → execute → ver
|
|
|
1692
1684
|
`callLog should have at least 5 resolveDispatch entries (got ${dispatchEntries.length})`,
|
|
1693
1685
|
);
|
|
1694
1686
|
|
|
1695
|
-
// Verify interleaving:
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1701
|
-
if (entry === "deriveState" && dispatchSeen > 0) {
|
|
1702
|
-
// A deriveState after a resolveDispatch confirms the loop advanced
|
|
1703
|
-
break;
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
assert.ok(dispatchSeen > 0, "resolveDispatch should appear in callLog");
|
|
1687
|
+
// Verify interleaving: a deriveState must follow a resolveDispatch (confirms loop advanced)
|
|
1688
|
+
const firstDispatchIdx = deps.callLog.indexOf("resolveDispatch");
|
|
1689
|
+
const firstDeriveAfterDispatch = deps.callLog.indexOf("deriveState", firstDispatchIdx + 1);
|
|
1690
|
+
assert.ok(firstDispatchIdx >= 0, "resolveDispatch should appear in callLog");
|
|
1691
|
+
assert.ok(firstDeriveAfterDispatch > firstDispatchIdx, "deriveState should follow resolveDispatch to confirm loop advanced");
|
|
1707
1692
|
|
|
1708
1693
|
// Assert the exact sequence of dispatched unit types
|
|
1709
1694
|
assert.deepEqual(
|
|
@@ -1776,6 +1761,8 @@ test("autoLoop re-iterates when postUnitPreVerification returns retry (#1571)",
|
|
|
1776
1761
|
const s = makeLoopSession();
|
|
1777
1762
|
|
|
1778
1763
|
let preVerifyCallCount = 0;
|
|
1764
|
+
// Pre-queued responses: first call returns "retry", second returns "continue"
|
|
1765
|
+
const preVerifyResponses = ["retry", "continue"] as const;
|
|
1779
1766
|
|
|
1780
1767
|
const deps = makeMockDeps({
|
|
1781
1768
|
deriveState: async () => {
|
|
@@ -1791,11 +1778,7 @@ test("autoLoop re-iterates when postUnitPreVerification returns retry (#1571)",
|
|
|
1791
1778
|
},
|
|
1792
1779
|
postUnitPreVerification: async () => {
|
|
1793
1780
|
deps.callLog.push("postUnitPreVerification");
|
|
1794
|
-
preVerifyCallCount
|
|
1795
|
-
if (preVerifyCallCount === 1) {
|
|
1796
|
-
return "retry" as const;
|
|
1797
|
-
}
|
|
1798
|
-
return "continue" as const;
|
|
1781
|
+
return preVerifyResponses[preVerifyCallCount++] ?? "continue";
|
|
1799
1782
|
},
|
|
1800
1783
|
postUnitPostVerification: async () => {
|
|
1801
1784
|
deps.callLog.push("postUnitPostVerification");
|
|
@@ -1908,9 +1891,8 @@ test("autoLoop rejects execute-task with 0 tool calls as hallucinated (#1833)",
|
|
|
1908
1891
|
postUnitPostVerification: async () => {
|
|
1909
1892
|
deps.callLog.push("postUnitPostVerification");
|
|
1910
1893
|
iterationCount++;
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
}
|
|
1894
|
+
// Deactivate after 2nd iteration
|
|
1895
|
+
s.active = iterationCount < 2;
|
|
1914
1896
|
return "continue" as const;
|
|
1915
1897
|
},
|
|
1916
1898
|
});
|
|
@@ -101,8 +101,8 @@ test('secrets gate: pending keys exist — gate triggers collection, manifest up
|
|
|
101
101
|
const status = await getManifestStatus(tmp, 'M001');
|
|
102
102
|
assert.notStrictEqual(status, null, 'manifest should exist');
|
|
103
103
|
assert.ok(status!.pending.length > 0, 'should have pending keys');
|
|
104
|
-
assert.deepStrictEqual(status!.pending, ['GSD_GATE_TEST_PEND_A', 'GSD_GATE_TEST_PEND_B']);
|
|
105
|
-
assert.deepStrictEqual(status!.existing, ['GSD_GATE_TEST_EXISTING']);
|
|
104
|
+
assert.deepStrictEqual(status!.pending, ['GSD_GATE_TEST_PEND_A', 'GSD_GATE_TEST_PEND_B'], 'pending keys');
|
|
105
|
+
assert.deepStrictEqual(status!.existing, ['GSD_GATE_TEST_EXISTING'], 'existing keys');
|
|
106
106
|
|
|
107
107
|
// (b) Call collectSecretsFromManifest with no-UI context
|
|
108
108
|
// With hasUI: false, collectOneSecret returns null → pending keys become "skipped"
|
|
@@ -119,12 +119,23 @@ test("captures: loadPendingCaptures filters resolved entries", () => {
|
|
|
119
119
|
const id1 = appendCapture(tmp, "pending one");
|
|
120
120
|
appendCapture(tmp, "pending two");
|
|
121
121
|
|
|
122
|
-
// Resolve the first one
|
|
123
122
|
markCaptureResolved(tmp, id1, "note", "acknowledged", "just a note");
|
|
124
123
|
|
|
125
124
|
const pending = loadPendingCaptures(tmp);
|
|
126
125
|
assert.strictEqual(pending.length, 1, "should have 1 pending");
|
|
127
126
|
assert.strictEqual(pending[0].text, "pending two");
|
|
127
|
+
} finally {
|
|
128
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("captures: loadAllCaptures preserves resolved entries", () => {
|
|
133
|
+
const tmp = makeTempDir("cap-all-resolved");
|
|
134
|
+
try {
|
|
135
|
+
const id1 = appendCapture(tmp, "pending one");
|
|
136
|
+
appendCapture(tmp, "pending two");
|
|
137
|
+
|
|
138
|
+
markCaptureResolved(tmp, id1, "note", "acknowledged", "just a note");
|
|
128
139
|
|
|
129
140
|
const all = loadAllCaptures(tmp);
|
|
130
141
|
assert.strictEqual(all.length, 2, "all should still have 2");
|
|
@@ -72,18 +72,17 @@ describe("continue-here", () => {
|
|
|
72
72
|
const budget = computeBudgets(128_000);
|
|
73
73
|
const threshold = budget.continueThresholdPercent;
|
|
74
74
|
|
|
75
|
-
// Simulate repeated polls with percent above threshold
|
|
76
|
-
|
|
77
|
-
let fireCount = 0;
|
|
75
|
+
// Simulate repeated polls with percent above threshold using a reducer
|
|
76
|
+
// so there is no control flow inside the test body.
|
|
78
77
|
const usagePercents = [75, 80, 85, 90, 95];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
78
|
+
const { fired, fireCount } = usagePercents.reduce(
|
|
79
|
+
(acc, percent) => {
|
|
80
|
+
if (acc.fired) return acc; // one-shot guard
|
|
81
|
+
if (percent >= threshold) return { fired: true, fireCount: acc.fireCount + 1 };
|
|
82
|
+
return acc;
|
|
83
|
+
},
|
|
84
|
+
{ fired: false, fireCount: 0 },
|
|
85
|
+
);
|
|
87
86
|
|
|
88
87
|
assert.equal(fireCount, 1, "must fire exactly once");
|
|
89
88
|
assert.equal(fired, true);
|
|
@@ -97,16 +96,17 @@ describe("continue-here", () => {
|
|
|
97
96
|
{ name: "1M", contextWindow: 1_000_000 },
|
|
98
97
|
];
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const thresholdCases: Array<[string, number]> = [
|
|
100
|
+
["128K", 128_000],
|
|
101
|
+
["200K", 200_000],
|
|
102
|
+
["1M", 1_000_000],
|
|
103
|
+
];
|
|
104
|
+
for (const [name, contextWindow] of thresholdCases) {
|
|
105
|
+
it(`${name} model produces continueThresholdPercent of 70`, () => {
|
|
102
106
|
const budget = computeBudgets(contextWindow);
|
|
103
|
-
assert.equal(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`${name} model should have 70% threshold`,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
107
|
+
assert.equal(budget.continueThresholdPercent, 70, `${name} model should have 70% threshold`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
110
|
|
|
111
111
|
it("larger models produce larger verificationBudgetChars", () => {
|
|
112
112
|
const budgets = modelSizes.map(({ contextWindow }) => computeBudgets(contextWindow));
|