@vanillagreen/pi-claude-bridge 1.1.0 → 1.1.1

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/README.md CHANGED
@@ -27,7 +27,7 @@ Restart Pi after installation.
27
27
 
28
28
  - `claude-bridge/claude-opus-4-7`, Sonnet, and Haiku models in `/model`.
29
29
  - Pi tool calls bridged to Claude Code through a local MCP server.
30
- - Session reuse/rebuild so Claude Code follows Pi history across normal turns, `/compact`, forks, tree navigation, and abort recovery.
30
+ - Session reuse/rebuild so Claude Code follows Pi history across normal turns, `/compact`, tree navigation, and abort recovery. Forks rebuild on the first turn so the fork never inherits the parent's external Claude jsonl.
31
31
  - Thinking-level forwarding, summarized Opus thinking display, MCP isolation, and Claude cloud-MCP suppression to reduce token overhead.
32
32
  - Optional forwarding of Pi-only context that upstream does not pass to Claude Code.
33
33
 
package/bundle/index.js CHANGED
@@ -19186,6 +19186,7 @@ function readSession(jsonlPath, projectPath) {
19186
19186
  // src/index.ts
19187
19187
  import { createHash } from "crypto";
19188
19188
  import { accessSync, appendFileSync as appendFileSync3, constants as fsConstants, mkdirSync as mkdirSync3, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
19189
+ import { resolve as pathResolve } from "path";
19189
19190
  import { homedir as homedir5 } from "os";
19190
19191
  import { delimiter, dirname as dirname5, join as join5 } from "path";
19191
19192
 
@@ -19797,11 +19798,7 @@ ${taskReminder}`);
19797
19798
  }
19798
19799
  }
19799
19800
  if (settings.includeCavemanHook) {
19800
- const caveman = extractBlockByMarkers(systemPrompt, [
19801
- /^Caveman communication mode active/m,
19802
- /^Token efficiency mode: terse smart caveman/m,
19803
- /^Caveman mode is active/m
19804
- ]);
19801
+ const caveman = extractBlockByMarkers(systemPrompt, [/^You MUST respond in caveman /m]);
19805
19802
  if (caveman) {
19806
19803
  parts.push(`### before_agent_start: caveman
19807
19804
 
@@ -34544,9 +34541,34 @@ function claudeSessionExists(sessionId, cwd) {
34544
34541
  return false;
34545
34542
  }
34546
34543
  }
34544
+ function canonicalize(p4) {
34545
+ if (!p4) return void 0;
34546
+ try {
34547
+ return realpathSync3.native(p4);
34548
+ } catch {
34549
+ return pathResolve(p4);
34550
+ }
34551
+ }
34552
+ function shouldRestorePersistedBridgeEntry(persisted, currentPiSessionId, currentCwd) {
34553
+ if (!persisted.piSessionId) return "missing piSessionId";
34554
+ if (currentPiSessionId && persisted.piSessionId !== currentPiSessionId) {
34555
+ return `piSessionId mismatch (persisted=${persisted.piSessionId} current=${currentPiSessionId})`;
34556
+ }
34557
+ if (currentCwd && canonicalize(persisted.cwd) !== canonicalize(currentCwd)) {
34558
+ return `cwd mismatch (persisted=${persisted.cwd} current=${currentCwd})`;
34559
+ }
34560
+ return void 0;
34561
+ }
34547
34562
  function restoreSharedSessionFromPi(ctx2) {
34548
34563
  const persisted = latestPersistedBridgeSession(ctx2.sessionManager);
34549
34564
  if (!persisted) return;
34565
+ const currentPiSessionId = typeof ctx2.sessionManager?.getSessionId === "function" ? ctx2.sessionManager.getSessionId() : void 0;
34566
+ const currentCwd = typeof ctx2.sessionManager?.getCwd === "function" ? ctx2.sessionManager.getCwd() : ctx2.cwd;
34567
+ const rejection = shouldRestorePersistedBridgeEntry(persisted, currentPiSessionId, currentCwd);
34568
+ if (rejection) {
34569
+ debug(`restoreSharedSession: ${rejection} \u2014 forcing rebuild`);
34570
+ return;
34571
+ }
34550
34572
  const built = readBuiltSessionContext(ctx2.sessionManager);
34551
34573
  if (!built) return;
34552
34574
  const cursor = Math.max(0, Math.min(persisted.cursor, built.messages.length));
@@ -35322,7 +35344,7 @@ function index_default(pi) {
35322
35344
  if (event.reason === "new" || event.reason === "resume" || event.reason === "fork") {
35323
35345
  clearSession(`session_start:${event.reason}`);
35324
35346
  }
35325
- if (event.reason === "startup" || event.reason === "resume" || event.reason === "fork") restoreSharedSessionFromPi(ctx2);
35347
+ if (event.reason === "startup" || event.reason === "resume") restoreSharedSessionFromPi(ctx2);
35326
35348
  });
35327
35349
  pi.on("session_shutdown", () => clearSession("session_shutdown"));
35328
35350
  pi.on("message_end", (event, ctx2) => {
@@ -35356,5 +35378,7 @@ export {
35356
35378
  CLAUDE_BRIDGE_TOOL_ISOLATION,
35357
35379
  DISALLOWED_BUILTIN_TOOLS,
35358
35380
  index_default as default,
35359
- mapToolName
35381
+ mapToolName,
35382
+ restoreSharedSessionFromPi,
35383
+ shouldRestorePersistedBridgeEntry
35360
35384
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillagreen/pi-claude-bridge",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Pi provider bridge that runs Claude Code through the Claude Agent SDK, with opt-in forwarding for Pi prompt context.",
5
5
  "type": "module",
6
6
  "keywords": [
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import type { Base64ImageSource, ContentBlockParam, MessageParam } from "@anthro
6
6
  import { createSession, deleteSession, openSession, repairToolPairing } from "cc-session-io";
7
7
  import { createHash } from "crypto";
8
8
  import { accessSync, appendFileSync, constants as fsConstants, mkdirSync, realpathSync, statSync } from "fs";
9
+ import { resolve as pathResolve } from "path";
9
10
  import { homedir } from "os";
10
11
  import { delimiter, dirname, join } from "path";
11
12
  import { PROVIDER_ID, messageContentToText, convertPiMessages } from "./convert.js";
@@ -238,9 +239,47 @@ function claudeSessionExists(sessionId: string, cwd: string): boolean {
238
239
  }
239
240
  }
240
241
 
241
- function restoreSharedSessionFromPi(ctx: { sessionManager?: unknown }): void {
242
+ function canonicalize(p: string | undefined): string | undefined {
243
+ if (!p) return undefined;
244
+ try { return realpathSync.native(p); } catch { return pathResolve(p); }
245
+ }
246
+
247
+ // Decides whether a persisted bridge-session marker is safe to restore.
248
+ //
249
+ // The fork case is the load-bearing one: pi/core's createBranchedSession copies
250
+ // every non-label entry from root→leaf into the new session file. That includes
251
+ // our claude-bridge-session markers from the parent. Restoring from them would
252
+ // --resume parent's Claude jsonl on the fork's first turn, leaking conversation
253
+ // past the fork point.
254
+ //
255
+ // Returns undefined when the entry is safe to use, or a short rejection reason
256
+ // for diagnostic logging. Old entries without piSessionId always reject, which
257
+ // degrades safely to the rebuild path.
258
+ export function shouldRestorePersistedBridgeEntry(
259
+ persisted: { piSessionId?: string; cwd: string },
260
+ currentPiSessionId: string | undefined,
261
+ currentCwd: string | undefined,
262
+ ): string | undefined {
263
+ if (!persisted.piSessionId) return "missing piSessionId";
264
+ if (currentPiSessionId && persisted.piSessionId !== currentPiSessionId) {
265
+ return `piSessionId mismatch (persisted=${persisted.piSessionId} current=${currentPiSessionId})`;
266
+ }
267
+ if (currentCwd && canonicalize(persisted.cwd) !== canonicalize(currentCwd)) {
268
+ return `cwd mismatch (persisted=${persisted.cwd} current=${currentCwd})`;
269
+ }
270
+ return undefined;
271
+ }
272
+
273
+ export function restoreSharedSessionFromPi(ctx: { sessionManager?: unknown; cwd?: string }): void {
242
274
  const persisted = latestPersistedBridgeSession(ctx.sessionManager);
243
275
  if (!persisted) return;
276
+ const currentPiSessionId = typeof (ctx.sessionManager as any)?.getSessionId === "function" ? (ctx.sessionManager as any).getSessionId() : undefined;
277
+ const currentCwd = typeof (ctx.sessionManager as any)?.getCwd === "function" ? (ctx.sessionManager as any).getCwd() : ctx.cwd;
278
+ const rejection = shouldRestorePersistedBridgeEntry(persisted, currentPiSessionId, currentCwd);
279
+ if (rejection) {
280
+ debug(`restoreSharedSession: ${rejection} — forcing rebuild`);
281
+ return;
282
+ }
244
283
  const built = readBuiltSessionContext(ctx.sessionManager);
245
284
  if (!built) return;
246
285
  const cursor = Math.max(0, Math.min(persisted.cursor, built.messages.length));
@@ -1292,7 +1331,11 @@ export default function (pi: ExtensionAPI) {
1292
1331
  if (event.reason === "new" || event.reason === "resume" || event.reason === "fork") {
1293
1332
  clearSession(`session_start:${event.reason}`);
1294
1333
  }
1295
- if (event.reason === "startup" || event.reason === "resume" || event.reason === "fork") restoreSharedSessionFromPi(ctx);
1334
+ // Note: "fork" intentionally omitted from restoration. createBranchedSession
1335
+ // copies the parent's persisted bridge entries into the fork; restoring from
1336
+ // them would --resume the parent's Claude jsonl and leak conversation past the
1337
+ // fork point. Letting the first fork turn rebuild is the correct path.
1338
+ if (event.reason === "startup" || event.reason === "resume") restoreSharedSessionFromPi(ctx);
1296
1339
  });
1297
1340
  pi.on("session_shutdown", () => clearSession("session_shutdown"));
1298
1341
  pi.on("message_end", (event, ctx) => {
@@ -115,11 +115,7 @@ export function buildPromptContextAppend(systemPrompt: string | undefined, cwd:
115
115
  }
116
116
 
117
117
  if (settings.includeCavemanHook) {
118
- const caveman = extractBlockByMarkers(systemPrompt, [
119
- /^Caveman communication mode active/m,
120
- /^Token efficiency mode: terse smart caveman/m,
121
- /^Caveman mode is active/m,
122
- ]);
118
+ const caveman = extractBlockByMarkers(systemPrompt, [/^You MUST respond in caveman /m]);
123
119
  if (caveman) {
124
120
  parts.push(`### before_agent_start: caveman\n\n${caveman}`);
125
121
  labels.push("caveman hook");