@monotykamary/pi-retry 0.3.9 → 0.3.11
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 +46 -9
package/package.json
CHANGED
package/retry.ts
CHANGED
|
@@ -58,8 +58,18 @@ Agent.prototype.subscribe = function (this: Agent, ...args: any[]) {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// Monkey-patch continue() so the session's built-in retry loop cooperates
|
|
61
|
-
// with our _continueInProgress mutex
|
|
62
|
-
//
|
|
61
|
+
// with our _continueInProgress mutex AND can convert the "Cannot continue
|
|
62
|
+
// from assistant" error into a prompt([]) call when the agent was mid-task.
|
|
63
|
+
//
|
|
64
|
+
// When continue() throws "Cannot continue from message role: assistant":
|
|
65
|
+
// - stopReason "stop" → agent finished cleanly, don't continue
|
|
66
|
+
// - stopReason "aborted" → user cancelled, don't continue
|
|
67
|
+
// - stopReason "error" → pi-retry IS the error handler, fall back to prompt([])
|
|
68
|
+
// so the retry actually happens (rather than swallowing and stalling)
|
|
69
|
+
// - stopReason "toolUse" or "length" → mid-task, fall back to prompt([])
|
|
70
|
+
//
|
|
71
|
+
// This ensures the agent loop actually continues after compaction instead
|
|
72
|
+
// of swallowing the error and letting the while-loop die.
|
|
63
73
|
const _origContinue = Agent.prototype.continue as (this: Agent) => Promise<unknown>;
|
|
64
74
|
Agent.prototype.continue = function (this: Agent) {
|
|
65
75
|
const self = this;
|
|
@@ -71,15 +81,32 @@ Agent.prototype.continue = function (this: Agent) {
|
|
|
71
81
|
try {
|
|
72
82
|
return await _origContinue.call(self);
|
|
73
83
|
} catch (e: any) {
|
|
74
|
-
// After pi-retry finishes, the transcript ends with a fresh
|
|
75
|
-
// assistant message. The session's continue() sees this and
|
|
76
|
-
// would throw "Cannot continue from an assistant message".
|
|
77
|
-
// Catch it — the while-loop will poll _handlePostAgentRun()
|
|
78
|
-
// again, find no error, and exit cleanly.
|
|
79
84
|
const msg = e?.message ?? '';
|
|
80
85
|
if (msg.includes('Cannot continue from message role') ||
|
|
81
|
-
msg.includes('Cannot continue from an assistant message')
|
|
82
|
-
|
|
86
|
+
msg.includes('Cannot continue from an assistant message')) {
|
|
87
|
+
// Check stopReason — only continue if the agent was mid-task
|
|
88
|
+
const lastMsg = self.state.messages[self.state.messages.length - 1];
|
|
89
|
+
if (lastMsg?.role === 'assistant' &&
|
|
90
|
+
lastMsg.stopReason !== 'stop' &&
|
|
91
|
+
lastMsg.stopReason !== 'aborted') {
|
|
92
|
+
// Agent was mid-task (toolUse, length, or error) — fall back to prompt([])
|
|
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) {
|
|
96
|
+
_continueInProgress = true;
|
|
97
|
+
try {
|
|
98
|
+
await self.prompt([]);
|
|
99
|
+
} catch {
|
|
100
|
+
// Agent already processing or other transient error
|
|
101
|
+
} finally {
|
|
102
|
+
_continueInProgress = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// For stop/aborted: return void, the session loop exits naturally
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (msg.includes('Agent is already processing')) {
|
|
83
110
|
return;
|
|
84
111
|
}
|
|
85
112
|
throw e;
|
|
@@ -102,6 +129,11 @@ const stateContinuation = new ContinuationState();
|
|
|
102
129
|
// producing "Agent is already processing".
|
|
103
130
|
let _continueInProgress = false;
|
|
104
131
|
|
|
132
|
+
// Timestamp of the last completed triggerInvisibleContinue().
|
|
133
|
+
// Used by the continue() monkey-patch to avoid double continuation when
|
|
134
|
+
// triggerInvisibleContinue just ran and the session's continue() unblocks.
|
|
135
|
+
let _lastInvisibleContinueTime = 0;
|
|
136
|
+
|
|
105
137
|
// Sleep helper
|
|
106
138
|
function sleep(ms: number): Promise<void> {
|
|
107
139
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -336,6 +368,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
336
368
|
stateOther.reset();
|
|
337
369
|
stateContinuation.reset();
|
|
338
370
|
_continueInProgress = false;
|
|
371
|
+
_lastInvisibleContinueTime = 0;
|
|
339
372
|
});
|
|
340
373
|
|
|
341
374
|
// Resume the agent loop invisibly — no message injected into context.
|
|
@@ -370,6 +403,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
370
403
|
}
|
|
371
404
|
} finally {
|
|
372
405
|
_continueInProgress = false;
|
|
406
|
+
// Record completion time so the continue() monkey-patch can
|
|
407
|
+
// detect that an invisible continue just ran and avoid firing
|
|
408
|
+
// a duplicate prompt([]) (RC7: double continuation guard).
|
|
409
|
+
_lastInvisibleContinueTime = Date.now();
|
|
373
410
|
}
|
|
374
411
|
}
|
|
375
412
|
}
|