@khalilgharbaoui/opencode-claude-code-plugin 0.9.3 → 0.10.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/dist/index.js CHANGED
@@ -733,6 +733,10 @@ import * as fs2 from "fs";
733
733
  import * as path2 from "path";
734
734
  import * as os2 from "os";
735
735
  import * as crypto from "crypto";
736
+ import {
737
+ parse as parseJsonc,
738
+ printParseErrorCode
739
+ } from "jsonc-parser";
736
740
 
737
741
  // src/tmp.ts
738
742
  import * as fs from "fs";
@@ -776,49 +780,18 @@ function dirExists(p) {
776
780
  return false;
777
781
  }
778
782
  }
779
- function stripJsonComments(text) {
780
- let out = "";
781
- let i = 0;
782
- let inString = null;
783
- while (i < text.length) {
784
- const c = text[i];
785
- if (inString) {
786
- out += c;
787
- if (c === "\\" && i + 1 < text.length) {
788
- out += text[i + 1];
789
- i += 2;
790
- continue;
791
- }
792
- if (c === inString) inString = null;
793
- i++;
794
- continue;
795
- }
796
- if (c === '"' || c === "'") {
797
- inString = c;
798
- out += c;
799
- i++;
800
- continue;
801
- }
802
- if (c === "/" && text[i + 1] === "/") {
803
- while (i < text.length && text[i] !== "\n") i++;
804
- continue;
805
- }
806
- if (c === "/" && text[i + 1] === "*") {
807
- i += 2;
808
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
809
- i++;
810
- i += 2;
811
- continue;
812
- }
813
- out += c;
814
- i++;
815
- }
816
- return out;
817
- }
818
783
  function readAndParse(file) {
819
784
  try {
820
785
  const raw = fs2.readFileSync(file, "utf8");
821
- return JSON.parse(stripJsonComments(raw));
786
+ const errors = [];
787
+ const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
788
+ if (errors.length > 0) {
789
+ const first = errors[0];
790
+ throw new Error(
791
+ `${printParseErrorCode(first.error)} at offset ${first.offset}`
792
+ );
793
+ }
794
+ return parsed;
822
795
  } catch (e) {
823
796
  log.warn("failed to parse opencode config", {
824
797
  file,
@@ -1243,6 +1216,8 @@ function cliSupportsThinking(v) {
1243
1216
  var activeProcesses = /* @__PURE__ */ new Map();
1244
1217
  var claudeSessions = /* @__PURE__ */ new Map();
1245
1218
  var MAX_ACTIVE_PROCESSES = 16;
1219
+ var PROCESS_EXIT_TIMEOUT_MS = 1500;
1220
+ var PROCESS_FORCE_EXIT_TIMEOUT_MS = 500;
1246
1221
  function envFlagEnabled(value) {
1247
1222
  if (value === void 0) return false;
1248
1223
  const normalized = value.trim().toLowerCase();
@@ -1289,13 +1264,54 @@ function getActiveProcess(key) {
1289
1264
  function setActiveProcess(key, ap) {
1290
1265
  activeProcesses.set(key, ap);
1291
1266
  }
1292
- function deleteActiveProcess(key) {
1267
+ function detachActiveProcess(key) {
1293
1268
  const ap = activeProcesses.get(key);
1294
- if (ap) {
1295
- void ap.proxyServer?.close();
1296
- ap.proc.kill();
1297
- activeProcesses.delete(key);
1298
- }
1269
+ if (!ap) return void 0;
1270
+ activeProcesses.delete(key);
1271
+ void ap.proxyServer?.close();
1272
+ return ap;
1273
+ }
1274
+ function deleteActiveProcess(key) {
1275
+ const ap = detachActiveProcess(key);
1276
+ ap?.proc.kill();
1277
+ }
1278
+ function hasProcessExited(proc) {
1279
+ return proc.exitCode !== null || proc.signalCode !== null;
1280
+ }
1281
+ function waitForProcessExit(proc, timeoutMs) {
1282
+ if (hasProcessExited(proc)) return Promise.resolve(true);
1283
+ return new Promise((resolve4) => {
1284
+ const onExit = () => {
1285
+ clearTimeout(timer);
1286
+ resolve4(true);
1287
+ };
1288
+ const timer = setTimeout(() => {
1289
+ proc.off("exit", onExit);
1290
+ resolve4(hasProcessExited(proc));
1291
+ }, timeoutMs);
1292
+ proc.once("exit", onExit);
1293
+ });
1294
+ }
1295
+ async function deleteActiveProcessAndWait(key, options = {}) {
1296
+ const ap = detachActiveProcess(key);
1297
+ if (!ap || hasProcessExited(ap.proc)) return true;
1298
+ const gracefulExit = waitForProcessExit(
1299
+ ap.proc,
1300
+ options.exitTimeoutMs ?? PROCESS_EXIT_TIMEOUT_MS
1301
+ );
1302
+ ap.proc.kill();
1303
+ if (await gracefulExit) return true;
1304
+ const forcedExit = waitForProcessExit(
1305
+ ap.proc,
1306
+ options.forceExitTimeoutMs ?? PROCESS_FORCE_EXIT_TIMEOUT_MS
1307
+ );
1308
+ ap.proc.kill("SIGKILL");
1309
+ if (await forcedExit) return true;
1310
+ log.warn("claude process did not exit; starting a fresh session", {
1311
+ sessionKey: key
1312
+ });
1313
+ deleteClaudeSessionId(key);
1314
+ return false;
1299
1315
  }
1300
1316
  function getClaudeSessionId(key) {
1301
1317
  return claudeSessions.get(key);
@@ -1343,8 +1359,9 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
1343
1359
  void unlink(systemPromptFile).catch(() => {
1344
1360
  });
1345
1361
  }
1346
- activeProcesses.delete(sessionKey2);
1347
- if (code !== 0 && code !== null) {
1362
+ const ownsSessionKey = activeProcesses.get(sessionKey2) === ap;
1363
+ if (ownsSessionKey) activeProcesses.delete(sessionKey2);
1364
+ if (ownsSessionKey && code !== 0 && code !== null) {
1348
1365
  log.info("process exited with error, clearing session", {
1349
1366
  code,
1350
1367
  sessionKey: sessionKey2
@@ -1355,16 +1372,50 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
1355
1372
  proc.stderr?.on("data", (data) => {
1356
1373
  const stderr = data.toString();
1357
1374
  log.debug("stderr", { data: stderr.slice(0, 200) });
1358
- if (stderr.includes("Session ID") && (stderr.includes("already in use") || stderr.includes("not found") || stderr.includes("invalid"))) {
1359
- log.warn("claude session ID error, clearing session", {
1360
- sessionKey: sessionKey2,
1361
- error: stderr.slice(0, 200)
1362
- });
1363
- claudeSessions.delete(sessionKey2);
1375
+ if (stderr.includes("No conversation found") || stderr.includes("Session ID") && (stderr.includes("already in use") || stderr.includes("not found") || stderr.includes("invalid"))) {
1376
+ if (activeProcesses.get(sessionKey2) === ap) {
1377
+ log.warn("claude session ID error, clearing session", {
1378
+ sessionKey: sessionKey2,
1379
+ error: stderr.slice(0, 200)
1380
+ });
1381
+ claudeSessions.delete(sessionKey2);
1382
+ } else {
1383
+ log.debug("ignoring session ID error from stale claude process", {
1384
+ sessionKey: sessionKey2
1385
+ });
1386
+ }
1364
1387
  }
1365
1388
  });
1366
1389
  return ap;
1367
1390
  }
1391
+ function appendResumeIfNeeded(sessionKey2, cliArgs) {
1392
+ if (cliArgs.includes("--resume") || cliArgs.includes("--session-id")) {
1393
+ return cliArgs;
1394
+ }
1395
+ const sid = claudeSessions.get(sessionKey2);
1396
+ if (!sid) return cliArgs;
1397
+ return [...cliArgs, "--resume", sid];
1398
+ }
1399
+ function respawnActiveProcess(sessionKey2, cliPath, cliArgs, cwd, ignoreAnthropicApiKey) {
1400
+ const old = activeProcesses.get(sessionKey2);
1401
+ if (!old) return void 0;
1402
+ activeProcesses.delete(sessionKey2);
1403
+ old.proc.removeAllListeners("exit");
1404
+ try {
1405
+ old.proc.kill();
1406
+ } catch {
1407
+ }
1408
+ return spawnClaudeProcess(
1409
+ cliPath,
1410
+ appendResumeIfNeeded(sessionKey2, cliArgs),
1411
+ cwd,
1412
+ sessionKey2,
1413
+ old.proxyServer,
1414
+ old.mcpHash,
1415
+ old.systemPromptFile,
1416
+ ignoreAnthropicApiKey
1417
+ );
1418
+ }
1368
1419
  function buildCliArgs(opts) {
1369
1420
  const {
1370
1421
  sessionKey: sessionKey2,
@@ -1398,7 +1449,7 @@ function buildCliArgs(opts) {
1398
1449
  if (includeSessionId) {
1399
1450
  const sessionId = claudeSessions.get(sessionKey2);
1400
1451
  if (sessionId && !activeProcesses.has(sessionKey2)) {
1401
- args.push("--session-id", sessionId);
1452
+ args.push("--resume", sessionId);
1402
1453
  }
1403
1454
  }
1404
1455
  if (mcpConfig) {
@@ -1990,10 +2041,61 @@ import * as fs4 from "fs";
1990
2041
  import * as path4 from "path";
1991
2042
  import * as crypto2 from "crypto";
1992
2043
  import { EventEmitter as EventEmitter3 } from "events";
2044
+ var SERVER_CLOSED_MESSAGE = "proxy MCP server closed";
2045
+ function isExpectedCleanupError(message) {
2046
+ return message.includes("timed out after") && message.includes("waiting for opencode to resolve") || message.includes("rejecting as orphaned") || message.includes("was orphaned by a new user turn") || message.includes("stream was aborted") || message.includes(SERVER_CLOSED_MESSAGE);
2047
+ }
1993
2048
  var PROTOCOL_VERSION = "2024-11-05";
1994
2049
  var SERVER_NAME = "opencode_proxy";
1995
2050
  var PROXY_TOOL_PREFIX = `mcp__${SERVER_NAME}__`;
1996
- var PROXY_CALL_TIMEOUT_MS = 10 * 60 * 1e3;
2051
+ var PROXY_DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
2052
+ var PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS = {
2053
+ task: 60 * 60 * 1e3
2054
+ // 60 min
2055
+ };
2056
+ var MAX_PROXY_TIMEOUT_MS = 2 ** 31 - 1;
2057
+ function resolveProxyCallTimeoutMs(toolName, input, overrides) {
2058
+ const key = toolName.toLowerCase();
2059
+ let ms = PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS[key] ?? PROXY_DEFAULT_TIMEOUT_MS;
2060
+ if (overrides) {
2061
+ const ov = lookupCaseInsensitive(overrides, key);
2062
+ if (typeof ov === "number" && ov > 0) ms = ov;
2063
+ }
2064
+ if (key === "bash") {
2065
+ const requested = input?.timeout;
2066
+ if (typeof requested === "number" && requested > ms) ms = requested;
2067
+ }
2068
+ return Math.min(ms, MAX_PROXY_TIMEOUT_MS);
2069
+ }
2070
+ function lookupCaseInsensitive(map, key) {
2071
+ if (Object.prototype.hasOwnProperty.call(map, key)) return map[key];
2072
+ for (const k of Object.keys(map)) {
2073
+ if (k.toLowerCase() === key) return map[k];
2074
+ }
2075
+ return void 0;
2076
+ }
2077
+ function resolveProxyClientCeilingMs(overrides) {
2078
+ let ms = PROXY_DEFAULT_TIMEOUT_MS;
2079
+ for (const v of Object.values(PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS)) {
2080
+ if (v > ms) ms = v;
2081
+ }
2082
+ if (overrides) {
2083
+ for (const v of Object.values(overrides)) {
2084
+ if (typeof v === "number" && v > ms) ms = v;
2085
+ }
2086
+ }
2087
+ return Math.min(ms, MAX_PROXY_TIMEOUT_MS);
2088
+ }
2089
+ function buildProxyTimeoutError(toolName, ms) {
2090
+ const key = toolName.toLowerCase();
2091
+ const base = `Proxy tool '${toolName}' timed out after ${ms}ms waiting for opencode to resolve the call`;
2092
+ if (key === "task") {
2093
+ return new Error(
2094
+ base + " (the subagent). The subagent may still be running but its result is no longer reachable in this session. Do not declare the dispatch failed, and do not 'schedule a wake-up' or defer -- that mechanism does not apply here. If the result is required, re-dispatch or verify it directly now."
2095
+ );
2096
+ }
2097
+ return new Error(base);
2098
+ }
1997
2099
  var DEFAULT_PROXY_TOOLS = [
1998
2100
  {
1999
2101
  name: "bash",
@@ -2086,7 +2188,7 @@ var DEFAULT_PROXY_TOOLS = [
2086
2188
  },
2087
2189
  {
2088
2190
  name: "task",
2089
- description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json). The call blocks until the subagent finishes; the 10-minute proxy timeout applies.",
2191
+ description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json). Foreground calls block until the subagent finishes; set `background` to request opencode's background execution mode. Task calls get a 60-minute proxy deadline by default (configurable via proxyToolTimeoutMs).",
2090
2192
  inputSchema: {
2091
2193
  type: "object",
2092
2194
  properties: {
@@ -2109,13 +2211,17 @@ var DEFAULT_PROXY_TOOLS = [
2109
2211
  command: {
2110
2212
  type: "string",
2111
2213
  description: "The command that triggered this task"
2214
+ },
2215
+ background: {
2216
+ type: "boolean",
2217
+ description: "Run the task in the background when supported by opencode"
2112
2218
  }
2113
2219
  },
2114
2220
  required: ["description", "prompt", "subagent_type"]
2115
2221
  }
2116
2222
  }
2117
2223
  ];
2118
- async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2224
+ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverrides) {
2119
2225
  const calls = new EventEmitter3();
2120
2226
  const pending = /* @__PURE__ */ new Map();
2121
2227
  const server2 = createServer(async (req, res) => {
@@ -2124,13 +2230,17 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2124
2230
  res.end();
2125
2231
  return;
2126
2232
  }
2233
+ let requestId = null;
2234
+ let requestMethod = null;
2127
2235
  try {
2128
2236
  const body = await readBody(req);
2129
2237
  const request = JSON.parse(body);
2238
+ requestId = request?.id ?? null;
2239
+ requestMethod = typeof request?.method === "string" ? request.method : null;
2130
2240
  if (request?.jsonrpc !== "2.0" || typeof request.method !== "string") {
2131
2241
  writeJson(res, {
2132
2242
  jsonrpc: "2.0",
2133
- id: request?.id ?? null,
2243
+ id: requestId,
2134
2244
  error: { code: -32600, message: "Invalid request" }
2135
2245
  });
2136
2246
  return;
@@ -2142,7 +2252,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2142
2252
  if (request.method === "initialize") {
2143
2253
  writeJson(res, {
2144
2254
  jsonrpc: "2.0",
2145
- id: request.id ?? null,
2255
+ id: requestId,
2146
2256
  result: {
2147
2257
  protocolVersion: PROTOCOL_VERSION,
2148
2258
  capabilities: { tools: {} },
@@ -2162,7 +2272,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2162
2272
  if (request.method === "tools/list") {
2163
2273
  writeJson(res, {
2164
2274
  jsonrpc: "2.0",
2165
- id: request.id ?? null,
2275
+ id: requestId,
2166
2276
  result: {
2167
2277
  tools: tools.map((t) => ({
2168
2278
  name: t.name,
@@ -2180,10 +2290,10 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2180
2290
  if (!tools.some((t) => t.name === toolName)) {
2181
2291
  writeJson(res, {
2182
2292
  jsonrpc: "2.0",
2183
- id: request.id ?? null,
2184
- error: {
2185
- code: -32601,
2186
- message: `Unknown proxy tool: ${toolName}`
2293
+ id: requestId,
2294
+ result: {
2295
+ content: [{ type: "text", text: `Unknown proxy tool: ${toolName}` }],
2296
+ isError: true
2187
2297
  }
2188
2298
  });
2189
2299
  return;
@@ -2205,20 +2315,21 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2205
2315
  reject
2206
2316
  };
2207
2317
  pending.set(callId, entry);
2318
+ const deadlineMs = resolveProxyCallTimeoutMs(
2319
+ toolName,
2320
+ input,
2321
+ timeoutOverrides
2322
+ );
2208
2323
  timer = setTimeout(() => {
2209
2324
  if (!pending.has(callId)) return;
2210
2325
  pending.delete(callId);
2211
2326
  log.notice("proxy-mcp tool call timed out", {
2212
2327
  callId,
2213
2328
  toolName,
2214
- timeoutMs: PROXY_CALL_TIMEOUT_MS
2329
+ deadlineMs
2215
2330
  });
2216
- reject(
2217
- new Error(
2218
- `Proxy tool '${toolName}' timed out after ${PROXY_CALL_TIMEOUT_MS}ms waiting for opencode to resolve the call`
2219
- )
2220
- );
2221
- }, PROXY_CALL_TIMEOUT_MS);
2331
+ reject(buildProxyTimeoutError(toolName, deadlineMs));
2332
+ }, deadlineMs);
2222
2333
  calls.emit("call", entry);
2223
2334
  }
2224
2335
  ).finally(() => {
@@ -2228,17 +2339,17 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2228
2339
  if (result.kind === "error") {
2229
2340
  writeJson(res, {
2230
2341
  jsonrpc: "2.0",
2231
- id: request.id ?? null,
2232
- error: {
2233
- code: -32e3,
2234
- message: result.message
2342
+ id: requestId,
2343
+ result: {
2344
+ content: [{ type: "text", text: result.message }],
2345
+ isError: true
2235
2346
  }
2236
2347
  });
2237
2348
  return;
2238
2349
  }
2239
2350
  writeJson(res, {
2240
2351
  jsonrpc: "2.0",
2241
- id: request.id ?? null,
2352
+ id: requestId,
2242
2353
  result: {
2243
2354
  content: [{ type: "text", text: result.text }],
2244
2355
  isError: result.isError === true
@@ -2248,20 +2359,38 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2248
2359
  }
2249
2360
  writeJson(res, {
2250
2361
  jsonrpc: "2.0",
2251
- id: request.id ?? null,
2362
+ id: requestId,
2252
2363
  error: { code: -32601, message: `Unknown method: ${request.method}` }
2253
2364
  });
2254
2365
  } catch (error) {
2255
2366
  const errorMessage = error instanceof Error ? error.message : String(error);
2256
- const isExpectedCleanup = errorMessage.includes("timed out after") && errorMessage.includes("waiting for opencode to resolve") || errorMessage.includes("rejecting as orphaned") || errorMessage.includes("was orphaned by a new user turn");
2257
- const logFn = isExpectedCleanup ? log.notice : log.warn;
2367
+ const logFn = isExpectedCleanupError(errorMessage) ? log.notice : log.warn;
2258
2368
  logFn("proxy-mcp error handling request", {
2259
2369
  error: errorMessage
2260
2370
  });
2371
+ if (requestMethod === "tools/call") {
2372
+ try {
2373
+ writeJson(res, {
2374
+ jsonrpc: "2.0",
2375
+ id: requestId,
2376
+ result: {
2377
+ content: [{ type: "text", text: errorMessage }],
2378
+ isError: true
2379
+ }
2380
+ });
2381
+ } catch {
2382
+ try {
2383
+ res.statusCode = 500;
2384
+ res.end();
2385
+ } catch {
2386
+ }
2387
+ }
2388
+ return;
2389
+ }
2261
2390
  try {
2262
2391
  writeJson(res, {
2263
2392
  jsonrpc: "2.0",
2264
- id: null,
2393
+ id: requestId,
2265
2394
  error: {
2266
2395
  code: -32603,
2267
2396
  message: error instanceof Error ? error.message : "Internal error"
@@ -2306,7 +2435,8 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2306
2435
  mcpServers: {
2307
2436
  [SERVER_NAME]: {
2308
2437
  type: "http",
2309
- url
2438
+ url,
2439
+ timeout: resolveProxyClientCeilingMs(timeoutOverrides)
2310
2440
  }
2311
2441
  }
2312
2442
  },
@@ -2324,7 +2454,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
2324
2454
  },
2325
2455
  async close() {
2326
2456
  for (const entry of pending.values()) {
2327
- entry.reject(new Error("proxy MCP server closed"));
2457
+ entry.reject(new Error(SERVER_CLOSED_MESSAGE));
2328
2458
  }
2329
2459
  pending.clear();
2330
2460
  await new Promise((resolve4) => {
@@ -2386,7 +2516,6 @@ import { EventEmitter as EventEmitter4 } from "events";
2386
2516
  var pendingByCallId = /* @__PURE__ */ new Map();
2387
2517
  var callIdsBySession = /* @__PURE__ */ new Map();
2388
2518
  var emitter = new EventEmitter4();
2389
- var PENDING_PROXY_CALL_TIMEOUT_MS = 10 * 60 * 1e3;
2390
2519
  function eventName(sessionKey2) {
2391
2520
  return `pending:${sessionKey2}`;
2392
2521
  }
@@ -2409,7 +2538,7 @@ function onPendingProxyCall(sessionKey2, handler) {
2409
2538
  emitter.on(name, handler);
2410
2539
  return () => emitter.off(name, handler);
2411
2540
  }
2412
- function queuePendingProxyCall(sessionKey2, call) {
2541
+ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides) {
2413
2542
  const previous = pendingByCallId.get(call.id);
2414
2543
  if (previous) {
2415
2544
  clearTimeout(previous.timer);
@@ -2419,23 +2548,24 @@ function queuePendingProxyCall(sessionKey2, call) {
2419
2548
  pendingByCallId.delete(call.id);
2420
2549
  indexRemove(previous.sessionKey, call.id);
2421
2550
  }
2551
+ const deadlineMs = resolveProxyCallTimeoutMs(
2552
+ call.toolName,
2553
+ call.input,
2554
+ timeoutOverrides
2555
+ );
2422
2556
  const timer = setTimeout(() => {
2423
2557
  const current = pendingByCallId.get(call.id);
2424
2558
  if (!current) return;
2425
2559
  pendingByCallId.delete(call.id);
2426
2560
  indexRemove(current.sessionKey, call.id);
2427
- current.reject(
2428
- new Error(
2429
- `Proxy tool call '${call.toolName}' timed out after ${PENDING_PROXY_CALL_TIMEOUT_MS}ms waiting for opencode to resolve the call`
2430
- )
2431
- );
2561
+ current.reject(buildProxyTimeoutError(call.toolName, deadlineMs));
2432
2562
  log.notice("timed out pending proxy call", {
2433
2563
  sessionKey: current.sessionKey,
2434
2564
  toolCallId: call.id,
2435
2565
  toolName: call.toolName,
2436
- timeoutMs: PENDING_PROXY_CALL_TIMEOUT_MS
2566
+ deadlineMs
2437
2567
  });
2438
- }, PENDING_PROXY_CALL_TIMEOUT_MS);
2568
+ }, deadlineMs);
2439
2569
  const pending = {
2440
2570
  sessionKey: sessionKey2,
2441
2571
  toolCallId: call.id,
@@ -2574,6 +2704,7 @@ function hasNewUserContent(prompt) {
2574
2704
  var AUTO_CONTINUE_MAX_ATTEMPTS = 8;
2575
2705
  var AUTO_CONTINUE_MAX_ELAPSED_MS = 10 * 60 * 1e3;
2576
2706
  var AUTO_CONTINUE_NO_PROGRESS_LIMIT = 2;
2707
+ var PROXY_RESULT_BOUNDARY_GRACE_MS = 250;
2577
2708
  var AUTO_CONTINUE_PROMPT = "Continue the task from where you stopped. Do not summarize; keep working until the requested task is complete, you need clarification, or you hit a real blocker.";
2578
2709
  function normalizeVisibleText(text) {
2579
2710
  return text.replace(/\s+/g, " ").trim();
@@ -2911,9 +3042,10 @@ var ClaudeCodeLanguageModel = class {
2911
3042
  * The process lifecycle owns the server lifecycle via session-manager.
2912
3043
  */
2913
3044
  async ensureProxyServer(tools, sessionKeyForCalls) {
2914
- const srv = await createProxyMcpServer(tools);
3045
+ const timeoutOverrides = this.config.proxyToolTimeoutMs;
3046
+ const srv = await createProxyMcpServer(tools, timeoutOverrides);
2915
3047
  srv.calls.on("call", (call) => {
2916
- queuePendingProxyCall(sessionKeyForCalls, call);
3048
+ queuePendingProxyCall(sessionKeyForCalls, call, timeoutOverrides);
2917
3049
  });
2918
3050
  return srv;
2919
3051
  }
@@ -3668,22 +3800,32 @@ ${plan}
3668
3800
  let activeProcess = getActiveProcess(sk);
3669
3801
  let proc;
3670
3802
  let lineEmitter;
3803
+ let cliArgs;
3671
3804
  let proxyServer = activeProcess?.proxyServer ?? null;
3672
- if (!compactionMode && activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
3673
- const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
3674
- const previousHash = activeProcess.mcpHash ?? null;
3675
- if (previousHash !== probe.bridgedHash) {
3676
- log.info("opencode MCP config changed, respawning claude", {
3677
- sk,
3678
- previousHash,
3679
- currentHash: probe.bridgedHash
3680
- });
3681
- deleteActiveProcess(sk);
3682
- activeProcess = void 0;
3683
- proxyServer = null;
3684
- }
3685
- }
3686
3805
  const setup = async () => {
3806
+ if (!compactionMode && activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
3807
+ const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
3808
+ const previousHash = activeProcess.mcpHash ?? null;
3809
+ if (previousHash !== probe.bridgedHash) {
3810
+ if (previousPendingProxyCalls.length > 0) {
3811
+ log.info("deferring MCP hot reload until proxy calls resolve", {
3812
+ sk,
3813
+ previousHash,
3814
+ currentHash: probe.bridgedHash,
3815
+ pendingCalls: previousPendingProxyCalls.length
3816
+ });
3817
+ } else {
3818
+ log.info("opencode MCP config changed, respawning claude", {
3819
+ sk,
3820
+ previousHash,
3821
+ currentHash: probe.bridgedHash
3822
+ });
3823
+ await deleteActiveProcessAndWait(sk);
3824
+ activeProcess = void 0;
3825
+ proxyServer = null;
3826
+ }
3827
+ }
3828
+ }
3687
3829
  if (useInteractive && !compactionMode) {
3688
3830
  const mcp = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
3689
3831
  if (activeProcess) {
@@ -3744,7 +3886,6 @@ ${plan}
3744
3886
  });
3745
3887
  }
3746
3888
  } else {
3747
- let cliArgs;
3748
3889
  let spawnSystemPromptFile;
3749
3890
  let spawnProxyServer = null;
3750
3891
  let spawnMcpHash = null;
@@ -3847,6 +3988,7 @@ ${plan}
3847
3988
  let controllerClosed = false;
3848
3989
  let pendingProxyUnsubscribe = null;
3849
3990
  let resultFallbackTimer = null;
3991
+ let pendingResultCompletion = null;
3850
3992
  let hasReceivedContent = false;
3851
3993
  let visibleTextSinceContinue = "";
3852
3994
  let lastVisibleTextSinceContinue = "";
@@ -3877,6 +4019,103 @@ ${plan}
3877
4019
  closeHandler();
3878
4020
  }, delayMs);
3879
4021
  };
4022
+ const START_WATCHDOG_MS = (() => {
4023
+ const env = process.env.CLAUDE_CODE_START_WATCHDOG_MS;
4024
+ const parsed = env ? Number.parseInt(env, 10) : NaN;
4025
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 9e4;
4026
+ })();
4027
+ let startWatchdog = null;
4028
+ let respawnAttempted = false;
4029
+ const clearStartWatchdog = () => {
4030
+ if (startWatchdog) {
4031
+ clearTimeout(startWatchdog);
4032
+ startWatchdog = null;
4033
+ }
4034
+ };
4035
+ const onStartWatchdogFire = () => {
4036
+ startWatchdog = null;
4037
+ if (controllerClosed || hasReceivedContent) return;
4038
+ if (respawnAttempted) {
4039
+ log.error(
4040
+ "claude process still silent after respawn; ending turn",
4041
+ { sessionKey: sk }
4042
+ );
4043
+ deleteActiveProcess(sk);
4044
+ deleteClaudeSessionId(sk);
4045
+ controllerClosed = true;
4046
+ cleanupTurn();
4047
+ controller.enqueue({
4048
+ type: "error",
4049
+ error: new Error(
4050
+ "Claude process produced no output after the envelope write (start watchdog timeout)."
4051
+ )
4052
+ });
4053
+ try {
4054
+ controller.close();
4055
+ } catch {
4056
+ }
4057
+ return;
4058
+ }
4059
+ respawnAttempted = true;
4060
+ log.warn(
4061
+ "no stdout after envelope write; respawning claude process to resume conversation",
4062
+ { sessionKey: sk, startWatchdogMs: START_WATCHDOG_MS }
4063
+ );
4064
+ lineEmitter.off("line", lineHandler);
4065
+ lineEmitter.off("close", closeHandler);
4066
+ proc.off("error", procErrorHandler);
4067
+ const newAp = respawnActiveProcess(
4068
+ sk,
4069
+ cliPath,
4070
+ cliArgs,
4071
+ cwd,
4072
+ self.config.ignoreAnthropicApiKey
4073
+ );
4074
+ if (!newAp) {
4075
+ log.error(
4076
+ "no active process to respawn (start watchdog); ending turn",
4077
+ { sessionKey: sk }
4078
+ );
4079
+ controllerClosed = true;
4080
+ cleanupTurn();
4081
+ controller.enqueue({
4082
+ type: "error",
4083
+ error: new Error(
4084
+ "No active claude process to respawn after start watchdog timeout."
4085
+ )
4086
+ });
4087
+ try {
4088
+ controller.close();
4089
+ } catch {
4090
+ }
4091
+ return;
4092
+ }
4093
+ proc = newAp.proc;
4094
+ lineEmitter = newAp.lineEmitter;
4095
+ activeProcess = newAp;
4096
+ lineEmitter.on("line", lineHandler);
4097
+ lineEmitter.on("close", closeHandler);
4098
+ proc.on("error", procErrorHandler);
4099
+ try {
4100
+ proc.stdin?.write(userMsg + "\n");
4101
+ log.debug("re-sent user message after respawn", {
4102
+ textLength: userMsg.length
4103
+ });
4104
+ } catch (err) {
4105
+ log.error("failed to re-send envelope after respawn", {
4106
+ error: err instanceof Error ? err.message : String(err)
4107
+ });
4108
+ }
4109
+ startWatchdog = setTimeout(
4110
+ onStartWatchdogFire,
4111
+ START_WATCHDOG_MS
4112
+ );
4113
+ };
4114
+ const armStartWatchdog = () => {
4115
+ clearStartWatchdog();
4116
+ if (controllerClosed) return;
4117
+ startWatchdog = setTimeout(onStartWatchdogFire, START_WATCHDOG_MS);
4118
+ };
3880
4119
  const toolCallMap = /* @__PURE__ */ new Map();
3881
4120
  const skipResultForIds = /* @__PURE__ */ new Set();
3882
4121
  const toolCallsById = /* @__PURE__ */ new Map();
@@ -3932,6 +4171,28 @@ ${plan}
3932
4171
  });
3933
4172
  finishWithToolCalls(batch);
3934
4173
  };
4174
+ const settleResultBoundary = () => {
4175
+ drainTimer = null;
4176
+ const completeResult2 = pendingResultCompletion;
4177
+ pendingResultCompletion = null;
4178
+ if (!completeResult2 || controllerClosed) return;
4179
+ if (drainBuffer.length > 0) {
4180
+ drainNow();
4181
+ return;
4182
+ }
4183
+ completeResult2();
4184
+ };
4185
+ const scheduleResultBoundary = (completeResult2, delayMs) => {
4186
+ pendingResultCompletion = completeResult2;
4187
+ if (drainTimer) clearTimeout(drainTimer);
4188
+ drainTimer = setTimeout(settleResultBoundary, delayMs);
4189
+ };
4190
+ const noteResultBoundaryCall = () => {
4191
+ if (!pendingResultCompletion) return false;
4192
+ if (drainTimer) clearTimeout(drainTimer);
4193
+ drainTimer = setTimeout(settleResultBoundary, DRAIN_QUIET_MS);
4194
+ return true;
4195
+ };
3935
4196
  const noteVisibleText = (text) => {
3936
4197
  visibleTextSinceContinue += text;
3937
4198
  lastVisibleTextSinceContinue += text;
@@ -3956,11 +4217,106 @@ ${plan}
3956
4217
  hadProxyActivitySinceContinue = false;
3957
4218
  lastStopReason = null;
3958
4219
  };
4220
+ const completeResult = (msg) => {
4221
+ if (controllerClosed) return;
4222
+ if (drainBuffer.length > 0) {
4223
+ drainNow();
4224
+ return;
4225
+ }
4226
+ const pendingSiblings = getPendingProxyCalls(sk);
4227
+ if (pendingSiblings.length > 0) {
4228
+ log.info("leaving parallel proxy calls pending at result boundary", {
4229
+ sessionKey: sk,
4230
+ count: pendingSiblings.length
4231
+ });
4232
+ }
4233
+ const autoDecision = shouldAutoContinueIncompleteTurn(
4234
+ autoContinueState,
4235
+ {
4236
+ text: visibleTextSinceContinue,
4237
+ lastVisibleText: lastVisibleTextSinceContinue,
4238
+ hadReasoning: hadReasoningSinceContinue,
4239
+ hadToolActivity: hadToolActivitySinceContinue,
4240
+ hadProxyActivity: hadProxyActivitySinceContinue,
4241
+ isError: msg.is_error,
4242
+ stopReason: lastStopReason
4243
+ }
4244
+ );
4245
+ if (autoDecision.continue) {
4246
+ const signature = continuationSignature({
4247
+ text: visibleTextSinceContinue,
4248
+ lastVisibleText: lastVisibleTextSinceContinue,
4249
+ hadReasoning: hadReasoningSinceContinue,
4250
+ hadToolActivity: hadToolActivitySinceContinue,
4251
+ hadProxyActivity: hadProxyActivitySinceContinue,
4252
+ isError: msg.is_error
4253
+ });
4254
+ autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
4255
+ autoContinueState.lastSignature = signature;
4256
+ autoContinueState.attempts++;
4257
+ log.notice("auto-continuing incomplete claude result", {
4258
+ sessionKey: sk,
4259
+ reason: autoDecision.reason,
4260
+ attempts: autoContinueState.attempts,
4261
+ textLength: visibleTextSinceContinue.length,
4262
+ lastTextLength: lastVisibleTextSinceContinue.length,
4263
+ hadReasoning: hadReasoningSinceContinue,
4264
+ hadToolActivity: hadToolActivitySinceContinue,
4265
+ hadProxyActivity: hadProxyActivitySinceContinue
4266
+ });
4267
+ turnCompleted = false;
4268
+ resetAutoContinueWindow();
4269
+ proc.stdin?.write(makeAutoContinueMessage() + "\n");
4270
+ return;
4271
+ }
4272
+ log.notice("auto-continuation stopped", {
4273
+ sessionKey: sk,
4274
+ reason: autoDecision.reason,
4275
+ stopReason: lastStopReason,
4276
+ attempts: autoContinueState.attempts,
4277
+ textLength: visibleTextSinceContinue.length,
4278
+ lastTextLength: lastVisibleTextSinceContinue.length,
4279
+ hadReasoning: hadReasoningSinceContinue,
4280
+ hadToolActivity: hadToolActivitySinceContinue,
4281
+ hadProxyActivity: hadProxyActivitySinceContinue
4282
+ });
4283
+ for (const [idx, reasoningId] of reasoningIds) {
4284
+ if (reasoningStarted.get(idx)) {
4285
+ controller.enqueue({
4286
+ type: "reasoning-end",
4287
+ id: reasoningId
4288
+ });
4289
+ }
4290
+ }
4291
+ controller.enqueue({
4292
+ type: "finish",
4293
+ finishReason: toFinishReason("stop"),
4294
+ usage: toUsage(msg.usage),
4295
+ providerMetadata: {
4296
+ "claude-code": {
4297
+ ...resultMeta,
4298
+ ...compactionMode ? { compactionModel: effectiveModelId } : {}
4299
+ },
4300
+ ...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
4301
+ anthropic: {
4302
+ cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
4303
+ }
4304
+ } : {}
4305
+ }
4306
+ });
4307
+ controllerClosed = true;
4308
+ cleanupTurn();
4309
+ try {
4310
+ controller.close();
4311
+ } catch {
4312
+ }
4313
+ };
3959
4314
  let gotPartialEvents = false;
3960
4315
  const lineHandler = (line) => {
3961
4316
  if (!line.trim()) return;
3962
4317
  if (controllerClosed) return;
3963
4318
  startResultFallback();
4319
+ clearStartWatchdog();
3964
4320
  try {
3965
4321
  const outer = JSON.parse(line);
3966
4322
  const msg = outer.type === "stream_event" && outer.event ? { ...outer.event, session_id: outer.session_id } : outer;
@@ -4150,6 +4506,7 @@ ${plan}
4150
4506
  });
4151
4507
  endTextBlock();
4152
4508
  } else if (tc.name.startsWith(PROXY_TOOL_PREFIX)) {
4509
+ noteProxyActivity();
4153
4510
  log.debug("ignoring proxy tool_use block; broker handles it", {
4154
4511
  name: tc.name,
4155
4512
  id: tc.id
@@ -4316,6 +4673,7 @@ ${plan}
4316
4673
  });
4317
4674
  endTextBlock();
4318
4675
  } else if (block.name.startsWith(PROXY_TOOL_PREFIX)) {
4676
+ noteProxyActivity();
4319
4677
  log.debug("ignoring proxy tool_use from assistant message", {
4320
4678
  name: block.name,
4321
4679
  id: block.id
@@ -4466,113 +4824,36 @@ ${plan}
4466
4824
  });
4467
4825
  turnCompleted = true;
4468
4826
  endTextBlock();
4469
- if (drainBuffer.length > 0) {
4827
+ const shouldDeferResult = !msg.is_error && !autoContinueState.aborted && !autoContinueState.sawAskUserQuestion;
4828
+ if (drainBuffer.length > 0 && shouldDeferResult) {
4470
4829
  log.info(
4471
- "draining pending proxy calls at turn-result boundary",
4830
+ "waiting for parallel proxy calls at turn-result boundary",
4472
4831
  {
4473
4832
  sessionKey: sk,
4474
4833
  count: drainBuffer.length
4475
4834
  }
4476
4835
  );
4477
- drainNow();
4836
+ scheduleResultBoundary(
4837
+ () => completeResult(msg),
4838
+ DRAIN_QUIET_MS
4839
+ );
4478
4840
  return;
4479
4841
  }
4480
- const orphanPending = getPendingProxyCalls(sk);
4481
- if (orphanPending.length > 0) {
4482
- log.warn(
4483
- "rejecting orphan pending proxy calls at turn-result boundary",
4842
+ if (drainBuffer.length === 0 && hadProxyActivitySinceContinue && shouldDeferResult) {
4843
+ log.info(
4844
+ "waiting for delayed proxy call at turn-result boundary",
4484
4845
  {
4485
4846
  sessionKey: sk,
4486
- count: orphanPending.length
4847
+ graceMs: PROXY_RESULT_BOUNDARY_GRACE_MS
4487
4848
  }
4488
4849
  );
4489
- rejectAllPendingProxyCallsForSession(
4490
- sk,
4491
- new Error(
4492
- "Claude CLI emitted result with pending proxy calls not in drain buffer"
4493
- )
4850
+ scheduleResultBoundary(
4851
+ () => completeResult(msg),
4852
+ PROXY_RESULT_BOUNDARY_GRACE_MS
4494
4853
  );
4495
- }
4496
- const autoDecision = shouldAutoContinueIncompleteTurn(
4497
- autoContinueState,
4498
- {
4499
- text: visibleTextSinceContinue,
4500
- lastVisibleText: lastVisibleTextSinceContinue,
4501
- hadReasoning: hadReasoningSinceContinue,
4502
- hadToolActivity: hadToolActivitySinceContinue,
4503
- hadProxyActivity: hadProxyActivitySinceContinue,
4504
- isError: msg.is_error,
4505
- stopReason: lastStopReason
4506
- }
4507
- );
4508
- if (autoDecision.continue) {
4509
- const signature = continuationSignature({
4510
- text: visibleTextSinceContinue,
4511
- lastVisibleText: lastVisibleTextSinceContinue,
4512
- hadReasoning: hadReasoningSinceContinue,
4513
- hadToolActivity: hadToolActivitySinceContinue,
4514
- hadProxyActivity: hadProxyActivitySinceContinue,
4515
- isError: msg.is_error
4516
- });
4517
- autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
4518
- autoContinueState.lastSignature = signature;
4519
- autoContinueState.attempts++;
4520
- log.notice("auto-continuing incomplete claude result", {
4521
- sessionKey: sk,
4522
- reason: autoDecision.reason,
4523
- attempts: autoContinueState.attempts,
4524
- textLength: visibleTextSinceContinue.length,
4525
- lastTextLength: lastVisibleTextSinceContinue.length,
4526
- hadReasoning: hadReasoningSinceContinue,
4527
- hadToolActivity: hadToolActivitySinceContinue,
4528
- hadProxyActivity: hadProxyActivitySinceContinue
4529
- });
4530
- turnCompleted = false;
4531
- resetAutoContinueWindow();
4532
- proc.stdin?.write(makeAutoContinueMessage() + "\n");
4533
4854
  return;
4534
4855
  }
4535
- log.notice("auto-continuation stopped", {
4536
- sessionKey: sk,
4537
- reason: autoDecision.reason,
4538
- stopReason: lastStopReason,
4539
- attempts: autoContinueState.attempts,
4540
- textLength: visibleTextSinceContinue.length,
4541
- lastTextLength: lastVisibleTextSinceContinue.length,
4542
- hadReasoning: hadReasoningSinceContinue,
4543
- hadToolActivity: hadToolActivitySinceContinue,
4544
- hadProxyActivity: hadProxyActivitySinceContinue
4545
- });
4546
- for (const [idx, reasoningId] of reasoningIds) {
4547
- if (reasoningStarted.get(idx)) {
4548
- controller.enqueue({
4549
- type: "reasoning-end",
4550
- id: reasoningId
4551
- });
4552
- }
4553
- }
4554
- controller.enqueue({
4555
- type: "finish",
4556
- finishReason: toFinishReason("stop"),
4557
- usage: toUsage(msg.usage),
4558
- providerMetadata: {
4559
- "claude-code": {
4560
- ...resultMeta,
4561
- ...compactionMode ? { compactionModel: effectiveModelId } : {}
4562
- },
4563
- ...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
4564
- anthropic: {
4565
- cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
4566
- }
4567
- } : {}
4568
- }
4569
- });
4570
- controllerClosed = true;
4571
- cleanupTurn();
4572
- try {
4573
- controller.close();
4574
- } catch {
4575
- }
4856
+ completeResult(msg);
4576
4857
  }
4577
4858
  } catch (e) {
4578
4859
  log.debug("failed to parse line", {
@@ -4616,6 +4897,8 @@ ${plan}
4616
4897
  if (cleanedUp) return;
4617
4898
  cleanedUp = true;
4618
4899
  clearFallbackTimer();
4900
+ pendingResultCompletion = null;
4901
+ clearStartWatchdog();
4619
4902
  if (drainTimer) {
4620
4903
  clearTimeout(drainTimer);
4621
4904
  drainTimer = null;
@@ -4676,6 +4959,7 @@ ${plan}
4676
4959
  noteProxyActivity();
4677
4960
  noteToolActivity();
4678
4961
  drainBuffer.push(call);
4962
+ if (noteResultBoundaryCall()) return;
4679
4963
  if (drainTimer) clearTimeout(drainTimer);
4680
4964
  drainTimer = setTimeout(drainNow, DRAIN_QUIET_MS);
4681
4965
  });
@@ -4689,6 +4973,15 @@ ${plan}
4689
4973
  "abort signal received before content, closing stream immediately",
4690
4974
  { cwd }
4691
4975
  );
4976
+ if (drainBuffer.length > 0 || getPendingProxyCalls(sk).length > 0) {
4977
+ rejectAllPendingProxyCallsForSession(
4978
+ sk,
4979
+ new Error(
4980
+ "Provider stream was aborted before pending proxy calls were emitted"
4981
+ )
4982
+ );
4983
+ drainBuffer.length = 0;
4984
+ }
4692
4985
  controllerClosed = true;
4693
4986
  cleanupTurn();
4694
4987
  try {
@@ -4714,20 +5007,14 @@ ${plan}
4714
5007
  });
4715
5008
  resolvePendingProxyCallById(call.toolCallId, result);
4716
5009
  } else {
4717
- log.notice(
4718
- "pending proxy call had no matching tool-result; rejecting as orphan",
5010
+ log.info(
5011
+ "leaving unmatched parallel proxy call pending",
4719
5012
  {
4720
5013
  sessionKey: sk,
4721
5014
  toolCallId: call.toolCallId,
4722
5015
  toolName: call.toolName
4723
5016
  }
4724
5017
  );
4725
- rejectPendingProxyCallById(
4726
- call.toolCallId,
4727
- new Error(
4728
- `Pending proxy call '${call.toolName}' (${call.toolCallId}) was not matched in tool-result turn; rejecting as orphaned`
4729
- )
4730
- );
4731
5018
  }
4732
5019
  }
4733
5020
  return;
@@ -4744,6 +5031,7 @@ ${plan}
4744
5031
  }
4745
5032
  proc.stdin?.write(userMsg + "\n");
4746
5033
  log.debug("sent user message", { textLength: userMsg.length });
5034
+ armStartWatchdog();
4747
5035
  };
4748
5036
  void setup().catch((err) => {
4749
5037
  log.error("failed to set up doStream", {
@@ -5236,6 +5524,13 @@ function pickOpencodeDirectory(input) {
5236
5524
  return void 0;
5237
5525
  }
5238
5526
  var warnedAnthropicApiKey = false;
5527
+ var DEFAULT_PROXY_TOOL_NAMES = [
5528
+ "Bash",
5529
+ "Edit",
5530
+ "Write",
5531
+ "WebFetch",
5532
+ "Task"
5533
+ ];
5239
5534
  function warnIfAnthropicApiKey(ignore) {
5240
5535
  if (warnedAnthropicApiKey) return;
5241
5536
  if (!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_AUTH_TOKEN) return;
@@ -5262,7 +5557,7 @@ function createClaudeCode(settings = {}) {
5262
5557
  warnIfAnthropicApiKey(settings.ignoreAnthropicApiKey);
5263
5558
  const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
5264
5559
  const providerName = settings.providerID ?? settings.name ?? "claude-code";
5265
- const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];
5560
+ const proxyTools = settings.proxyTools ?? [...DEFAULT_PROXY_TOOL_NAMES];
5266
5561
  const createModel = (modelId) => {
5267
5562
  return new ClaudeCodeLanguageModel(modelId, {
5268
5563
  provider: providerName,
@@ -5280,6 +5575,7 @@ function createClaudeCode(settings = {}) {
5280
5575
  controlRequestToolBehaviors: settings.controlRequestToolBehaviors,
5281
5576
  controlRequestDenyMessage: settings.controlRequestDenyMessage,
5282
5577
  proxyTools,
5578
+ proxyToolTimeoutMs: settings.proxyToolTimeoutMs,
5283
5579
  webSearch: settings.webSearch,
5284
5580
  hotReloadMcp: settings.hotReloadMcp ?? true,
5285
5581
  proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
@@ -5374,7 +5670,7 @@ function configModelsForProvider(providerModels, providerID, modelSuffix) {
5374
5670
  async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefaults = {}, displayName) {
5375
5671
  const mergedOptions = {
5376
5672
  cliPath: "claude",
5377
- proxyTools: ["Bash", "Edit", "Write", "WebFetch"],
5673
+ proxyTools: [...DEFAULT_PROXY_TOOL_NAMES],
5378
5674
  ...optionDefaults,
5379
5675
  ...cleanProviderOptions(existing?.options),
5380
5676
  providerID