@vanillagreen/pi-claude-bridge 1.9.0 → 2.0.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/bundle/index.js CHANGED
@@ -8788,6 +8788,7 @@ function zO(e) {
8788
8788
  }
8789
8789
  var $O = typeof String.prototype.isWellFormed === "function" ? Function.prototype.call.bind(String.prototype.isWellFormed) : void 0;
8790
8790
  var qUe = typeof String.prototype.toWellFormed === "function" ? Function.prototype.call.bind(String.prototype.toWellFormed) : void 0;
8791
+ var qO = ["You've hit your", "You've reached your", "You're out of usage credits", "Your org is out of usage \xB7 add funds to continue", "Your org is out of usage \xB7 contact your admin", "Your seat type doesn't include usage credits", "Your seat type doesn't include usage", "Your usage allocation has been disabled by your admin", "Your group's usage limit is set to $0", "Fable 5 requires usage credits", "You're out of extra usage", "Your seat type doesn't include extra usage"];
8791
8792
  var It = class extends Error {
8792
8793
  };
8793
8794
  function Cs() {
@@ -26937,514 +26938,6 @@ function splitPrefixSuffix(input, options = {}) {
26937
26938
  ];
26938
26939
  }
26939
26940
 
26940
- // src/convert.ts
26941
- var PROVIDER_ID = "claude-bridge";
26942
- var PI_TO_SDK_TOOL_NAME = {
26943
- read: "Read",
26944
- write: "Write",
26945
- edit: "Edit",
26946
- bash: "Bash"
26947
- };
26948
- function sanitizeToolId(id, cache) {
26949
- const existing = cache.get(id);
26950
- if (existing) return existing;
26951
- const clean = id.replace(/[^a-zA-Z0-9_-]/g, "_");
26952
- cache.set(id, clean);
26953
- return clean;
26954
- }
26955
- function mapPiToolNameToSdk(name, customToolNameToSdk) {
26956
- if (!name) return "";
26957
- const normalized = name.toLowerCase();
26958
- if (customToolNameToSdk) {
26959
- const mapped = customToolNameToSdk.get(name) ?? customToolNameToSdk.get(normalized);
26960
- if (mapped) return mapped;
26961
- }
26962
- if (PI_TO_SDK_TOOL_NAME[normalized]) return PI_TO_SDK_TOOL_NAME[normalized];
26963
- return pascalCase(name);
26964
- }
26965
- function messageContentToText(content) {
26966
- if (typeof content === "string") return content;
26967
- if (!Array.isArray(content)) return "";
26968
- const parts = [];
26969
- let hasText = false;
26970
- for (const block of content) {
26971
- if (block.type === "text" && block.text) {
26972
- parts.push(block.text);
26973
- hasText = true;
26974
- } else if (block.type !== "text" && block.type !== "image") {
26975
- parts.push(`[${block.type}]`);
26976
- }
26977
- }
26978
- return hasText ? parts.join("\n") : "";
26979
- }
26980
- function imageBlockToAnthropic(block) {
26981
- if (!block.data || !block.mimeType) return void 0;
26982
- return { type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } };
26983
- }
26984
- function toolResultContentToAnthropic(content) {
26985
- if (typeof content === "string") return content;
26986
- if (!Array.isArray(content)) return "";
26987
- const blocks = [];
26988
- for (const block of content) {
26989
- if (block.type === "text" && block.text) {
26990
- blocks.push({ type: "text", text: block.text });
26991
- } else if (block.type === "image") {
26992
- const image = imageBlockToAnthropic(block);
26993
- if (image) blocks.push(image);
26994
- } else if (block.type) {
26995
- blocks.push({ type: "text", text: `[${block.type}]` });
26996
- }
26997
- }
26998
- if (blocks.length === 0) return "";
26999
- if (blocks.every((block) => block.type === "text")) return blocks.map((block) => block.text).join("\n");
27000
- return blocks;
27001
- }
27002
- function assistantProvenancePrefix(msg) {
27003
- if (msg.role !== "assistant") return void 0;
27004
- const provider = typeof msg.provider === "string" ? msg.provider : void 0;
27005
- const model = typeof msg.model === "string" ? msg.model : void 0;
27006
- const api = typeof msg.api === "string" ? msg.api : void 0;
27007
- if (!provider && !model && !api) return void 0;
27008
- if (provider === PROVIDER_ID || api === "anthropic") return void 0;
27009
- return `[Prior Pi assistant response from ${provider ?? api ?? "unknown-provider"}${model ? `/${model}` : ""}]
27010
- `;
27011
- }
27012
- function userMessageToAnthropic(msg) {
27013
- if (typeof msg.content === "string") return { role: "user", content: msg.content || "[empty]" };
27014
- if (Array.isArray(msg.content)) {
27015
- const parts = [];
27016
- for (const block of msg.content) {
27017
- if (block.type === "text" && block.text) parts.push({ type: "text", text: block.text });
27018
- else if (block.type === "image" && block.data && block.mimeType) parts.push(imageBlockToAnthropic(block));
27019
- }
27020
- const kept = parts.filter(Boolean);
27021
- return { role: "user", content: kept.length ? kept : "[image]" };
27022
- }
27023
- return { role: "user", content: "[empty]" };
27024
- }
27025
- function toolResultToAnthropicBlock(msg, sanitizedIds) {
27026
- const content = toolResultContentToAnthropic(msg.content);
27027
- return {
27028
- type: "tool_result",
27029
- tool_use_id: sanitizeToolId(msg.toolCallId, sanitizedIds),
27030
- content: content || "",
27031
- is_error: msg.isError
27032
- };
27033
- }
27034
- function hasToolUse(msg) {
27035
- return msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((block) => block.type === "toolCall");
27036
- }
27037
- function convertPiMessages(messages, customToolNameToSdk) {
27038
- const anthropicMessages = [];
27039
- const sanitizedIds = /* @__PURE__ */ new Map();
27040
- const pushToolResultGroup = (toolMessages) => {
27041
- if (toolMessages.length === 0) return;
27042
- anthropicMessages.push({
27043
- role: "user",
27044
- content: toolMessages.map((toolMsg) => {
27045
- const content = toolResultContentToAnthropic(toolMsg.content);
27046
- return {
27047
- type: "tool_result",
27048
- tool_use_id: sanitizeToolId(toolMsg.toolCallId, sanitizedIds),
27049
- content: content || "",
27050
- is_error: toolMsg.isError
27051
- };
27052
- })
27053
- });
27054
- };
27055
- for (let i = 0; i < messages.length; i++) {
27056
- const msg = messages[i];
27057
- if (msg.role === "user") {
27058
- anthropicMessages.push(userMessageToAnthropic(msg));
27059
- } else if (msg.role === "assistant") {
27060
- const content = Array.isArray(msg.content) ? msg.content : [];
27061
- const blocks = [];
27062
- const provenance = assistantProvenancePrefix(msg);
27063
- if (provenance) blocks.push({ type: "text", text: provenance });
27064
- for (const block of content) {
27065
- if (block.type === "text" && block.text) {
27066
- blocks.push({ type: "text", text: block.text });
27067
- } else if (block.type === "thinking") {
27068
- const sig = block.thinkingSignature;
27069
- const isAnthropicProvider = msg.provider === PROVIDER_ID || msg.api === "anthropic";
27070
- if (isAnthropicProvider && sig) {
27071
- blocks.push({ type: "thinking", thinking: block.thinking ?? "", signature: sig });
27072
- }
27073
- } else if (block.type === "toolCall") {
27074
- const toolName = mapPiToolNameToSdk(block.name, customToolNameToSdk);
27075
- blocks.push({ type: "tool_use", id: sanitizeToolId(block.id, sanitizedIds), name: toolName, input: block.arguments ?? {} });
27076
- }
27077
- }
27078
- if (!blocks.length) blocks.push({ type: "text", text: "[incompatible content omitted]" });
27079
- anthropicMessages.push({ role: "assistant", content: blocks });
27080
- if (hasToolUse(msg)) {
27081
- const toolMessages = [];
27082
- const interleavedUsers = [];
27083
- let j2 = i + 1;
27084
- for (; j2 < messages.length; j2++) {
27085
- const next = messages[j2];
27086
- if (next.role === "assistant") break;
27087
- if (next.role === "toolResult") toolMessages.push(next);
27088
- else if (next.role === "user") interleavedUsers.push(next);
27089
- else break;
27090
- }
27091
- if (toolMessages.length > 0) {
27092
- pushToolResultGroup(toolMessages);
27093
- for (const userMsg of interleavedUsers) anthropicMessages.push(userMessageToAnthropic(userMsg));
27094
- i = j2 - 1;
27095
- }
27096
- }
27097
- } else if (msg.role === "toolResult") {
27098
- const blocks = [];
27099
- for (; i < messages.length; i++) {
27100
- const toolMsg = messages[i];
27101
- if (toolMsg.role !== "toolResult") {
27102
- i--;
27103
- break;
27104
- }
27105
- blocks.push(toolResultToAnthropicBlock(toolMsg, sanitizedIds));
27106
- }
27107
- anthropicMessages.push({ role: "user", content: blocks });
27108
- }
27109
- }
27110
- return { anthropicMessages, sanitizedIds };
27111
- }
27112
-
27113
- // src/models.ts
27114
- var FABLE_MODEL_ID = "claude-fable-5";
27115
- var FABLE_FALLBACK_MODEL_ID = "claude-opus-4-8";
27116
- var OPUS_5_MODEL_ID = "claude-opus-5";
27117
- var SONNET_5_MODEL_ID = "claude-sonnet-5";
27118
- function fallbackModelForPrimaryModel(modelId) {
27119
- return modelId === FABLE_MODEL_ID || modelId === OPUS_5_MODEL_ID ? FABLE_FALLBACK_MODEL_ID : void 0;
27120
- }
27121
- var MODEL_IDS_IN_ORDER = [
27122
- FABLE_MODEL_ID,
27123
- OPUS_5_MODEL_ID,
27124
- FABLE_FALLBACK_MODEL_ID,
27125
- "claude-opus-4-7",
27126
- "claude-opus-4-6",
27127
- SONNET_5_MODEL_ID,
27128
- "claude-sonnet-4-6",
27129
- "claude-haiku-4-5"
27130
- ];
27131
- var FALLBACK_MODELS = {
27132
- [FABLE_MODEL_ID]: {
27133
- id: FABLE_MODEL_ID,
27134
- name: "Claude Fable 5",
27135
- reasoning: true,
27136
- thinkingLevelMap: { xhigh: "xhigh", max: "max" },
27137
- input: ["text", "image"],
27138
- contextWindow: 1e6,
27139
- maxTokens: 128e3
27140
- },
27141
- [OPUS_5_MODEL_ID]: {
27142
- id: OPUS_5_MODEL_ID,
27143
- name: "Claude Opus 5",
27144
- reasoning: true,
27145
- thinkingLevelMap: { xhigh: "xhigh", max: "max" },
27146
- input: ["text", "image"],
27147
- contextWindow: 1e6,
27148
- maxTokens: 128e3
27149
- },
27150
- [FABLE_FALLBACK_MODEL_ID]: {
27151
- id: FABLE_FALLBACK_MODEL_ID,
27152
- name: "Claude Opus 4.8",
27153
- reasoning: true,
27154
- thinkingLevelMap: { xhigh: "xhigh" },
27155
- input: ["text", "image"],
27156
- contextWindow: 1e6,
27157
- maxTokens: 128e3
27158
- },
27159
- [SONNET_5_MODEL_ID]: {
27160
- id: SONNET_5_MODEL_ID,
27161
- name: "Claude Sonnet 5",
27162
- reasoning: true,
27163
- thinkingLevelMap: { xhigh: "xhigh", max: "max" },
27164
- input: ["text", "image"],
27165
- contextWindow: 1e6,
27166
- maxTokens: 128e3
27167
- }
27168
- };
27169
- function modelDisplayName(modelId) {
27170
- return FALLBACK_MODELS[modelId]?.name ?? modelId;
27171
- }
27172
- function buildModels(piAiModels) {
27173
- return MODEL_IDS_IN_ORDER.map((id) => piAiModels.find((m) => m.id === id) ?? FALLBACK_MODELS[id]).filter((m) => m != null).map(({ id, name, reasoning, input, contextWindow, maxTokens, thinkingLevelMap }) => ({
27174
- id,
27175
- name,
27176
- reasoning,
27177
- input,
27178
- contextWindow,
27179
- maxTokens,
27180
- thinkingLevelMap,
27181
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
27182
- }));
27183
- }
27184
-
27185
- // src/skills.ts
27186
- var MCP_SERVER_NAME = "custom-tools";
27187
- var MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
27188
- function extractSkillsBlock(systemPrompt) {
27189
- if (!systemPrompt) return void 0;
27190
- const startMarker = "The following skills provide specialized instructions for specific tasks.";
27191
- const endMarker = "</available_skills>";
27192
- const start = systemPrompt.indexOf(startMarker);
27193
- if (start === -1) return void 0;
27194
- const end = systemPrompt.indexOf(endMarker, start);
27195
- if (end === -1) return void 0;
27196
- return rewriteSkillsBlock(systemPrompt.slice(start, end + endMarker.length).trim());
27197
- }
27198
- function rewriteSkillsBlock(skillsBlock) {
27199
- return skillsBlock.replace(
27200
- "Use the read tool to load a skill's file",
27201
- `Use the read tool (mcp__${MCP_SERVER_NAME}__read) to load a skill's file`
27202
- );
27203
- }
27204
-
27205
- // src/extract-tool-results.ts
27206
- function toolResultToMcpContent(content) {
27207
- if (typeof content === "string") return [{ type: "text", text: content || "" }];
27208
- if (!Array.isArray(content)) return [{ type: "text", text: "" }];
27209
- const blocks = [];
27210
- for (const block of content) {
27211
- if (block.type === "text" && block.text) blocks.push({ type: "text", text: block.text });
27212
- else if (block.type === "image" && block.data && block.mimeType) blocks.push({ type: "image", data: block.data, mimeType: block.mimeType });
27213
- }
27214
- return blocks.length ? blocks : [{ type: "text", text: "" }];
27215
- }
27216
- function extractAllToolResults(messages) {
27217
- const results = [];
27218
- let stopIdx = -1;
27219
- for (let i = messages.length - 1; i >= 0; i--) {
27220
- const msg = messages[i];
27221
- if (msg.role === "toolResult") {
27222
- results.unshift({ content: toolResultToMcpContent(msg.content), isError: msg.isError, toolCallId: msg.toolCallId });
27223
- } else if (msg.role === "assistant") {
27224
- stopIdx = i;
27225
- break;
27226
- }
27227
- }
27228
- return { results, stopIdx };
27229
- }
27230
-
27231
- // src/query-state.ts
27232
- var DRAIN_CAUSE_TEXT = {
27233
- "abort": "the turn was aborted",
27234
- "stream-idle-timeout": "the Claude Code stream went idle and the turn timed out",
27235
- "query-end": "the query ended"
27236
- };
27237
- function interruptedToolCallResult(cause) {
27238
- return {
27239
- content: [{ type: "text", text: `Claude bridge: ${DRAIN_CAUSE_TEXT[cause]} before this tool call's result was delivered. The call did not complete and produced no output.` }],
27240
- isError: true
27241
- };
27242
- }
27243
- function toolCallDrainCause(flags) {
27244
- if (flags.wasAborted || flags.signalAborted) return "abort";
27245
- if (flags.streamIdleTimedOut) return "stream-idle-timeout";
27246
- return "query-end";
27247
- }
27248
- function drainPendingToolCalls(queryCtx, cause) {
27249
- const drained = queryCtx.pendingToolCalls.size;
27250
- if (drained === 0) return 0;
27251
- const result = interruptedToolCallResult(cause);
27252
- for (const pending of queryCtx.pendingToolCalls.values()) pending.resolve(result);
27253
- queryCtx.pendingToolCalls.clear();
27254
- return drained;
27255
- }
27256
- function normalizeForCompare(value) {
27257
- if (Array.isArray(value)) return value.map(normalizeForCompare);
27258
- if (value && typeof value === "object") {
27259
- const out = {};
27260
- for (const key of Object.keys(value).sort()) {
27261
- const child = value[key];
27262
- if (child !== void 0) out[key] = normalizeForCompare(child);
27263
- }
27264
- return out;
27265
- }
27266
- return value;
27267
- }
27268
- function argsKey(value) {
27269
- return JSON.stringify(normalizeForCompare(value ?? {}));
27270
- }
27271
- function sameArgs(left, right) {
27272
- return argsKey(left) === argsKey(right);
27273
- }
27274
- function hasRecordedArgs(args) {
27275
- return Object.keys(args ?? {}).length > 0;
27276
- }
27277
- function unique(values) {
27278
- const out = [];
27279
- const seen = /* @__PURE__ */ new Set();
27280
- for (const value of values) {
27281
- if (!value || seen.has(value)) continue;
27282
- seen.add(value);
27283
- out.push(value);
27284
- }
27285
- return out;
27286
- }
27287
- var QueryContext = class {
27288
- // Query-scoped (fully isolated per query)
27289
- activeQuery = null;
27290
- currentPiStream = null;
27291
- latestCursor = 0;
27292
- pendingToolCalls = /* @__PURE__ */ new Map();
27293
- pendingResults = /* @__PURE__ */ new Map();
27294
- turnToolCallIds = [];
27295
- turnToolCalls = [];
27296
- claimedToolCallIds = /* @__PURE__ */ new Set();
27297
- deliveredToolResultIds = /* @__PURE__ */ new Set();
27298
- resolvedToolResultIds = /* @__PURE__ */ new Set();
27299
- unmatchedToolResultIds = /* @__PURE__ */ new Set();
27300
- reportedToolResultMismatch = false;
27301
- deferredUserMessages = [];
27302
- handledTerminalError = false;
27303
- // Per-turn (reset together)
27304
- turnOutput = null;
27305
- turnStarted = false;
27306
- turnSawStreamEvent = false;
27307
- turnSawToolCall = false;
27308
- get turnBlocks() {
27309
- if (!this.turnOutput) throw new Error("turnBlocks accessed before resetTurnState");
27310
- return this.turnOutput.content;
27311
- }
27312
- resetTurnState(model) {
27313
- this.turnOutput = {
27314
- role: "assistant",
27315
- content: [],
27316
- api: model.api,
27317
- provider: model.provider,
27318
- model: model.id,
27319
- usage: {
27320
- input: 0,
27321
- output: 0,
27322
- cacheRead: 0,
27323
- cacheWrite: 0,
27324
- totalTokens: 0,
27325
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
27326
- },
27327
- stopReason: "stop",
27328
- timestamp: Date.now()
27329
- };
27330
- this.turnStarted = false;
27331
- this.turnSawStreamEvent = false;
27332
- this.turnSawToolCall = false;
27333
- this.handledTerminalError = false;
27334
- }
27335
- resetToolTracking() {
27336
- this.turnToolCallIds = [];
27337
- this.turnToolCalls = [];
27338
- this.claimedToolCallIds.clear();
27339
- this.deliveredToolResultIds.clear();
27340
- this.resolvedToolResultIds.clear();
27341
- this.unmatchedToolResultIds.clear();
27342
- this.reportedToolResultMismatch = false;
27343
- }
27344
- recordToolCall(id, toolName, args = {}) {
27345
- if (!id) return;
27346
- if (!this.turnToolCallIds.includes(id)) this.turnToolCallIds.push(id);
27347
- const existing = this.turnToolCalls.find((call) => call.id === id);
27348
- if (existing) {
27349
- existing.toolName = toolName;
27350
- existing.arguments = args;
27351
- return;
27352
- }
27353
- this.turnToolCalls.push({ id, toolName, arguments: args });
27354
- }
27355
- updateToolCallArgs(id, args) {
27356
- if (!id) return;
27357
- const existing = this.turnToolCalls.find((call) => call.id === id);
27358
- if (existing) existing.arguments = args;
27359
- }
27360
- hasRecordedToolCall(id) {
27361
- return Boolean(id && (this.turnToolCallIds.includes(id) || this.turnToolCalls.some((call) => call.id === id)));
27362
- }
27363
- claimToolCall(toolName, args = {}) {
27364
- const unclaimed = this.turnToolCalls.filter((call) => !this.claimedToolCallIds.has(call.id));
27365
- const byName = unclaimed.filter((call) => call.toolName === toolName);
27366
- const exact = byName.filter((call) => sameArgs(call.arguments, args));
27367
- let chosen;
27368
- let match = "none";
27369
- let ambiguous = false;
27370
- if (exact.length > 0) {
27371
- chosen = exact[0];
27372
- match = "tool-args";
27373
- ambiguous = exact.length > 1;
27374
- } else if (byName.length === 1 && !hasRecordedArgs(byName[0].arguments)) {
27375
- chosen = byName[0];
27376
- match = "tool-name";
27377
- }
27378
- if (!chosen) return { match: "none", ambiguous: false, available: unclaimed.length };
27379
- this.claimedToolCallIds.add(chosen.id);
27380
- return { toolCallId: chosen.id, match, ambiguous, available: unclaimed.length };
27381
- }
27382
- markToolResultDelivered(id) {
27383
- if (id) this.deliveredToolResultIds.add(id);
27384
- }
27385
- markToolResultResolved(id) {
27386
- if (id) this.resolvedToolResultIds.add(id);
27387
- }
27388
- markToolResultUnmatched(id) {
27389
- if (id) this.unmatchedToolResultIds.add(id);
27390
- }
27391
- toolResultProgress() {
27392
- const expectedIds = unique([
27393
- ...this.turnToolCalls.map((call) => call.id),
27394
- ...this.turnToolCallIds
27395
- ]);
27396
- const deliveredIds = unique(this.deliveredToolResultIds);
27397
- const resolvedIds = unique(this.resolvedToolResultIds);
27398
- const waitingIds = unique(this.pendingToolCalls.keys());
27399
- const queuedIds = unique(this.pendingResults.keys());
27400
- const unmatchedResultIds = unique(this.unmatchedToolResultIds);
27401
- const missingDeliveredIds = expectedIds.filter((id) => !this.deliveredToolResultIds.has(id));
27402
- const unresolvedIds = expectedIds.filter((id) => !this.resolvedToolResultIds.has(id));
27403
- const affectedIds = /* @__PURE__ */ new Set([...missingDeliveredIds, ...unresolvedIds, ...waitingIds, ...queuedIds, ...unmatchedResultIds]);
27404
- const counts = /* @__PURE__ */ new Map();
27405
- for (const call of this.turnToolCalls) {
27406
- if (affectedIds.size > 0 && !affectedIds.has(call.id)) continue;
27407
- counts.set(call.toolName, (counts.get(call.toolName) ?? 0) + 1);
27408
- }
27409
- return {
27410
- expectedIds,
27411
- deliveredIds,
27412
- resolvedIds,
27413
- waitingIds,
27414
- queuedIds,
27415
- unmatchedResultIds,
27416
- missingDeliveredIds,
27417
- unresolvedIds,
27418
- toolNames: [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name, count]) => ({ name, count })),
27419
- expectedCount: expectedIds.length,
27420
- deliveredCount: deliveredIds.length,
27421
- resolvedCount: resolvedIds.length,
27422
- waitingCount: waitingIds.length,
27423
- queuedCount: queuedIds.length,
27424
- unmatchedResultCount: unmatchedResultIds.length
27425
- };
27426
- }
27427
- };
27428
- var _ctx = new QueryContext();
27429
- var contextStack = [];
27430
- function ctx() {
27431
- return _ctx;
27432
- }
27433
- function stackDepth() {
27434
- return contextStack.length;
27435
- }
27436
- function pushContext() {
27437
- if (!_ctx.activeQuery) throw new Error("pushContext() called with no active query");
27438
- contextStack.push(_ctx);
27439
- _ctx = new QueryContext();
27440
- }
27441
- function popContext() {
27442
- if (contextStack.length === 0) throw new Error("popContext() called with empty stack");
27443
- const parent = contextStack[contextStack.length - 1];
27444
- parent.deferredUserMessages.push(..._ctx.deferredUserMessages);
27445
- _ctx = contextStack.pop();
27446
- }
27447
-
27448
26941
  // src/config.ts
27449
26942
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
27450
26943
  import { homedir } from "os";
@@ -27649,187 +27142,1646 @@ function loadConfig(cwd) {
27649
27142
  };
27650
27143
  }
27651
27144
 
