@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/retry.ts +46 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monotykamary/pi-retry",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Extension suite for pi coding agent that handles 400/413 errors and connection errors with automatic retry",
5
5
  "type": "module",
6
6
  "author": "Tom X Nguyen",
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 instead of throwing "Agent is already
62
- // processing" when we beat it to the punch.
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
- msg.includes('Agent is already processing')) {
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
  }