@monotykamary/pi-retry 0.2.0 → 0.3.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monotykamary/pi-retry",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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
@@ -1,7 +1,6 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { Agent } from "@earendil-works/pi-agent-core";
2
3
  import {
3
- RETRY_TRIGGER_CUSTOM_TYPE,
4
- CONTINUATION_CUSTOM_TYPE,
5
4
  has400or413Error,
6
5
  hasCreditError,
7
6
  hasConnectionError,
@@ -31,16 +30,27 @@ import {
31
30
  * - Automatic detection and retry for ALL errors (catch-all)
32
31
  * - Indefinite retry with exponential backoff (capped at 60s)
33
32
  * - Auto-continuation when model hits max output tokens (stopReason "length")
34
- * - ALL triggers are invisible — custom messages with display:false, stripped by context handler
33
+ * - ALL triggers are invisible — agent.prompt([]) resumes the loop with no new message
35
34
  * - Unified manual controls via /retry command
36
35
  *
37
- * Silent continue trick (ported from pi-invisible-continue):
38
- * - sendMessage() with customType + display:false + triggerTurn:true
39
- * - pi's default convertToLlm filters custom-role messages LLM never sees them
40
- * - context event handler strips them as insurance against custom convertToLlm overrides
41
- * - No user-visible "Continue" message pollution in the conversation
36
+ * Invisibility mechanism:
37
+ * - Agent.prototype.subscribe monkey-patch captures the Agent instance
38
+ * - agent.prompt([]) starts a fresh agent loop with an empty prompt array
39
+ * - No message injected into context LLM sees the exact same message list
40
+ * - No convertToLlm involvement, no filter needed, no session artifact
42
41
  */
43
42
 
43
+ // Capture the live Agent instance when AgentSession subscribes to it.
44
+ // subscribe() is called during AgentSession construction — fires on both
45
+ // fresh sessions and session resumes.
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ let _agent: Agent | null = null;
48
+ const _origSubscribe = Agent.prototype.subscribe as (...args: any[]) => any;
49
+ Agent.prototype.subscribe = function (this: Agent, ...args: any[]) {
50
+ _agent = this;
51
+ return _origSubscribe.apply(this, args);
52
+ };
53
+
44
54
  // Per-category retry state (for diagnostics / messaging)
45
55
  const state400 = new RetryState();
46
56
  const stateCredit = new RetryState();
@@ -93,14 +103,14 @@ export default function (pi: ExtensionAPI) {
93
103
  return;
94
104
  }
95
105
 
96
- // Check for max_tokens stop — auto-continue (silent, invisible to LLM)
106
+ // Check for max_tokens stop — auto-continue (invisible to LLM)
97
107
  if (hasMaxTokensStop(lastAssistant) && !stateContinuation.getIsContinuing()) {
98
108
  stateContinuation.startContinuation();
99
109
  ctx.ui.notify(
100
110
  `Max tokens reached — auto-continuing (continuation ${stateContinuation.getCount()})...`,
101
111
  "info",
102
112
  );
103
- triggerContinuation(pi);
113
+ triggerInvisibleContinue();
104
114
  stateContinuation.endContinuation();
105
115
  return;
106
116
  }
@@ -133,7 +143,7 @@ export default function (pi: ExtensionAPI) {
133
143
  const delay = calculateDelay(state.getAttempt());
134
144
 
135
145
  await sleep(delay);
136
- triggerRetry(pi);
146
+ triggerInvisibleContinue();
137
147
  state.endRetry();
138
148
  return;
139
149
  }
@@ -147,23 +157,6 @@ export default function (pi: ExtensionAPI) {
147
157
 
148
158
 
149
159
 
150
- // Strip hidden retry/continuation markers from context before each LLM call.
151
- // This is insurance — convertToLlm already filters custom roles, but a
152
- // custom convertToLlm override could leak them. Clean proactively.
153
- pi.on("context", async (event) => {
154
- const cleaned = event.messages.filter(
155
- (msg: any) =>
156
- !(
157
- msg.role === "custom" &&
158
- (msg.customType === RETRY_TRIGGER_CUSTOM_TYPE ||
159
- msg.customType === CONTINUATION_CUSTOM_TYPE)
160
- ),
161
- );
162
- if (cleaned.length !== event.messages.length) {
163
- return { messages: cleaned };
164
- }
165
- });
166
-
167
160
  // Unified /retry command with subcommands
168
161
  pi.registerCommand("retry", {
169
162
  description: "Unified retry controls: /retry (manual trigger), /retry status (diagnostics), /retry reset (clear state)",
@@ -205,14 +198,14 @@ export default function (pi: ExtensionAPI) {
205
198
  status += "Max Tokens Continuation:\n";
206
199
  status += ` Continuations used: ${stateContinuation.getCount()}\n`;
207
200
  status += ` Is continuing: ${stateContinuation.getIsContinuing()}\n`;
208
- status += ` Trigger: invisible (custom message, LLM never sees a prompt)\n\n`;
201
+ status += ` Trigger: invisible (agent.prompt([]), LLM never sees a prompt)\n\n`;
209
202
 
210
203
  // Config
211
204
  status += "Configuration:\n";
212
205
  status += ` Base delay: 2000ms\n`;
213
206
  status += ` Max delay: 60000ms\n`;
214
207
  status += ` Backoff multiplier: 2\n`;
215
- status += ` Continuation: invisible custom message\n\n`;
208
+ status += ` Continuation: invisible (agent.prompt([]))\n\n`;
216
209
 
217
210
  // Last assistant info
218
211
  if (lastAssistant && isAssistantMessage(lastAssistant)) {
@@ -251,7 +244,7 @@ export default function (pi: ExtensionAPI) {
251
244
  // Auto-detect: max_tokens continuation takes priority
252
245
  if (hasMaxTokensStop(lastAssistant)) {
253
246
  ctx.ui.notify("Manually continuing after max_tokens...", "info");
254
- triggerContinuation(pi);
247
+ triggerInvisibleContinue();
255
248
  return;
256
249
  }
257
250
 
@@ -259,21 +252,21 @@ export default function (pi: ExtensionAPI) {
259
252
  if (has400or413Error(lastAssistant)) {
260
253
  ctx.ui.notify("Manually retrying 400/413 error...", "info");
261
254
  state400.reset();
262
- triggerRetry(pi);
255
+ triggerInvisibleContinue();
263
256
  return;
264
257
  }
265
258
 
266
259
  if (hasCreditError(lastAssistant)) {
267
260
  ctx.ui.notify("Manually retrying credit error...", "info");
268
261
  stateCredit.reset();
269
- triggerRetry(pi);
262
+ triggerInvisibleContinue();
270
263
  return;
271
264
  }
272
265
 
273
266
  if (hasConnectionError(lastAssistant)) {
274
267
  ctx.ui.notify("Manually retrying connection error...", "info");
275
268
  stateConnection.reset();
276
- triggerRetry(pi);
269
+ triggerInvisibleContinue();
277
270
  return;
278
271
  }
279
272
 
@@ -281,7 +274,7 @@ export default function (pi: ExtensionAPI) {
281
274
  if (hasRetryableError(lastAssistant)) {
282
275
  ctx.ui.notify("Manually retrying error...", "info");
283
276
  stateOther.reset();
284
- triggerRetry(pi);
277
+ triggerInvisibleContinue();
285
278
  return;
286
279
  }
287
280
 
@@ -299,29 +292,10 @@ export default function (pi: ExtensionAPI) {
299
292
  stateContinuation.reset();
300
293
  });
301
294
 
302
- // Helper: send the hidden retry trigger
303
- function triggerRetry(pi: ExtensionAPI) {
304
- pi.sendMessage(
305
- {
306
- customType: RETRY_TRIGGER_CUSTOM_TYPE,
307
- content: "",
308
- display: false,
309
- details: {},
310
- },
311
- { triggerTurn: true },
312
- );
313
- }
314
-
315
- // Helper: send the hidden continuation trigger (silent — LLM never sees a prompt)
316
- function triggerContinuation(pi: ExtensionAPI) {
317
- pi.sendMessage(
318
- {
319
- customType: CONTINUATION_CUSTOM_TYPE,
320
- content: "",
321
- display: false,
322
- details: {},
323
- },
324
- { triggerTurn: true },
325
- );
295
+ // Resume the agent loop invisibly — no message injected into context.
296
+ // The LLM sees the exact same message list it had before.
297
+ function triggerInvisibleContinue() {
298
+ if (!_agent) return;
299
+ _agent.prompt([]);
326
300
  }
327
301
  }
@@ -9,17 +9,6 @@
9
9
 
10
10
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
11
11
 
12
- // Custom message types for invisible triggers.
13
- // These are sent with role="custom" and display=false, so pi's default
14
- // convertToLlm filters them out. The context event handler also strips
15
- // them as insurance.
16
-
17
- /** Custom type used for the invisible error-retry trigger. */
18
- export const RETRY_TRIGGER_CUSTOM_TYPE = "__retry_trigger";
19
-
20
- /** Custom type used for the invisible max_tokens continuation trigger. */
21
- export const CONTINUATION_CUSTOM_TYPE = "__retry_continuation";
22
-
23
12
  // ── Specific pattern groups (used for categorisation / messaging) ──
24
13
 
25
14
  const ERROR_400_413_PATTERNS = [