@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 +1 -1
- package/bundle/index.js +31 -7
- package/package.json +1 -1
- package/src/index.ts +45 -2
- package/src/prompt-context.ts +1 -5
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`,
|
|
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"
|
|
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
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
|
|
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
|
-
|
|
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) => {
|
package/src/prompt-context.ts
CHANGED
|
@@ -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");
|