27652
- // src/auth-presence.ts
27653
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
27654
- import { homedir as homedir2, platform as osPlatform } from "os";
27655
- import { join as join3 } from "path";
27656
- function resolveClaudeConfigDir(env = process.env) {
27657
- const configured = env.CLAUDE_CONFIG_DIR;
27658
- if (typeof configured === "string" && configured.trim().length > 0) return configured.trim();
27659
- return join3(homedir2(), ".claude");
27660
- }
27661
- function nonEmptyEnv(value) {
27662
- return typeof value === "string" && value.trim().length > 0;
27145
+ // src/skills.ts
27146
+ var MCP_SERVER_NAME = "custom-tools";
27147
+ var MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
27148
+ function extractSkillsBlock(systemPrompt) {
27149
+ if (!systemPrompt) return void 0;
27150
+ const startMarker = "The following skills provide specialized instructions for specific tasks.";
27151
+ const endMarker = "</available_skills>";
27152
+ const start = systemPrompt.indexOf(startMarker);
27153
+ if (start === -1) return void 0;
27154
+ const end = systemPrompt.indexOf(endMarker, start);
27155
+ if (end === -1) return void 0;
27156
+ return rewriteSkillsBlock(systemPrompt.slice(start, end + endMarker.length).trim());
27663
27157
  }
27664
- function envTruthy(value) {
27665
- const v2 = value?.trim().toLowerCase();
27666
- return v2 === "1" || v2 === "true";
27158
+ function rewriteSkillsBlock(skillsBlock) {
27159
+ return skillsBlock.replace(
27160
+ "Use the read tool to load a skill's file",
27161
+ `Use the read tool (mcp__${MCP_SERVER_NAME}__read) to load a skill's file`
27162
+ );
27667
27163
  }
27668
- function hasApiKeyHelper(configDir) {
27669
- try {
27670
- const settingsPath = join3(configDir, "settings.json");
27671
- if (!existsSync3(settingsPath)) return false;
27672
- const parsed = JSON.parse(readFileSync3(settingsPath, "utf8"));
27673
- return typeof parsed?.apiKeyHelper === "string" && parsed.apiKeyHelper.trim().length > 0;
27674
- } catch {
27675
- return false;
27164
+
27165
+ // src/connector-inventory.ts
27166
+ var CONNECTOR_NS_PREFIX = "mcp__claude_ai_";
27167
+ var DEFAULT_API_BASE = "https://api.anthropic.com";
27168
+ var DEFAULT_PROXY_BASE = "https://mcp-proxy.anthropic.com/v1/mcp";
27169
+ var OAUTH_BETA_HEADER = "oauth-2025-04-20";
27170
+ function connectorServerName(connectorName) {
27171
+ return `claude.ai ${connectorName.trim()}`;
27172
+ }
27173
+ function connectorProxyUrl(installedServerId, proxyBase = DEFAULT_PROXY_BASE) {
27174
+ return `${trimTrailingSlashes(proxyBase)}/${encodeURIComponent(installedServerId)}`;
27175
+ }
27176
+ function connectorServerNamespace(connectorName) {
27177
+ return `${CONNECTOR_NS_PREFIX}${connectorName.trim().replace(/\s+/g, "_")}__`;
27178
+ }
27179
+ function credentialCandidatePaths(env = process.env) {
27180
+ const roots = [];
27181
+ const configDir = env.CLAUDE_CONFIG_DIR?.trim();
27182
+ if (configDir) roots.push(configDir);
27183
+ const home = env.HOME?.trim();
27184
+ if (home) roots.push(`${home}/.claude`, home);
27185
+ const seen = /* @__PURE__ */ new Set();
27186
+ const paths = [];
27187
+ for (const root of roots) {
27188
+ for (const name of [".credentials.json", ".claude.json"]) {
27189
+ const p2 = `${root}/${name}`;
27190
+ if (!seen.has(p2)) {
27191
+ seen.add(p2);
27192
+ paths.push(p2);
27193
+ }
27194
+ }
27676
27195
  }
27196
+ return paths;
27677
27197
  }
27678
- function hasClaudeCredentials(env = process.env, platform = osPlatform()) {
27679
- if (nonEmptyEnv(env.CLAUDE_CODE_OAUTH_TOKEN)) return true;
27680
- if (nonEmptyEnv(env.ANTHROPIC_API_KEY)) return true;
27681
- if (nonEmptyEnv(env.ANTHROPIC_AUTH_TOKEN)) return true;
27682
- if (envTruthy(env.CLAUDE_CODE_USE_BEDROCK)) return true;
27683
- if (envTruthy(env.CLAUDE_CODE_USE_VERTEX)) return true;
27684
- if (envTruthy(env.CLAUDE_CODE_USE_FOUNDRY)) return true;
27685
- if (envTruthy(env.CLAUDE_CODE_USE_ANTHROPIC_AWS)) return true;
27686
- if (envTruthy(env.CLAUDE_CODE_USE_MANTLE)) return true;
27687
- const configDir = resolveClaudeConfigDir(env);
27688
- if (existsSync3(join3(configDir, ".credentials.json"))) return true;
27689
- if (hasApiKeyHelper(configDir)) return true;
27690
- if (platform === "darwin") return true;
27691
- return false;
27198
+ function resolveClaudeOAuth(readFile, env = process.env) {
27199
+ let accessToken;
27200
+ let organizationUuid;
27201
+ for (const path of credentialCandidatePaths(env)) {
27202
+ const raw = readFile(path);
27203
+ if (!raw) continue;
27204
+ let parsed;
27205
+ try {
27206
+ parsed = JSON.parse(raw);
27207
+ } catch {
27208
+ continue;
27209
+ }
27210
+ accessToken ??= nonEmptyString(parsed?.claudeAiOauth?.accessToken);
27211
+ organizationUuid ??= nonEmptyString(parsed?.oauthAccount?.organizationUuid);
27212
+ if (accessToken && organizationUuid) break;
27213
+ }
27214
+ if (!accessToken || !organizationUuid) return void 0;
27215
+ return { accessToken, organizationUuid };
27692
27216
  }
27693
- function decideRegistration(state) {
27694
- if (!state.isPrimary) return "noop";
27695
- if (state.credentialed) return state.registered ? "noop" : "register";
27696
- return "unregister";
27217
+ function nonEmptyString(value) {
27218
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
27697
27219
  }
27698
-
27699
- // src/agents-md.ts
27700
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
27701
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
27702
- function globalAgentsPath() {
27703
- return join4(piUserDir(), "AGENTS.md");
27220
+ function connectorsListUrl(organizationUuid, apiBase = DEFAULT_API_BASE) {
27221
+ return `${trimTrailingSlashes(apiBase)}/api/oauth/organizations/${encodeURIComponent(organizationUuid)}/mcp/connectors/list`;
27704
27222
  }
27705
- function resolveAgentsMdPath() {
27706
- if (isolatedFromEnv()) return void 0;
27707
- const fromCwd = findAgentsMdInParents(process.cwd());
27708
- if (fromCwd) return fromCwd;
27709
- const globalPath = globalAgentsPath();
27710
- if (existsSync4(globalPath)) return globalPath;
27711
- return void 0;
27223
+ function trimTrailingSlashes(value) {
27224
+ let end = value.length;
27225
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
27226
+ return value.slice(0, end);
27712
27227
  }
27713
- function findAgentsMdInParents(startDir) {
27714
- let current = resolve3(startDir);
27715
- while (true) {
27716
- const candidate = join4(current, "AGENTS.md");
27717
- if (existsSync4(candidate)) return candidate;
27718
- const parent = dirname2(current);
27719
- if (parent === current) break;
27720
- current = parent;
27228
+ async function listAccountConnectors(deps) {
27229
+ const { credentials, apiBase, signal } = deps;
27230
+ const fetchImpl = deps.fetchImpl ?? fetch;
27231
+ const url2 = connectorsListUrl(credentials.organizationUuid, apiBase);
27232
+ const fail = (reason) => ({ ok: false, complete: false, reason: redactSecret(reason, credentials.accessToken) });
27233
+ let response;
27234
+ try {
27235
+ response = await fetchImpl(url2, {
27236
+ method: "POST",
27237
+ headers: {
27238
+ "Authorization": `Bearer ${credentials.accessToken}`,
27239
+ "anthropic-beta": OAUTH_BETA_HEADER,
27240
+ "Content-Type": "application/json"
27241
+ },
27242
+ body: "{}",
27243
+ signal
27244
+ });
27245
+ } catch (error51) {
27246
+ return fail(`connector list request failed: ${errorText(error51)}`);
27721
27247
  }
27722
- return void 0;
27723
- }
27724
- function extractAgentsAppend() {
27725
- const agentsPath = resolveAgentsMdPath();
27726
- if (!agentsPath) return void 0;
27248
+ let bodyText;
27727
27249
  try {
27728
- const content = readFileSync4(agentsPath, "utf-8").trim();
27729
- if (!content) return void 0;
27730
- const sanitized = sanitizeAgentsContent(content);
27731
- return sanitized.length > 0 ? `# CLAUDE.md
27732
-
27733
- ${sanitized}` : void 0;
27250
+ bodyText = await response.text();
27251
+ } catch (error51) {
27252
+ return fail(`connector list response unreadable: ${errorText(error51)}`);
27253
+ }
27254
+ if (!response.ok) {
27255
+ return fail(`connector list returned HTTP ${response.status}${apiErrorSuffix(bodyText)}`);
27256
+ }
27257
+ let parsed;
27258
+ try {
27259
+ parsed = JSON.parse(bodyText);
27734
27260
  } catch {
27735
- return void 0;
27261
+ return fail("connector list returned a non-JSON body");
27736
27262
  }
27263
+ if (!Array.isArray(parsed?.results)) {
27264
+ return fail("connector list response had no results array");
27265
+ }
27266
+ const connectors = [];
27267
+ for (const raw of parsed.results) {
27268
+ const entry = raw;
27269
+ const name = nonEmptyString(entry?.name);
27270
+ if (!name) {
27271
+ return fail("connector list contained an entry with no name");
27272
+ }
27273
+ connectors.push({
27274
+ name,
27275
+ installedServerId: nonEmptyString(entry?.installedServerId),
27276
+ directoryUuid: nonEmptyString(entry?.directoryUuid),
27277
+ installState: nonEmptyString(entry?.installState),
27278
+ description: nonEmptyString(entry?.description),
27279
+ isAuthless: typeof entry?.isAuthless === "boolean" ? entry.isAuthless : void 0
27280
+ });
27281
+ }
27282
+ return { ok: true, complete: true, connectors };
27737
27283
  }
27738
- function sanitizeAgentsContent(content) {
27739
- let sanitized = content;
27740
- sanitized = sanitized.replace(/~\/\.pi\b/gi, "~/.claude");
27741
- sanitized = sanitized.replace(/(^|[\s'"`])\.pi\//g, "$1.claude/");
27742
- sanitized = sanitized.replace(/\b\.pi\b/gi, ".claude");
27743
- sanitized = sanitized.replace(/\bpi\b/gi, "environment");
27744
- return sanitized;
27745
- }
27746
-
27747
- // src/prompt-context.ts
27748
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
27749
- import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
27750
- function readTrimmed(path) {
27284
+ function apiErrorSuffix(bodyText) {
27751
27285
  try {
27752
- if (!existsSync5(path)) return void 0;
27753
- const content = readFileSync5(path, "utf8").trim();
27754
- return content.length > 0 ? content : void 0;
27286
+ const message = JSON.parse(bodyText)?.error?.message;
27287
+ return typeof message === "string" && message.trim() ? ` (${message.trim()})` : "";
27755
27288
  } catch {
27756
- return void 0;
27289
+ return "";
27757
27290
  }
27758
27291
  }
27759
- function findProjectAppendSystem(startDir) {
27760
- let current = resolve4(startDir);
27761
- while (true) {
27762
- const candidate = join5(current, ".pi", "APPEND_SYSTEM.md");
27763
- if (existsSync5(candidate)) return candidate;
27764
- const parent = dirname3(current);
27765
- if (parent === current) break;
27766
- current = parent;
27292
+ function redactSecret(text, secret) {
27293
+ if (!secret || secret.length < 8) return text;
27294
+ let out = text;
27295
+ for (const form of /* @__PURE__ */ new Set([secret, encodeURIComponent(secret)])) {
27296
+ out = out.split(form).join("[redacted]");
27767
27297
  }
27768
- return void 0;
27298
+ return out;
27769
27299
  }
27770
- function readAppendSystemPromptFiles(cwd) {
27771
- const files = [
27772
- { label: "global APPEND_SYSTEM.md", path: join5(piUserDir(), "APPEND_SYSTEM.md") }
27773
- ];
27774
- const projectPath = isolatedFromEnv() ? void 0 : findProjectAppendSystem(cwd);
27775
- if (projectPath) files.push({ label: "project .pi/APPEND_SYSTEM.md", path: projectPath });
27776
- const seen = /* @__PURE__ */ new Set();
27777
- const output = [];
27778
- for (const file2 of files) {
27779
- if (seen.has(file2.path)) continue;
27780
- seen.add(file2.path);
27781
- const content = readTrimmed(file2.path);
27782
- if (content) output.push({ label: file2.label, content });
27783
- }
27784
- return output;
27300
+ function errorText(error51) {
27301
+ return error51 instanceof Error ? error51.message : String(error51);
27785
27302
  }
27786
- function splitPromptBlocks(systemPrompt) {
27787
- return (systemPrompt ?? "").split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
27303
+
27304
+ // src/connectors.ts
27305
+ var DISALLOWED_BUILTIN_TOOLS = [
27306
+ "Read",
27307
+ "Write",
27308
+ "Edit",
27309
+ "MultiEdit",
27310
+ "Glob",
27311
+ "Grep",
27312
+ "Bash",
27313
+ "Agent",
27314
+ "Task",
27315
+ "NotebookEdit",
27316
+ "EnterWorktree",
27317
+ "ExitWorktree",
27318
+ "CronList",
27319
+ "CronCreate",
27320
+ "CronDelete",
27321
+ "TeamCreate",
27322
+ "TeamDelete",
27323
+ "TaskOutput",
27324
+ "TaskStop",
27325
+ "SendMessage",
27326
+ "Skill",
27327
+ "TodoRead",
27328
+ "TodoWrite",
27329
+ "ListMcpResources",
27330
+ "ReadMcpResource",
27331
+ "WebFetch",
27332
+ "WebSearch",
27333
+ "AskUserQuestion",
27334
+ "EnterPlanMode",
27335
+ "ExitPlanMode",
27336
+ "ToolSearch",
27337
+ "ScheduleWakeup"
27338
+ ];
27339
+ var CLAUDE_BRIDGE_TOOL_ISOLATION = {
27340
+ tools: [],
27341
+ disallowedTools: DISALLOWED_BUILTIN_TOOLS,
27342
+ allowedTools: [`mcp__${MCP_SERVER_NAME}__*`]
27343
+ };
27344
+ function connectorsEnabledFromEnv() {
27345
+ const v2 = (process.env.CLAUDE_BRIDGE_ENABLE_CONNECTORS ?? "").trim().toLowerCase();
27346
+ return v2 === "1" || v2 === "true" || v2 === "yes" || v2 === "on";
27788
27347
  }
27789
- function extractHeadingSection(systemPrompt, headings) {
27790
- if (!systemPrompt) return void 0;
27791
- let start = -1;
27792
- for (const heading of headings) {
27793
- const index = systemPrompt.indexOf(heading);
27794
- if (index >= 0 && (start < 0 || index < start)) start = index;
27795
- }
27796
- if (start < 0) return void 0;
27797
- const rest = systemPrompt.slice(start).trim();
27798
- const endCandidates = [
27799
- rest.slice(1).search(/\n##\s+/),
27800
- rest.search(/\n<\/project_instructions>/),
27801
- rest.search(/\n<\/project_context>/)
27802
- ].map((index, offset) => index >= 0 && offset === 0 ? index + 1 : index).filter((index) => index >= 0);
27803
- const end = endCandidates.length > 0 ? Math.min(...endCandidates) : -1;
27804
- return (end >= 0 ? rest.slice(0, end) : rest).trim();
27348
+ function connectorsEnabledFor(config2) {
27349
+ return connectorsEnabledFromEnv() || config2?.provider?.enableConnectors === true;
27805
27350
  }
27806
- function extractBlockByMarkers(systemPrompt, markers) {
27807
- for (const block of splitPromptBlocks(systemPrompt)) {
27808
- if (markers.some((marker) => marker.test(block))) return block;
27809
- }
27351
+ var CLAUDE_AI_CONNECTOR_TOOL_PATTERNS = [
27352
+ "mcp__claude_ai_Gmail__*",
27353
+ "mcp__claude_ai_Google_Calendar__*",
27354
+ "mcp__claude_ai_Google_Drive__*",
27355
+ "mcp__claude_ai_Slack__*",
27356
+ "mcp__claude_ai_Atlassian__*"
27357
+ ];
27358
+ var CONNECTOR_DISCOVERY_TOOLS = ["ToolSearch", "ListMcpResources", "ReadMcpResource"];
27359
+ var CONNECTOR_NS_PREFIX2 = "mcp__claude_ai_";
27360
+ var CONNECTOR_NS_GMAIL = `${CONNECTOR_NS_PREFIX2}Gmail__`;
27361
+ var CONNECTOR_NS_CALENDAR = `${CONNECTOR_NS_PREFIX2}Google_Calendar__`;
27362
+ var CONNECTOR_NS_DRIVE = `${CONNECTOR_NS_PREFIX2}Google_Drive__`;
27363
+ var CONNECTOR_NS_SLACK = `${CONNECTOR_NS_PREFIX2}Slack__`;
27364
+ var CONNECTOR_NS_ATLASSIAN = `${CONNECTOR_NS_PREFIX2}Atlassian__`;
27365
+ var CONNECTOR_READ_VERBS = /* @__PURE__ */ new Set([
27366
+ "list",
27367
+ "search",
27368
+ "get",
27369
+ "read",
27370
+ "fetch",
27371
+ "find",
27372
+ "download",
27373
+ "describe",
27374
+ "query",
27375
+ "count",
27376
+ "view",
27377
+ "lookup",
27378
+ "whoami"
27379
+ ]);
27380
+ var CONNECTOR_MUTATION_WORDS = /* @__PURE__ */ new Set([
27381
+ "create",
27382
+ "update",
27383
+ "delete",
27384
+ "remove",
27385
+ "add",
27386
+ "edit",
27387
+ "send",
27388
+ "post",
27389
+ "write",
27390
+ "upload",
27391
+ "publish",
27392
+ "schedule",
27393
+ "transition",
27394
+ "archive",
27395
+ "move",
27396
+ "copy",
27397
+ "revoke",
27398
+ "assign",
27399
+ "invite",
27400
+ "share",
27401
+ "rename",
27402
+ "replace",
27403
+ "set",
27404
+ "merge",
27405
+ "resolve",
27406
+ "lock",
27407
+ "unlock",
27408
+ "acknowledge",
27409
+ "ack",
27410
+ "book",
27411
+ "start",
27412
+ "stop",
27413
+ "terminate",
27414
+ "restart",
27415
+ "join",
27416
+ "leave",
27417
+ "star",
27418
+ "unstar",
27419
+ "forward",
27420
+ "sync",
27421
+ "approve",
27422
+ "reject",
27423
+ "close",
27424
+ "reopen",
27425
+ "cancel",
27426
+ "enable",
27427
+ "disable",
27428
+ "grant",
27429
+ "trigger",
27430
+ "execute",
27431
+ "apply",
27432
+ "submit",
27433
+ "pin",
27434
+ "unpin",
27435
+ "mute",
27436
+ "unmute",
27437
+ "subscribe",
27438
+ "unsubscribe",
27439
+ "follow",
27440
+ "unfollow",
27441
+ "clear",
27442
+ "purge",
27443
+ "reset",
27444
+ "rotate",
27445
+ "deploy",
27446
+ "install",
27447
+ "uninstall",
27448
+ "save",
27449
+ "store",
27450
+ "put",
27451
+ "patch",
27452
+ "insert",
27453
+ "append",
27454
+ "prepend",
27455
+ "duplicate",
27456
+ "restore",
27457
+ "revert",
27458
+ "import",
27459
+ "export",
27460
+ "upsert",
27461
+ "sign",
27462
+ "complete",
27463
+ "claim",
27464
+ "release",
27465
+ "promote",
27466
+ "demote",
27467
+ "escalate",
27468
+ "resend",
27469
+ "retry",
27470
+ "react",
27471
+ "vote"
27472
+ ]);
27473
+ function connectorNameWords(segment) {
27474
+ return segment.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word.toLowerCase());
27475
+ }
27476
+ var CONNECTOR_WRITE_TOOLS = [
27477
+ `${CONNECTOR_NS_GMAIL}create_draft`,
27478
+ `${CONNECTOR_NS_GMAIL}create_label`,
27479
+ `${CONNECTOR_NS_GMAIL}label_message`,
27480
+ `${CONNECTOR_NS_GMAIL}label_thread`,
27481
+ `${CONNECTOR_NS_GMAIL}unlabel_message`,
27482
+ `${CONNECTOR_NS_GMAIL}unlabel_thread`,
27483
+ `${CONNECTOR_NS_GMAIL}apply_sensitive_label`,
27484
+ `${CONNECTOR_NS_GMAIL}remove_sensitive_label`,
27485
+ `${CONNECTOR_NS_CALENDAR}create_event`,
27486
+ `${CONNECTOR_NS_CALENDAR}update_event`,
27487
+ `${CONNECTOR_NS_CALENDAR}delete_event`,
27488
+ `${CONNECTOR_NS_CALENDAR}respond_to_event`,
27489
+ `${CONNECTOR_NS_DRIVE}create_file`,
27490
+ `${CONNECTOR_NS_DRIVE}copy_file`,
27491
+ // Slack + Atlassian writes, taken from a live enumeration of an account with
27492
+ // both connectors attached. The PreToolUse hook already denies these by verb;
27493
+ // listing them by id also removes them from the model's context in a
27494
+ // read-only session (the CLI matcher needs exact ids). Additive only — an id
27495
+ // missing here is still denied at call time.
27496
+ `${CONNECTOR_NS_SLACK}slack_send_message`,
27497
+ `${CONNECTOR_NS_SLACK}slack_send_message_draft`,
27498
+ `${CONNECTOR_NS_SLACK}slack_schedule_message`,
27499
+ `${CONNECTOR_NS_SLACK}slack_create_canvas`,
27500
+ `${CONNECTOR_NS_SLACK}slack_update_canvas`,
27501
+ `${CONNECTOR_NS_ATLASSIAN}createJiraIssue`,
27502
+ `${CONNECTOR_NS_ATLASSIAN}editJiraIssue`,
27503
+ `${CONNECTOR_NS_ATLASSIAN}transitionJiraIssue`,
27504
+ `${CONNECTOR_NS_ATLASSIAN}addCommentToJiraIssue`,
27505
+ `${CONNECTOR_NS_ATLASSIAN}addWorklogToJiraIssue`,
27506
+ `${CONNECTOR_NS_ATLASSIAN}createIssueLink`,
27507
+ `${CONNECTOR_NS_ATLASSIAN}createConfluencePage`,
27508
+ `${CONNECTOR_NS_ATLASSIAN}updateConfluencePage`,
27509
+ `${CONNECTOR_NS_ATLASSIAN}createConfluenceFooterComment`,
27510
+ `${CONNECTOR_NS_ATLASSIAN}createConfluenceInlineComment`,
27511
+ `${CONNECTOR_NS_ATLASSIAN}createCompassComponent`,
27512
+ `${CONNECTOR_NS_ATLASSIAN}createCompassComponentRelationship`,
27513
+ `${CONNECTOR_NS_ATLASSIAN}createCompassCustomFieldDefinition`
27514
+ ];
27515
+ function isChildExecutedTool(name) {
27516
+ return typeof name === "string" && name.startsWith(CONNECTOR_NS_PREFIX2);
27517
+ }
27518
+ function isConnectorWriteTool(name) {
27519
+ if (!name.startsWith(CONNECTOR_NS_PREFIX2)) return false;
27520
+ const sep2 = name.indexOf("__", CONNECTOR_NS_PREFIX2.length);
27521
+ if (sep2 <= CONNECTOR_NS_PREFIX2.length) return true;
27522
+ const server = name.slice(CONNECTOR_NS_PREFIX2.length, sep2);
27523
+ const words = connectorNameWords(name.slice(sep2 + "__".length));
27524
+ const serverWords = connectorNameWords(server);
27525
+ let skipped = 0;
27526
+ while (skipped < serverWords.length && words[skipped] === serverWords[skipped] && !CONNECTOR_MUTATION_WORDS.has(words[skipped])) skipped++;
27527
+ const rest = words.slice(skipped);
27528
+ if (rest.length === 0) return true;
27529
+ if (!CONNECTOR_READ_VERBS.has(rest[0])) return true;
27530
+ return rest.some((word) => CONNECTOR_MUTATION_WORDS.has(word));
27531
+ }
27532
+ function connectorWriteModeFromEnv() {
27533
+ const v2 = (process.env.CLAUDE_BRIDGE_CONNECTOR_WRITE ?? "").trim().toLowerCase();
27534
+ if (v2 === "allow") return "allow";
27535
+ if (v2 === "deny") return "deny";
27810
27536
  return void 0;
27811
27537
  }
27812
- function buildPromptContextAppend(systemPrompt, cwd, settings) {
27813
- const parts = [];
27814
- const labels = [];
27815
- if (settings.includeAppendSystemPromptMd) {
27816
- for (const file2 of readAppendSystemPromptFiles(cwd)) {
27817
- parts.push(xmlBlock("append_system_prompt", { label: file2.label }, file2.content));
27818
- labels.push(file2.label);
27538
+ function connectorWriteModeFor(config2) {
27539
+ const resolved = connectorWriteModeFromEnv() ?? normalizeConnectorWriteMode(config2?.provider?.connectorWriteMode);
27540
+ return resolved === "allow" ? "allow" : "deny";
27541
+ }
27542
+ function connectorWriteDenyHook() {
27543
+ return async (input) => {
27544
+ try {
27545
+ if (input.hook_event_name !== "PreToolUse") return { continue: true };
27546
+ if (!isConnectorWriteTool(input.tool_name)) return { continue: true };
27547
+ return connectorWriteDenyOutput(String(input.tool_name));
27548
+ } catch {
27549
+ const toolName = typeof input?.tool_name === "string" ? input.tool_name : "<unknown>";
27550
+ return connectorWriteDenyOutput(toolName);
27819
27551
  }
27820
- }
27821
- if (settings.includeProjectAgentsHook) {
27822
- const projectAgents = extractHeadingSection(systemPrompt, ["## Project Agents", "## Project Subagents"]);
27823
- if (projectAgents) {
27824
- parts.push(xmlBlock("before_agent_start", { source: "project-agents" }, projectAgents));
27825
- labels.push("project agents hook");
27552
+ };
27553
+ }
27554
+ function connectorWriteDenyOutput(toolName) {
27555
+ return {
27556
+ hookSpecificOutput: {
27557
+ hookEventName: "PreToolUse",
27558
+ permissionDecision: "deny",
27559
+ permissionDecisionReason: `Connector write tool "${toolName}" is blocked in read-only connector mode. Connector writes must go through the host application's gated approval flow.`
27826
27560
  }
27827
- }
27828
- if (settings.includeTaskPanelHook) {
27829
- const taskReminder = extractBlockByMarkers(systemPrompt, [/^Task workflow reminder:/]);
27830
- if (taskReminder) {
27831
- parts.push(xmlBlock("before_agent_start", { source: "task-panel" }, taskReminder));
27832
- labels.push("task panel hook");
27561
+ };
27562
+ }
27563
+ function connectorQueryOptions(connectorsEnabled, writeMode = "deny") {
27564
+ const isolation = toolIsolationForQuery(connectorsEnabled, writeMode);
27565
+ if (!connectorsEnabled || writeMode === "allow") return isolation;
27566
+ return { ...isolation, hooks: { PreToolUse: [{ hooks: [connectorWriteDenyHook()] }] } };
27567
+ }
27568
+ function toolIsolationForQuery(connectorsEnabled, writeMode = "deny") {
27569
+ if (!connectorsEnabled) return CLAUDE_BRIDGE_TOOL_ISOLATION;
27570
+ const disallowedTools = DISALLOWED_BUILTIN_TOOLS.filter((t) => !CONNECTOR_DISCOVERY_TOOLS.includes(t));
27571
+ if (writeMode !== "allow") disallowedTools.push(...CONNECTOR_WRITE_TOOLS);
27572
+ return {
27573
+ disallowedTools,
27574
+ allowedTools: [...CLAUDE_BRIDGE_TOOL_ISOLATION.allowedTools, ...CLAUDE_AI_CONNECTOR_TOOL_PATTERNS]
27575
+ };
27576
+ }
27577
+ function connectorMcpServers(inventory) {
27578
+ if (!inventory.ok) return {};
27579
+ if (connectorDeclarationsDisabled()) return {};
27580
+ const servers = {};
27581
+ for (const entry of inventory.connectors) {
27582
+ if (entry.installState !== "connected") continue;
27583
+ if (!entry.installedServerId) continue;
27584
+ servers[connectorServerName(entry.name)] = {
27585
+ type: "claudeai-proxy",
27586
+ url: connectorProxyUrl(entry.installedServerId),
27587
+ id: entry.installedServerId,
27588
+ alwaysLoad: true
27589
+ };
27590
+ }
27591
+ return servers;
27592
+ }
27593
+ function connectorDeclarationsDisabled(env = process.env) {
27594
+ const v2 = (env.CLAUDE_BRIDGE_CONNECTOR_DECLARE ?? "").trim().toLowerCase();
27595
+ return v2 === "off" || v2 === "0" || v2 === "false" || v2 === "no";
27596
+ }
27597
+
27598
+ // src/convert.ts
27599
+ var PROVIDER_ID = "claude-bridge";
27600
+ var PI_TO_SDK_TOOL_NAME = {
27601
+ read: "Read",
27602
+ write: "Write",
27603
+ edit: "Edit",
27604
+ bash: "Bash"
27605
+ };
27606
+ function sanitizeToolId(id, cache) {
27607
+ const existing = cache.get(id);
27608
+ if (existing) return existing;
27609
+ const clean = id.replace(/[^a-zA-Z0-9_-]/g, "_");
27610
+ cache.set(id, clean);
27611
+ return clean;
27612
+ }
27613
+ function mapPiToolNameToSdk(name, customToolNameToSdk) {
27614
+ if (!name) return "";
27615
+ if (isChildExecutedTool(name)) return name;
27616
+ const normalized = name.toLowerCase();
27617
+ if (customToolNameToSdk) {
27618
+ const mapped = customToolNameToSdk.get(name) ?? customToolNameToSdk.get(normalized);
27619
+ if (mapped) return mapped;
27620
+ }
27621
+ if (PI_TO_SDK_TOOL_NAME[normalized]) return PI_TO_SDK_TOOL_NAME[normalized];
27622
+ return pascalCase(name);
27623
+ }
27624
+ function messageContentToText(content) {
27625
+ if (typeof content === "string") return content;
27626
+ if (!Array.isArray(content)) return "";
27627
+ const parts = [];
27628
+ let hasText = false;
27629
+ for (const block of content) {
27630
+ if (block.type === "text" && block.text) {
27631
+ parts.push(block.text);
27632
+ hasText = true;
27633
+ } else if (block.type !== "text" && block.type !== "image") {
27634
+ parts.push(`[${block.type}]`);
27635
+ }
27636
+ }
27637
+ return hasText ? parts.join("\n") : "";
27638
+ }
27639
+ function imageBlockToAnthropic(block) {
27640
+ if (!block.data || !block.mimeType) return void 0;
27641
+ return { type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } };
27642
+ }
27643
+ function toolResultContentToAnthropic(content) {
27644
+ if (typeof content === "string") return content;
27645
+ if (!Array.isArray(content)) return "";
27646
+ const blocks = [];
27647
+ for (const block of content) {
27648
+ if (block.type === "text" && block.text) {
27649
+ blocks.push({ type: "text", text: block.text });
27650
+ } else if (block.type === "image") {
27651
+ const image = imageBlockToAnthropic(block);
27652
+ if (image) blocks.push(image);
27653
+ } else if (block.type) {
27654
+ blocks.push({ type: "text", text: `[${block.type}]` });
27655
+ }
27656
+ }
27657
+ if (blocks.length === 0) return "";
27658
+ if (blocks.every((block) => block.type === "text")) return blocks.map((block) => block.text).join("\n");
27659
+ return blocks;
27660
+ }
27661
+ function assistantProvenancePrefix(msg) {
27662
+ if (msg.role !== "assistant") return void 0;
27663
+ const provider = typeof msg.provider === "string" ? msg.provider : void 0;
27664
+ const model = typeof msg.model === "string" ? msg.model : void 0;
27665
+ const api = typeof msg.api === "string" ? msg.api : void 0;
27666
+ if (!provider && !model && !api) return void 0;
27667
+ if (provider === PROVIDER_ID || api === "anthropic") return void 0;
27668
+ return `[Prior Pi assistant response from ${provider ?? api ?? "unknown-provider"}${model ? `/${model}` : ""}]
27669
+ `;
27670
+ }
27671
+ function userMessageToAnthropic(msg) {
27672
+ if (typeof msg.content === "string") return { role: "user", content: msg.content || "[empty]" };
27673
+ if (Array.isArray(msg.content)) {
27674
+ const parts = [];
27675
+ for (const block of msg.content) {
27676
+ if (block.type === "text" && block.text) parts.push({ type: "text", text: block.text });
27677
+ else if (block.type === "image" && block.data && block.mimeType) parts.push(imageBlockToAnthropic(block));
27678
+ }
27679
+ const kept = parts.filter(Boolean);
27680
+ return { role: "user", content: kept.length ? kept : "[image]" };
27681
+ }
27682
+ return { role: "user", content: "[empty]" };
27683
+ }
27684
+ function toolResultToAnthropicBlock(msg, sanitizedIds) {
27685
+ const content = toolResultContentToAnthropic(msg.content);
27686
+ return {
27687
+ type: "tool_result",
27688
+ tool_use_id: sanitizeToolId(msg.toolCallId, sanitizedIds),
27689
+ content: content || "",
27690
+ is_error: msg.isError
27691
+ };
27692
+ }
27693
+ function hasToolUse(msg) {
27694
+ return msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((block) => block.type === "toolCall");
27695
+ }
27696
+ function convertPiMessages(messages, customToolNameToSdk) {
27697
+ const anthropicMessages = [];
27698
+ const sanitizedIds = /* @__PURE__ */ new Map();
27699
+ const pushToolResultGroup = (toolMessages) => {
27700
+ if (toolMessages.length === 0) return;
27701
+ anthropicMessages.push({
27702
+ role: "user",
27703
+ content: toolMessages.map((toolMsg) => {
27704
+ const content = toolResultContentToAnthropic(toolMsg.content);
27705
+ return {
27706
+ type: "tool_result",
27707
+ tool_use_id: sanitizeToolId(toolMsg.toolCallId, sanitizedIds),
27708
+ content: content || "",
27709
+ is_error: toolMsg.isError
27710
+ };
27711
+ })
27712
+ });
27713
+ };
27714
+ for (let i = 0; i < messages.length; i++) {
27715
+ const msg = messages[i];
27716
+ if (msg.role === "user") {
27717
+ anthropicMessages.push(userMessageToAnthropic(msg));
27718
+ } else if (msg.role === "assistant") {
27719
+ const content = Array.isArray(msg.content) ? msg.content : [];
27720
+ const blocks = [];
27721
+ const provenance = assistantProvenancePrefix(msg);
27722
+ if (provenance) blocks.push({ type: "text", text: provenance });
27723
+ for (const block of content) {
27724
+ if (block.type === "text" && block.text) {
27725
+ blocks.push({ type: "text", text: block.text });
27726
+ } else if (block.type === "thinking") {
27727
+ const sig = block.thinkingSignature;
27728
+ const isAnthropicProvider = msg.provider === PROVIDER_ID || msg.api === "anthropic";
27729
+ if (isAnthropicProvider && sig) {
27730
+ blocks.push({ type: "thinking", thinking: block.thinking ?? "", signature: sig });
27731
+ }
27732
+ } else if (block.type === "toolCall") {
27733
+ const toolName = mapPiToolNameToSdk(block.name, customToolNameToSdk);
27734
+ blocks.push({ type: "tool_use", id: sanitizeToolId(block.id, sanitizedIds), name: toolName, input: block.arguments ?? {} });
27735
+ }
27736
+ }
27737
+ if (!blocks.length) blocks.push({ type: "text", text: "[incompatible content omitted]" });
27738
+ anthropicMessages.push({ role: "assistant", content: blocks });
27739
+ if (hasToolUse(msg)) {
27740
+ const toolMessages = [];
27741
+ const interleavedUsers = [];
27742
+ let j2 = i + 1;
27743
+ for (; j2 < messages.length; j2++) {
27744
+ const next = messages[j2];
27745
+ if (next.role === "assistant") break;
27746
+ if (next.role === "toolResult") toolMessages.push(next);
27747
+ else if (next.role === "user") interleavedUsers.push(next);
27748
+ else break;
27749
+ }
27750
+ if (toolMessages.length > 0) {
27751
+ pushToolResultGroup(toolMessages);
27752
+ for (const userMsg of interleavedUsers) anthropicMessages.push(userMessageToAnthropic(userMsg));
27753
+ i = j2 - 1;
27754
+ }
27755
+ }
27756
+ } else if (msg.role === "toolResult") {
27757
+ const blocks = [];
27758
+ for (; i < messages.length; i++) {
27759
+ const toolMsg = messages[i];
27760
+ if (toolMsg.role !== "toolResult") {
27761
+ i--;
27762
+ break;
27763
+ }
27764
+ blocks.push(toolResultToAnthropicBlock(toolMsg, sanitizedIds));
27765
+ }
27766
+ anthropicMessages.push({ role: "user", content: blocks });
27767
+ }
27768
+ }
27769
+ return { anthropicMessages, sanitizedIds };
27770
+ }
27771
+
27772
+ // src/models.ts
27773
+ var FABLE_MODEL_ID = "claude-fable-5";
27774
+ var FABLE_FALLBACK_MODEL_ID = "claude-opus-4-8";
27775
+ var OPUS_5_MODEL_ID = "claude-opus-5";
27776
+ var SONNET_5_MODEL_ID = "claude-sonnet-5";
27777
+ function fallbackModelForPrimaryModel(modelId) {
27778
+ return modelId === FABLE_MODEL_ID || modelId === OPUS_5_MODEL_ID ? FABLE_FALLBACK_MODEL_ID : void 0;
27779
+ }
27780
+ var MODEL_IDS_IN_ORDER = [
27781
+ FABLE_MODEL_ID,
27782
+ OPUS_5_MODEL_ID,
27783
+ FABLE_FALLBACK_MODEL_ID,
27784
+ "claude-opus-4-7",
27785
+ "claude-opus-4-6",
27786
+ SONNET_5_MODEL_ID,
27787
+ "claude-sonnet-4-6",
27788
+ "claude-haiku-4-5"
27789
+ ];
27790
+ var FALLBACK_MODELS = {
27791
+ [FABLE_MODEL_ID]: {
27792
+ id: FABLE_MODEL_ID,
27793
+ name: "Claude Fable 5",
27794
+ reasoning: true,
27795
+ thinkingLevelMap: { xhigh: "xhigh", max: "max" },
27796
+ input: ["text", "image"],
27797
+ contextWindow: 1e6,
27798
+ maxTokens: 128e3
27799
+ },
27800
+ [OPUS_5_MODEL_ID]: {
27801
+ id: OPUS_5_MODEL_ID,
27802
+ name: "Claude Opus 5",
27803
+ reasoning: true,
27804
+ thinkingLevelMap: { xhigh: "xhigh", max: "max" },
27805
+ input: ["text", "image"],
27806
+ contextWindow: 1e6,
27807
+ maxTokens: 128e3
27808
+ },
27809
+ [FABLE_FALLBACK_MODEL_ID]: {
27810
+ id: FABLE_FALLBACK_MODEL_ID,
27811
+ name: "Claude Opus 4.8",
27812
+ reasoning: true,
27813
+ thinkingLevelMap: { xhigh: "xhigh" },
27814
+ input: ["text", "image"],
27815
+ contextWindow: 1e6,
27816
+ maxTokens: 128e3
27817
+ },
27818
+ [SONNET_5_MODEL_ID]: {
27819
+ id: SONNET_5_MODEL_ID,
27820
+ name: "Claude Sonnet 5",
27821
+ reasoning: true,
27822
+ thinkingLevelMap: { xhigh: "xhigh", max: "max" },
27823
+ input: ["text", "image"],
27824
+ contextWindow: 1e6,
27825
+ maxTokens: 128e3
27826
+ }
27827
+ };
27828
+ function modelDisplayName(modelId) {
27829
+ return FALLBACK_MODELS[modelId]?.name ?? modelId;
27830
+ }
27831
+ function buildModels(piAiModels) {
27832
+ return MODEL_IDS_IN_ORDER.map((id) => piAiModels.find((m) => m.id === id) ?? FALLBACK_MODELS[id]).filter((m) => m != null).map(({ id, name, reasoning, input, contextWindow, maxTokens, thinkingLevelMap }) => ({
27833
+ id,
27834
+ name,
27835
+ reasoning,
27836
+ input,
27837
+ contextWindow,
27838
+ maxTokens,
27839
+ thinkingLevelMap,
27840
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
27841
+ }));
27842
+ }
27843
+
27844
+ // src/extract-tool-results.ts
27845
+ function toolResultToMcpContent(content) {
27846
+ if (typeof content === "string") return [{ type: "text", text: content || "" }];
27847
+ if (!Array.isArray(content)) return [{ type: "text", text: "" }];
27848
+ const blocks = [];
27849
+ for (const block of content) {
27850
+ if (block.type === "text" && block.text) blocks.push({ type: "text", text: block.text });
27851
+ else if (block.type === "image" && block.data && block.mimeType) blocks.push({ type: "image", data: block.data, mimeType: block.mimeType });
27852
+ }
27853
+ return blocks.length ? blocks : [{ type: "text", text: "" }];
27854
+ }
27855
+ function extractAllToolResults(messages) {
27856
+ const results = [];
27857
+ let stopIdx = -1;
27858
+ for (let i = messages.length - 1; i >= 0; i--) {
27859
+ const msg = messages[i];
27860
+ if (msg.role === "toolResult") {
27861
+ results.unshift({ content: toolResultToMcpContent(msg.content), isError: msg.isError, toolCallId: msg.toolCallId });
27862
+ } else if (msg.role === "assistant") {
27863
+ stopIdx = i;
27864
+ break;
27865
+ }
27866
+ }
27867
+ return { results, stopIdx };
27868
+ }
27869
+
27870
+ // src/query-state.ts
27871
+ var DRAIN_CAUSE_TEXT = {
27872
+ "abort": "the turn was aborted",
27873
+ "stream-idle-timeout": "the Claude Code stream went idle and the turn timed out",
27874
+ "query-end": "the query ended"
27875
+ };
27876
+ function interruptedToolCallResult(cause) {
27877
+ return {
27878
+ content: [{ type: "text", text: `Claude bridge: ${DRAIN_CAUSE_TEXT[cause]} before this tool call's result was delivered. The call did not complete and produced no output.` }],
27879
+ isError: true
27880
+ };
27881
+ }
27882
+ function toolCallDrainCause(flags) {
27883
+ if (flags.wasAborted || flags.signalAborted) return "abort";
27884
+ if (flags.streamIdleTimedOut) return "stream-idle-timeout";
27885
+ return "query-end";
27886
+ }
27887
+ function drainPendingToolCalls(queryCtx, cause) {
27888
+ const drained = queryCtx.pendingToolCalls.size;
27889
+ if (drained === 0) return 0;
27890
+ const result = interruptedToolCallResult(cause);
27891
+ for (const pending of queryCtx.pendingToolCalls.values()) pending.resolve(result);
27892
+ queryCtx.pendingToolCalls.clear();
27893
+ return drained;
27894
+ }
27895
+ function normalizeForCompare(value) {
27896
+ if (Array.isArray(value)) return value.map(normalizeForCompare);
27897
+ if (value && typeof value === "object") {
27898
+ const out = {};
27899
+ for (const key of Object.keys(value).sort()) {
27900
+ const child = value[key];
27901
+ if (child !== void 0) out[key] = normalizeForCompare(child);
27902
+ }
27903
+ return out;
27904
+ }
27905
+ return value;
27906
+ }
27907
+ function argsKey(value) {
27908
+ return JSON.stringify(normalizeForCompare(value ?? {}));
27909
+ }
27910
+ function sameArgs(left, right) {
27911
+ return argsKey(left) === argsKey(right);
27912
+ }
27913
+ function hasRecordedArgs(args) {
27914
+ return Object.keys(args ?? {}).length > 0;
27915
+ }
27916
+ function unique(values) {
27917
+ const out = [];
27918
+ const seen = /* @__PURE__ */ new Set();
27919
+ for (const value of values) {
27920
+ if (!value || seen.has(value)) continue;
27921
+ seen.add(value);
27922
+ out.push(value);
27923
+ }
27924
+ return out;
27925
+ }
27926
+ var QueryContext = class {
27927
+ // Query-scoped (fully isolated per query)
27928
+ activeQuery = null;
27929
+ currentPiStream = null;
27930
+ latestCursor = 0;
27931
+ pendingToolCalls = /* @__PURE__ */ new Map();
27932
+ pendingResults = /* @__PURE__ */ new Map();
27933
+ turnToolCallIds = [];
27934
+ turnToolCalls = [];
27935
+ /**
27936
+ * id → Pi tool name for every tool call this QUERY recorded, across all child
27937
+ * messages. Deliberately NOT cleared by resetToolTracking: per-message tracking
27938
+ * resets at every message boundary, but `pendingResults` is query-scoped, so a
27939
+ * result stranded there outlives the message that named it. Without this map a
27940
+ * teardown report can only say "1 queued" with empty toolNames and 0/0
27941
+ * counters — which is exactly the unactionable record the 2026-07-28 diag log
27942
+ * showed. Bounded by the number of tool calls in one query.
27943
+ */
27944
+ queryToolNames = /* @__PURE__ */ new Map();
27945
+ claimedToolCallIds = /* @__PURE__ */ new Set();
27946
+ deliveredToolResultIds = /* @__PURE__ */ new Set();
27947
+ resolvedToolResultIds = /* @__PURE__ */ new Set();
27948
+ unmatchedToolResultIds = /* @__PURE__ */ new Set();
27949
+ reportedToolResultMismatch = false;
27950
+ deferredUserMessages = [];
27951
+ handledTerminalError = false;
27952
+ /** Armed grace timer for ending a tool_use turn whose terminal stream events
27953
+ * (message_delta/message_stop) never arrive. The normal path ends the turn at
27954
+ * message_stop, AFTER message_delta delivered the real output-token count;
27955
+ * this is the deadlock backstop for streams that go silent instead. Managed
27956
+ * by schedule/cancelToolUseTurnEnd in assistant-stream.ts. */
27957
+ scheduledToolUseEnd = null;
27958
+ // Tool calls the CHILD executes itself (claude.ai connectors — see
27959
+ // isChildExecutedTool). Deliberately NOT in turnToolCalls/turnToolCallIds:
27960
+ // those track calls Pi owes a result for, and Pi owes nothing here. Kept only
27961
+ // so the child's real result can be recognized when it comes back on the SDK's
27962
+ // `user` message, and so the streamed block's deltas can be skipped silently
27963
+ // instead of logging as "unmatched" (which reads like a bug).
27964
+ /** tool_use id → raw SDK tool name. */
27965
+ childExecutedToolCalls = /* @__PURE__ */ new Map();
27966
+ /**
27967
+ * The same calls, for the connector-call audit trail (see connector-audit.ts).
27968
+ *
27969
+ * Query-scoped and deliberately NOT cleared by resetToolTracking: that runs at
27970
+ * every child message boundary, and a call issued in one child message is only
27971
+ * reconciled after that message ends. Clearing it there would make an abandoned
27972
+ * call unrecordable at teardown — which is the one case the trail exists for.
27973
+ */
27974
+ connectorCallAudit = /* @__PURE__ */ new Map();
27975
+ /** Claude Code session id for this query, from the SDK's `system` init message.
27976
+ * Undefined until it arrives; the audit trail omits the field rather than
27977
+ * guessing. */
27978
+ childSessionId;
27979
+ /** Anthropic content-block indexes of the current assistant message that carry
27980
+ * a child-executed tool_use. Scoped to one message: cleared at message_start,
27981
+ * and an index is released as soon as a new block starts there. */
27982
+ childExecutedStreamIndexes = /* @__PURE__ */ new Set();
27983
+ // Usage accounting for a Pi turn that spans SEVERAL child assistant messages.
27984
+ //
27985
+ // Every child message is a separate billed API call, and each reports its own
27986
+ // counters — `message_start`/`message_delta` REPLACE rather than accumulate. A
27987
+ // Pi turn used to end at the first tool call, so one Pi message meant one child
27988
+ // message and replacing was right. A turn containing a child-executed connector
27989
+ // call now keeps running across the child's follow-up messages, so replacing
27990
+ // would silently drop everything the earlier ones billed (measured: 55,685
27991
+ // cache-write tokens lost on a single connector turn).
27992
+ //
27993
+ // So: `turnUsageCarry` holds the totals of the child messages already COMPLETE
27994
+ // in this Pi turn, `currentMessageUsage` holds the one in flight, and the Pi
27995
+ // message reports their sum. Summing is the correct model for input and cache
27996
+ // too — each call bills its own.
27997
+ turnUsageCarry = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
27998
+ currentMessageUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
27999
+ /** Anthropic id of the child message `currentMessageUsage` describes. */
28000
+ currentMessageId;
28001
+ /**
28002
+ * Declare which child message the following usage belongs to, banking the
28003
+ * previous one's counters into the turn total.
28004
+ *
28005
+ * Keyed on the MESSAGE ID rather than on the call site, because both paths
28006
+ * that see a message boundary can fire for the SAME message: `message_start`
28007
+ * arrives on the stream, and the SDK then yields that message again in
28008
+ * completed form. Banking per call site double-counted whenever the completed
28009
+ * copy took the no-stream-events branch — which it does whenever a message
28010
+ * produced no content blocks, since `turnSawStreamEvent` only tracks those.
28011
+ *
28012
+ * With no id on either side (older/streamless shapes) this degrades to
28013
+ * banking on every call, which is what each caller means when it cannot
28014
+ * prove otherwise.
28015
+ */
28016
+ beginChildMessage(messageId) {
28017
+ const id = typeof messageId === "string" && messageId.length > 0 ? messageId : void 0;
28018
+ if (id !== void 0 && id === this.currentMessageId) return;
28019
+ this.turnUsageCarry.input += this.currentMessageUsage.input;
28020
+ this.turnUsageCarry.output += this.currentMessageUsage.output;
28021
+ this.turnUsageCarry.cacheRead += this.currentMessageUsage.cacheRead;
28022
+ this.turnUsageCarry.cacheWrite += this.currentMessageUsage.cacheWrite;
28023
+ this.currentMessageUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
28024
+ this.currentMessageId = id;
28025
+ }
28026
+ // Per-turn (reset together)
28027
+ turnOutput = null;
28028
+ turnStarted = false;
28029
+ turnSawStreamEvent = false;
28030
+ turnSawToolCall = false;
28031
+ get turnBlocks() {
28032
+ if (!this.turnOutput) throw new Error("turnBlocks accessed before resetTurnState");
28033
+ return this.turnOutput.content;
28034
+ }
28035
+ resetTurnState(model) {
28036
+ this.turnOutput = {
28037
+ role: "assistant",
28038
+ content: [],
28039
+ api: model.api,
28040
+ provider: model.provider,
28041
+ model: model.id,
28042
+ usage: {
28043
+ input: 0,
28044
+ output: 0,
28045
+ cacheRead: 0,
28046
+ cacheWrite: 0,
28047
+ totalTokens: 0,
28048
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
28049
+ },
28050
+ stopReason: "stop",
28051
+ timestamp: Date.now()
28052
+ };
28053
+ this.turnStarted = false;
28054
+ this.turnSawStreamEvent = false;
28055
+ this.turnSawToolCall = false;
28056
+ this.handledTerminalError = false;
28057
+ if (this.scheduledToolUseEnd) {
28058
+ clearTimeout(this.scheduledToolUseEnd.timer);
28059
+ this.scheduledToolUseEnd = null;
28060
+ }
28061
+ this.turnUsageCarry = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
28062
+ this.currentMessageUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
28063
+ this.currentMessageId = void 0;
28064
+ }
28065
+ resetToolTracking() {
28066
+ this.turnToolCallIds = [];
28067
+ this.turnToolCalls = [];
28068
+ this.claimedToolCallIds.clear();
28069
+ this.deliveredToolResultIds.clear();
28070
+ this.resolvedToolResultIds.clear();
28071
+ this.unmatchedToolResultIds.clear();
28072
+ this.reportedToolResultMismatch = false;
28073
+ this.childExecutedToolCalls.clear();
28074
+ this.childExecutedStreamIndexes.clear();
28075
+ }
28076
+ /** Note a tool_use the child runs itself. `streamIndex` is present only on the
28077
+ * streamed path, where later deltas/stops for that block must be skipped. */
28078
+ noteChildExecutedToolCall(id, rawName, streamIndex) {
28079
+ if (id) {
28080
+ this.childExecutedToolCalls.set(id, rawName);
28081
+ if (!this.connectorCallAudit.has(id)) {
28082
+ this.connectorCallAudit.set(id, {
28083
+ name: rawName,
28084
+ ...this.childSessionId ? { childSessionId: this.childSessionId } : {},
28085
+ recorded: false
28086
+ });
28087
+ }
28088
+ }
28089
+ if (typeof streamIndex === "number") this.childExecutedStreamIndexes.add(streamIndex);
28090
+ }
28091
+ recordToolCall(id, toolName, args = {}) {
28092
+ if (!id) return;
28093
+ this.queryToolNames.set(id, toolName);
28094
+ if (!this.turnToolCallIds.includes(id)) this.turnToolCallIds.push(id);
28095
+ const existing = this.turnToolCalls.find((call) => call.id === id);
28096
+ if (existing) {
28097
+ existing.toolName = toolName;
28098
+ existing.arguments = args;
28099
+ return;
28100
+ }
28101
+ this.turnToolCalls.push({ id, toolName, arguments: args });
28102
+ }
28103
+ updateToolCallArgs(id, args) {
28104
+ if (!id) return;
28105
+ const existing = this.turnToolCalls.find((call) => call.id === id);
28106
+ if (existing) existing.arguments = args;
28107
+ }
28108
+ hasRecordedToolCall(id) {
28109
+ return Boolean(id && (this.turnToolCallIds.includes(id) || this.turnToolCalls.some((call) => call.id === id)));
28110
+ }
28111
+ claimToolCall(toolName, args = {}) {
28112
+ const unclaimed = this.turnToolCalls.filter((call) => !this.claimedToolCallIds.has(call.id));
28113
+ const byName = unclaimed.filter((call) => call.toolName === toolName);
28114
+ const exact = byName.filter((call) => sameArgs(call.arguments, args));
28115
+ let chosen;
28116
+ let match = "none";
28117
+ let ambiguous = false;
28118
+ let argsMismatch = false;
28119
+ if (exact.length > 0) {
28120
+ chosen = exact[0];
28121
+ match = "tool-args";
28122
+ ambiguous = exact.length > 1;
28123
+ } else if (byName.length === 1) {
28124
+ chosen = byName[0];
28125
+ match = "tool-name";
28126
+ argsMismatch = hasRecordedArgs(byName[0].arguments);
28127
+ }
28128
+ if (!chosen) return { match: "none", ambiguous: false, available: unclaimed.length };
28129
+ this.claimedToolCallIds.add(chosen.id);
28130
+ return { toolCallId: chosen.id, match, ambiguous, available: unclaimed.length, ...argsMismatch ? { argsMismatch } : {} };
28131
+ }
28132
+ /**
28133
+ * Drain results still queued in `pendingResults` and report what was dropped.
28134
+ *
28135
+ * Called at a child MESSAGE boundary (message_start / the no-stream-events
28136
+ * assistant fallback): by then the child has necessarily received every tool
28137
+ * result for the previous message — a handler that matched resolved its result
28138
+ * directly or from this queue, and one that never matched already returned an
28139
+ * error. Whatever is still queued therefore belongs to a call whose handler
28140
+ * gave up, and no consumer will ever come for it. Left in place, each entry
28141
+ * poisons every later mismatch report for the whole query (queued>0 with 0/0
28142
+ * counters and no tool names) and forces a session rebuild per turn.
28143
+ */
28144
+ takeStaleQueuedResults() {
28145
+ if (this.pendingResults.size === 0) return [];
28146
+ const stale = [...this.pendingResults.keys()].map((id) => ({
28147
+ id,
28148
+ toolName: this.queryToolNames.get(id) ?? "unknown"
28149
+ }));
28150
+ this.pendingResults.clear();
28151
+ return stale;
28152
+ }
28153
+ markToolResultDelivered(id) {
28154
+ if (id) this.deliveredToolResultIds.add(id);
28155
+ }
28156
+ markToolResultResolved(id) {
28157
+ if (id) this.resolvedToolResultIds.add(id);
28158
+ }
28159
+ markToolResultUnmatched(id) {
28160
+ if (id) this.unmatchedToolResultIds.add(id);
28161
+ }
28162
+ toolResultProgress() {
28163
+ const expectedIds = unique([
28164
+ ...this.turnToolCalls.map((call) => call.id),
28165
+ ...this.turnToolCallIds
28166
+ ]);
28167
+ const deliveredIds = unique(this.deliveredToolResultIds);
28168
+ const resolvedIds = unique(this.resolvedToolResultIds);
28169
+ const waitingIds = unique(this.pendingToolCalls.keys());
28170
+ const queuedIds = unique(this.pendingResults.keys());
28171
+ const unmatchedResultIds = unique(this.unmatchedToolResultIds);
28172
+ const missingDeliveredIds = expectedIds.filter((id) => !this.deliveredToolResultIds.has(id));
28173
+ const unresolvedIds = expectedIds.filter((id) => !this.resolvedToolResultIds.has(id));
28174
+ const affectedIds = /* @__PURE__ */ new Set([...missingDeliveredIds, ...unresolvedIds, ...waitingIds, ...queuedIds, ...unmatchedResultIds]);
28175
+ const counts = /* @__PURE__ */ new Map();
28176
+ if (affectedIds.size > 0) {
28177
+ for (const id of affectedIds) {
28178
+ const name = this.queryToolNames.get(id) ?? this.turnToolCalls.find((call) => call.id === id)?.toolName ?? "unknown";
28179
+ counts.set(name, (counts.get(name) ?? 0) + 1);
28180
+ }
28181
+ } else {
28182
+ for (const call of this.turnToolCalls) {
28183
+ counts.set(call.toolName, (counts.get(call.toolName) ?? 0) + 1);
28184
+ }
28185
+ }
28186
+ return {
28187
+ expectedIds,
28188
+ deliveredIds,
28189
+ resolvedIds,
28190
+ waitingIds,
28191
+ queuedIds,
28192
+ unmatchedResultIds,
28193
+ missingDeliveredIds,
28194
+ unresolvedIds,
28195
+ toolNames: [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name, count]) => ({ name, count })),
28196
+ expectedCount: expectedIds.length,
28197
+ deliveredCount: deliveredIds.length,
28198
+ resolvedCount: resolvedIds.length,
28199
+ waitingCount: waitingIds.length,
28200
+ queuedCount: queuedIds.length,
28201
+ unmatchedResultCount: unmatchedResultIds.length
28202
+ };
28203
+ }
28204
+ };
28205
+ var _ctx = new QueryContext();
28206
+ var contextStack = [];
28207
+ function ctx() {
28208
+ return _ctx;
28209
+ }
28210
+ function stackDepth() {
28211
+ return contextStack.length;
28212
+ }
28213
+ function pushContext() {
28214
+ if (!_ctx.activeQuery) throw new Error("pushContext() called with no active query");
28215
+ contextStack.push(_ctx);
28216
+ _ctx = new QueryContext();
28217
+ }
28218
+ function popContext() {
28219
+ if (contextStack.length === 0) throw new Error("popContext() called with empty stack");
28220
+ const parent = contextStack[contextStack.length - 1];
28221
+ parent.deferredUserMessages.push(..._ctx.deferredUserMessages);
28222
+ _ctx = contextStack.pop();
28223
+ }
28224
+ function popContextFor(target) {
28225
+ if (_ctx === target) {
28226
+ popContext();
28227
+ return true;
28228
+ }
28229
+ const idx = contextStack.indexOf(target);
28230
+ if (idx < 0) return false;
28231
+ const parent = idx > 0 ? contextStack[idx - 1] : void 0;
28232
+ parent?.deferredUserMessages.push(...target.deferredUserMessages);
28233
+ contextStack.splice(idx, 1);
28234
+ return true;
28235
+ }
28236
+
28237
+ // src/debug.ts
28238
+ import { appendFileSync as appendFileSync2, chmodSync, mkdirSync as mkdirSync2 } from "fs";
28239
+ import { dirname as dirname2, join as join3 } from "path";
28240
+ var DEBUG = process.env.CLAUDE_BRIDGE_DEBUG === "1";
28241
+ var DEBUG_LOG_PATH = process.env.CLAUDE_BRIDGE_DEBUG_PATH || join3(piUserDir(), "claude-bridge.log");
28242
+ function diagLogPath() {
28243
+ return process.env.CLAUDE_BRIDGE_DIAG_PATH || join3(piUserDir(), "claude-bridge-diag.log");
28244
+ }
28245
+ if (DEBUG) {
28246
+ try {
28247
+ mkdirSync2(dirname2(DEBUG_LOG_PATH), { recursive: true });
28248
+ mkdirSync2(dirname2(diagLogPath()), { recursive: true, mode: 448 });
28249
+ } catch {
28250
+ }
28251
+ }
28252
+ var moduleInstanceId = Math.random().toString(36).slice(2, 8);
28253
+ function debug(...args) {
28254
+ if (!DEBUG) return;
28255
+ const ts2 = (/* @__PURE__ */ new Date()).toISOString();
28256
+ const fmt = (a) => {
28257
+ if (typeof a === "string") return a;
28258
+ if (a instanceof Error) return `${a.name}: ${a.message}${a.stack ? "\n" + a.stack : ""}`;
28259
+ return JSON.stringify(a);
28260
+ };
28261
+ const msg = args.map(fmt).join(" ");
28262
+ try {
28263
+ appendFileSync2(DEBUG_LOG_PATH, `[${ts2}] [${moduleInstanceId}] ${msg}
28264
+ `);
28265
+ } catch {
28266
+ }
28267
+ }
28268
+ var nextCliDebugSeq = 1;
28269
+ function makeCliDebugOptions(tag) {
28270
+ if (!DEBUG) return {};
28271
+ const seq = nextCliDebugSeq++;
28272
+ const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28273
+ const logDir = join3(dirname2(DEBUG_LOG_PATH), "cc-cli-logs");
28274
+ try {
28275
+ mkdirSync2(logDir, { recursive: true });
28276
+ } catch {
28277
+ }
28278
+ const debugFile = join3(logDir, `${ts2}-${tag}-${seq}.log`);
28279
+ debug(`cli-debug: ${tag} #${seq} \u2192 ${debugFile}`);
28280
+ return {
28281
+ debug: true,
28282
+ debugFile,
28283
+ stderr: (data) => {
28284
+ for (const line of data.split(/\r?\n/)) {
28285
+ if (line) debug(`[cli-stderr ${tag}#${seq}] ${line}`);
28286
+ }
28287
+ }
28288
+ };
28289
+ }
28290
+ function diagDump(label, data) {
28291
+ try {
28292
+ const ts2 = (/* @__PURE__ */ new Date()).toISOString();
28293
+ const entry = { ts: ts2, moduleInstanceId, label, ...data };
28294
+ const path = diagLogPath();
28295
+ try {
28296
+ mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
28297
+ } catch {
28298
+ }
28299
+ appendFileSync2(path, JSON.stringify(entry) + "\n", { mode: 384 });
28300
+ try {
28301
+ chmodSync(path, 384);
28302
+ } catch {
28303
+ }
28304
+ debug(`DIAG: ${label} (see ${path})`);
28305
+ } catch (error51) {
28306
+ debug(`DIAG FAILED: ${label}`, error51);
28307
+ }
28308
+ }
28309
+
28310
+ // src/tool-pairing-audit.ts
28311
+ function contentBlocks(content) {
28312
+ return Array.isArray(content) ? content.filter((block) => Boolean(block && typeof block === "object")) : [];
28313
+ }
28314
+ function toolUses(content) {
28315
+ return contentBlocks(content).filter((block) => block.type === "tool_use" && typeof block.id === "string").map((block) => ({ id: block.id, name: typeof block.name === "string" && block.name ? block.name : "unknown" }));
28316
+ }
28317
+ function toolResultIds(content) {
28318
+ const ids = /* @__PURE__ */ new Set();
28319
+ for (const block of contentBlocks(content)) {
28320
+ if (block.type === "tool_result" && typeof block.tool_use_id === "string") ids.add(block.tool_use_id);
28321
+ }
28322
+ return ids;
28323
+ }
28324
+ function findUnpairedToolUses(messages) {
28325
+ const missing = [];
28326
+ for (let i = 0; i < messages.length; i++) {
28327
+ const msg = messages[i];
28328
+ if (msg?.role !== "assistant") continue;
28329
+ const uses = toolUses(msg.content);
28330
+ if (uses.length === 0) continue;
28331
+ const next = messages[i + 1];
28332
+ const nextUserIndex = next?.role === "user" ? i + 1 : null;
28333
+ const resultIds = nextUserIndex == null ? /* @__PURE__ */ new Set() : toolResultIds(next.content);
28334
+ for (const use2 of uses) {
28335
+ if (!resultIds.has(use2.id)) {
28336
+ missing.push({ id: use2.id, toolName: use2.name, assistantIndex: i, userIndex: nextUserIndex });
28337
+ }
28338
+ }
28339
+ }
28340
+ return missing;
28341
+ }
28342
+ var LOST_TOOL_RESULT_TEXT = "Claude bridge: the result of this tool call was lost before the session was rebuilt (the turn was interrupted). Treat the call as failed \u2014 it may or may not have executed. Re-run the tool if its output is still needed.";
28343
+ function insertLostToolResultPlaceholders(messages, missing) {
28344
+ const block = (id) => ({ type: "tool_result", tool_use_id: id, content: LOST_TOOL_RESULT_TEXT, is_error: true });
28345
+ const byAssistant = /* @__PURE__ */ new Map();
28346
+ for (const item of missing) {
28347
+ const group = byAssistant.get(item.assistantIndex) ?? [];
28348
+ group.push(item);
28349
+ byAssistant.set(item.assistantIndex, group);
28350
+ }
28351
+ for (const assistantIndex of [...byAssistant.keys()].sort((a, b) => b - a)) {
28352
+ const group = byAssistant.get(assistantIndex);
28353
+ const blocks = group.map((item) => block(item.id));
28354
+ const userIndex = group[0].userIndex;
28355
+ if (userIndex != null && messages[userIndex]?.role === "user") {
28356
+ const user = messages[userIndex];
28357
+ const existing = typeof user.content === "string" ? user.content ? [{ type: "text", text: user.content }] : [] : Array.isArray(user.content) ? user.content : [];
28358
+ user.content = [...blocks, ...existing];
28359
+ } else {
28360
+ messages.splice(assistantIndex + 1, 0, { role: "user", content: blocks });
28361
+ }
28362
+ }
28363
+ }
28364
+ function summarizeMissingToolNames(missing) {
28365
+ const counts = /* @__PURE__ */ new Map();
28366
+ for (const item of missing) counts.set(item.toolName, (counts.get(item.toolName) ?? 0) + 1);
28367
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name, count]) => ({ name, count }));
28368
+ }
28369
+
28370
+ // src/bridge-state.ts
28371
+ var sharedSession = null;
28372
+ var extensionApi;
28373
+ var piUI;
28374
+ function setSharedSession(next) {
28375
+ sharedSession = next;
28376
+ }
28377
+ function setExtensionApi(next) {
28378
+ extensionApi = next;
28379
+ }
28380
+ function setPiUI(next) {
28381
+ piUI = next;
28382
+ }
28383
+ function safeNotify(message, level = "warning") {
28384
+ try {
28385
+ piUI?.notify(message, level);
28386
+ } catch (error51) {
28387
+ debug("notify failed:", error51);
28388
+ }
28389
+ }
28390
+ function argKeys(args) {
28391
+ return Object.keys(args ?? {}).sort();
28392
+ }
28393
+ function safeToolCallSummary(calls) {
28394
+ return calls.map((call) => ({ id: call.id, toolName: call.toolName, argKeys: argKeys(call.arguments) }));
28395
+ }
28396
+ var INTEGRITY_CUSTOM_TYPE = "claude-bridge-integrity";
28397
+ function appendIntegrityEntry(label, data) {
28398
+ try {
28399
+ if (!extensionApi) return false;
28400
+ extensionApi.appendEntry(INTEGRITY_CUSTOM_TYPE, { label, at: (/* @__PURE__ */ new Date()).toISOString(), ...data });
28401
+ return true;
28402
+ } catch (error51) {
28403
+ debug("appendIntegrityEntry failed:", error51);
28404
+ return false;
28405
+ }
28406
+ }
28407
+ function compactToolNameSummary(names, limit = 12) {
28408
+ const shown = names.slice(0, limit).map(({ name, count }) => count > 1 ? `${name}\xD7${count}` : name);
28409
+ if (names.length > limit) shown.push(`+${names.length - limit} more`);
28410
+ return shown;
28411
+ }
28412
+ function reportSyntheticToolResultRepair(missing, context) {
28413
+ try {
28414
+ if (missing.length === 0) return;
28415
+ const toolNames = summarizeMissingToolNames(missing);
28416
+ const toolNameSummary = compactToolNameSummary(toolNames);
28417
+ const sampledToolCallIds = missing.slice(0, 50).map((item) => item.id);
28418
+ diagDump("repair_tool_pairing_synthetic_results", {
28419
+ count: missing.length,
28420
+ toolNames,
28421
+ sampledToolCallIds,
28422
+ missing: missing.slice(0, 50),
28423
+ ...context
28424
+ });
28425
+ appendIntegrityEntry("repair_tool_pairing_synthetic_results", {
28426
+ count: missing.length,
28427
+ toolNames,
28428
+ sampledToolCallIds: sampledToolCallIds.slice(0, 12)
28429
+ });
28430
+ safeNotify(
28431
+ `Claude bridge: ${missing.length} missing tool result(s) repaired with an explicit error placeholder${toolNameSummary.length ? ` for ${toolNameSummary.join(", ")}` : ""}. Real tool output was lost before Claude session import; see ${diagLogPath()}.`,
28432
+ "error"
28433
+ );
28434
+ } catch (error51) {
28435
+ debug("reportSyntheticToolResultRepair failed:", error51);
28436
+ }
28437
+ }
28438
+ function reportToolResultMismatch(queryCtx, reason, cwd, opts = {}) {
28439
+ try {
28440
+ if (queryCtx.reportedToolResultMismatch) return false;
28441
+ const progress = queryCtx.toolResultProgress();
28442
+ const hasMismatch = progress.expectedCount > 0 ? progress.unresolvedIds.length > 0 || progress.waitingCount > 0 || progress.queuedCount > 0 || progress.unmatchedResultCount > 0 : progress.waitingCount > 0 || progress.queuedCount > 0 || progress.unmatchedResultCount > 0;
28443
+ if (!hasMismatch) return false;
28444
+ queryCtx.reportedToolResultMismatch = true;
28445
+ if (sharedSession) {
28446
+ sharedSession = { ...sharedSession, needsRebuild: true, ...opts.forceRotate ? { forceRotate: true } : {} };
28447
+ }
28448
+ const toolNameSummary = compactToolNameSummary(progress.toolNames);
28449
+ diagDump("tool_result_delivery_mismatch", {
28450
+ reason,
28451
+ cwd,
28452
+ progress,
28453
+ activeQueryExists: queryCtx.activeQuery !== null,
28454
+ sharedSession: sharedSession ? {
28455
+ sessionId: sharedSession.sessionId.slice(0, 8),
28456
+ cursor: sharedSession.cursor,
28457
+ needsRebuild: sharedSession.needsRebuild === true,
28458
+ forceRotate: sharedSession.forceRotate === true
28459
+ } : null
28460
+ });
28461
+ appendIntegrityEntry("tool_result_delivery_mismatch", {
28462
+ reason,
28463
+ toolNames: progress.toolNames,
28464
+ expectedCount: progress.expectedCount,
28465
+ deliveredCount: progress.deliveredCount,
28466
+ resolvedCount: progress.resolvedCount,
28467
+ waitingIds: progress.waitingIds,
28468
+ queuedIds: progress.queuedIds,
28469
+ unmatchedResultIds: progress.unmatchedResultIds
28470
+ });
28471
+ safeNotify(
28472
+ `Claude bridge: tool result delivery interrupted during ${reason}; delivered ${progress.deliveredCount}/${progress.expectedCount}, resolved ${progress.resolvedCount}/${progress.expectedCount}, waiting=${progress.waitingCount}, queued=${progress.queuedCount}, unmatched=${progress.unmatchedResultCount}${toolNameSummary.length ? `, tools=${toolNameSummary.join(", ")}` : ""}. Claude session will rebuild before the next turn; see ${diagLogPath()}.`,
28473
+ "error"
28474
+ );
28475
+ return true;
28476
+ } catch (error51) {
28477
+ debug("reportToolResultMismatch failed:", error51);
28478
+ return false;
28479
+ }
28480
+ }
28481
+ function __testSetBridgeIntegrityState(state) {
28482
+ if ("ui" in state) piUI = state.ui;
28483
+ if ("sharedSession" in state) sharedSession = state.sharedSession ?? null;
28484
+ }
28485
+ function __testGetBridgeIntegrityState() {
28486
+ return { sharedSession };
28487
+ }
28488
+
28489
+ // src/connector-audit.ts
28490
+ var CONNECTOR_CALL_CUSTOM_TYPE = "claude-bridge-connector-call";
28491
+ function connectorResultByteSize(content) {
28492
+ if (content === void 0 || content === null) return void 0;
28493
+ if (typeof content === "string") return Buffer.byteLength(content, "utf8");
28494
+ try {
28495
+ const json2 = JSON.stringify(content);
28496
+ return typeof json2 === "string" ? Buffer.byteLength(json2, "utf8") : void 0;
28497
+ } catch {
28498
+ return void 0;
28499
+ }
28500
+ }
28501
+ var auditSink;
28502
+ function setConnectorCallAuditSink(sink) {
28503
+ auditSink = sink;
28504
+ }
28505
+ function appendConnectorCallAudit(data) {
28506
+ let delivered = false;
28507
+ if (extensionApi) {
28508
+ try {
28509
+ extensionApi.appendEntry(CONNECTOR_CALL_CUSTOM_TYPE, data);
28510
+ delivered = true;
28511
+ } catch (error51) {
28512
+ debug("appendConnectorCallAudit failed:", error51);
28513
+ }
28514
+ }
28515
+ if (auditSink) {
28516
+ try {
28517
+ auditSink({ ...data });
28518
+ delivered = true;
28519
+ } catch (error51) {
28520
+ debug("connector call audit sink failed:", error51);
28521
+ }
28522
+ }
28523
+ return delivered;
28524
+ }
28525
+ function recordConnectorCallResult(queryCtx, toolUseId, name, isError, byteSize) {
28526
+ const pending = queryCtx.connectorCallAudit.get(toolUseId);
28527
+ if (pending?.recorded) return false;
28528
+ const childSessionId = pending?.childSessionId ?? queryCtx.childSessionId;
28529
+ queryCtx.connectorCallAudit.set(toolUseId, { ...pending, name, childSessionId, recorded: true });
28530
+ return appendConnectorCallAudit({
28531
+ name,
28532
+ toolUseId,
28533
+ outcome: isError ? "error" : "ok",
28534
+ ...byteSize !== void 0 ? { byteSize } : {},
28535
+ ...childSessionId ? { childSessionId } : {}
28536
+ });
28537
+ }
28538
+ function flushConnectorCallAudit(queryCtx, reason) {
28539
+ let appended = 0;
28540
+ for (const [toolUseId, state] of queryCtx.connectorCallAudit) {
28541
+ if (state.recorded) continue;
28542
+ queryCtx.connectorCallAudit.set(toolUseId, { ...state, recorded: true });
28543
+ const childSessionId = state.childSessionId ?? queryCtx.childSessionId;
28544
+ if (appendConnectorCallAudit({
28545
+ name: state.name,
28546
+ toolUseId,
28547
+ outcome: "unobserved",
28548
+ reason,
28549
+ ...childSessionId ? { childSessionId } : {}
28550
+ })) appended++;
28551
+ }
28552
+ return appended;
28553
+ }
28554
+
28555
+ // src/query-teardown.ts
28556
+ function teardownQuery(queryCtx, sdkQuery, cause, cwd, isReentrant) {
28557
+ if (queryCtx.activeQuery !== sdkQuery) return false;
28558
+ reportToolResultMismatch(queryCtx, "query teardown", cwd, { forceRotate: cause !== "query-end" });
28559
+ const drained = drainPendingToolCalls(queryCtx, cause);
28560
+ if (drained > 0) debug(`provider: query teardown drained ${drained} waiting MCP handler(s) as errors (cause=${cause})`);
28561
+ queryCtx.pendingResults.clear();
28562
+ const unobserved = flushConnectorCallAudit(queryCtx, cause);
28563
+ if (unobserved > 0) debug(`provider: query teardown recorded ${unobserved} connector call(s) with no observed result (cause=${cause})`);
28564
+ if (isReentrant) {
28565
+ if (!popContextFor(queryCtx)) debug("provider: query teardown found context already popped; skipping pop");
28566
+ } else {
28567
+ queryCtx.activeQuery = null;
28568
+ }
28569
+ return true;
28570
+ }
28571
+
28572
+ // src/auth-presence.ts
28573
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
28574
+ import { homedir as homedir2, platform as osPlatform } from "os";
28575
+ import { join as join4 } from "path";
28576
+ function resolveClaudeConfigDir(env = process.env) {
28577
+ const configured = env.CLAUDE_CONFIG_DIR;
28578
+ if (typeof configured === "string" && configured.trim().length > 0) return configured.trim();
28579
+ return join4(homedir2(), ".claude");
28580
+ }
28581
+ function nonEmptyEnv(value) {
28582
+ return typeof value === "string" && value.trim().length > 0;
28583
+ }
28584
+ function envTruthy(value) {
28585
+ const v2 = value?.trim().toLowerCase();
28586
+ return v2 === "1" || v2 === "true";
28587
+ }
28588
+ function hasApiKeyHelper(configDir) {
28589
+ try {
28590
+ const settingsPath = join4(configDir, "settings.json");
28591
+ if (!existsSync3(settingsPath)) return false;
28592
+ const parsed = JSON.parse(readFileSync3(settingsPath, "utf8"));
28593
+ return typeof parsed?.apiKeyHelper === "string" && parsed.apiKeyHelper.trim().length > 0;
28594
+ } catch {
28595
+ return false;
28596
+ }
28597
+ }
28598
+ function hasClaudeCredentials(env = process.env, platform = osPlatform()) {
28599
+ if (nonEmptyEnv(env.CLAUDE_CODE_OAUTH_TOKEN)) return true;
28600
+ if (nonEmptyEnv(env.ANTHROPIC_API_KEY)) return true;
28601
+ if (nonEmptyEnv(env.ANTHROPIC_AUTH_TOKEN)) return true;
28602
+ if (envTruthy(env.CLAUDE_CODE_USE_BEDROCK)) return true;
28603
+ if (envTruthy(env.CLAUDE_CODE_USE_VERTEX)) return true;
28604
+ if (envTruthy(env.CLAUDE_CODE_USE_FOUNDRY)) return true;
28605
+ if (envTruthy(env.CLAUDE_CODE_USE_ANTHROPIC_AWS)) return true;
28606
+ if (envTruthy(env.CLAUDE_CODE_USE_MANTLE)) return true;
28607
+ const configDir = resolveClaudeConfigDir(env);
28608
+ if (existsSync3(join4(configDir, ".credentials.json"))) return true;
28609
+ if (hasApiKeyHelper(configDir)) return true;
28610
+ if (platform === "darwin") return true;
28611
+ return false;
28612
+ }
28613
+
28614
+ // src/native-provider.ts
28615
+ var NATIVE_PROVIDER_UNSUPPORTED_MESSAGE = "Claude bridge 2.x requires pi >= 0.81 (native provider API). Upgrade the host pi, or pin @vanillagreen/pi-claude-bridge@1.x.";
28616
+ function supportsNativeProvider(piAi2) {
28617
+ return typeof piAi2?.createProvider === "function";
28618
+ }
28619
+ function claudeAuthSourceLabel(env = process.env) {
28620
+ if (env.CLAUDE_CODE_OAUTH_TOKEN?.trim()) return "CLAUDE_CODE_OAUTH_TOKEN";
28621
+ if (env.ANTHROPIC_API_KEY?.trim()) return "ANTHROPIC_API_KEY";
28622
+ if (env.ANTHROPIC_AUTH_TOKEN?.trim()) return "ANTHROPIC_AUTH_TOKEN";
28623
+ return "Claude Code login";
28624
+ }
28625
+ function buildNativeProvider(piAi2, models, streamSimple, env = process.env) {
28626
+ if (!supportsNativeProvider(piAi2)) throw new Error(NATIVE_PROVIDER_UNSUPPORTED_MESSAGE);
28627
+ const stamped = models.map((model) => ({ api: "claude-bridge", baseUrl: "claude-bridge", provider: PROVIDER_ID, ...model }));
28628
+ const streams = {
28629
+ stream: streamSimple,
28630
+ streamSimple
28631
+ };
28632
+ return piAi2.createProvider({
28633
+ id: PROVIDER_ID,
28634
+ name: "Claude (Claude Code)",
28635
+ baseUrl: "claude-bridge",
28636
+ auth: {
28637
+ apiKey: {
28638
+ name: "Claude Code credentials",
28639
+ // check() exists so pi's availability pass never has to call
28640
+ // resolve(): both are existence-only, but check is the documented
28641
+ // side-effect-free probe.
28642
+ check: async () => hasClaudeCredentials(env) ? { type: "api_key", source: claudeAuthSourceLabel(env) } : void 0,
28643
+ resolve: async () => hasClaudeCredentials(env) ? { auth: { apiKey: "not-used" }, source: claudeAuthSourceLabel(env) } : void 0
28644
+ }
28645
+ },
28646
+ models: stamped,
28647
+ api: streams
28648
+ });
28649
+ }
28650
+
28651
+ // src/agents-md.ts
28652
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
28653
+ import { dirname as dirname3, join as join5, resolve as resolve3 } from "path";
28654
+ function globalAgentsPath() {
28655
+ return join5(piUserDir(), "AGENTS.md");
28656
+ }
28657
+ function resolveAgentsMdPath() {
28658
+ if (isolatedFromEnv()) return void 0;
28659
+ const fromCwd = findAgentsMdInParents(process.cwd());
28660
+ if (fromCwd) return fromCwd;
28661
+ const globalPath = globalAgentsPath();
28662
+ if (existsSync4(globalPath)) return globalPath;
28663
+ return void 0;
28664
+ }
28665
+ function findAgentsMdInParents(startDir) {
28666
+ let current = resolve3(startDir);
28667
+ while (true) {
28668
+ const candidate = join5(current, "AGENTS.md");
28669
+ if (existsSync4(candidate)) return candidate;
28670
+ const parent = dirname3(current);
28671
+ if (parent === current) break;
28672
+ current = parent;
28673
+ }
28674
+ return void 0;
28675
+ }
28676
+ function extractAgentsAppend() {
28677
+ const agentsPath = resolveAgentsMdPath();
28678
+ if (!agentsPath) return void 0;
28679
+ try {
28680
+ const content = readFileSync4(agentsPath, "utf-8").trim();
28681
+ if (!content) return void 0;
28682
+ const sanitized = sanitizeAgentsContent(content);
28683
+ return sanitized.length > 0 ? `# CLAUDE.md
28684
+
28685
+ ${sanitized}` : void 0;
28686
+ } catch {
28687
+ return void 0;
28688
+ }
28689
+ }
28690
+ function sanitizeAgentsContent(content) {
28691
+ let sanitized = content;
28692
+ sanitized = sanitized.replace(/~\/\.pi\b/gi, "~/.claude");
28693
+ sanitized = sanitized.replace(/(^|[\s'"`])\.pi\//g, "$1.claude/");
28694
+ sanitized = sanitized.replace(/\b\.pi\b/gi, ".claude");
28695
+ sanitized = sanitized.replace(/\bpi\b/gi, "environment");
28696
+ return sanitized;
28697
+ }
28698
+
28699
+ // src/prompt-context.ts
28700
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
28701
+ import { dirname as dirname4, join as join6, resolve as resolve4 } from "path";
28702
+ function readTrimmed(path) {
28703
+ try {
28704
+ if (!existsSync5(path)) return void 0;
28705
+ const content = readFileSync5(path, "utf8").trim();
28706
+ return content.length > 0 ? content : void 0;
28707
+ } catch {
28708
+ return void 0;
28709
+ }
28710
+ }
28711
+ function findProjectAppendSystem(startDir) {
28712
+ let current = resolve4(startDir);
28713
+ while (true) {
28714
+ const candidate = join6(current, ".pi", "APPEND_SYSTEM.md");
28715
+ if (existsSync5(candidate)) return candidate;
28716
+ const parent = dirname4(current);
28717
+ if (parent === current) break;
28718
+ current = parent;
28719
+ }
28720
+ return void 0;
28721
+ }
28722
+ function readAppendSystemPromptFiles(cwd) {
28723
+ const files = [
28724
+ { label: "global APPEND_SYSTEM.md", path: join6(piUserDir(), "APPEND_SYSTEM.md") }
28725
+ ];
28726
+ const projectPath = isolatedFromEnv() ? void 0 : findProjectAppendSystem(cwd);
28727
+ if (projectPath) files.push({ label: "project .pi/APPEND_SYSTEM.md", path: projectPath });
28728
+ const seen = /* @__PURE__ */ new Set();
28729
+ const output = [];
28730
+ for (const file2 of files) {
28731
+ if (seen.has(file2.path)) continue;
28732
+ seen.add(file2.path);
28733
+ const content = readTrimmed(file2.path);
28734
+ if (content) output.push({ label: file2.label, content });
28735
+ }
28736
+ return output;
28737
+ }
28738
+ function splitPromptBlocks(systemPrompt) {
28739
+ return (systemPrompt ?? "").split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
28740
+ }
28741
+ function extractHeadingSection(systemPrompt, headings) {
28742
+ if (!systemPrompt) return void 0;
28743
+ let start = -1;
28744
+ for (const heading of headings) {
28745
+ const index = systemPrompt.indexOf(heading);
28746
+ if (index >= 0 && (start < 0 || index < start)) start = index;
28747
+ }
28748
+ if (start < 0) return void 0;
28749
+ const rest = systemPrompt.slice(start).trim();
28750
+ const endCandidates = [
28751
+ rest.slice(1).search(/\n##\s+/),
28752
+ rest.search(/\n<\/project_instructions>/),
28753
+ rest.search(/\n<\/project_context>/)
28754
+ ].map((index, offset) => index >= 0 && offset === 0 ? index + 1 : index).filter((index) => index >= 0);
28755
+ const end = endCandidates.length > 0 ? Math.min(...endCandidates) : -1;
28756
+ return (end >= 0 ? rest.slice(0, end) : rest).trim();
28757
+ }
28758
+ function extractBlockByMarkers(systemPrompt, markers) {
28759
+ for (const block of splitPromptBlocks(systemPrompt)) {
28760
+ if (markers.some((marker) => marker.test(block))) return block;
28761
+ }
28762
+ return void 0;
28763
+ }
28764
+ function buildPromptContextAppend(systemPrompt, cwd, settings) {
28765
+ const parts = [];
28766
+ const labels = [];
28767
+ if (settings.includeAppendSystemPromptMd) {
28768
+ for (const file2 of readAppendSystemPromptFiles(cwd)) {
28769
+ parts.push(xmlBlock("append_system_prompt", { label: file2.label }, file2.content));
28770
+ labels.push(file2.label);
28771
+ }
28772
+ }
28773
+ if (settings.includeProjectAgentsHook) {
28774
+ const projectAgents = extractHeadingSection(systemPrompt, ["## Project Agents", "## Project Subagents"]);
28775
+ if (projectAgents) {
28776
+ parts.push(xmlBlock("before_agent_start", { source: "project-agents" }, projectAgents));
28777
+ labels.push("project agents hook");
28778
+ }
28779
+ }
28780
+ if (settings.includeTaskPanelHook) {
28781
+ const taskReminder = extractBlockByMarkers(systemPrompt, [/^Task workflow reminder:/]);
28782
+ if (taskReminder) {
28783
+ parts.push(xmlBlock("before_agent_start", { source: "task-panel" }, taskReminder));
28784
+ labels.push("task panel hook");
27833
28785
  }
27834
28786
  }
27835
28787
  if (settings.includeCavemanHook) {
@@ -42258,1026 +43210,471 @@ function convertBaseSchema(schema, ctx2) {
42258
43210
  }
42259
43211
  zodSchema = arraySchema;
42260
43212
  } else {
42261
- zodSchema = z2.array(z2.any());
42262
- }
42263
- break;
42264
- }
42265
- default:
42266
- throw new Error(`Unsupported type: ${type}`);
42267
- }
42268
- return zodSchema;
42269
- }
42270
- function convertSchema(schema, ctx2) {
42271
- if (typeof schema === "boolean") {
42272
- return schema ? z2.any() : z2.never();
42273
- }
42274
- let baseSchema = convertBaseSchema(schema, ctx2);
42275
- const hasExplicitType = schema.type || schema.enum !== void 0 || schema.const !== void 0;
42276
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
42277
- const options = schema.anyOf.map((s) => convertSchema(s, ctx2));
42278
- const anyOfUnion = z2.union(options);
42279
- baseSchema = hasExplicitType ? z2.intersection(baseSchema, anyOfUnion) : anyOfUnion;
42280
- }
42281
- if (schema.oneOf && Array.isArray(schema.oneOf)) {
42282
- const options = schema.oneOf.map((s) => convertSchema(s, ctx2));
42283
- const oneOfUnion = z2.xor(options);
42284
- baseSchema = hasExplicitType ? z2.intersection(baseSchema, oneOfUnion) : oneOfUnion;
42285
- }
42286
- if (schema.allOf && Array.isArray(schema.allOf)) {
42287
- if (schema.allOf.length === 0) {
42288
- baseSchema = hasExplicitType ? baseSchema : z2.any();
42289
- } else {
42290
- let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx2);
42291
- const startIdx = hasExplicitType ? 0 : 1;
42292
- for (let i = startIdx; i < schema.allOf.length; i++) {
42293
- result = z2.intersection(result, convertSchema(schema.allOf[i], ctx2));
42294
- }
42295
- baseSchema = result;
42296
- }
42297
- }
42298
- if (schema.nullable === true && ctx2.version === "openapi-3.0") {
42299
- baseSchema = z2.nullable(baseSchema);
42300
- }
42301
- if (schema.readOnly === true) {
42302
- baseSchema = z2.readonly(baseSchema);
42303
- }
42304
- if (schema.default !== void 0) {
42305
- baseSchema = baseSchema.default(schema.default);
42306
- }
42307
- const extraMeta = {};
42308
- const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
42309
- for (const key of coreMetadataKeys) {
42310
- if (key in schema) {
42311
- extraMeta[key] = schema[key];
42312
- }
42313
- }
42314
- const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
42315
- for (const key of contentMetadataKeys) {
42316
- if (key in schema) {
42317
- extraMeta[key] = schema[key];
42318
- }
42319
- }
42320
- for (const key of Object.keys(schema)) {
42321
- if (!RECOGNIZED_KEYS.has(key)) {
42322
- extraMeta[key] = schema[key];
42323
- }
42324
- }
42325
- if (Object.keys(extraMeta).length > 0) {
42326
- ctx2.registry.add(baseSchema, extraMeta);
42327
- }
42328
- if (schema.description) {
42329
- baseSchema = baseSchema.describe(schema.description);
42330
- }
42331
- return baseSchema;
42332
- }
42333
- function fromJSONSchema(schema, params) {
42334
- if (typeof schema === "boolean") {
42335
- return schema ? z2.any() : z2.never();
42336
- }
42337
- let normalized;
42338
- try {
42339
- normalized = JSON.parse(JSON.stringify(schema));
42340
- } catch {
42341
- throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
42342
- }
42343
- const version2 = detectVersion(normalized, params?.defaultTarget);
42344
- const defs = normalized.$defs || normalized.definitions || {};
42345
- const ctx2 = {
42346
- version: version2,
42347
- defs,
42348
- refs: /* @__PURE__ */ new Map(),
42349
- processing: /* @__PURE__ */ new Set(),
42350
- rootSchema: normalized,
42351
- registry: params?.registry ?? globalRegistry
42352
- };
42353
- return convertSchema(normalized, ctx2);
42354
- }
42355
-
42356
- // node_modules/zod/v4/classic/coerce.js
42357
- var coerce_exports = {};
42358
- __export(coerce_exports, {
42359
- bigint: () => bigint3,
42360
- boolean: () => boolean3,
42361
- date: () => date4,
42362
- number: () => number3,
42363
- string: () => string3
42364
- });
42365
- function string3(params) {
42366
- return _coercedString(ZodString, params);
42367
- }
42368
- function number3(params) {
42369
- return _coercedNumber(ZodNumber, params);
42370
- }
42371
- function boolean3(params) {
42372
- return _coercedBoolean(ZodBoolean, params);
42373
- }
42374
- function bigint3(params) {
42375
- return _coercedBigint(ZodBigInt, params);
42376
- }
42377
- function date4(params) {
42378
- return _coercedDate(ZodDate, params);
42379
- }
42380
-
42381
- // node_modules/zod/v4/classic/external.js
42382
- config(en_default());
42383
-
42384
- // src/typebox-to-zod.ts
42385
- function jsonSchemaPropertyToZod(prop) {
42386
- let base;
42387
- if (Array.isArray(prop.enum) && prop.enum.length > 0) base = external_exports.enum(prop.enum);
42388
- else switch (prop.type) {
42389
- case "string":
42390
- base = external_exports.string();
42391
- break;
42392
- case "number":
42393
- case "integer":
42394
- base = external_exports.number();
42395
- break;
42396
- case "boolean":
42397
- base = external_exports.boolean();
42398
- break;
42399
- case "array": {
42400
- base = prop.items ? external_exports.array(jsonSchemaPropertyToZod(prop.items)) : external_exports.array(external_exports.unknown());
42401
- const minItems = typeof prop.minItems === "number" ? prop.minItems : void 0;
42402
- if (minItems !== void 0) base = base.min(minItems);
42403
- break;
42404
- }
42405
- case "object": {
42406
- if (prop.properties && typeof prop.properties === "object" && !Array.isArray(prop.properties)) {
42407
- base = external_exports.object(jsonSchemaToZodShape(prop));
42408
- if (prop.additionalProperties === false) base = base.strict();
42409
- else if (prop.additionalProperties === true) base = base.passthrough();
42410
- } else {
42411
- base = external_exports.record(external_exports.string(), external_exports.unknown());
42412
- }
42413
- break;
42414
- }
42415
- default:
42416
- base = external_exports.unknown();
42417
- }
42418
- if (typeof prop.description === "string") base = base.describe(prop.description);
42419
- return base;
42420
- }
42421
- function jsonSchemaToZodShape(schema) {
42422
- const s = schema;
42423
- if (!s || s.type !== "object" || !s.properties) return {};
42424
- const props = s.properties;
42425
- const required2 = new Set(Array.isArray(s.required) ? s.required : []);
42426
- const shape = {};
42427
- for (const [key, prop] of Object.entries(props)) {
42428
- const zodProp = jsonSchemaPropertyToZod(prop);
42429
- shape[key] = required2.has(key) ? zodProp : zodProp.optional();
42430
- }
42431
- return shape;
42432
- }
42433
-
42434
- // src/index.ts
42435
- import { readFileSync as nodeReadFileSync } from "node:fs";
42436
-
42437
- // src/pi-ai-compat.ts
42438
- var dynamicImport = (specifier) => import(specifier);
42439
- async function resolveGetModels(root, loadCompat = () => dynamicImport("@earendil-works/pi-ai/compat")) {
42440
- if (typeof root?.getModels === "function") return root.getModels;
42441
- const compat = await loadCompat();
42442
- if (typeof compat?.getModels !== "function") throw new Error("pi-ai getModels API is unavailable");
42443
- return compat.getModels;
42444
- }
42445
-
42446
- // src/connector-inventory.ts
42447
- var CONNECTOR_NS_PREFIX = "mcp__claude_ai_";
42448
- var DEFAULT_API_BASE = "https://api.anthropic.com";
42449
- var OAUTH_BETA_HEADER = "oauth-2025-04-20";
42450
- function connectorServerNamespace(connectorName) {
42451
- return `${CONNECTOR_NS_PREFIX}${connectorName.trim().replace(/\s+/g, "_")}__`;
42452
- }
42453
- function credentialCandidatePaths(env = process.env) {
42454
- const roots = [];
42455
- const configDir = env.CLAUDE_CONFIG_DIR?.trim();
42456
- if (configDir) roots.push(configDir);
42457
- const home = env.HOME?.trim();
42458
- if (home) roots.push(`${home}/.claude`, home);
42459
- const seen = /* @__PURE__ */ new Set();
42460
- const paths = [];
42461
- for (const root of roots) {
42462
- for (const name of [".credentials.json", ".claude.json"]) {
42463
- const p2 = `${root}/${name}`;
42464
- if (!seen.has(p2)) {
42465
- seen.add(p2);
42466
- paths.push(p2);
42467
- }
42468
- }
42469
- }
42470
- return paths;
42471
- }
42472
- function resolveClaudeOAuth(readFile, env = process.env) {
42473
- let accessToken;
42474
- let organizationUuid;
42475
- for (const path of credentialCandidatePaths(env)) {
42476
- const raw = readFile(path);
42477
- if (!raw) continue;
42478
- let parsed;
42479
- try {
42480
- parsed = JSON.parse(raw);
42481
- } catch {
42482
- continue;
42483
- }
42484
- accessToken ??= nonEmptyString(parsed?.claudeAiOauth?.accessToken);
42485
- organizationUuid ??= nonEmptyString(parsed?.oauthAccount?.organizationUuid);
42486
- if (accessToken && organizationUuid) break;
42487
- }
42488
- if (!accessToken || !organizationUuid) return void 0;
42489
- return { accessToken, organizationUuid };
42490
- }
42491
- function nonEmptyString(value) {
42492
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
42493
- }
42494
- function connectorsListUrl(organizationUuid, apiBase = DEFAULT_API_BASE) {
42495
- return `${trimTrailingSlashes(apiBase)}/api/oauth/organizations/${encodeURIComponent(organizationUuid)}/mcp/connectors/list`;
42496
- }
42497
- function trimTrailingSlashes(value) {
42498
- let end = value.length;
42499
- while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
42500
- return value.slice(0, end);
42501
- }
42502
- async function listAccountConnectors(deps) {
42503
- const { credentials, apiBase, signal } = deps;
42504
- const fetchImpl = deps.fetchImpl ?? fetch;
42505
- const url2 = connectorsListUrl(credentials.organizationUuid, apiBase);
42506
- const fail = (reason) => ({ ok: false, complete: false, reason: redactSecret(reason, credentials.accessToken) });
42507
- let response;
42508
- try {
42509
- response = await fetchImpl(url2, {
42510
- method: "POST",
42511
- headers: {
42512
- "Authorization": `Bearer ${credentials.accessToken}`,
42513
- "anthropic-beta": OAUTH_BETA_HEADER,
42514
- "Content-Type": "application/json"
42515
- },
42516
- body: "{}",
42517
- signal
42518
- });
42519
- } catch (error51) {
42520
- return fail(`connector list request failed: ${errorText(error51)}`);
42521
- }
42522
- let bodyText;
42523
- try {
42524
- bodyText = await response.text();
42525
- } catch (error51) {
42526
- return fail(`connector list response unreadable: ${errorText(error51)}`);
42527
- }
42528
- if (!response.ok) {
42529
- return fail(`connector list returned HTTP ${response.status}${apiErrorSuffix(bodyText)}`);
42530
- }
42531
- let parsed;
42532
- try {
42533
- parsed = JSON.parse(bodyText);
42534
- } catch {
42535
- return fail("connector list returned a non-JSON body");
42536
- }
42537
- if (!Array.isArray(parsed?.results)) {
42538
- return fail("connector list response had no results array");
42539
- }
42540
- const connectors = [];
42541
- for (const raw of parsed.results) {
42542
- const entry = raw;
42543
- const name = nonEmptyString(entry?.name);
42544
- if (!name) {
42545
- return fail("connector list contained an entry with no name");
42546
- }
42547
- connectors.push({
42548
- name,
42549
- installedServerId: nonEmptyString(entry?.installedServerId),
42550
- directoryUuid: nonEmptyString(entry?.directoryUuid),
42551
- description: nonEmptyString(entry?.description),
42552
- isAuthless: typeof entry?.isAuthless === "boolean" ? entry.isAuthless : void 0
42553
- });
42554
- }
42555
- return { ok: true, complete: true, connectors };
42556
- }
42557
- function apiErrorSuffix(bodyText) {
42558
- try {
42559
- const message = JSON.parse(bodyText)?.error?.message;
42560
- return typeof message === "string" && message.trim() ? ` (${message.trim()})` : "";
42561
- } catch {
42562
- return "";
42563
- }
42564
- }
42565
- function redactSecret(text, secret) {
42566
- if (!secret || secret.length < 8) return text;
42567
- let out = text;
42568
- for (const form of /* @__PURE__ */ new Set([secret, encodeURIComponent(secret)])) {
42569
- out = out.split(form).join("[redacted]");
42570
- }
42571
- return out;
42572
- }
42573
- function errorText(error51) {
42574
- return error51 instanceof Error ? error51.message : String(error51);
42575
- }
42576
-
42577
- // src/debug.ts
42578
- import { appendFileSync as appendFileSync2, chmodSync, mkdirSync as mkdirSync2 } from "fs";
42579
- import { dirname as dirname4, join as join6 } from "path";
42580
- var DEBUG = process.env.CLAUDE_BRIDGE_DEBUG === "1";
42581
- var DEBUG_LOG_PATH = process.env.CLAUDE_BRIDGE_DEBUG_PATH || join6(piUserDir(), "claude-bridge.log");
42582
- function diagLogPath() {
42583
- return process.env.CLAUDE_BRIDGE_DIAG_PATH || join6(piUserDir(), "claude-bridge-diag.log");
42584
- }
42585
- if (DEBUG) {
42586
- try {
42587
- mkdirSync2(dirname4(DEBUG_LOG_PATH), { recursive: true });
42588
- mkdirSync2(dirname4(diagLogPath()), { recursive: true, mode: 448 });
42589
- } catch {
42590
- }
42591
- }
42592
- var moduleInstanceId = Math.random().toString(36).slice(2, 8);
42593
- function debug(...args) {
42594
- if (!DEBUG) return;
42595
- const ts2 = (/* @__PURE__ */ new Date()).toISOString();
42596
- const fmt = (a) => {
42597
- if (typeof a === "string") return a;
42598
- if (a instanceof Error) return `${a.name}: ${a.message}${a.stack ? "\n" + a.stack : ""}`;
42599
- return JSON.stringify(a);
42600
- };
42601
- const msg = args.map(fmt).join(" ");
42602
- try {
42603
- appendFileSync2(DEBUG_LOG_PATH, `[${ts2}] [${moduleInstanceId}] ${msg}
42604
- `);
42605
- } catch {
42606
- }
42607
- }
42608
- var nextCliDebugSeq = 1;
42609
- function makeCliDebugOptions(tag) {
42610
- if (!DEBUG) return {};
42611
- const seq = nextCliDebugSeq++;
42612
- const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
42613
- const logDir = join6(dirname4(DEBUG_LOG_PATH), "cc-cli-logs");
42614
- try {
42615
- mkdirSync2(logDir, { recursive: true });
42616
- } catch {
42617
- }
42618
- const debugFile = join6(logDir, `${ts2}-${tag}-${seq}.log`);
42619
- debug(`cli-debug: ${tag} #${seq} \u2192 ${debugFile}`);
42620
- return {
42621
- debug: true,
42622
- debugFile,
42623
- stderr: (data) => {
42624
- for (const line of data.split(/\r?\n/)) {
42625
- if (line) debug(`[cli-stderr ${tag}#${seq}] ${line}`);
42626
- }
42627
- }
42628
- };
42629
- }
42630
- function diagDump(label, data) {
42631
- try {
42632
- const ts2 = (/* @__PURE__ */ new Date()).toISOString();
42633
- const entry = { ts: ts2, moduleInstanceId, label, ...data };
42634
- const path = diagLogPath();
42635
- try {
42636
- mkdirSync2(dirname4(path), { recursive: true, mode: 448 });
42637
- } catch {
42638
- }
42639
- appendFileSync2(path, JSON.stringify(entry) + "\n", { mode: 384 });
42640
- try {
42641
- chmodSync(path, 384);
42642
- } catch {
42643
- }
42644
- debug(`DIAG: ${label} (see ${path})`);
42645
- } catch (error51) {
42646
- debug(`DIAG FAILED: ${label}`, error51);
42647
- }
42648
- }
42649
-
42650
- // src/claude-executable.ts
42651
- import { spawn as spawnProcess } from "child_process";
42652
- import { accessSync, constants as fsConstants, readFileSync as readFileSync6, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
42653
- import { delimiter, join as join7 } from "path";
42654
- function executableFromPath(name) {
42655
- const paths = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
42656
- for (const dir of paths) {
42657
- const candidate = join7(dir, name);
42658
- try {
42659
- accessSync(candidate, fsConstants.X_OK);
42660
- return candidate;
42661
- } catch {
42662
- }
42663
- }
42664
- return void 0;
42665
- }
42666
- function resolveClaudeExecutable(configured) {
42667
- const trimmed = configured?.trim();
42668
- if (trimmed) return trimmed;
42669
- if (isolatedFromEnv()) return void 0;
42670
- return executableFromPath("claude") ?? executableFromPath("claude-code");
42671
- }
42672
- function errnoValue(err) {
42673
- return typeof err?.errno === "number" ? err.errno : void 0;
42674
- }
42675
- function syscallValue(err) {
42676
- return typeof err?.syscall === "string" ? err.syscall : void 0;
42677
- }
42678
- function pathValue(err) {
42679
- const value = err?.path;
42680
- return typeof value === "string" ? value : void 0;
42681
- }
42682
- function codeValue(err, fallback) {
42683
- const value = err?.code;
42684
- return typeof value === "string" ? value : fallback;
42685
- }
42686
- function displayValue(value) {
42687
- return value === void 0 || value === null || value === "" ? "<none>" : String(value);
42688
- }
42689
- function makeClaudePreflightError(summary, details) {
42690
- const detail = [
42691
- `code=${details.code}`,
42692
- `errno=${displayValue(details.errno)}`,
42693
- `syscall=${displayValue(details.syscall)}`,
42694
- `path=${details.path}`,
42695
- `cwd=${details.cwd}`,
42696
- ...details.fileType ? [`fileType=${details.fileType}`] : [],
42697
- ...details.realPath ? [`realPath=${details.realPath}`] : []
42698
- ].join(" ");
42699
- const error51 = new Error(`${summary} (${detail})`);
42700
- error51.name = "ClaudeExecutablePreflightError";
42701
- error51.code = details.code;
42702
- if (details.errno !== void 0) error51.errno = typeof details.errno === "number" ? details.errno : Number(details.errno);
42703
- if (details.syscall) error51.syscall = details.syscall;
42704
- error51.path = details.path;
42705
- error51.cwd = details.cwd;
42706
- if (details.fileType) error51.fileType = details.fileType;
42707
- if (details.realPath) error51.realPath = details.realPath;
42708
- if (details.cause !== void 0) error51.cause = details.cause;
42709
- return error51;
42710
- }
42711
- function classifyClaudeExecutableBytes(bytes) {
42712
- if (bytes.length === 0) return "empty";
42713
- if (bytes.length >= 2 && bytes[0] === 35 && bytes[1] === 33) return "shebang-script";
42714
- if (bytes.length >= 4 && bytes[0] === 127 && bytes[1] === 69 && bytes[2] === 76 && bytes[3] === 70) return "elf";
42715
- if (bytes.length >= 2 && bytes[0] === 77 && bytes[1] === 90) return "pe";
42716
- if (bytes.length >= 4) {
42717
- const magic = bytes[0] * 16777216 + bytes[1] * 65536 + bytes[2] * 256 + bytes[3];
42718
- if (magic === 4277009102 || magic === 4277009103 || magic === 3472551422 || magic === 3489328638 || magic === 3405691582 || magic === 3199925962) return "mach-o";
43213
+ zodSchema = z2.array(z2.any());
43214
+ }
43215
+ break;
43216
+ }
43217
+ default:
43218
+ throw new Error(`Unsupported type: ${type}`);
42719
43219
  }
42720
- return "unknown";
43220
+ return zodSchema;
42721
43221
  }
42722
- function preflightClaudeExecutable(path, cwd) {
42723
- let realCwd;
42724
- try {
42725
- const cwdStat = statSync2(cwd);
42726
- if (!cwdStat.isDirectory()) {
42727
- throw makeClaudePreflightError("Claude Code spawn cwd preflight failed: cwd is not a directory.", {
42728
- code: "ENOTDIR",
42729
- syscall: "chdir",
42730
- path: cwd,
42731
- cwd
42732
- });
42733
- }
42734
- accessSync(cwd, fsConstants.X_OK);
42735
- realCwd = realpathSync2(cwd);
42736
- } catch (err) {
42737
- if (err.name === "ClaudeExecutablePreflightError") throw err;
42738
- throw makeClaudePreflightError("Claude Code spawn cwd preflight failed: cwd is not reachable before spawning Claude Code.", {
42739
- code: codeValue(err, "EACCES"),
42740
- errno: errnoValue(err),
42741
- syscall: syscallValue(err),
42742
- path: pathValue(err) ?? cwd,
42743
- cwd,
42744
- cause: err
42745
- });
43222
+ function convertSchema(schema, ctx2) {
43223
+ if (typeof schema === "boolean") {
43224
+ return schema ? z2.any() : z2.never();
42746
43225
  }
42747
- let realPath;
42748
- try {
42749
- const stat = statSync2(path);
42750
- if (!stat.isFile()) {
42751
- throw makeClaudePreflightError("Claude Code executable preflight failed: resolved path is not a file.", {
42752
- code: "EACCES",
42753
- syscall: "exec",
42754
- path,
42755
- cwd
42756
- });
43226
+ let baseSchema = convertBaseSchema(schema, ctx2);
43227
+ const hasExplicitType = schema.type || schema.enum !== void 0 || schema.const !== void 0;
43228
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
43229
+ const options = schema.anyOf.map((s) => convertSchema(s, ctx2));
43230
+ const anyOfUnion = z2.union(options);
43231
+ baseSchema = hasExplicitType ? z2.intersection(baseSchema, anyOfUnion) : anyOfUnion;
43232
+ }
43233
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
43234
+ const options = schema.oneOf.map((s) => convertSchema(s, ctx2));
43235
+ const oneOfUnion = z2.xor(options);
43236
+ baseSchema = hasExplicitType ? z2.intersection(baseSchema, oneOfUnion) : oneOfUnion;
43237
+ }
43238
+ if (schema.allOf && Array.isArray(schema.allOf)) {
43239
+ if (schema.allOf.length === 0) {
43240
+ baseSchema = hasExplicitType ? baseSchema : z2.any();
43241
+ } else {
43242
+ let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx2);
43243
+ const startIdx = hasExplicitType ? 0 : 1;
43244
+ for (let i = startIdx; i < schema.allOf.length; i++) {
43245
+ result = z2.intersection(result, convertSchema(schema.allOf[i], ctx2));
43246
+ }
43247
+ baseSchema = result;
42757
43248
  }
42758
- accessSync(path, fsConstants.X_OK);
42759
- realPath = realpathSync2(path);
42760
- } catch (err) {
42761
- if (err.name === "ClaudeExecutablePreflightError") throw err;
42762
- throw makeClaudePreflightError("Claude Code executable preflight failed: cannot access resolved executable before spawning Claude Code.", {
42763
- code: codeValue(err, "ENOENT"),
42764
- errno: errnoValue(err),
42765
- syscall: syscallValue(err),
42766
- path: pathValue(err) ?? path,
42767
- cwd,
42768
- cause: err
42769
- });
42770
43249
  }
42771
- let fileType;
42772
- try {
42773
- fileType = classifyClaudeExecutableBytes(readFileSync6(realPath).subarray(0, 16));
42774
- } catch (err) {
42775
- throw makeClaudePreflightError("Claude Code executable preflight failed: cannot read executable header before spawning Claude Code.", {
42776
- code: codeValue(err, "EACCES"),
42777
- errno: errnoValue(err),
42778
- syscall: syscallValue(err),
42779
- path: pathValue(err) ?? realPath,
42780
- cwd,
42781
- realPath,
42782
- cause: err
42783
- });
43250
+ if (schema.nullable === true && ctx2.version === "openapi-3.0") {
43251
+ baseSchema = z2.nullable(baseSchema);
42784
43252
  }
42785
- if (!["elf", "mach-o", "pe", "shebang-script"].includes(fileType)) {
42786
- throw makeClaudePreflightError("Claude Code executable preflight failed: executable header is not an ELF, Mach-O, PE, or shebang script.", {
42787
- code: "ENOEXEC",
42788
- syscall: "exec",
42789
- path,
42790
- cwd,
42791
- fileType,
42792
- realPath
42793
- });
43253
+ if (schema.readOnly === true) {
43254
+ baseSchema = z2.readonly(baseSchema);
42794
43255
  }
42795
- return { path, realPath, cwd, realCwd, fileType };
42796
- }
42797
- function envFlagEnabled(value) {
42798
- return value === "1" || value?.toLowerCase() === "true";
42799
- }
42800
- function wrapClaudeSpawnErrorForSdk(err, options) {
42801
- const originalCode = codeValue(err, "SPAWN_ERROR");
42802
- const originalMessage = err.message;
42803
- const spawnPath = pathValue(err) ?? options.command;
42804
- const cwd = options.cwd ?? process.cwd();
42805
- const detail = [
42806
- `code=${originalCode}`,
42807
- `errno=${displayValue(errnoValue(err))}`,
42808
- `syscall=${displayValue(syscallValue(err))}`,
42809
- `path=${spawnPath}`,
42810
- `cwd=${cwd}`,
42811
- `command=${options.command}`
42812
- ].join(" ");
42813
- const wrapped = new Error(`Claude Code spawn failed: ${originalMessage} (${detail})`);
42814
- wrapped.name = "ClaudeSpawnDiagnosticError";
42815
- wrapped.code = originalCode === "ENOENT" ? "CLAUDE_BRIDGE_SPAWN_FAILED" : originalCode;
42816
- wrapped.originalCode = originalCode;
42817
- wrapped.originalMessage = originalMessage;
42818
- const errno = errnoValue(err);
42819
- if (errno !== void 0) wrapped.errno = typeof errno === "number" ? errno : Number(errno);
42820
- const syscall = syscallValue(err);
42821
- if (syscall) wrapped.syscall = syscall;
42822
- wrapped.path = spawnPath;
42823
- wrapped.cwd = cwd;
42824
- return wrapped;
42825
- }
42826
- function spawnClaudeCodeWithDiagnostics(options) {
42827
- const pipeStderr = DEBUG || envFlagEnabled(options.env.DEBUG_CLAUDE_AGENT_SDK);
42828
- const child = spawnProcess(options.command, options.args, {
42829
- cwd: options.cwd,
42830
- env: options.env,
42831
- signal: options.signal,
42832
- stdio: ["pipe", "pipe", pipeStderr ? "pipe" : "ignore"],
42833
- windowsHide: true
42834
- });
42835
- if (pipeStderr) {
42836
- child.stderr?.on("data", (data) => {
42837
- for (const line of data.toString().split(/\r?\n/)) {
42838
- if (line) debug(`[cli-stderr spawn] ${line}`);
42839
- }
42840
- });
43256
+ if (schema.default !== void 0) {
43257
+ baseSchema = baseSchema.default(schema.default);
42841
43258
  }
42842
- child.prependListener("error", (err) => {
42843
- const originalStack = err.stack;
42844
- const wrapped = wrapClaudeSpawnErrorForSdk(err, options);
42845
- Object.assign(err, wrapped);
42846
- err.name = wrapped.name;
42847
- err.message = wrapped.message;
42848
- if (originalStack) err.stack = originalStack;
42849
- });
42850
- return {
42851
- stdin: child.stdin,
42852
- stdout: child.stdout,
42853
- get killed() {
42854
- return child.killed;
42855
- },
42856
- get exitCode() {
42857
- return child.exitCode;
42858
- },
42859
- kill: child.kill.bind(child),
42860
- on: child.on.bind(child),
42861
- once: child.once.bind(child),
42862
- off: child.off.bind(child)
42863
- };
42864
- }
42865
-
42866
- // src/tool-pairing-audit.ts
42867
- function contentBlocks(content) {
42868
- return Array.isArray(content) ? content.filter((block) => Boolean(block && typeof block === "object")) : [];
42869
- }
42870
- function toolUses(content) {
42871
- return contentBlocks(content).filter((block) => block.type === "tool_use" && typeof block.id === "string").map((block) => ({ id: block.id, name: typeof block.name === "string" && block.name ? block.name : "unknown" }));
42872
- }
42873
- function toolResultIds(content) {
42874
- const ids = /* @__PURE__ */ new Set();
42875
- for (const block of contentBlocks(content)) {
42876
- if (block.type === "tool_result" && typeof block.tool_use_id === "string") ids.add(block.tool_use_id);
43259
+ const extraMeta = {};
43260
+ const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
43261
+ for (const key of coreMetadataKeys) {
43262
+ if (key in schema) {
43263
+ extraMeta[key] = schema[key];
43264
+ }
42877
43265
  }
42878
- return ids;
42879
- }
42880
- function findUnpairedToolUses(messages) {
42881
- const missing = [];
42882
- for (let i = 0; i < messages.length; i++) {
42883
- const msg = messages[i];
42884
- if (msg?.role !== "assistant") continue;
42885
- const uses = toolUses(msg.content);
42886
- if (uses.length === 0) continue;
42887
- const next = messages[i + 1];
42888
- const nextUserIndex = next?.role === "user" ? i + 1 : null;
42889
- const resultIds = nextUserIndex == null ? /* @__PURE__ */ new Set() : toolResultIds(next.content);
42890
- for (const use2 of uses) {
42891
- if (!resultIds.has(use2.id)) {
42892
- missing.push({ id: use2.id, toolName: use2.name, assistantIndex: i, userIndex: nextUserIndex });
42893
- }
43266
+ const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
43267
+ for (const key of contentMetadataKeys) {
43268
+ if (key in schema) {
43269
+ extraMeta[key] = schema[key];
42894
43270
  }
42895
43271
  }
42896
- return missing;
42897
- }
42898
- function summarizeMissingToolNames(missing) {
42899
- const counts = /* @__PURE__ */ new Map();
42900
- for (const item of missing) counts.set(item.toolName, (counts.get(item.toolName) ?? 0) + 1);
42901
- return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name, count]) => ({ name, count }));
42902
- }
42903
-
42904
- // src/bridge-state.ts
42905
- var sharedSession = null;
42906
- var extensionApi;
42907
- var piUI;
42908
- function setSharedSession(next) {
42909
- sharedSession = next;
42910
- }
42911
- function setExtensionApi(next) {
42912
- extensionApi = next;
42913
- }
42914
- function setPiUI(next) {
42915
- piUI = next;
42916
- }
42917
- function safeNotify(message, level = "warning") {
42918
- try {
42919
- piUI?.notify(message, level);
42920
- } catch (error51) {
42921
- debug("notify failed:", error51);
43272
+ for (const key of Object.keys(schema)) {
43273
+ if (!RECOGNIZED_KEYS.has(key)) {
43274
+ extraMeta[key] = schema[key];
43275
+ }
42922
43276
  }
42923
- }
42924
- function argKeys(args) {
42925
- return Object.keys(args ?? {}).sort();
42926
- }
42927
- function safeToolCallSummary(calls) {
42928
- return calls.map((call) => ({ id: call.id, toolName: call.toolName, argKeys: argKeys(call.arguments) }));
42929
- }
42930
- function compactToolNameSummary(names, limit = 12) {
42931
- const shown = names.slice(0, limit).map(({ name, count }) => count > 1 ? `${name}\xD7${count}` : name);
42932
- if (names.length > limit) shown.push(`+${names.length - limit} more`);
42933
- return shown;
42934
- }
42935
- function reportSyntheticToolResultRepair(missing, context) {
42936
- try {
42937
- if (missing.length === 0) return;
42938
- const toolNames = summarizeMissingToolNames(missing);
42939
- const toolNameSummary = compactToolNameSummary(toolNames);
42940
- const sampledToolCallIds = missing.slice(0, 50).map((item) => item.id);
42941
- diagDump("repair_tool_pairing_synthetic_results", {
42942
- count: missing.length,
42943
- toolNames,
42944
- sampledToolCallIds,
42945
- missing: missing.slice(0, 50),
42946
- ...context
42947
- });
42948
- safeNotify(
42949
- `Claude bridge: ${missing.length} missing tool result(s) repaired with "[no tool result recorded]"${toolNameSummary.length ? ` for ${toolNameSummary.join(", ")}` : ""}. Real tool output was lost before Claude session import; see ${diagLogPath()}.`,
42950
- "error"
42951
- );
42952
- } catch (error51) {
42953
- debug("reportSyntheticToolResultRepair failed:", error51);
43277
+ if (Object.keys(extraMeta).length > 0) {
43278
+ ctx2.registry.add(baseSchema, extraMeta);
43279
+ }
43280
+ if (schema.description) {
43281
+ baseSchema = baseSchema.describe(schema.description);
42954
43282
  }
43283
+ return baseSchema;
42955
43284
  }
42956
- function reportToolResultMismatch(queryCtx, reason, cwd, opts = {}) {
43285
+ function fromJSONSchema(schema, params) {
43286
+ if (typeof schema === "boolean") {
43287
+ return schema ? z2.any() : z2.never();
43288
+ }
43289
+ let normalized;
42957
43290
  try {
42958
- if (queryCtx.reportedToolResultMismatch) return false;
42959
- const progress = queryCtx.toolResultProgress();
42960
- const hasMismatch = progress.expectedCount > 0 ? progress.unresolvedIds.length > 0 || progress.waitingCount > 0 || progress.queuedCount > 0 || progress.unmatchedResultCount > 0 : progress.waitingCount > 0 || progress.queuedCount > 0 || progress.unmatchedResultCount > 0;
42961
- if (!hasMismatch) return false;
42962
- queryCtx.reportedToolResultMismatch = true;
42963
- if (sharedSession) {
42964
- sharedSession = { ...sharedSession, needsRebuild: true, ...opts.forceRotate ? { forceRotate: true } : {} };
42965
- }
42966
- const toolNameSummary = compactToolNameSummary(progress.toolNames);
42967
- diagDump("tool_result_delivery_mismatch", {
42968
- reason,
42969
- cwd,
42970
- progress,
42971
- activeQueryExists: queryCtx.activeQuery !== null,
42972
- sharedSession: sharedSession ? {
42973
- sessionId: sharedSession.sessionId.slice(0, 8),
42974
- cursor: sharedSession.cursor,
42975
- needsRebuild: sharedSession.needsRebuild === true,
42976
- forceRotate: sharedSession.forceRotate === true
42977
- } : null
42978
- });
42979
- safeNotify(
42980
- `Claude bridge: tool result delivery interrupted during ${reason}; delivered ${progress.deliveredCount}/${progress.expectedCount}, resolved ${progress.resolvedCount}/${progress.expectedCount}, waiting=${progress.waitingCount}, queued=${progress.queuedCount}, unmatched=${progress.unmatchedResultCount}${toolNameSummary.length ? `, tools=${toolNameSummary.join(", ")}` : ""}. Claude session will rebuild before the next turn; see ${diagLogPath()}.`,
42981
- "error"
42982
- );
42983
- return true;
42984
- } catch (error51) {
42985
- debug("reportToolResultMismatch failed:", error51);
42986
- return false;
43291
+ normalized = JSON.parse(JSON.stringify(schema));
43292
+ } catch {
43293
+ throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
42987
43294
  }
42988
- }
42989
- function __testSetBridgeIntegrityState(state) {
42990
- if ("ui" in state) piUI = state.ui;
42991
- if ("sharedSession" in state) sharedSession = state.sharedSession ?? null;
42992
- }
42993
- function __testGetBridgeIntegrityState() {
42994
- return { sharedSession };
43295
+ const version2 = detectVersion(normalized, params?.defaultTarget);
43296
+ const defs = normalized.$defs || normalized.definitions || {};
43297
+ const ctx2 = {
43298
+ version: version2,
43299
+ defs,
43300
+ refs: /* @__PURE__ */ new Map(),
43301
+ processing: /* @__PURE__ */ new Set(),
43302
+ rootSchema: normalized,
43303
+ registry: params?.registry ?? globalRegistry
43304
+ };
43305
+ return convertSchema(normalized, ctx2);
42995
43306
  }
42996
43307
 
42997
- // src/connectors.ts
42998
- var DISALLOWED_BUILTIN_TOOLS = [
42999
- "Read",
43000
- "Write",
43001
- "Edit",
43002
- "MultiEdit",
43003
- "Glob",
43004
- "Grep",
43005
- "Bash",
43006
- "Agent",
43007
- "Task",
43008
- "NotebookEdit",
43009
- "EnterWorktree",
43010
- "ExitWorktree",
43011
- "CronList",
43012
- "CronCreate",
43013
- "CronDelete",
43014
- "TeamCreate",
43015
- "TeamDelete",
43016
- "TaskOutput",
43017
- "TaskStop",
43018
- "SendMessage",
43019
- "Skill",
43020
- "TodoRead",
43021
- "TodoWrite",
43022
- "ListMcpResources",
43023
- "ReadMcpResource",
43024
- "WebFetch",
43025
- "WebSearch",
43026
- "AskUserQuestion",
43027
- "EnterPlanMode",
43028
- "ExitPlanMode",
43029
- "ToolSearch",
43030
- "ScheduleWakeup"
43031
- ];
43032
- var CLAUDE_BRIDGE_TOOL_ISOLATION = {
43033
- tools: [],
43034
- disallowedTools: DISALLOWED_BUILTIN_TOOLS,
43035
- allowedTools: [`mcp__${MCP_SERVER_NAME}__*`]
43036
- };
43037
- function connectorsEnabledFromEnv() {
43038
- const v2 = (process.env.CLAUDE_BRIDGE_ENABLE_CONNECTORS ?? "").trim().toLowerCase();
43039
- return v2 === "1" || v2 === "true" || v2 === "yes" || v2 === "on";
43040
- }
43041
- function connectorsEnabledFor(config2) {
43042
- return connectorsEnabledFromEnv() || config2?.provider?.enableConnectors === true;
43043
- }
43044
- var CLAUDE_AI_CONNECTOR_TOOL_PATTERNS = [
43045
- "mcp__claude_ai_Gmail__*",
43046
- "mcp__claude_ai_Google_Calendar__*",
43047
- "mcp__claude_ai_Google_Drive__*",
43048
- "mcp__claude_ai_Slack__*",
43049
- "mcp__claude_ai_Atlassian__*"
43050
- ];
43051
- var CONNECTOR_DISCOVERY_TOOLS = ["ToolSearch", "ListMcpResources", "ReadMcpResource"];
43052
- var CONNECTOR_NS_PREFIX2 = "mcp__claude_ai_";
43053
- var CONNECTOR_NS_GMAIL = `${CONNECTOR_NS_PREFIX2}Gmail__`;
43054
- var CONNECTOR_NS_CALENDAR = `${CONNECTOR_NS_PREFIX2}Google_Calendar__`;
43055
- var CONNECTOR_NS_DRIVE = `${CONNECTOR_NS_PREFIX2}Google_Drive__`;
43056
- var CONNECTOR_NS_SLACK = `${CONNECTOR_NS_PREFIX2}Slack__`;
43057
- var CONNECTOR_NS_ATLASSIAN = `${CONNECTOR_NS_PREFIX2}Atlassian__`;
43058
- var CONNECTOR_READ_VERBS = /* @__PURE__ */ new Set([
43059
- "list",
43060
- "search",
43061
- "get",
43062
- "read",
43063
- "fetch",
43064
- "find",
43065
- "download",
43066
- "describe",
43067
- "query",
43068
- "count",
43069
- "view",
43070
- "lookup",
43071
- "whoami"
43072
- ]);
43073
- var CONNECTOR_MUTATION_WORDS = /* @__PURE__ */ new Set([
43074
- "create",
43075
- "update",
43076
- "delete",
43077
- "remove",
43078
- "add",
43079
- "edit",
43080
- "send",
43081
- "post",
43082
- "write",
43083
- "upload",
43084
- "publish",
43085
- "schedule",
43086
- "transition",
43087
- "archive",
43088
- "move",
43089
- "copy",
43090
- "revoke",
43091
- "assign",
43092
- "invite",
43093
- "share",
43094
- "rename",
43095
- "replace",
43096
- "set",
43097
- "merge",
43098
- "resolve",
43099
- "lock",
43100
- "unlock",
43101
- "acknowledge",
43102
- "ack",
43103
- "book",
43104
- "start",
43105
- "stop",
43106
- "terminate",
43107
- "restart",
43108
- "join",
43109
- "leave",
43110
- "star",
43111
- "unstar",
43112
- "forward",
43113
- "sync",
43114
- "approve",
43115
- "reject",
43116
- "close",
43117
- "reopen",
43118
- "cancel",
43119
- "enable",
43120
- "disable",
43121
- "grant",
43122
- "trigger",
43123
- "execute",
43124
- "apply",
43125
- "submit",
43126
- "pin",
43127
- "unpin",
43128
- "mute",
43129
- "unmute",
43130
- "subscribe",
43131
- "unsubscribe",
43132
- "follow",
43133
- "unfollow",
43134
- "clear",
43135
- "purge",
43136
- "reset",
43137
- "rotate",
43138
- "deploy",
43139
- "install",
43140
- "uninstall",
43141
- "save",
43142
- "store",
43143
- "put",
43144
- "patch",
43145
- "insert",
43146
- "append",
43147
- "prepend",
43148
- "duplicate",
43149
- "restore",
43150
- "revert",
43151
- "import",
43152
- "export",
43153
- "upsert",
43154
- "sign",
43155
- "complete",
43156
- "claim",
43157
- "release",
43158
- "promote",
43159
- "demote",
43160
- "escalate",
43161
- "resend",
43162
- "retry",
43163
- "react",
43164
- "vote"
43165
- ]);
43166
- function connectorNameWords(segment) {
43167
- return segment.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word.toLowerCase());
43308
+ // node_modules/zod/v4/classic/coerce.js
43309
+ var coerce_exports = {};
43310
+ __export(coerce_exports, {
43311
+ bigint: () => bigint3,
43312
+ boolean: () => boolean3,
43313
+ date: () => date4,
43314
+ number: () => number3,
43315
+ string: () => string3
43316
+ });
43317
+ function string3(params) {
43318
+ return _coercedString(ZodString, params);
43168
43319
  }
43169
- var CONNECTOR_WRITE_TOOLS = [
43170
- `${CONNECTOR_NS_GMAIL}create_draft`,
43171
- `${CONNECTOR_NS_GMAIL}create_label`,
43172
- `${CONNECTOR_NS_GMAIL}label_message`,
43173
- `${CONNECTOR_NS_GMAIL}label_thread`,
43174
- `${CONNECTOR_NS_GMAIL}unlabel_message`,
43175
- `${CONNECTOR_NS_GMAIL}unlabel_thread`,
43176
- `${CONNECTOR_NS_GMAIL}apply_sensitive_label`,
43177
- `${CONNECTOR_NS_GMAIL}remove_sensitive_label`,
43178
- `${CONNECTOR_NS_CALENDAR}create_event`,
43179
- `${CONNECTOR_NS_CALENDAR}update_event`,
43180
- `${CONNECTOR_NS_CALENDAR}delete_event`,
43181
- `${CONNECTOR_NS_CALENDAR}respond_to_event`,
43182
- `${CONNECTOR_NS_DRIVE}create_file`,
43183
- `${CONNECTOR_NS_DRIVE}copy_file`,
43184
- // Slack + Atlassian writes, taken from a live enumeration of an account with
43185
- // both connectors attached. The PreToolUse hook already denies these by verb;
43186
- // listing them by id also removes them from the model's context in a
43187
- // read-only session (the CLI matcher needs exact ids). Additive only — an id
43188
- // missing here is still denied at call time.
43189
- `${CONNECTOR_NS_SLACK}slack_send_message`,
43190
- `${CONNECTOR_NS_SLACK}slack_send_message_draft`,
43191
- `${CONNECTOR_NS_SLACK}slack_schedule_message`,
43192
- `${CONNECTOR_NS_SLACK}slack_create_canvas`,
43193
- `${CONNECTOR_NS_SLACK}slack_update_canvas`,
43194
- `${CONNECTOR_NS_ATLASSIAN}createJiraIssue`,
43195
- `${CONNECTOR_NS_ATLASSIAN}editJiraIssue`,
43196
- `${CONNECTOR_NS_ATLASSIAN}transitionJiraIssue`,
43197
- `${CONNECTOR_NS_ATLASSIAN}addCommentToJiraIssue`,
43198
- `${CONNECTOR_NS_ATLASSIAN}addWorklogToJiraIssue`,
43199
- `${CONNECTOR_NS_ATLASSIAN}createIssueLink`,
43200
- `${CONNECTOR_NS_ATLASSIAN}createConfluencePage`,
43201
- `${CONNECTOR_NS_ATLASSIAN}updateConfluencePage`,
43202
- `${CONNECTOR_NS_ATLASSIAN}createConfluenceFooterComment`,
43203
- `${CONNECTOR_NS_ATLASSIAN}createConfluenceInlineComment`,
43204
- `${CONNECTOR_NS_ATLASSIAN}createCompassComponent`,
43205
- `${CONNECTOR_NS_ATLASSIAN}createCompassComponentRelationship`,
43206
- `${CONNECTOR_NS_ATLASSIAN}createCompassCustomFieldDefinition`
43207
- ];
43208
- function isConnectorWriteTool(name) {
43209
- if (!name.startsWith(CONNECTOR_NS_PREFIX2)) return false;
43210
- const sep2 = name.indexOf("__", CONNECTOR_NS_PREFIX2.length);
43211
- if (sep2 <= CONNECTOR_NS_PREFIX2.length) return true;
43212
- const server = name.slice(CONNECTOR_NS_PREFIX2.length, sep2);
43213
- const words = connectorNameWords(name.slice(sep2 + "__".length));
43214
- const serverWords = connectorNameWords(server);
43215
- let skipped = 0;
43216
- while (skipped < serverWords.length && words[skipped] === serverWords[skipped] && !CONNECTOR_MUTATION_WORDS.has(words[skipped])) skipped++;
43217
- const rest = words.slice(skipped);
43218
- if (rest.length === 0) return true;
43219
- if (!CONNECTOR_READ_VERBS.has(rest[0])) return true;
43220
- return rest.some((word) => CONNECTOR_MUTATION_WORDS.has(word));
43320
+ function number3(params) {
43321
+ return _coercedNumber(ZodNumber, params);
43221
43322
  }
43222
- function connectorWriteModeFromEnv() {
43223
- const v2 = (process.env.CLAUDE_BRIDGE_CONNECTOR_WRITE ?? "").trim().toLowerCase();
43224
- if (v2 === "allow") return "allow";
43225
- if (v2 === "deny") return "deny";
43226
- return void 0;
43323
+ function boolean3(params) {
43324
+ return _coercedBoolean(ZodBoolean, params);
43227
43325
  }
43228
- function connectorWriteModeFor(config2) {
43229
- const resolved = connectorWriteModeFromEnv() ?? normalizeConnectorWriteMode(config2?.provider?.connectorWriteMode);
43230
- return resolved === "allow" ? "allow" : "deny";
43326
+ function bigint3(params) {
43327
+ return _coercedBigint(ZodBigInt, params);
43231
43328
  }
43232
- function connectorWriteDenyHook() {
43233
- return async (input) => {
43329
+ function date4(params) {
43330
+ return _coercedDate(ZodDate, params);
43331
+ }
43332
+
43333
+ // node_modules/zod/v4/classic/external.js
43334
+ config(en_default());
43335
+
43336
+ // src/typebox-to-zod.ts
43337
+ function jsonSchemaPropertyToZod(prop) {
43338
+ let base;
43339
+ if (Array.isArray(prop.enum) && prop.enum.length > 0) base = external_exports.enum(prop.enum);
43340
+ else switch (prop.type) {
43341
+ case "string":
43342
+ base = external_exports.string();
43343
+ break;
43344
+ case "number":
43345
+ case "integer":
43346
+ base = external_exports.number();
43347
+ break;
43348
+ case "boolean":
43349
+ base = external_exports.boolean();
43350
+ break;
43351
+ case "array": {
43352
+ base = prop.items ? external_exports.array(jsonSchemaPropertyToZod(prop.items)) : external_exports.array(external_exports.unknown());
43353
+ const minItems = typeof prop.minItems === "number" ? prop.minItems : void 0;
43354
+ if (minItems !== void 0) base = base.min(minItems);
43355
+ break;
43356
+ }
43357
+ case "object": {
43358
+ if (prop.properties && typeof prop.properties === "object" && !Array.isArray(prop.properties)) {
43359
+ const obj = external_exports.object(jsonSchemaToZodShape(prop));
43360
+ base = prop.additionalProperties === false ? obj.strict() : obj.passthrough();
43361
+ } else {
43362
+ base = external_exports.record(external_exports.string(), external_exports.unknown());
43363
+ }
43364
+ break;
43365
+ }
43366
+ default:
43367
+ base = external_exports.unknown();
43368
+ }
43369
+ if (typeof prop.description === "string") base = base.describe(prop.description);
43370
+ return base;
43371
+ }
43372
+ function jsonSchemaToZodShape(schema) {
43373
+ const s = schema;
43374
+ if (!s || s.type !== "object" || !s.properties) return {};
43375
+ const props = s.properties;
43376
+ const required2 = new Set(Array.isArray(s.required) ? s.required : []);
43377
+ const shape = {};
43378
+ for (const [key, prop] of Object.entries(props)) {
43379
+ const zodProp = jsonSchemaPropertyToZod(prop);
43380
+ shape[key] = required2.has(key) ? zodProp : zodProp.optional();
43381
+ }
43382
+ return shape;
43383
+ }
43384
+
43385
+ // src/index.ts
43386
+ import { readFileSync as nodeReadFileSync } from "node:fs";
43387
+
43388
+ // src/pi-ai-compat.ts
43389
+ var dynamicImport = (specifier) => import(specifier);
43390
+ async function resolveGetModels(root, loadCompat = () => dynamicImport("@earendil-works/pi-ai/compat")) {
43391
+ if (typeof root?.getModels === "function") return root.getModels;
43392
+ const compat = await loadCompat();
43393
+ if (typeof compat?.getModels !== "function") throw new Error("pi-ai getModels API is unavailable");
43394
+ return compat.getModels;
43395
+ }
43396
+
43397
+ // src/connector-cache.ts
43398
+ import { createHash } from "node:crypto";
43399
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync } from "node:fs";
43400
+ import { dirname as dirname5, join as join7 } from "node:path";
43401
+ var CACHE_VERSION = 1;
43402
+ var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
43403
+ function connectorCacheScopeKey(env = process.env) {
43404
+ return env.CLAUDE_CONFIG_DIR?.trim() || "<default>";
43405
+ }
43406
+ function connectorCachePath(scopeKey = connectorCacheScopeKey()) {
43407
+ const digest = createHash("sha256").update(scopeKey).digest("hex").slice(0, 16);
43408
+ return join7(piUserDir(), "connector-cache", `${digest}.json`);
43409
+ }
43410
+ function readCachedConnectors(scopeKey = connectorCacheScopeKey(), now = Date.now()) {
43411
+ let raw;
43412
+ try {
43413
+ raw = readFileSync6(connectorCachePath(scopeKey), "utf8");
43414
+ } catch {
43415
+ return void 0;
43416
+ }
43417
+ let parsed;
43418
+ try {
43419
+ parsed = JSON.parse(raw);
43420
+ } catch {
43421
+ return void 0;
43422
+ }
43423
+ if (parsed?.version !== CACHE_VERSION) return void 0;
43424
+ if (parsed?.scope !== scopeKey) return void 0;
43425
+ const savedAt = typeof parsed?.savedAt === "number" ? parsed.savedAt : 0;
43426
+ if (!savedAt || now - savedAt > MAX_AGE_MS || savedAt > now) return void 0;
43427
+ if (!Array.isArray(parsed?.connectors)) return void 0;
43428
+ const connectors = parsed.connectors.filter(
43429
+ (entry) => entry && typeof entry.name === "string" && entry.name.trim()
43430
+ );
43431
+ return connectors.length > 0 ? connectors : void 0;
43432
+ }
43433
+ function writeCachedConnectors(connectors, scopeKey = connectorCacheScopeKey(), now = Date.now()) {
43434
+ if (!Array.isArray(connectors) || connectors.length === 0) return false;
43435
+ const path = connectorCachePath(scopeKey);
43436
+ try {
43437
+ mkdirSync3(dirname5(path), { recursive: true, mode: 448 });
43438
+ writeFileSync(
43439
+ path,
43440
+ JSON.stringify({ version: CACHE_VERSION, scope: scopeKey, savedAt: now, connectors }),
43441
+ { mode: 384 }
43442
+ );
43443
+ return true;
43444
+ } catch {
43445
+ return false;
43446
+ }
43447
+ }
43448
+
43449
+ // src/claude-executable.ts
43450
+ import { spawn as spawnProcess } from "child_process";
43451
+ import { accessSync, constants as fsConstants, readFileSync as readFileSync7, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
43452
+ import { delimiter, join as join8 } from "path";
43453
+ function executableFromPath(name) {
43454
+ const paths = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
43455
+ for (const dir of paths) {
43456
+ const candidate = join8(dir, name);
43234
43457
  try {
43235
- if (input.hook_event_name !== "PreToolUse") return { continue: true };
43236
- if (!isConnectorWriteTool(input.tool_name)) return { continue: true };
43237
- return connectorWriteDenyOutput(String(input.tool_name));
43458
+ accessSync(candidate, fsConstants.X_OK);
43459
+ return candidate;
43238
43460
  } catch {
43239
- const toolName = typeof input?.tool_name === "string" ? input.tool_name : "<unknown>";
43240
- return connectorWriteDenyOutput(toolName);
43241
43461
  }
43242
- };
43462
+ }
43463
+ return void 0;
43243
43464
  }
43244
- function connectorWriteDenyOutput(toolName) {
43245
- return {
43246
- hookSpecificOutput: {
43247
- hookEventName: "PreToolUse",
43248
- permissionDecision: "deny",
43249
- permissionDecisionReason: `Connector write tool "${toolName}" is blocked in read-only connector mode. Connector writes must go through Memsira's gated approval flow.`
43465
+ function resolveClaudeExecutable(configured) {
43466
+ const trimmed = configured?.trim();
43467
+ if (trimmed) return trimmed;
43468
+ if (isolatedFromEnv()) return void 0;
43469
+ return executableFromPath("claude") ?? executableFromPath("claude-code");
43470
+ }
43471
+ function errnoValue(err) {
43472
+ return typeof err?.errno === "number" ? err.errno : void 0;
43473
+ }
43474
+ function syscallValue(err) {
43475
+ return typeof err?.syscall === "string" ? err.syscall : void 0;
43476
+ }
43477
+ function pathValue(err) {
43478
+ const value = err?.path;
43479
+ return typeof value === "string" ? value : void 0;
43480
+ }
43481
+ function codeValue(err, fallback) {
43482
+ const value = err?.code;
43483
+ return typeof value === "string" ? value : fallback;
43484
+ }
43485
+ function displayValue(value) {
43486
+ return value === void 0 || value === null || value === "" ? "<none>" : String(value);
43487
+ }
43488
+ function makeClaudePreflightError(summary, details) {
43489
+ const detail = [
43490
+ `code=${details.code}`,
43491
+ `errno=${displayValue(details.errno)}`,
43492
+ `syscall=${displayValue(details.syscall)}`,
43493
+ `path=${details.path}`,
43494
+ `cwd=${details.cwd}`,
43495
+ ...details.fileType ? [`fileType=${details.fileType}`] : [],
43496
+ ...details.realPath ? [`realPath=${details.realPath}`] : []
43497
+ ].join(" ");
43498
+ const error51 = new Error(`${summary} (${detail})`);
43499
+ error51.name = "ClaudeExecutablePreflightError";
43500
+ error51.code = details.code;
43501
+ if (details.errno !== void 0) error51.errno = typeof details.errno === "number" ? details.errno : Number(details.errno);
43502
+ if (details.syscall) error51.syscall = details.syscall;
43503
+ error51.path = details.path;
43504
+ error51.cwd = details.cwd;
43505
+ if (details.fileType) error51.fileType = details.fileType;
43506
+ if (details.realPath) error51.realPath = details.realPath;
43507
+ if (details.cause !== void 0) error51.cause = details.cause;
43508
+ return error51;
43509
+ }
43510
+ function classifyClaudeExecutableBytes(bytes) {
43511
+ if (bytes.length === 0) return "empty";
43512
+ if (bytes.length >= 2 && bytes[0] === 35 && bytes[1] === 33) return "shebang-script";
43513
+ if (bytes.length >= 4 && bytes[0] === 127 && bytes[1] === 69 && bytes[2] === 76 && bytes[3] === 70) return "elf";
43514
+ if (bytes.length >= 2 && bytes[0] === 77 && bytes[1] === 90) return "pe";
43515
+ if (bytes.length >= 4) {
43516
+ const magic = bytes[0] * 16777216 + bytes[1] * 65536 + bytes[2] * 256 + bytes[3];
43517
+ if (magic === 4277009102 || magic === 4277009103 || magic === 3472551422 || magic === 3489328638 || magic === 3405691582 || magic === 3199925962) return "mach-o";
43518
+ }
43519
+ return "unknown";
43520
+ }
43521
+ function preflightClaudeExecutable(path, cwd) {
43522
+ let realCwd;
43523
+ try {
43524
+ const cwdStat = statSync2(cwd);
43525
+ if (!cwdStat.isDirectory()) {
43526
+ throw makeClaudePreflightError("Claude Code spawn cwd preflight failed: cwd is not a directory.", {
43527
+ code: "ENOTDIR",
43528
+ syscall: "chdir",
43529
+ path: cwd,
43530
+ cwd
43531
+ });
43250
43532
  }
43251
- };
43533
+ accessSync(cwd, fsConstants.X_OK);
43534
+ realCwd = realpathSync2(cwd);
43535
+ } catch (err) {
43536
+ if (err.name === "ClaudeExecutablePreflightError") throw err;
43537
+ throw makeClaudePreflightError("Claude Code spawn cwd preflight failed: cwd is not reachable before spawning Claude Code.", {
43538
+ code: codeValue(err, "EACCES"),
43539
+ errno: errnoValue(err),
43540
+ syscall: syscallValue(err),
43541
+ path: pathValue(err) ?? cwd,
43542
+ cwd,
43543
+ cause: err
43544
+ });
43545
+ }
43546
+ let realPath;
43547
+ try {
43548
+ const stat = statSync2(path);
43549
+ if (!stat.isFile()) {
43550
+ throw makeClaudePreflightError("Claude Code executable preflight failed: resolved path is not a file.", {
43551
+ code: "EACCES",
43552
+ syscall: "exec",
43553
+ path,
43554
+ cwd
43555
+ });
43556
+ }
43557
+ accessSync(path, fsConstants.X_OK);
43558
+ realPath = realpathSync2(path);
43559
+ } catch (err) {
43560
+ if (err.name === "ClaudeExecutablePreflightError") throw err;
43561
+ throw makeClaudePreflightError("Claude Code executable preflight failed: cannot access resolved executable before spawning Claude Code.", {
43562
+ code: codeValue(err, "ENOENT"),
43563
+ errno: errnoValue(err),
43564
+ syscall: syscallValue(err),
43565
+ path: pathValue(err) ?? path,
43566
+ cwd,
43567
+ cause: err
43568
+ });
43569
+ }
43570
+ let fileType;
43571
+ try {
43572
+ fileType = classifyClaudeExecutableBytes(readFileSync7(realPath).subarray(0, 16));
43573
+ } catch (err) {
43574
+ throw makeClaudePreflightError("Claude Code executable preflight failed: cannot read executable header before spawning Claude Code.", {
43575
+ code: codeValue(err, "EACCES"),
43576
+ errno: errnoValue(err),
43577
+ syscall: syscallValue(err),
43578
+ path: pathValue(err) ?? realPath,
43579
+ cwd,
43580
+ realPath,
43581
+ cause: err
43582
+ });
43583
+ }
43584
+ if (!["elf", "mach-o", "pe", "shebang-script"].includes(fileType)) {
43585
+ throw makeClaudePreflightError("Claude Code executable preflight failed: executable header is not an ELF, Mach-O, PE, or shebang script.", {
43586
+ code: "ENOEXEC",
43587
+ syscall: "exec",
43588
+ path,
43589
+ cwd,
43590
+ fileType,
43591
+ realPath
43592
+ });
43593
+ }
43594
+ return { path, realPath, cwd, realCwd, fileType };
43252
43595
  }
43253
- function connectorQueryOptions(connectorsEnabled, writeMode = "deny") {
43254
- const isolation = toolIsolationForQuery(connectorsEnabled, writeMode);
43255
- if (!connectorsEnabled || writeMode === "allow") return isolation;
43256
- return { ...isolation, hooks: { PreToolUse: [{ hooks: [connectorWriteDenyHook()] }] } };
43596
+ function envFlagEnabled(value) {
43597
+ return value === "1" || value?.toLowerCase() === "true";
43257
43598
  }
43258
- function toolIsolationForQuery(connectorsEnabled, writeMode = "deny") {
43259
- if (!connectorsEnabled) return CLAUDE_BRIDGE_TOOL_ISOLATION;
43260
- const disallowedTools = DISALLOWED_BUILTIN_TOOLS.filter((t) => !CONNECTOR_DISCOVERY_TOOLS.includes(t));
43261
- if (writeMode !== "allow") disallowedTools.push(...CONNECTOR_WRITE_TOOLS);
43599
+ function wrapClaudeSpawnErrorForSdk(err, options) {
43600
+ const originalCode = codeValue(err, "SPAWN_ERROR");
43601
+ const originalMessage = err.message;
43602
+ const spawnPath = pathValue(err) ?? options.command;
43603
+ const cwd = options.cwd ?? process.cwd();
43604
+ const detail = [
43605
+ `code=${originalCode}`,
43606
+ `errno=${displayValue(errnoValue(err))}`,
43607
+ `syscall=${displayValue(syscallValue(err))}`,
43608
+ `path=${spawnPath}`,
43609
+ `cwd=${cwd}`,
43610
+ `command=${options.command}`
43611
+ ].join(" ");
43612
+ const wrapped = new Error(`Claude Code spawn failed: ${originalMessage} (${detail})`);
43613
+ wrapped.name = "ClaudeSpawnDiagnosticError";
43614
+ wrapped.code = originalCode === "ENOENT" ? "CLAUDE_BRIDGE_SPAWN_FAILED" : originalCode;
43615
+ wrapped.originalCode = originalCode;
43616
+ wrapped.originalMessage = originalMessage;
43617
+ const errno = errnoValue(err);
43618
+ if (errno !== void 0) wrapped.errno = typeof errno === "number" ? errno : Number(errno);
43619
+ const syscall = syscallValue(err);
43620
+ if (syscall) wrapped.syscall = syscall;
43621
+ wrapped.path = spawnPath;
43622
+ wrapped.cwd = cwd;
43623
+ return wrapped;
43624
+ }
43625
+ function spawnClaudeCodeWithDiagnostics(options) {
43626
+ const pipeStderr = DEBUG || envFlagEnabled(options.env.DEBUG_CLAUDE_AGENT_SDK);
43627
+ const child = spawnProcess(options.command, options.args, {
43628
+ cwd: options.cwd,
43629
+ env: options.env,
43630
+ signal: options.signal,
43631
+ stdio: ["pipe", "pipe", pipeStderr ? "pipe" : "ignore"],
43632
+ windowsHide: true
43633
+ });
43634
+ if (pipeStderr) {
43635
+ child.stderr?.on("data", (data) => {
43636
+ for (const line of data.toString().split(/\r?\n/)) {
43637
+ if (line) debug(`[cli-stderr spawn] ${line}`);
43638
+ }
43639
+ });
43640
+ }
43641
+ child.prependListener("error", (err) => {
43642
+ const originalStack = err.stack;
43643
+ const wrapped = wrapClaudeSpawnErrorForSdk(err, options);
43644
+ Object.assign(err, wrapped);
43645
+ err.name = wrapped.name;
43646
+ err.message = wrapped.message;
43647
+ if (originalStack) err.stack = originalStack;
43648
+ });
43262
43649
  return {
43263
- disallowedTools,
43264
- allowedTools: [...CLAUDE_BRIDGE_TOOL_ISOLATION.allowedTools, ...CLAUDE_AI_CONNECTOR_TOOL_PATTERNS]
43650
+ stdin: child.stdin,
43651
+ stdout: child.stdout,
43652
+ get killed() {
43653
+ return child.killed;
43654
+ },
43655
+ get exitCode() {
43656
+ return child.exitCode;
43657
+ },
43658
+ kill: child.kill.bind(child),
43659
+ on: child.on.bind(child),
43660
+ once: child.once.bind(child),
43661
+ off: child.off.bind(child)
43265
43662
  };
43266
43663
  }
43267
43664
 
43268
43665
  // node_modules/cc-session-io/dist/chunk-D6EZBJOC.js
43269
43666
  import { randomUUID } from "crypto";
43270
- import { mkdirSync as mkdirSync3, writeFileSync, appendFileSync as appendFileSync3, existsSync as existsSync6, rmSync as rmSync2 } from "fs";
43271
- import { dirname as dirname5 } from "path";
43272
- import { readFileSync as readFileSync7 } from "fs";
43667
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync3, existsSync as existsSync6, rmSync as rmSync2 } from "fs";
43668
+ import { dirname as dirname6 } from "path";
43669
+ import { readFileSync as readFileSync8 } from "fs";
43273
43670
  import { realpathSync as realpathSync3 } from "fs";
43274
43671
  import { homedir as homedir3 } from "os";
43275
- import { join as join8 } from "path";
43672
+ import { join as join9 } from "path";
43276
43673
  function parseJsonl(content) {
43277
43674
  return content.split("\n").filter((line) => line.trim()).map(parseRecord);
43278
43675
  }
43279
43676
  function parseJsonlFile(path) {
43280
- return parseJsonl(readFileSync7(path, "utf-8"));
43677
+ return parseJsonl(readFileSync8(path, "utf-8"));
43281
43678
  }
43282
43679
  function parseRecord(line) {
43283
43680
  const raw = JSON.parse(line);
@@ -43290,7 +43687,7 @@ function serializeRecord(record2) {
43290
43687
  }
43291
43688
  var MAX_SANITIZED_LENGTH = 200;
43292
43689
  function getClaudeDir(claudeDir) {
43293
- return claudeDir ?? process.env.CLAUDE_CONFIG_DIR ?? join8(homedir3(), ".claude");
43690
+ return claudeDir ?? process.env.CLAUDE_CONFIG_DIR ?? join9(homedir3(), ".claude");
43294
43691
  }
43295
43692
  function normalizeProjectPath(projectPath) {
43296
43693
  try {
@@ -43308,10 +43705,10 @@ function projectPathToHash(projectPath) {
43308
43705
  return `${sanitized.slice(0, MAX_SANITIZED_LENGTH)}-${Math.abs(h).toString(36)}`;
43309
43706
  }
43310
43707
  function getProjectDir(projectPath, claudeDir) {
43311
- return join8(getClaudeDir(claudeDir), "projects", projectPathToHash(normalizeProjectPath(projectPath)));
43708
+ return join9(getClaudeDir(claudeDir), "projects", projectPathToHash(normalizeProjectPath(projectPath)));
43312
43709
  }
43313
43710
  function getSessionPath(sessionId, projectPath, claudeDir) {
43314
- return join8(getProjectDir(projectPath, claudeDir), `${sessionId}.jsonl`);
43711
+ return join9(getProjectDir(projectPath, claudeDir), `${sessionId}.jsonl`);
43315
43712
  }
43316
43713
  function repairToolPairing(messages) {
43317
43714
  const result = [];
@@ -43646,15 +44043,15 @@ var Session = class {
43646
44043
  /** Write pending records to disk. Creates the file/directory if needed. */
43647
44044
  save() {
43648
44045
  if (this._pendingRecords.length === 0) return;
43649
- const dir = dirname5(this.jsonlPath);
44046
+ const dir = dirname6(this.jsonlPath);
43650
44047
  if (!existsSync6(dir)) {
43651
- mkdirSync3(dir, { recursive: true });
44048
+ mkdirSync4(dir, { recursive: true });
43652
44049
  }
43653
44050
  const data = this._pendingRecords.map((r) => serializeRecord(r) + "\n").join("");
43654
44051
  if (this._fileExists) {
43655
44052
  appendFileSync3(this.jsonlPath, data, "utf-8");
43656
44053
  } else {
43657
- writeFileSync(this.jsonlPath, data, "utf-8");
44054
+ writeFileSync2(this.jsonlPath, data, "utf-8");
43658
44055
  this._fileExists = true;
43659
44056
  }
43660
44057
  this._records.push(...this._pendingRecords);
@@ -43709,7 +44106,7 @@ function readSession(jsonlPath, projectPath) {
43709
44106
  }
43710
44107
 
43711
44108
  // src/session-persistence.ts
43712
- import { createHash } from "crypto";
44109
+ import { createHash as createHash2 } from "crypto";
43713
44110
  import { realpathSync as realpathSync4, statSync as statSync4 } from "fs";
43714
44111
  import { resolve as pathResolve } from "path";
43715
44112
 
@@ -43800,7 +44197,7 @@ function fingerprintMessages(messages) {
43800
44197
  }
43801
44198
  return message;
43802
44199
  });
43803
- return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
44200
+ return createHash2("sha256").update(JSON.stringify(normalized)).digest("hex");
43804
44201
  }
43805
44202
  function readBuiltSessionContext(sessionManager) {
43806
44203
  const built = typeof sessionManager?.buildSessionContext === "function" ? sessionManager.buildSessionContext() : void 0;
@@ -43909,6 +44306,7 @@ function convertAndImportMessages(session, messages, customToolNameToSdk, cwd) {
43909
44306
  );
43910
44307
  }
43911
44308
  const missingToolResults = findUnpairedToolUses(anthropicMessages);
44309
+ if (missingToolResults.length > 0) insertLostToolResultPlaceholders(anthropicMessages, missingToolResults);
43912
44310
  const repaired = repairToolPairing(anthropicMessages);
43913
44311
  if (missingToolResults.length > 0) {
43914
44312
  reportSyntheticToolResultRepair(missingToolResults, {
@@ -44097,18 +44495,22 @@ function createStreamIdleWatchdog({
44097
44495
  // src/rate-limit.ts
44098
44496
  var RATE_LIMIT_AUTO_RESUME_EVENT = "vstack:rate-limit";
44099
44497
  var RATE_LIMIT_TOKEN = "\x1B[31m[rate-limit]\x1B[39m";
44100
- function isExtraUsageRequiredMessage(value) {
44101
- let text;
44102
- if (typeof value === "string") text = value;
44103
- else if (value instanceof Error) text = value.message;
44104
- else {
44105
- try {
44106
- text = JSON.stringify(value ?? "");
44107
- } catch {
44108
- text = String(value);
44109
- }
44498
+ var USAGE_LIMIT_PREFIXES = Array.isArray(qO) ? qO : [];
44499
+ function coerceMessageText(value) {
44500
+ if (typeof value === "string") return value;
44501
+ if (value instanceof Error) return value.message;
44502
+ try {
44503
+ return JSON.stringify(value ?? "");
44504
+ } catch {
44505
+ return String(value);
44110
44506
  }
44111
- return /extra[-\s]?usage|overage|extra usage billing|extra usage credits|1M context/i.test(text);
44507
+ }
44508
+ function isExtraUsageRequiredMessage(value) {
44509
+ return /extra[-\s]?usage|overage|extra usage billing|extra usage credits|1M context/i.test(coerceMessageText(value));
44510
+ }
44511
+ function isUsageLimitMessage(value) {
44512
+ const text = coerceMessageText(value);
44513
+ return USAGE_LIMIT_PREFIXES.some((prefix) => text.includes(prefix));
44112
44514
  }
44113
44515
  function uniqueNonEmptyLines(values) {
44114
44516
  const seen = /* @__PURE__ */ new Set();
@@ -44121,9 +44523,15 @@ function uniqueNonEmptyLines(values) {
44121
44523
  }
44122
44524
  return out;
44123
44525
  }
44526
+ function resetTimestampMs(value) {
44527
+ let parsed = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
44528
+ if (!Number.isFinite(parsed)) return void 0;
44529
+ if (typeof value === "number" && Math.abs(parsed) < 1e12) parsed *= 1e3;
44530
+ return parsed;
44531
+ }
44124
44532
  function formatResetTimestamp(value) {
44125
- const parsed = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
44126
- if (!Number.isFinite(parsed)) return "unknown";
44533
+ const parsed = resetTimestampMs(value);
44534
+ if (parsed === void 0) return "unknown";
44127
44535
  return new Date(parsed).toLocaleString(void 0, {
44128
44536
  day: "numeric",
44129
44537
  hour: "numeric",
@@ -44200,10 +44608,17 @@ function mapToolArgs(toolName, args) {
44200
44608
  // src/assistant-stream.ts
44201
44609
  import { calculateCost } from "@earendil-works/pi-ai";
44202
44610
  function updateUsage(output, usage, model) {
44203
- if (usage.input_tokens != null) output.usage.input = usage.input_tokens;
44204
- if (usage.output_tokens != null) output.usage.output = usage.output_tokens;
44205
- if (usage.cache_read_input_tokens != null) output.usage.cacheRead = usage.cache_read_input_tokens;
44206
- if (usage.cache_creation_input_tokens != null) output.usage.cacheWrite = usage.cache_creation_input_tokens;
44611
+ const c = ctx();
44612
+ const current = c.currentMessageUsage;
44613
+ const carry = c.turnUsageCarry;
44614
+ if (usage.input_tokens != null) current.input = usage.input_tokens;
44615
+ if (usage.output_tokens != null) current.output = usage.output_tokens;
44616
+ if (usage.cache_read_input_tokens != null) current.cacheRead = usage.cache_read_input_tokens;
44617
+ if (usage.cache_creation_input_tokens != null) current.cacheWrite = usage.cache_creation_input_tokens;
44618
+ output.usage.input = carry.input + current.input;
44619
+ output.usage.output = carry.output + current.output;
44620
+ output.usage.cacheRead = carry.cacheRead + current.cacheRead;
44621
+ output.usage.cacheWrite = carry.cacheWrite + current.cacheWrite;
44207
44622
  output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
44208
44623
  calculateCost(model, output.usage);
44209
44624
  const promptTokens = output.usage.input + output.usage.cacheRead + output.usage.cacheWrite;
@@ -44229,20 +44644,60 @@ function parsePartialJson(input, fallback) {
44229
44644
  return fallback;
44230
44645
  }
44231
44646
  }
44232
- function ensureTurnStarted() {
44233
- if (!ctx().turnStarted && ctx().currentPiStream && ctx().turnOutput) {
44234
- ctx().currentPiStream.push({ type: "start", partial: ctx().turnOutput });
44235
- ctx().turnStarted = true;
44647
+ function ensureTurnStarted(c = ctx()) {
44648
+ if (!c.turnStarted && c.currentPiStream && c.turnOutput) {
44649
+ c.currentPiStream.push({ type: "start", partial: c.turnOutput });
44650
+ c.turnStarted = true;
44236
44651
  }
44237
44652
  }
44238
- function finalizeCurrentStream(stopReason) {
44239
- if (!ctx().currentPiStream || !ctx().turnOutput) return;
44240
- debug(`provider: finalizeCurrentStream called, stopReason=${stopReason}, turnOutput=${JSON.stringify({ stopReason: ctx().turnOutput.stopReason, error: ctx().turnOutput.errorMessage })}`);
44241
- if (!ctx().turnStarted) ensureTurnStarted();
44653
+ function finalizeCurrentStream(stopReason, c = ctx()) {
44654
+ if (!c.currentPiStream || !c.turnOutput) return;
44655
+ debug(`provider: finalizeCurrentStream called, stopReason=${stopReason}, turnOutput=${JSON.stringify({ stopReason: c.turnOutput.stopReason, error: c.turnOutput.errorMessage })}`);
44656
+ if (!c.turnStarted) ensureTurnStarted(c);
44242
44657
  const reason = stopReason === "length" ? "length" : "stop";
44243
- ctx().currentPiStream.push({ type: "done", reason, message: ctx().turnOutput });
44244
- ctx().currentPiStream.end();
44245
- ctx().currentPiStream = null;
44658
+ c.currentPiStream.push({ type: "done", reason, message: c.turnOutput });
44659
+ c.currentPiStream.end();
44660
+ c.currentPiStream = null;
44661
+ }
44662
+ var TOOL_USE_END_GRACE_MS = 1500;
44663
+ function endToolUseTurn(c) {
44664
+ if (!c.currentPiStream || !c.turnOutput) return;
44665
+ cancelScheduledToolUseEnd(c);
44666
+ c.turnOutput.stopReason = "toolUse";
44667
+ c.currentPiStream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
44668
+ c.currentPiStream.end();
44669
+ c.currentPiStream = null;
44670
+ }
44671
+ function cancelScheduledToolUseEnd(c) {
44672
+ if (!c.scheduledToolUseEnd) return;
44673
+ clearTimeout(c.scheduledToolUseEnd.timer);
44674
+ c.scheduledToolUseEnd = null;
44675
+ }
44676
+ function scheduleToolUseTurnEnd(c, action, source) {
44677
+ if (!c.currentPiStream || !c.turnOutput) return;
44678
+ if (c.scheduledToolUseEnd?.stream === c.currentPiStream) return;
44679
+ cancelScheduledToolUseEnd(c);
44680
+ const stream = c.currentPiStream;
44681
+ const timer = setTimeout(() => {
44682
+ if (c.currentPiStream !== stream) return;
44683
+ debug(`scheduleToolUseTurnEnd: no terminal stream event within ${TOOL_USE_END_GRACE_MS}ms (${source}) \u2014 force-ending tool_use turn`);
44684
+ c.scheduledToolUseEnd = null;
44685
+ action();
44686
+ }, TOOL_USE_END_GRACE_MS);
44687
+ timer.unref?.();
44688
+ c.scheduledToolUseEnd = { stream, timer };
44689
+ }
44690
+ function reapStaleQueuedResults(c) {
44691
+ const stale = c.takeStaleQueuedResults();
44692
+ if (stale.length === 0) return;
44693
+ const names = stale.map((entry) => entry.toolName);
44694
+ debug(`reapStaleQueuedResults: dropping ${stale.length} queued tool result(s) with no possible consumer:`, names.join(", "));
44695
+ diagDump("stale_queued_tool_results_dropped", { count: stale.length, stale });
44696
+ appendIntegrityEntry("stale_queued_tool_results_dropped", { count: stale.length, stale });
44697
+ safeNotify(
44698
+ `Claude bridge: dropped ${stale.length} tool result(s) whose handler never matched (${names.slice(0, 6).join(", ")}${names.length > 6 ? ", \u2026" : ""}). The model saw an error for these calls and may retry them.`,
44699
+ "warning"
44700
+ );
44246
44701
  }
44247
44702
  function updateTurnOutputModel(modelId) {
44248
44703
  const c = ctx();
@@ -44251,6 +44706,29 @@ function updateTurnOutputModel(modelId) {
44251
44706
  debug(`provider: active Claude model changed ${c.turnOutput.model} -> ${modelId}`);
44252
44707
  c.turnOutput.model = modelId;
44253
44708
  }
44709
+ function finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, toolName, mappedArgs) {
44710
+ if (!queryCtx.currentPiStream || !queryCtx.turnOutput) return;
44711
+ let idx = queryCtx.turnBlocks.findIndex((b) => b.type === "toolCall" && b.id === toolCallId);
44712
+ if (idx >= 0) {
44713
+ const block = queryCtx.turnBlocks[idx];
44714
+ if ("partialJson" in block) {
44715
+ block.arguments = mapToolArgs(block.name, parsePartialJson(block.partialJson, block.arguments));
44716
+ queryCtx.updateToolCallArgs(block.id, block.arguments);
44717
+ delete block.partialJson;
44718
+ delete block.index;
44719
+ queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: queryCtx.turnOutput });
44720
+ }
44721
+ } else {
44722
+ queryCtx.turnBlocks.push({ type: "toolCall", id: toolCallId, name: toolName, arguments: mappedArgs });
44723
+ idx = queryCtx.turnBlocks.length - 1;
44724
+ const block = queryCtx.turnBlocks[idx];
44725
+ queryCtx.currentPiStream.push({ type: "toolcall_start", contentIndex: idx, partial: queryCtx.turnOutput });
44726
+ queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: queryCtx.turnOutput });
44727
+ }
44728
+ queryCtx.turnSawToolCall = true;
44729
+ debug(`mcp handler: finalizing tool_use turn from MCP invocation [${toolCallId}] (${toolName}) \u2014 terminal stream events never arrived`);
44730
+ endToolUseTurn(queryCtx);
44731
+ }
44254
44732
  function processStreamEvent(message, customToolNameToPi, model) {
44255
44733
  const c = ctx();
44256
44734
  if (!c.currentPiStream || !c.turnOutput) return;
@@ -44261,7 +44739,9 @@ function processStreamEvent(message, customToolNameToPi, model) {
44261
44739
  return;
44262
44740
  }
44263
44741
  if (event?.type === "message_start") {
44742
+ reapStaleQueuedResults(c);
44264
44743
  c.resetToolTracking();
44744
+ c.beginChildMessage(event.message?.id);
44265
44745
  updateTurnOutputModel(event.message?.model);
44266
44746
  if (event.message?.usage) updateUsage(c.turnOutput, event.message.usage, model);
44267
44747
  return;
@@ -44269,6 +44749,12 @@ function processStreamEvent(message, customToolNameToPi, model) {
44269
44749
  if (event?.type === "content_block_start") {
44270
44750
  c.turnSawStreamEvent = true;
44271
44751
  ensureTurnStarted();
44752
+ c.childExecutedStreamIndexes.delete(event.index);
44753
+ if (event.content_block?.type === "tool_use" && isChildExecutedTool(event.content_block.name)) {
44754
+ c.noteChildExecutedToolCall(event.content_block.id, event.content_block.name, event.index);
44755
+ debug(`processStreamEvent: child-executed tool ${event.content_block.name} [${event.content_block.id}] \u2014 not mirrored as a Pi tool call`);
44756
+ return;
44757
+ }
44272
44758
  if (event.content_block?.type === "text") {
44273
44759
  c.turnBlocks.push({ type: "text", text: "", index: event.index });
44274
44760
  c.currentPiStream.push({ type: "text_start", contentIndex: c.turnBlocks.length - 1, partial: c.turnOutput });
@@ -44294,6 +44780,10 @@ function processStreamEvent(message, customToolNameToPi, model) {
44294
44780
  return;
44295
44781
  }
44296
44782
  if (event?.type === "content_block_delta") {
44783
+ if (c.childExecutedStreamIndexes.has(event.index)) {
44784
+ c.turnSawStreamEvent = true;
44785
+ return;
44786
+ }
44297
44787
  const index = c.turnBlocks.findIndex((b) => b.index === event.index);
44298
44788
  const block = c.turnBlocks[index];
44299
44789
  if (!block) {
@@ -44319,6 +44809,10 @@ function processStreamEvent(message, customToolNameToPi, model) {
44319
44809
  return;
44320
44810
  }
44321
44811
  if (event?.type === "content_block_stop") {
44812
+ if (c.childExecutedStreamIndexes.has(event.index)) {
44813
+ c.turnSawStreamEvent = true;
44814
+ return;
44815
+ }
44322
44816
  const index = c.turnBlocks.findIndex((b) => b.index === event.index);
44323
44817
  const block = c.turnBlocks[index];
44324
44818
  if (!block) {
@@ -44349,10 +44843,7 @@ function processStreamEvent(message, customToolNameToPi, model) {
44349
44843
  return;
44350
44844
  }
44351
44845
  if (event?.type === "message_stop" && c.turnSawToolCall) {
44352
- c.turnOutput.stopReason = "toolUse";
44353
- c.currentPiStream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
44354
- c.currentPiStream.end();
44355
- c.currentPiStream = null;
44846
+ endToolUseTurn(c);
44356
44847
  return;
44357
44848
  }
44358
44849
  if (event?.type !== "message_stop" && event?.type !== "ping") {
@@ -44365,6 +44856,11 @@ function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameT
44365
44856
  let sawToolUse = false;
44366
44857
  for (const block of assistantMsg.content) {
44367
44858
  if (block.type !== "tool_use") continue;
44859
+ if (isChildExecutedTool(block.name)) {
44860
+ c.noteChildExecutedToolCall(block.id, block.name);
44861
+ debug(`assistant message: child-executed tool ${block.name} [${block.id}] \u2014 not mirrored as a Pi tool call`);
44862
+ continue;
44863
+ }
44368
44864
  sawToolUse = true;
44369
44865
  const existingIdx = c.turnBlocks.findIndex((b) => b.type === "toolCall" && b.id === block.id);
44370
44866
  const name = mapToolName(block.name, customToolNameToPi);
@@ -44394,9 +44890,24 @@ function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameT
44394
44890
  c.currentPiStream?.push({ type: "toolcall_start", contentIndex: idx, partial: c.turnOutput });
44395
44891
  c.currentPiStream?.push({ type: "toolcall_end", contentIndex: idx, toolCall: toolBlock, partial: c.turnOutput });
44396
44892
  }
44397
- if (assistantMsg.usage && c.turnOutput) updateUsage(c.turnOutput, assistantMsg.usage, model);
44893
+ if (assistantMsg.usage && c.turnOutput && c.currentPiStream) updateUsage(c.turnOutput, assistantMsg.usage, model);
44398
44894
  return sawToolUse;
44399
44895
  }
44896
+ function noteChildExecutedToolResults(message) {
44897
+ const c = ctx();
44898
+ if (c.childExecutedToolCalls.size === 0) return;
44899
+ const content = message.message?.content;
44900
+ if (!Array.isArray(content)) return;
44901
+ for (const block of content) {
44902
+ if (block?.type !== "tool_result") continue;
44903
+ const name = c.childExecutedToolCalls.get(block.tool_use_id);
44904
+ if (!name) continue;
44905
+ const isError = block.is_error === true;
44906
+ const byteSize = connectorResultByteSize(block.content);
44907
+ const audited = recordConnectorCallResult(c, block.tool_use_id, name, isError, byteSize);
44908
+ debug(`child-executed tool result: ${name} [${block.tool_use_id}] isError=${isError} byteSize=${byteSize ?? "unknown"} audited=${audited}`);
44909
+ }
44910
+ }
44400
44911
  function processAssistantMessage(message, model, customToolNameToPi) {
44401
44912
  const c = ctx();
44402
44913
  const assistantMsg = message.message;
@@ -44405,20 +44916,21 @@ function processAssistantMessage(message, model, customToolNameToPi) {
44405
44916
  if (c.turnSawStreamEvent) {
44406
44917
  if (appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameToPi)) {
44407
44918
  c.turnSawToolCall = true;
44408
- if (c.currentPiStream && c.turnOutput) {
44409
- c.turnOutput.stopReason = "toolUse";
44410
- c.currentPiStream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
44411
- c.currentPiStream.end();
44412
- c.currentPiStream = null;
44413
- debug("processAssistantMessage boundary: ended streamed tool_use turn from assistant message");
44414
- }
44919
+ scheduleToolUseTurnEnd(c, () => endToolUseTurn(c), "assistant-boundary");
44415
44920
  }
44416
44921
  return;
44417
44922
  }
44418
- c.resetToolTracking();
44419
- debug(`processAssistantMessage fallback: ${assistantMsg.content.length} blocks, types=${assistantMsg.content.map((b) => b.type).join(",")}`);
44923
+ const sameMessage = typeof assistantMsg.id === "string" && assistantMsg.id.length > 0 && assistantMsg.id === c.currentMessageId;
44924
+ if (!sameMessage) {
44925
+ reapStaleQueuedResults(c);
44926
+ c.resetToolTracking();
44927
+ }
44928
+ c.beginChildMessage(assistantMsg.id);
44929
+ debug(`processAssistantMessage fallback: ${assistantMsg.content.length} blocks, types=${assistantMsg.content.map((b) => b.type).join(",")}${sameMessage ? " (same message re-yield)" : ""}`);
44930
+ const alreadyRendered = (type, content) => c.turnBlocks.some((b) => b.type === type && (type === "text" ? b.text : b.thinking) === content);
44420
44931
  for (const block of assistantMsg.content) {
44421
44932
  if (block.type === "text" && block.text) {
44933
+ if (alreadyRendered("text", block.text)) continue;
44422
44934
  ensureTurnStarted();
44423
44935
  c.turnBlocks.push({ type: "text", text: block.text });
44424
44936
  const idx = c.turnBlocks.length - 1;
@@ -44426,6 +44938,7 @@ function processAssistantMessage(message, model, customToolNameToPi) {
44426
44938
  c.currentPiStream?.push({ type: "text_delta", contentIndex: idx, delta: block.text, partial: c.turnOutput });
44427
44939
  c.currentPiStream?.push({ type: "text_end", contentIndex: idx, content: block.text, partial: c.turnOutput });
44428
44940
  } else if (block.type === "thinking") {
44941
+ if (alreadyRendered("thinking", block.thinking ?? "")) continue;
44429
44942
  ensureTurnStarted();
44430
44943
  c.turnBlocks.push({ type: "thinking", thinking: block.thinking ?? "", thinkingSignature: block.signature ?? "" });
44431
44944
  const idx = c.turnBlocks.length - 1;
@@ -44433,11 +44946,24 @@ function processAssistantMessage(message, model, customToolNameToPi) {
44433
44946
  if (block.thinking) c.currentPiStream?.push({ type: "thinking_delta", contentIndex: idx, delta: block.thinking, partial: c.turnOutput });
44434
44947
  c.currentPiStream?.push({ type: "thinking_end", contentIndex: idx, content: block.thinking ?? "", partial: c.turnOutput });
44435
44948
  } else if (block.type === "tool_use") {
44949
+ if (isChildExecutedTool(block.name)) {
44950
+ c.noteChildExecutedToolCall(block.id, block.name);
44951
+ debug(`processAssistantMessage fallback: child-executed tool ${block.name} [${block.id}] \u2014 not mirrored as a Pi tool call`);
44952
+ continue;
44953
+ }
44436
44954
  ensureTurnStarted();
44437
44955
  c.turnSawToolCall = true;
44438
44956
  const mappedName = mapToolName(block.name, customToolNameToPi);
44439
44957
  const mappedArgs = mapToolArgs(mappedName, block.input);
44440
44958
  c.recordToolCall(block.id, mappedName, mappedArgs);
44959
+ const existingIdx = c.turnBlocks.findIndex((b) => b.type === "toolCall" && b.id === block.id);
44960
+ if (existingIdx >= 0) {
44961
+ const existing = c.turnBlocks[existingIdx];
44962
+ existing.name = mappedName;
44963
+ existing.arguments = mappedArgs;
44964
+ c.updateToolCallArgs(block.id, mappedArgs);
44965
+ continue;
44966
+ }
44441
44967
  c.turnBlocks.push({
44442
44968
  type: "toolCall",
44443
44969
  id: block.id,
@@ -44456,10 +44982,7 @@ function processAssistantMessage(message, model, customToolNameToPi) {
44456
44982
  }
44457
44983
  if (assistantMsg.usage && c.turnOutput) updateUsage(c.turnOutput, assistantMsg.usage, model);
44458
44984
  if (c.turnSawToolCall && c.currentPiStream && c.turnOutput) {
44459
- c.turnOutput.stopReason = "toolUse";
44460
- c.currentPiStream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
44461
- c.currentPiStream.end();
44462
- c.currentPiStream = null;
44985
+ endToolUseTurn(c);
44463
44986
  }
44464
44987
  }
44465
44988
 
@@ -44481,6 +45004,27 @@ function emitRateLimitEvent(payload) {
44481
45004
  function extraUsageAllowed(config2) {
44482
45005
  return config2.provider?.allowExtraUsage === true;
44483
45006
  }
45007
+ var lastFastModeDisabledNoticeReason = null;
45008
+ var FAST_MODE_DISABLED_REASON_TEXT = {
45009
+ disabled_by_env: "disabled by an environment variable",
45010
+ extra_usage_disabled: "extra usage is disabled for this account",
45011
+ free: "not available on the free plan",
45012
+ model_not_allowed: "not available for this model",
45013
+ network_error: "the eligibility check hit a network error",
45014
+ not_first_party: "not available for this account type",
45015
+ preference: "disabled by a Claude Code preference",
45016
+ sdk_opt_in_required: "the SDK opt-in is missing",
45017
+ unknown: "unavailable for an unknown reason"
45018
+ };
45019
+ function noteFastModeDisabledReason(message, bridgeConfig) {
45020
+ if (bridgeConfig.provider?.fastMode !== true) return;
45021
+ const reason = message.fast_mode_disabled_reason;
45022
+ if (typeof reason !== "string" || reason === "pending") return;
45023
+ if (reason === lastFastModeDisabledNoticeReason) return;
45024
+ lastFastModeDisabledNoticeReason = reason;
45025
+ const text = FAST_MODE_DISABLED_REASON_TEXT[reason] ?? `unavailable (${reason})`;
45026
+ safeNotify(`Claude bridge: fast mode is enabled in settings but Claude Code declined it \u2014 ${text}.`, "warning");
45027
+ }
44484
45028
  function sdkTextFromMessage(message) {
44485
45029
  if (message.type === "result") return message.result;
44486
45030
  if (message.type === "assistant") {
@@ -44594,41 +45138,24 @@ function resolveMcpTools(context, excludeToolName) {
44594
45138
  if (!context.tools) return { mcpTools, customToolNameToSdk, customToolNameToPi };
44595
45139
  for (const tool of context.tools) {
44596
45140
  if (tool.name === excludeToolName) continue;
45141
+ if (isChildExecutedTool(tool.name)) {
45142
+ debug(`resolveMcpTools: not re-offering child-native tool ${tool.name}`);
45143
+ continue;
45144
+ }
44597
45145
  const sdkName = `${MCP_TOOL_PREFIX}${tool.name}`;
44598
45146
  mcpTools.push(tool);
45147
+ const lowerName = tool.name.toLowerCase();
45148
+ const collision = customToolNameToSdk.get(lowerName);
45149
+ if (collision !== void 0 && collision !== sdkName) {
45150
+ debug(`WARNING: resolveMcpTools lowercase alias collision: ${tool.name} overwrites mapping previously held by ${collision}`);
45151
+ }
44599
45152
  customToolNameToSdk.set(tool.name, sdkName);
44600
- customToolNameToSdk.set(tool.name.toLowerCase(), sdkName);
45153
+ customToolNameToSdk.set(lowerName, sdkName);
44601
45154
  customToolNameToPi.set(sdkName, tool.name);
44602
45155
  customToolNameToPi.set(sdkName.toLowerCase(), tool.name);
44603
45156
  }
44604
45157
  return { mcpTools, customToolNameToSdk, customToolNameToPi };
44605
45158
  }
44606
- function finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, toolName, mappedArgs) {
44607
- if (!queryCtx.currentPiStream || !queryCtx.turnOutput) return;
44608
- let idx = queryCtx.turnBlocks.findIndex((b) => b.type === "toolCall" && b.id === toolCallId);
44609
- if (idx >= 0) {
44610
- const block = queryCtx.turnBlocks[idx];
44611
- if ("partialJson" in block) {
44612
- block.arguments = mapToolArgs(block.name, parsePartialJson(block.partialJson, block.arguments));
44613
- queryCtx.updateToolCallArgs(block.id, block.arguments);
44614
- delete block.partialJson;
44615
- delete block.index;
44616
- queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: queryCtx.turnOutput });
44617
- }
44618
- } else {
44619
- queryCtx.turnBlocks.push({ type: "toolCall", id: toolCallId, name: toolName, arguments: mappedArgs });
44620
- idx = queryCtx.turnBlocks.length - 1;
44621
- const block = queryCtx.turnBlocks[idx];
44622
- queryCtx.currentPiStream.push({ type: "toolcall_start", contentIndex: idx, partial: queryCtx.turnOutput });
44623
- queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: queryCtx.turnOutput });
44624
- }
44625
- queryCtx.turnSawToolCall = true;
44626
- queryCtx.turnOutput.stopReason = "toolUse";
44627
- debug(`mcp handler: finalizing tool_use turn from MCP invocation [${toolCallId}] (${toolName}) \u2014 SDK invoked the tool before message_stop/assistant message`);
44628
- queryCtx.currentPiStream.push({ type: "done", reason: "toolUse", message: queryCtx.turnOutput });
44629
- queryCtx.currentPiStream.end();
44630
- queryCtx.currentPiStream = null;
44631
- }
44632
45159
  function buildMcpServers(tools, queryCtx) {
44633
45160
  if (!tools.length) return void 0;
44634
45161
  const mcpTools = tools.map((tool) => ({
@@ -44648,9 +45175,23 @@ function buildMcpServers(tools, queryCtx) {
44648
45175
  turnToolCallIds: queryCtx.turnToolCallIds,
44649
45176
  turnToolCalls: safeToolCallSummary(queryCtx.turnToolCalls)
44650
45177
  });
45178
+ appendIntegrityEntry("tool_handler_unmatched", {
45179
+ toolName: tool.name,
45180
+ argKeys: argKeys(mappedArgs),
45181
+ available: claim.available,
45182
+ turnToolCallIds: queryCtx.turnToolCallIds
45183
+ });
44651
45184
  return { content: [{ type: "text", text: `Claude bridge internal error: no matching tool_call id for ${tool.name}` }], isError: true };
44652
45185
  }
44653
- if (claim.match !== "tool-args" || claim.ambiguous) {
45186
+ if (claim.argsMismatch) {
45187
+ debug(`mcp handler: ${tool.name} [${toolCallId}] claimed sole same-name call despite args mismatch`);
45188
+ diagDump("tool_claim_args_mismatch", {
45189
+ toolName: tool.name,
45190
+ toolCallId,
45191
+ handlerArgKeys: argKeys(mappedArgs),
45192
+ recordedArgKeys: argKeys(queryCtx.turnToolCalls.find((call) => call.id === toolCallId)?.arguments)
45193
+ });
45194
+ } else if (claim.match !== "tool-args" || claim.ambiguous) {
44654
45195
  debug(`mcp handler: ${tool.name} [${toolCallId}] claimed by ${claim.match}${claim.ambiguous ? " (ambiguous)" : ""}`);
44655
45196
  }
44656
45197
  if (toolCallId && queryCtx.pendingResults.has(toolCallId)) {
@@ -44661,7 +45202,11 @@ function buildMcpServers(tools, queryCtx) {
44661
45202
  return result;
44662
45203
  }
44663
45204
  debug(`mcp handler: ${tool.name} [${toolCallId}] \u2192 waiting`);
44664
- finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, tool.name, mappedArgs);
45205
+ scheduleToolUseTurnEnd(
45206
+ queryCtx,
45207
+ () => finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, tool.name, mappedArgs),
45208
+ `mcp-invocation:${tool.name}`
45209
+ );
44665
45210
  return new Promise((resolve5) => {
44666
45211
  queryCtx.pendingToolCalls.set(toolCallId, {
44667
45212
  toolName: tool.name,
@@ -44715,20 +45260,26 @@ async function consumeQuery(sdkQuery, customToolNameToPi, model, cwd, bridgeConf
44715
45260
  break;
44716
45261
  case "result":
44717
45262
  if (!ctx().turnSawStreamEvent && message.subtype === "success") {
44718
- ensureTurnStarted();
44719
45263
  const text = message.result || "";
45264
+ if (ctx().turnBlocks.some((b) => b.type === "text" && b.text === text)) {
45265
+ debug("consumeQuery: result text already rendered by assistant fallback; skipping duplicate");
45266
+ break;
45267
+ }
45268
+ ensureTurnStarted();
44720
45269
  ctx().turnBlocks.push({ type: "text", text });
44721
45270
  const idx = ctx().turnBlocks.length - 1;
44722
45271
  ctx().currentPiStream?.push({ type: "text_start", contentIndex: idx, partial: ctx().turnOutput });
44723
45272
  ctx().currentPiStream?.push({ type: "text_delta", contentIndex: idx, delta: text, partial: ctx().turnOutput });
44724
45273
  ctx().currentPiStream?.push({ type: "text_end", contentIndex: idx, content: text, partial: ctx().turnOutput });
44725
- } else if (message.subtype !== "success" && isExtraUsageRequiredMessage(message)) {
45274
+ } else if (message.subtype !== "success" && (isExtraUsageRequiredMessage(message) || isUsageLimitMessage(message))) {
44726
45275
  const errorLines = Array.isArray(message.errors) ? uniqueNonEmptyLines(message.errors) : [];
44727
45276
  const errors = errorLines.length > 0 ? errorLines.join("\n") : String(message.subtype ?? "Claude Code rate limit");
44728
- const openedExtraUsage = launchExtraUsageHelperIfAllowed(cwd, bridgeConfig, "result error");
45277
+ const extraUsage = isExtraUsageRequiredMessage(message);
45278
+ const openedExtraUsage = extraUsage && launchExtraUsageHelperIfAllowed(cwd, bridgeConfig, "result error");
44729
45279
  ctx().handledTerminalError = true;
44730
45280
  ctx().turnOutput.stopReason = "error";
44731
- ctx().turnOutput.errorMessage = `${errors}${openedExtraUsage ? "\n\nOpened Claude Code /extra-usage helper. Complete billing/admin flow in the browser, then retry the prompt." : "\n\nRun /claude-bridge:extra, or enable Allow extra usage helper in settings."}`;
45281
+ const extraUsageHint = openedExtraUsage ? "\n\nOpened Claude Code /extra-usage helper. Complete billing/admin flow in the browser, then retry the prompt." : extraUsage ? "\n\nRun /claude-bridge:extra, or enable Allow extra usage helper in settings." : "";
45282
+ ctx().turnOutput.errorMessage = `${errors}${extraUsageHint}`;
44732
45283
  ctx().currentPiStream?.push({ type: "error", reason: "error", error: ctx().turnOutput });
44733
45284
  ctx().currentPiStream?.end();
44734
45285
  ctx().currentPiStream = null;
@@ -44737,6 +45288,8 @@ async function consumeQuery(sdkQuery, customToolNameToPi, model, cwd, bridgeConf
44737
45288
  case "system":
44738
45289
  if (message.subtype === "init" && message.session_id) {
44739
45290
  capturedSessionId = message.session_id;
45291
+ queryCtx.childSessionId = capturedSessionId;
45292
+ noteFastModeDisabledReason(message, bridgeConfig);
44740
45293
  } else if (message.subtype === "model_refusal_fallback") {
44741
45294
  const originalModel = message.original_model;
44742
45295
  const fallbackModel = message.fallback_model;
@@ -44751,14 +45304,14 @@ async function consumeQuery(sdkQuery, customToolNameToPi, model, cwd, bridgeConf
44751
45304
  }
44752
45305
  break;
44753
45306
  case "user":
45307
+ noteChildExecutedToolResults(message);
44754
45308
  break;
44755
- // SDK echo of user prompt — not needed
44756
45309
  case "rate_limit_event": {
44757
45310
  const info = message.rate_limit_info;
44758
45311
  debug("consumeQuery: rate_limit_event", JSON.stringify(info).slice(0, 300));
44759
45312
  if (info?.status === "rejected") {
44760
45313
  const resetsAt = formatResetTimestamp(info.resetsAt);
44761
- const resetAtMs = typeof info.resetsAt === "string" ? Date.parse(info.resetsAt) : void 0;
45314
+ const resetAtMs = resetTimestampMs(info.resetsAt);
44762
45315
  const reason = `${info.rateLimitType ?? "unknown"} rate limit`;
44763
45316
  const launchedExtraUsage = isExtraUsageRequiredMessage(info) && launchExtraUsageHelperIfAllowed(cwd, bridgeConfig, reason);
44764
45317
  emitRateLimitEvent({
@@ -44803,6 +45356,8 @@ function releaseProviderTokens(event) {
44803
45356
  g[PRIMARY_INSTANCE_KEY] = void 0;
44804
45357
  }
44805
45358
  }
45359
+ var nativeProviderInstance;
45360
+ var notifiedNativeUnsupported = false;
44806
45361
  function applyProviderRegistration(trigger) {
44807
45362
  const pi = extensionApi;
44808
45363
  if (!pi) {
@@ -44811,32 +45366,28 @@ function applyProviderRegistration(trigger) {
44811
45366
  }
44812
45367
  const g = globalThis;
44813
45368
  const isPrimary = claimPrimaryInstance();
44814
- const credentialed = hasClaudeCredentials();
44815
- const registered = g[ACTIVE_STREAM_SIMPLE_KEY] === streamClaudeAgentSdk;
44816
- const decision = decideRegistration({ credentialed, isPrimary, registered });
44817
- debug(`${trigger}: registration decision=${decision} credentialed=${credentialed} isPrimary=${isPrimary} registered=${registered} (module=${moduleInstanceId})`);
44818
- if (decision === "register") {
44819
- g[ACTIVE_STREAM_SIMPLE_KEY] = streamClaudeAgentSdk;
44820
- try {
44821
- pi.registerProvider(PROVIDER_ID, {
44822
- baseUrl: "claude-bridge",
44823
- apiKey: "not-used",
44824
- api: "claude-bridge",
44825
- models: MODELS,
44826
- // Cast: pi-ai AssistantMessageEventStream diamond dep between pi-coding-agent and pi-agent-core
44827
- streamSimple: streamClaudeAgentSdk
44828
- });
44829
- } catch (err) {
44830
- if (g[ACTIVE_STREAM_SIMPLE_KEY] === streamClaudeAgentSdk) g[ACTIVE_STREAM_SIMPLE_KEY] = void 0;
44831
- debug(`${trigger}: registerProvider threw; released stream guard for retry (kept primary):`, err);
44832
- }
44833
- } else if (decision === "unregister") {
44834
- try {
44835
- pi.unregisterProvider(PROVIDER_ID);
44836
- } catch (err) {
44837
- debug(`${trigger}: unregisterProvider threw (ignored):`, err);
45369
+ if (!isPrimary) {
45370
+ debug(`${trigger}: registration noop \u2014 non-primary instance (module=${moduleInstanceId})`);
45371
+ return;
45372
+ }
45373
+ if (!supportsNativeProvider(_piAi)) {
45374
+ debug(`${trigger}: host pi-ai lacks createProvider; refusing to register (module=${moduleInstanceId})`);
45375
+ if (!notifiedNativeUnsupported) {
45376
+ notifiedNativeUnsupported = true;
45377
+ safeNotify(NATIVE_PROVIDER_UNSUPPORTED_MESSAGE, "error");
44838
45378
  }
45379
+ return;
45380
+ }
45381
+ const credentialed = hasClaudeCredentials();
45382
+ debug(`${trigger}: native registration upsert, credentialed=${credentialed} (module=${moduleInstanceId})`);
45383
+ if (credentialed && connectorsEnabledFor(loadConfig(process.cwd()))) primeConnectorServers();
45384
+ g[ACTIVE_STREAM_SIMPLE_KEY] = streamClaudeAgentSdk;
45385
+ try {
45386
+ nativeProviderInstance ??= buildNativeProvider(_piAi, MODELS, streamClaudeAgentSdk);
45387
+ pi.registerProvider(nativeProviderInstance);
45388
+ } catch (err) {
44839
45389
  if (g[ACTIVE_STREAM_SIMPLE_KEY] === streamClaudeAgentSdk) g[ACTIVE_STREAM_SIMPLE_KEY] = void 0;
45390
+ debug(`${trigger}: registerProvider threw; released stream guard for retry (kept primary):`, err);
44840
45391
  }
44841
45392
  }
44842
45393
  function streamClaudeAgentSdk(model, context, options) {
@@ -44974,6 +45525,7 @@ function streamClaudeAgentSdk(model, context, options) {
44974
45525
  const providerSettings = bridgeConfig.provider ?? {};
44975
45526
  const enableCloudMcp = connectorsEnabledFor(bridgeConfig);
44976
45527
  const connectorWriteMode = connectorWriteModeFor(bridgeConfig);
45528
+ const connectorServers = enableCloudMcp ? connectorServersSnapshot() : {};
44977
45529
  const appendSystemPrompt = providerSettings.appendSystemPrompt !== false;
44978
45530
  const agentsAppend = appendSystemPrompt ? extractAgentsAppend() : void 0;
44979
45531
  const skillsAppend = appendSystemPrompt ? extractSkillsBlock(context.systemPrompt) : void 0;
@@ -44988,7 +45540,6 @@ function streamClaudeAgentSdk(model, context, options) {
44988
45540
  const requestedEffort = options?.reasoning ? model.thinkingLevelMap?.[options.reasoning] ?? REASONING_TO_EFFORT[options.reasoning] : void 0;
44989
45541
  const effort = resolveConfiguredEffort(model.id, requestedEffort, providerSettings);
44990
45542
  const extraArgs = {};
44991
- if (strictMcpConfigEnabled) extraArgs["strict-mcp-config"] = null;
44992
45543
  if (effort) extraArgs["thinking-display"] = "summarized";
44993
45544
  const fallbackModel = fallbackModelForPrimaryModel(model.id);
44994
45545
  const childEnv = { ...process.env, ENABLE_CLAUDEAI_MCP_SERVERS: enableCloudMcp ? "1" : "0", DISABLE_AUTO_COMPACT: "1" };
@@ -45007,9 +45558,10 @@ function streamClaudeAgentSdk(model, context, options) {
45007
45558
  append: systemPromptAppend ? systemPromptAppend : void 0
45008
45559
  },
45009
45560
  extraArgs,
45561
+ ...strictMcpConfigEnabled ? { strictMcpConfig: true } : {},
45010
45562
  ...effort ? { effort } : {},
45011
45563
  ...settingSources ? { settingSources } : {},
45012
- ...mcpServers ? { mcpServers } : {},
45564
+ ...mcpServers || Object.keys(connectorServers).length > 0 ? { mcpServers: { ...mcpServers ?? {}, ...connectorServers } } : {},
45013
45565
  ...resumeSessionId ? { resume: resumeSessionId } : {},
45014
45566
  ...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
45015
45567
  spawnClaudeCodeProcess: spawnClaudeCodeWithDiagnostics,
@@ -45100,7 +45652,7 @@ function streamClaudeAgentSdk(model, context, options) {
45100
45652
  else options.signal.addEventListener("abort", onAbort, { once: true });
45101
45653
  }
45102
45654
  consumeQuery(sdkQuery, customToolNameToPi, model, cwd, bridgeConfig, () => wasAborted).then(async ({ capturedSessionId }) => {
45103
- debug(`provider: consumeQuery completed, stopReason=${ctx().turnOutput?.stopReason}, error=${ctx().turnOutput?.errorMessage}, aborted=${wasAborted}`);
45655
+ debug(`provider: consumeQuery completed, stopReason=${abortCtx.turnOutput?.stopReason}, error=${abortCtx.turnOutput?.errorMessage}, aborted=${wasAborted}`);
45104
45656
  if (streamIdleTimedOut) {
45105
45657
  abortCtx.deferredUserMessages = [];
45106
45658
  debug("provider: stream idle timeout already surfaced; skipping normal completion");
@@ -45108,29 +45660,29 @@ function streamClaudeAgentSdk(model, context, options) {
45108
45660
  }
45109
45661
  if (wasAborted || options?.signal?.aborted) {
45110
45662
  if (sharedSession) setSharedSession({ ...sharedSession, needsRebuild: true, forceRotate: true });
45111
- ctx().deferredUserMessages = [];
45663
+ abortCtx.deferredUserMessages = [];
45112
45664
  debug(`provider: abort detected, marked sharedSession needsRebuild + forceRotate`);
45113
- if (ctx().turnOutput) {
45114
- ctx().turnOutput.stopReason = "aborted";
45115
- ctx().turnOutput.errorMessage = "Operation aborted";
45665
+ if (abortCtx.turnOutput) {
45666
+ abortCtx.turnOutput.stopReason = "aborted";
45667
+ abortCtx.turnOutput.errorMessage = "Operation aborted";
45116
45668
  }
45117
- ctx().currentPiStream?.push({ type: "error", reason: "aborted", error: ctx().turnOutput });
45118
- ctx().currentPiStream?.end();
45119
- ctx().currentPiStream = null;
45669
+ abortCtx.currentPiStream?.push({ type: "error", reason: "aborted", error: abortCtx.turnOutput });
45670
+ abortCtx.currentPiStream?.end();
45671
+ abortCtx.currentPiStream = null;
45120
45672
  return;
45121
45673
  }
45122
45674
  const sessionId = capturedSessionId ?? sharedSession?.sessionId;
45123
45675
  if (sessionId) {
45124
- const cursor = Math.max(context.messages.length, ctx().latestCursor, sharedSession?.cursor ?? 0);
45676
+ const cursor = Math.max(context.messages.length, abortCtx.latestCursor, sharedSession?.cursor ?? 0);
45125
45677
  debug(`provider: query done, session=${sessionId.slice(0, 8)}, cursor=${cursor}`);
45126
45678
  setSharedSession({ sessionId, cursor, cwd });
45127
45679
  }
45128
45680
  try {
45129
- while (ctx().deferredUserMessages.length > 0 && !isReentrant && !wasAborted) {
45130
- const steerPrompt = ctx().deferredUserMessages.shift();
45681
+ while (abortCtx.deferredUserMessages.length > 0 && !isReentrant && !wasAborted) {
45682
+ const steerPrompt = abortCtx.deferredUserMessages.shift();
45131
45683
  debug(`provider: replaying deferred user message: ${steerPrompt.slice(0, 60)}`);
45132
- ctx().resetTurnState(model);
45133
- ctx().resetToolTracking();
45684
+ abortCtx.resetTurnState(model);
45685
+ abortCtx.resetToolTracking();
45134
45686
  const resumeId = sharedSession?.sessionId;
45135
45687
  if (!resumeId) {
45136
45688
  debug(`WARNING: no session to resume for deferred message, dropping`);
@@ -45138,7 +45690,7 @@ function streamClaudeAgentSdk(model, context, options) {
45138
45690
  }
45139
45691
  const contOptions = { ...queryOptions, resume: resumeId, ...makeCliDebugOptions("continuation") };
45140
45692
  const contQuery = Okt({ prompt: steerPrompt, options: contOptions });
45141
- ctx().activeQuery = contQuery;
45693
+ abortCtx.activeQuery = contQuery;
45142
45694
  debug(`provider: continuation query, model=${model.id}, resume=${resumeId.slice(0, 8)}, prompt=${steerPrompt.slice(0, 60)}`);
45143
45695
  try {
45144
45696
  const { capturedSessionId: contSid } = await consumeQuery(contQuery, customToolNameToPi, model, cwd, bridgeConfig, () => wasAborted);
@@ -45154,46 +45706,36 @@ function streamClaudeAgentSdk(model, context, options) {
45154
45706
  }
45155
45707
  }
45156
45708
  } finally {
45157
- ctx().activeQuery = sdkQuery;
45709
+ abortCtx.activeQuery = sdkQuery;
45158
45710
  }
45159
- finalizeCurrentStream(ctx().turnOutput?.stopReason);
45711
+ finalizeCurrentStream(abortCtx.turnOutput?.stopReason, abortCtx);
45160
45712
  }).catch((error51) => {
45161
45713
  debug(`provider: query error, model=${model.id}, aborted=${Boolean(options?.signal?.aborted)}, error=`, error51);
45162
- const suppressDuplicateError = ctx().handledTerminalError || streamIdleTimedOut;
45714
+ const suppressDuplicateError = abortCtx.handledTerminalError || streamIdleTimedOut;
45163
45715
  const openedExtraUsage = !suppressDuplicateError && isExtraUsageRequiredMessage(error51) && launchExtraUsageHelperIfAllowed(cwd, bridgeConfig, "query error");
45164
45716
  if ((wasAborted || options?.signal?.aborted) && sharedSession) {
45165
45717
  setSharedSession({ ...sharedSession, needsRebuild: true, forceRotate: true });
45166
45718
  } else {
45167
45719
  setSharedSession(null);
45168
45720
  }
45169
- ctx().deferredUserMessages = [];
45721
+ abortCtx.deferredUserMessages = [];
45170
45722
  if (suppressDuplicateError) {
45171
45723
  debug("provider: suppressing duplicate query error after terminal error was already emitted");
45172
45724
  return;
45173
45725
  }
45174
- if (ctx().turnOutput) {
45175
- ctx().turnOutput.stopReason = options?.signal?.aborted ? "aborted" : "error";
45176
- ctx().turnOutput.errorMessage = `${error51 instanceof Error ? error51.message : String(error51)}${openedExtraUsage ? "\n\nOpened Claude Code /extra-usage helper. Complete billing/admin flow in the browser, then retry the prompt." : ""}`;
45726
+ if (abortCtx.turnOutput) {
45727
+ abortCtx.turnOutput.stopReason = options?.signal?.aborted ? "aborted" : "error";
45728
+ abortCtx.turnOutput.errorMessage = `${error51 instanceof Error ? error51.message : String(error51)}${openedExtraUsage ? "\n\nOpened Claude Code /extra-usage helper. Complete billing/admin flow in the browser, then retry the prompt." : ""}`;
45177
45729
  }
45178
- ctx().currentPiStream?.push({ type: "error", reason: ctx().turnOutput?.stopReason ?? "error", error: ctx().turnOutput });
45179
- ctx().currentPiStream?.end();
45180
- ctx().currentPiStream = null;
45730
+ abortCtx.currentPiStream?.push({ type: "error", reason: abortCtx.turnOutput?.stopReason ?? "error", error: abortCtx.turnOutput });
45731
+ abortCtx.currentPiStream?.end();
45732
+ abortCtx.currentPiStream = null;
45181
45733
  }).finally(() => {
45182
45734
  streamIdleWatchdog?.dispose();
45183
45735
  activeStreamIdleWatchdogs.delete(abortCtx);
45184
45736
  if (options?.signal) options.signal.removeEventListener("abort", onAbort);
45185
- if (ctx().activeQuery === sdkQuery) {
45186
- const cause = toolCallDrainCause({ wasAborted, signalAborted: options?.signal?.aborted, streamIdleTimedOut });
45187
- reportToolResultMismatch(ctx(), "query teardown", cwd, { forceRotate: cause !== "query-end" });
45188
- const drained = drainPendingToolCalls(ctx(), cause);
45189
- if (drained > 0) debug(`provider: query teardown drained ${drained} waiting MCP handler(s) as errors (cause=${cause})`);
45190
- ctx().pendingResults.clear();
45191
- if (isReentrant) {
45192
- popContext();
45193
- } else {
45194
- ctx().activeQuery = null;
45195
- }
45196
- }
45737
+ const cause = toolCallDrainCause({ wasAborted, signalAborted: options?.signal?.aborted, streamIdleTimedOut });
45738
+ teardownQuery(abortCtx, sdkQuery, cause, cwd, isReentrant);
45197
45739
  sdkQuery.close();
45198
45740
  });
45199
45741
  return stream;
@@ -45228,6 +45770,58 @@ function readCredentialFile(path) {
45228
45770
  return void 0;
45229
45771
  }
45230
45772
  }
45773
+ var connectorServerCache = /* @__PURE__ */ new Map();
45774
+ var connectorServerPending = /* @__PURE__ */ new Set();
45775
+ function connectorScopeKey() {
45776
+ return process.env.CLAUDE_CONFIG_DIR?.trim() || "<default>";
45777
+ }
45778
+ function primeConnectorServers() {
45779
+ const key = connectorScopeKey();
45780
+ if (connectorServerCache.has(key) || connectorServerPending.has(key)) return;
45781
+ connectorServerPending.add(key);
45782
+ void (async () => {
45783
+ try {
45784
+ const credentials = resolveClaudeOAuth(readCredentialFile);
45785
+ if (!credentials) {
45786
+ debug("connectors: no OAuth credentials; declaring none");
45787
+ connectorServerCache.set(key, {});
45788
+ return;
45789
+ }
45790
+ const inventory = await listAccountConnectors({ credentials });
45791
+ if (!inventory.ok) {
45792
+ debug(`connectors: inventory failed (${inventory.reason}); declaring none`);
45793
+ connectorServerCache.set(key, {});
45794
+ return;
45795
+ }
45796
+ const servers = connectorMcpServers(inventory);
45797
+ debug(
45798
+ `connectors: declaring ${Object.keys(servers).length} of ${inventory.connectors.length} installed`,
45799
+ Object.keys(servers).join(", ") || "none"
45800
+ );
45801
+ connectorServerCache.set(key, servers);
45802
+ if (writeCachedConnectors(inventory.connectors, key)) {
45803
+ debug(`connectors: cached ${inventory.connectors.length} entries`);
45804
+ }
45805
+ } catch (error51) {
45806
+ debug("connectors: declaration lookup threw; declaring none", error51);
45807
+ connectorServerCache.set(key, {});
45808
+ } finally {
45809
+ connectorServerPending.delete(key);
45810
+ }
45811
+ })();
45812
+ }
45813
+ function connectorServersSnapshot() {
45814
+ const key = connectorScopeKey();
45815
+ const ready = connectorServerCache.get(key);
45816
+ if (ready) return ready;
45817
+ primeConnectorServers();
45818
+ const cached2 = readCachedConnectors(key);
45819
+ if (!cached2) return {};
45820
+ const servers = connectorMcpServers({ ok: true, complete: true, connectors: cached2 });
45821
+ if (Object.keys(servers).length === 0) return {};
45822
+ debug(`connectors: turn-1 declarations from cache \u2014 ${Object.keys(servers).join(", ")}`);
45823
+ return servers;
45824
+ }
45231
45825
  async function reportConnectorInventory(ctx2) {
45232
45826
  const credentials = resolveClaudeOAuth(readCredentialFile);
45233
45827
  if (!credentials) {
@@ -45334,17 +45928,31 @@ export {
45334
45928
  ALLOWED_RATE_LIMIT_WARNING_UTILIZATION_THRESHOLD,
45335
45929
  CLAUDE_AI_CONNECTOR_TOOL_PATTERNS,
45336
45930
  CLAUDE_BRIDGE_TOOL_ISOLATION,
45931
+ CONNECTOR_CALL_CUSTOM_TYPE,
45337
45932
  CONNECTOR_DISCOVERY_TOOLS,
45338
45933
  CONNECTOR_WRITE_TOOLS,
45339
45934
  DEFAULT_STREAM_IDLE_TIMEOUT_MS,
45340
45935
  DISALLOWED_BUILTIN_TOOLS,
45936
+ INTEGRITY_CUSTOM_TYPE,
45937
+ NATIVE_PROVIDER_UNSUPPORTED_MESSAGE,
45341
45938
  STREAM_IDLE_BACKOFF_HINT_MS,
45342
45939
  STREAM_IDLE_TIMEOUT_ENV,
45343
45940
  __testGetBridgeIntegrityState,
45344
45941
  __testSetBridgeIntegrityState,
45942
+ appendIntegrityEntry,
45943
+ buildNativeProvider,
45345
45944
  buildStreamIdleTimeoutErrorMessage,
45945
+ cancelScheduledToolUseEnd,
45346
45946
  classifyClaudeExecutableBytes,
45947
+ claudeAuthSourceLabel,
45948
+ connectorCachePath,
45949
+ connectorCacheScopeKey,
45950
+ connectorDeclarationsDisabled,
45951
+ connectorMcpServers,
45952
+ connectorProxyUrl,
45347
45953
  connectorQueryOptions,
45954
+ connectorResultByteSize,
45955
+ connectorServerName,
45348
45956
  connectorServerNamespace,
45349
45957
  connectorWriteDenyHook,
45350
45958
  connectorWriteModeFor,
@@ -45355,27 +45963,43 @@ export {
45355
45963
  createStreamIdleWatchdog,
45356
45964
  credentialCandidatePaths,
45357
45965
  index_default as default,
45966
+ endToolUseTurn,
45967
+ finalizeToolUseTurnFromMcpInvocation,
45968
+ flushConnectorCallAudit,
45358
45969
  formatAllowedRateLimitWarning,
45359
45970
  formatResetTimestamp,
45971
+ isChildExecutedTool,
45360
45972
  isConnectorWriteTool,
45361
45973
  isExtraUsageRequiredMessage,
45974
+ isUsageLimitMessage,
45362
45975
  listAccountConnectors,
45363
45976
  mapToolName,
45364
45977
  normalizeRateLimitUtilization,
45978
+ noteChildExecutedToolResults,
45365
45979
  preflightClaudeExecutable,
45980
+ primeConnectorServers,
45366
45981
  processAssistantMessage,
45367
45982
  processStreamEvent,
45983
+ readCachedConnectors,
45984
+ reapStaleQueuedResults,
45985
+ recordConnectorCallResult,
45368
45986
  reportToolResultMismatch,
45987
+ resetTimestampMs,
45369
45988
  resolveClaudeExecutable,
45370
45989
  resolveClaudeOAuth,
45371
45990
  resolveConfiguredEffort,
45991
+ resolveMcpTools,
45372
45992
  restoreSharedSessionFromPi,
45993
+ scheduleToolUseTurnEnd,
45994
+ setConnectorCallAuditSink,
45373
45995
  shouldRestorePersistedBridgeEntry,
45374
45996
  spawnClaudeCodeWithDiagnostics,
45375
45997
  streamIdleTimeoutMsFromEnv,
45998
+ supportsNativeProvider,
45376
45999
  toolIsolationForQuery,
45377
46000
  uniqueNonEmptyLines,
45378
- wrapClaudeSpawnErrorForSdk
46001
+ wrapClaudeSpawnErrorForSdk,
46002
+ writeCachedConnectors
45379
46003
  };
45380
46004
  /*! Bundled license information:
45381
46005