@saccolabs/pi-claude-cli 0.4.1 → 0.4.2

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/src/provider.ts +48 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saccolabs/pi-claude-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Pi coding agent extension that routes LLM calls through the Claude Code CLI",
5
5
  "main": "index.ts",
6
6
  "keywords": [
package/src/provider.ts CHANGED
@@ -81,9 +81,20 @@ export function streamViaCli(
81
81
  ): AssistantMessageEventStream {
82
82
  const stream = createAssistantMessageEventStream();
83
83
 
84
- (async () => {
84
+ /**
85
+ * One subprocess attempt. Returns "resume-miss" (without touching the
86
+ * stream) when a --resume pointed at a CLI session that does not exist —
87
+ * forks copy pi history into a NEW session id, so the prior-provider-turn
88
+ * heuristic says resume while the CLI cache is keyed to the old id (#2).
89
+ * The driver below retries once with a full-history replay, which also
90
+ * re-registers the CLI cache under the current session id.
91
+ */
92
+ async function runOnce(
93
+ forceFullReplay: boolean,
94
+ ): Promise<"ok" | "resume-miss"> {
85
95
  let proc: ReturnType<typeof spawnClaude> | undefined;
86
96
  let abortHandler: (() => void) | undefined;
97
+ let resumeMiss = false;
87
98
 
88
99
  try {
89
100
  const cwd = options?.cwd ?? process.cwd();
@@ -102,7 +113,9 @@ export function streamViaCli(
102
113
  (m?.provider === "pi-claude-cli" || m?.api === "pi-claude-cli"),
103
114
  );
104
115
  const resumeSessionId =
105
- options?.sessionId && hasPriorCliTurn ? options.sessionId : undefined;
116
+ !forceFullReplay && options?.sessionId && hasPriorCliTurn
117
+ ? options.sessionId
118
+ : undefined;
106
119
 
107
120
  // Build prompt: if resuming, only send the latest user turn;
108
121
  // otherwise build the full flattened conversation history
@@ -195,7 +208,7 @@ export function streamViaCli(
195
208
 
196
209
  if (options.signal.aborted) {
197
210
  abortHandler();
198
- return;
211
+ return "ok";
199
212
  }
200
213
  options.signal.addEventListener("abort", abortHandler, { once: true });
201
214
  }
@@ -308,7 +321,18 @@ export function streamViaCli(
308
321
  (Array.isArray(r.errors) && r.errors.length > 0
309
322
  ? r.errors.join("; ")
310
323
  : `Claude CLI returned ${r.subtype ?? "non-success result"}`);
311
- endStreamWithError(errMsg);
324
+ if (
325
+ resumeSessionId &&
326
+ /No conversation found with session ID/i.test(errMsg)
327
+ ) {
328
+ // Recoverable: the driver replays full history once. The CLI
329
+ // also exits non-zero after this result — silence this
330
+ // attempt's close/error handlers so the retry owns the stream.
331
+ resumeMiss = true;
332
+ broken = true;
333
+ } else {
334
+ endStreamWithError(errMsg);
335
+ }
312
336
  }
313
337
  if (!isError) {
314
338
  // Authoritative episode usage + final-answer safety net.
@@ -326,6 +350,8 @@ export function streamViaCli(
326
350
  rl.on("close", resolve);
327
351
  });
328
352
 
353
+ if (resumeMiss) return "resume-miss";
354
+
329
355
  // Push done event after readline closes (async). Pushing synchronously
330
356
  // inside handleMessageStop prevents pi from executing tools.
331
357
  // Guard with streamEnded to avoid pushing done after an error was already pushed.
@@ -356,6 +382,24 @@ export function streamViaCli(
356
382
  });
357
383
  stream.end();
358
384
  }
385
+ return "ok";
386
+ } finally {
387
+ // Clean up this attempt's abort listener
388
+ if (options?.signal && abortHandler) {
389
+ options.signal.removeEventListener("abort", abortHandler);
390
+ }
391
+ }
392
+ }
393
+
394
+ (async () => {
395
+ try {
396
+ const outcome = await runOnce(false);
397
+ if (outcome === "resume-miss") {
398
+ console.error(
399
+ "[pi-claude-cli] CLI session missing for --resume — replaying full history under the current session id",
400
+ );
401
+ await runOnce(true);
402
+ }
359
403
  } catch (err: any) {
360
404
  stream.push({
361
405
  type: "error",
@@ -364,10 +408,6 @@ export function streamViaCli(
364
408
  } as any);
365
409
  stream.end();
366
410
  } finally {
367
- // Clean up abort listener
368
- if (options?.signal && abortHandler) {
369
- options.signal.removeEventListener("abort", abortHandler);
370
- }
371
411
  cleanupSystemPromptFile();
372
412
  }
373
413
  })();