@monotykamary/pi-retry 0.3.11 → 0.3.12
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/package.json +1 -1
- package/retry.ts +39 -0
package/package.json
CHANGED
package/retry.ts
CHANGED
|
@@ -123,6 +123,11 @@ const stateOther = new RetryState();
|
|
|
123
123
|
// Max_tokens continuation state (indefinite — no cap needed)
|
|
124
124
|
const stateContinuation = new ContinuationState();
|
|
125
125
|
|
|
126
|
+
// Abort flag: set when turn_end reports stopReason "aborted", cleared on
|
|
127
|
+
// session_start and on fresh user activity. Prevents triggerInvisibleContinue()
|
|
128
|
+
// from driving a new prompt([]) after the user explicitly cancelled.
|
|
129
|
+
let _userAborted = false;
|
|
130
|
+
|
|
126
131
|
// Mutex: only one triggerInvisibleContinue may be in-flight at a time.
|
|
127
132
|
// Without this, concurrent agent_end events (or a manual /retry during an
|
|
128
133
|
// automatic retry) race through waitForIdle() and both call prompt([]),
|
|
@@ -155,6 +160,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
155
160
|
// Do NOT reset continuation state — a user abort of a continuation
|
|
156
161
|
// turn is different from aborting an error retry.
|
|
157
162
|
stateContinuation.endContinuation();
|
|
163
|
+
// Signal to any in-flight triggerInvisibleContinue or pending retry
|
|
164
|
+
// that the user has cancelled — don't drive a new prompt([]).
|
|
165
|
+
_userAborted = true;
|
|
158
166
|
return;
|
|
159
167
|
}
|
|
160
168
|
if (msg.stopReason !== "length") {
|
|
@@ -164,6 +172,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
164
172
|
stateConnection.succeed();
|
|
165
173
|
stateOther.succeed();
|
|
166
174
|
stateContinuation.complete();
|
|
175
|
+
// Clear abort flag — this is a fresh successful turn, so any
|
|
176
|
+
// previous abort is stale and shouldn't block future retries.
|
|
177
|
+
_userAborted = false;
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
});
|
|
@@ -177,6 +188,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
177
188
|
return;
|
|
178
189
|
}
|
|
179
190
|
|
|
191
|
+
// Guard: if the user aborted, don't drive any new prompt([])
|
|
192
|
+
if (_userAborted) return;
|
|
193
|
+
|
|
180
194
|
// Check for max_tokens stop — auto-continue (invisible to LLM)
|
|
181
195
|
if (hasMaxTokensStop(lastAssistant) && !stateContinuation.getIsContinuing()) {
|
|
182
196
|
stateContinuation.startContinuation();
|
|
@@ -218,6 +232,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
232
|
const delay = calculateDelay(state.getAttempt());
|
|
219
233
|
|
|
220
234
|
await sleep(delay);
|
|
235
|
+
|
|
236
|
+
// Re-check: user may have aborted during the backoff sleep.
|
|
237
|
+
// turn_end with stopReason "aborted" sets _userAborted, but this
|
|
238
|
+
// handler was already past the initial check.
|
|
239
|
+
if (_userAborted) {
|
|
240
|
+
state.endRetry();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
221
244
|
// Must NOT await — see triggerInvisibleContinue() for explanation
|
|
222
245
|
void triggerInvisibleContinue();
|
|
223
246
|
state.endRetry();
|
|
@@ -305,6 +328,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
305
328
|
stateConnection.reset();
|
|
306
329
|
stateOther.reset();
|
|
307
330
|
stateContinuation.reset();
|
|
331
|
+
_userAborted = false;
|
|
308
332
|
ctx.ui.notify("All retry counters reset", "info");
|
|
309
333
|
return;
|
|
310
334
|
}
|
|
@@ -318,6 +342,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
318
342
|
return;
|
|
319
343
|
}
|
|
320
344
|
|
|
345
|
+
// Manual /retry overrides any previous abort — the user is
|
|
346
|
+
// explicitly requesting a retry, so clear the abort flag.
|
|
347
|
+
_userAborted = false;
|
|
348
|
+
|
|
321
349
|
// Auto-detect: max_tokens continuation takes priority
|
|
322
350
|
if (hasMaxTokensStop(lastAssistant)) {
|
|
323
351
|
ctx.ui.notify("Manually continuing after max_tokens...", "info");
|
|
@@ -369,6 +397,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
369
397
|
stateContinuation.reset();
|
|
370
398
|
_continueInProgress = false;
|
|
371
399
|
_lastInvisibleContinueTime = 0;
|
|
400
|
+
_userAborted = false;
|
|
372
401
|
});
|
|
373
402
|
|
|
374
403
|
// Resume the agent loop invisibly — no message injected into context.
|
|
@@ -382,6 +411,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
382
411
|
async function triggerInvisibleContinue() {
|
|
383
412
|
if (!_agent) return;
|
|
384
413
|
|
|
414
|
+
// Guard 0: if the user aborted, don't drive a new prompt([]).
|
|
415
|
+
// This catches aborts that happened between agent_end firing and
|
|
416
|
+
// this async function actually executing (e.g. during backoff sleep
|
|
417
|
+
// or while waitForIdle was pending).
|
|
418
|
+
if (_userAborted) return;
|
|
419
|
+
|
|
385
420
|
// Guard 1: mutex — if a previous continue is still in-flight, skip
|
|
386
421
|
if (_continueInProgress) return;
|
|
387
422
|
_continueInProgress = true;
|
|
@@ -391,6 +426,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
391
426
|
// finishRun() after agent_end listeners return).
|
|
392
427
|
await _agent.waitForIdle();
|
|
393
428
|
|
|
429
|
+
// Re-check after waitForIdle: the user may have aborted while
|
|
430
|
+
// we were waiting for the agent to become idle.
|
|
431
|
+
if (_userAborted) return;
|
|
432
|
+
|
|
394
433
|
try {
|
|
395
434
|
// Await so _continueInProgress stays true for the full retry.
|
|
396
435
|
// The session's continue() is blocked (monkey-patch) and the
|