@monotykamary/pi-retry 0.3.10 → 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 +52 -1
package/package.json
CHANGED
package/retry.ts
CHANGED
|
@@ -90,7 +90,9 @@ Agent.prototype.continue = function (this: Agent) {
|
|
|
90
90
|
lastMsg.stopReason !== 'stop' &&
|
|
91
91
|
lastMsg.stopReason !== 'aborted') {
|
|
92
92
|
// Agent was mid-task (toolUse, length, or error) — fall back to prompt([])
|
|
93
|
-
if (
|
|
93
|
+
// Guard: if triggerInvisibleContinue() just completed (within 500ms),
|
|
94
|
+
// the agent already continued — skip to avoid double continuation.
|
|
95
|
+
if (!_continueInProgress && Date.now() - _lastInvisibleContinueTime > 500) {
|
|
94
96
|
_continueInProgress = true;
|
|
95
97
|
try {
|
|
96
98
|
await self.prompt([]);
|
|
@@ -121,12 +123,22 @@ const stateOther = new RetryState();
|
|
|
121
123
|
// Max_tokens continuation state (indefinite — no cap needed)
|
|
122
124
|
const stateContinuation = new ContinuationState();
|
|
123
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
|
+
|
|
124
131
|
// Mutex: only one triggerInvisibleContinue may be in-flight at a time.
|
|
125
132
|
// Without this, concurrent agent_end events (or a manual /retry during an
|
|
126
133
|
// automatic retry) race through waitForIdle() and both call prompt([]),
|
|
127
134
|
// producing "Agent is already processing".
|
|
128
135
|
let _continueInProgress = false;
|
|
129
136
|
|
|
137
|
+
// Timestamp of the last completed triggerInvisibleContinue().
|
|
138
|
+
// Used by the continue() monkey-patch to avoid double continuation when
|
|
139
|
+
// triggerInvisibleContinue just ran and the session's continue() unblocks.
|
|
140
|
+
let _lastInvisibleContinueTime = 0;
|
|
141
|
+
|
|
130
142
|
// Sleep helper
|
|
131
143
|
function sleep(ms: number): Promise<void> {
|
|
132
144
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -148,6 +160,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
148
160
|
// Do NOT reset continuation state — a user abort of a continuation
|
|
149
161
|
// turn is different from aborting an error retry.
|
|
150
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;
|
|
151
166
|
return;
|
|
152
167
|
}
|
|
153
168
|
if (msg.stopReason !== "length") {
|
|
@@ -157,6 +172,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
172
|
stateConnection.succeed();
|
|
158
173
|
stateOther.succeed();
|
|
159
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;
|
|
160
178
|
}
|
|
161
179
|
}
|
|
162
180
|
});
|
|
@@ -170,6 +188,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
170
188
|
return;
|
|
171
189
|
}
|
|
172
190
|
|
|
191
|
+
// Guard: if the user aborted, don't drive any new prompt([])
|
|
192
|
+
if (_userAborted) return;
|
|
193
|
+
|
|
173
194
|
// Check for max_tokens stop — auto-continue (invisible to LLM)
|
|
174
195
|
if (hasMaxTokensStop(lastAssistant) && !stateContinuation.getIsContinuing()) {
|
|
175
196
|
stateContinuation.startContinuation();
|
|
@@ -211,6 +232,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
211
232
|
const delay = calculateDelay(state.getAttempt());
|
|
212
233
|
|
|
213
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
|
+
|
|
214
244
|
// Must NOT await — see triggerInvisibleContinue() for explanation
|
|
215
245
|
void triggerInvisibleContinue();
|
|
216
246
|
state.endRetry();
|
|
@@ -298,6 +328,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
298
328
|
stateConnection.reset();
|
|
299
329
|
stateOther.reset();
|
|
300
330
|
stateContinuation.reset();
|
|
331
|
+
_userAborted = false;
|
|
301
332
|
ctx.ui.notify("All retry counters reset", "info");
|
|
302
333
|
return;
|
|
303
334
|
}
|
|
@@ -311,6 +342,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
311
342
|
return;
|
|
312
343
|
}
|
|
313
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
|
+
|
|
314
349
|
// Auto-detect: max_tokens continuation takes priority
|
|
315
350
|
if (hasMaxTokensStop(lastAssistant)) {
|
|
316
351
|
ctx.ui.notify("Manually continuing after max_tokens...", "info");
|
|
@@ -361,6 +396,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
361
396
|
stateOther.reset();
|
|
362
397
|
stateContinuation.reset();
|
|
363
398
|
_continueInProgress = false;
|
|
399
|
+
_lastInvisibleContinueTime = 0;
|
|
400
|
+
_userAborted = false;
|
|
364
401
|
});
|
|
365
402
|
|
|
366
403
|
// Resume the agent loop invisibly — no message injected into context.
|
|
@@ -374,6 +411,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
374
411
|
async function triggerInvisibleContinue() {
|
|
375
412
|
if (!_agent) return;
|
|
376
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
|
+
|
|
377
420
|
// Guard 1: mutex — if a previous continue is still in-flight, skip
|
|
378
421
|
if (_continueInProgress) return;
|
|
379
422
|
_continueInProgress = true;
|
|
@@ -383,6 +426,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
383
426
|
// finishRun() after agent_end listeners return).
|
|
384
427
|
await _agent.waitForIdle();
|
|
385
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
|
+
|
|
386
433
|
try {
|
|
387
434
|
// Await so _continueInProgress stays true for the full retry.
|
|
388
435
|
// The session's continue() is blocked (monkey-patch) and the
|
|
@@ -395,6 +442,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
395
442
|
}
|
|
396
443
|
} finally {
|
|
397
444
|
_continueInProgress = false;
|
|
445
|
+
// Record completion time so the continue() monkey-patch can
|
|
446
|
+
// detect that an invisible continue just ran and avoid firing
|
|
447
|
+
// a duplicate prompt([]) (RC7: double continuation guard).
|
|
448
|
+
_lastInvisibleContinueTime = Date.now();
|
|
398
449
|
}
|
|
399
450
|
}
|
|
400
451
|
}
|