@leo000001/claude-code-mcp 1.6.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/dist/index.js CHANGED
@@ -11,16 +11,16 @@ import { z } from "zod";
11
11
  var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
12
12
  var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
13
13
  var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
14
- var SessionManager = class {
14
+ var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
15
+ var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
16
+ var SessionManager = class _SessionManager {
15
17
  sessions = /* @__PURE__ */ new Map();
18
+ runtime = /* @__PURE__ */ new Map();
16
19
  cleanupTimer;
17
- sessionTtlMs;
18
- runningSessionMaxMs;
19
- constructor(opts) {
20
- this.sessionTtlMs = opts?.sessionTtlMs !== void 0 ? opts.sessionTtlMs : DEFAULT_SESSION_TTL_MS;
21
- this.runningSessionMaxMs = opts?.runningSessionMaxMs !== void 0 ? opts.runningSessionMaxMs : DEFAULT_RUNNING_SESSION_MAX_MS;
22
- const cleanupIntervalMs = opts?.cleanupIntervalMs !== void 0 ? opts.cleanupIntervalMs : DEFAULT_CLEANUP_INTERVAL_MS;
23
- this.cleanupTimer = setInterval(() => this.cleanup(), cleanupIntervalMs);
20
+ sessionTtlMs = DEFAULT_SESSION_TTL_MS;
21
+ runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
22
+ constructor() {
23
+ this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
24
24
  if (this.cleanupTimer.unref) {
25
25
  this.cleanupTimer.unref();
26
26
  }
@@ -40,7 +40,7 @@ var SessionManager = class {
40
40
  totalCostUsd: 0,
41
41
  cwd: params.cwd,
42
42
  model: params.model,
43
- permissionMode: params.permissionMode ?? "dontAsk",
43
+ permissionMode: params.permissionMode ?? "default",
44
44
  allowedTools: params.allowedTools,
45
45
  disallowedTools: params.disallowedTools,
46
46
  tools: params.tools,
@@ -69,11 +69,23 @@ var SessionManager = class {
69
69
  abortController: params.abortController
70
70
  };
71
71
  this.sessions.set(params.sessionId, info);
72
+ this.runtime.set(params.sessionId, {
73
+ buffer: {
74
+ events: [],
75
+ maxSize: DEFAULT_EVENT_BUFFER_MAX_SIZE,
76
+ hardMaxSize: DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE,
77
+ nextId: 0
78
+ },
79
+ pendingPermissions: /* @__PURE__ */ new Map()
80
+ });
72
81
  return info;
73
82
  }
74
83
  get(sessionId) {
75
84
  return this.sessions.get(sessionId);
76
85
  }
86
+ updateStatus(sessionId, status) {
87
+ return this.update(sessionId, { status });
88
+ }
77
89
  list() {
78
90
  return Array.from(this.sessions.values());
79
91
  }
@@ -95,35 +107,199 @@ var SessionManager = class {
95
107
  info.status = "running";
96
108
  info.abortController = abortController;
97
109
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
110
+ this.clearTerminalEvents(sessionId);
98
111
  return info;
99
112
  }
100
- cancel(sessionId) {
113
+ cancel(sessionId, opts) {
101
114
  const info = this.sessions.get(sessionId);
102
115
  if (!info) return false;
103
- if (info.status !== "running") return false;
116
+ if (info.status !== "running" && info.status !== "waiting_permission") return false;
117
+ if (info.status === "waiting_permission") {
118
+ this.finishAllPending(
119
+ sessionId,
120
+ { behavior: "deny", message: "Session cancelled", interrupt: true },
121
+ "cancel"
122
+ );
123
+ }
104
124
  if (info.abortController) {
105
125
  info.abortController.abort();
106
126
  }
107
127
  info.status = "cancelled";
128
+ info.cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
129
+ info.cancelledReason = opts?.reason ?? "Session cancelled";
130
+ info.cancelledSource = opts?.source ?? "cancel";
108
131
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
109
132
  return true;
110
133
  }
111
134
  delete(sessionId) {
135
+ this.finishAllPending(
136
+ sessionId,
137
+ { behavior: "deny", message: "Session deleted", interrupt: true },
138
+ "cleanup"
139
+ );
140
+ this.runtime.delete(sessionId);
112
141
  return this.sessions.delete(sessionId);
113
142
  }
143
+ setResult(sessionId, result) {
144
+ const state = this.runtime.get(sessionId);
145
+ if (!state) return;
146
+ state.storedResult = result;
147
+ }
148
+ getResult(sessionId) {
149
+ return this.runtime.get(sessionId)?.storedResult;
150
+ }
151
+ setInitTools(sessionId, tools) {
152
+ const state = this.runtime.get(sessionId);
153
+ if (!state) return;
154
+ state.initTools = tools;
155
+ }
156
+ getInitTools(sessionId) {
157
+ return this.runtime.get(sessionId)?.initTools;
158
+ }
159
+ pushEvent(sessionId, event) {
160
+ const state = this.runtime.get(sessionId);
161
+ if (!state) return void 0;
162
+ const full = _SessionManager.pushEvent(
163
+ state.buffer,
164
+ event,
165
+ (requestId) => state.pendingPermissions.has(requestId)
166
+ );
167
+ const info = this.sessions.get(sessionId);
168
+ if (info) {
169
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
170
+ const data = event.data;
171
+ const toolUseId = typeof data?.tool_use_id === "string" && data.tool_use_id || typeof data?.toolUseID === "string" && data.toolUseID || typeof data?.parent_tool_use_id === "string" && data.parent_tool_use_id || void 0;
172
+ if (toolUseId) info.lastToolUseId = toolUseId;
173
+ }
174
+ return full;
175
+ }
176
+ getLastEventId(sessionId) {
177
+ const state = this.runtime.get(sessionId);
178
+ if (!state) return void 0;
179
+ return state.buffer.nextId > 0 ? state.buffer.nextId - 1 : void 0;
180
+ }
181
+ readEvents(sessionId, cursor) {
182
+ const state = this.runtime.get(sessionId);
183
+ if (!state) return { events: [], nextCursor: cursor ?? 0 };
184
+ return _SessionManager.readEvents(state.buffer, cursor);
185
+ }
186
+ clearTerminalEvents(sessionId) {
187
+ const state = this.runtime.get(sessionId);
188
+ if (!state) return;
189
+ _SessionManager.clearTerminalEvents(state.buffer);
190
+ }
191
+ setPendingPermission(sessionId, req, finish, timeoutMs) {
192
+ const state = this.runtime.get(sessionId);
193
+ const info = this.sessions.get(sessionId);
194
+ if (!state || !info) return false;
195
+ if (!state.pendingPermissions.has(req.requestId)) {
196
+ const timeoutId = setTimeout(() => {
197
+ this.finishRequest(
198
+ sessionId,
199
+ req.requestId,
200
+ {
201
+ behavior: "deny",
202
+ message: `Permission request timed out after ${timeoutMs}ms.`,
203
+ interrupt: false
204
+ },
205
+ "timeout"
206
+ );
207
+ }, timeoutMs);
208
+ state.pendingPermissions.set(req.requestId, { record: req, finish, timeoutId });
209
+ info.status = "waiting_permission";
210
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
211
+ this.pushEvent(sessionId, {
212
+ type: "permission_request",
213
+ data: req,
214
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
215
+ });
216
+ return true;
217
+ }
218
+ return false;
219
+ }
220
+ getPendingPermissionCount(sessionId) {
221
+ return this.runtime.get(sessionId)?.pendingPermissions.size ?? 0;
222
+ }
223
+ listPendingPermissions(sessionId) {
224
+ const state = this.runtime.get(sessionId);
225
+ if (!state) return [];
226
+ return Array.from(state.pendingPermissions.values()).map((p) => p.record).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
227
+ }
228
+ finishRequest(sessionId, requestId, result, source) {
229
+ const state = this.runtime.get(sessionId);
230
+ const info = this.sessions.get(sessionId);
231
+ if (!state || !info) return false;
232
+ const pending = state.pendingPermissions.get(requestId);
233
+ if (!pending) return false;
234
+ let finalResult = result;
235
+ if (finalResult.behavior === "allow") {
236
+ const disallowed = info.disallowedTools;
237
+ if (Array.isArray(disallowed) && disallowed.includes(pending.record.toolName) && pending.record.toolName.trim() !== "") {
238
+ finalResult = {
239
+ behavior: "deny",
240
+ message: `Tool '${pending.record.toolName}' is disallowed by session policy.`,
241
+ interrupt: false
242
+ };
243
+ }
244
+ }
245
+ if (pending.timeoutId) clearTimeout(pending.timeoutId);
246
+ state.pendingPermissions.delete(requestId);
247
+ this.pushEvent(sessionId, {
248
+ type: "permission_result",
249
+ data: { requestId, behavior: finalResult.behavior, source },
250
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
251
+ });
252
+ try {
253
+ pending.finish(finalResult);
254
+ } catch {
255
+ }
256
+ if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
257
+ info.status = "running";
258
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
259
+ }
260
+ return true;
261
+ }
262
+ finishAllPending(sessionId, result, source) {
263
+ const state = this.runtime.get(sessionId);
264
+ if (!state) return;
265
+ for (const requestId of Array.from(state.pendingPermissions.keys())) {
266
+ this.finishRequest(sessionId, requestId, result, source);
267
+ }
268
+ }
114
269
  /** Remove sessions that have been idle for too long, or stuck running too long */
115
270
  cleanup() {
116
271
  const now = Date.now();
117
272
  for (const [id, info] of this.sessions) {
118
273
  const lastActive = new Date(info.lastActiveAt).getTime();
119
274
  if (Number.isNaN(lastActive)) {
275
+ this.finishAllPending(
276
+ id,
277
+ { behavior: "deny", message: "Session expired", interrupt: true },
278
+ "cleanup"
279
+ );
120
280
  this.sessions.delete(id);
281
+ this.runtime.delete(id);
121
282
  } else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
122
283
  if (info.abortController) info.abortController.abort();
123
284
  info.status = "error";
124
285
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
125
- } else if (info.status !== "running" && now - lastActive > this.sessionTtlMs) {
286
+ } else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
287
+ this.finishAllPending(
288
+ id,
289
+ { behavior: "deny", message: "Session timed out", interrupt: true },
290
+ "cleanup"
291
+ );
292
+ if (info.abortController) info.abortController.abort();
293
+ info.status = "error";
294
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
295
+ } else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
296
+ this.finishAllPending(
297
+ id,
298
+ { behavior: "deny", message: "Session expired", interrupt: true },
299
+ "cleanup"
300
+ );
126
301
  this.sessions.delete(id);
302
+ this.runtime.delete(id);
127
303
  }
128
304
  }
129
305
  }
@@ -167,36 +343,97 @@ var SessionManager = class {
167
343
  destroy() {
168
344
  clearInterval(this.cleanupTimer);
169
345
  for (const info of this.sessions.values()) {
170
- if (info.status === "running" && info.abortController) {
346
+ this.finishAllPending(
347
+ info.sessionId,
348
+ { behavior: "deny", message: "Server shutting down", interrupt: true },
349
+ "destroy"
350
+ );
351
+ if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
171
352
  info.abortController.abort();
172
353
  }
173
354
  info.status = "cancelled";
355
+ info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
356
+ info.cancelledReason = info.cancelledReason ?? "Server shutting down";
357
+ info.cancelledSource = info.cancelledSource ?? "destroy";
358
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
359
+ }
360
+ }
361
+ static pushEvent(buffer, event, isActivePermissionRequest) {
362
+ const pinned = event.pinned ?? (event.type === "permission_request" || event.type === "permission_result" || event.type === "result" || event.type === "error");
363
+ const full = {
364
+ id: buffer.nextId++,
365
+ type: event.type,
366
+ data: event.data,
367
+ timestamp: event.timestamp,
368
+ pinned
369
+ };
370
+ buffer.events.push(full);
371
+ while (buffer.events.length > buffer.maxSize) {
372
+ const idx = buffer.events.findIndex((e) => !e.pinned);
373
+ if (idx !== -1) {
374
+ buffer.events.splice(idx, 1);
375
+ continue;
376
+ }
377
+ const pinnedDropIdx = buffer.events.findIndex((e) => {
378
+ if (e.type === "permission_result") return true;
379
+ if (e.type === "permission_request") {
380
+ const requestId = e.data?.requestId;
381
+ if (typeof requestId !== "string") return true;
382
+ return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
383
+ }
384
+ return false;
385
+ });
386
+ if (pinnedDropIdx === -1) break;
387
+ buffer.events.splice(pinnedDropIdx, 1);
388
+ }
389
+ while (buffer.events.length > buffer.hardMaxSize) {
390
+ const idx = buffer.events.findIndex((e) => {
391
+ if (e.type === "permission_request") {
392
+ const requestId = e.data?.requestId;
393
+ if (typeof requestId !== "string") return true;
394
+ return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
395
+ }
396
+ if (e.type === "permission_result") return true;
397
+ return false;
398
+ });
399
+ if (idx === -1) break;
400
+ buffer.events.splice(idx, 1);
174
401
  }
402
+ return full;
403
+ }
404
+ static readEvents(buffer, cursor) {
405
+ let cursorResetTo;
406
+ if (cursor != null) {
407
+ const earliest = buffer.events[0]?.id;
408
+ if (earliest != null && earliest > cursor) cursorResetTo = earliest;
409
+ if (earliest == null && buffer.nextId > cursor) cursorResetTo = buffer.nextId;
410
+ }
411
+ const startFrom = cursorResetTo ?? cursor ?? 0;
412
+ const filtered = buffer.events.filter((e) => e.id >= startFrom);
413
+ const nextCursor = filtered.length > 0 ? filtered[filtered.length - 1].id + 1 : startFrom;
414
+ return { events: filtered, nextCursor, cursorResetTo };
415
+ }
416
+ static clearTerminalEvents(buffer) {
417
+ buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
175
418
  }
176
419
  };
177
420
 
178
- // src/tools/claude-code.ts
179
- import { query, AbortError } from "@anthropic-ai/claude-agent-sdk";
180
-
181
421
  // src/types.ts
182
- var PERMISSION_MODES = [
183
- "default",
184
- "acceptEdits",
185
- "bypassPermissions",
186
- "plan",
187
- "delegate",
188
- "dontAsk"
189
- ];
190
422
  var EFFORT_LEVELS = ["low", "medium", "high", "max"];
191
423
  var AGENT_MODELS = ["sonnet", "opus", "haiku", "inherit"];
192
- var CONFIGURE_ACTIONS = ["enable_bypass", "disable_bypass", "get_config"];
193
424
  var SESSION_ACTIONS = ["list", "get", "cancel"];
194
425
  var DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
426
+ var CHECK_ACTIONS = ["poll", "respond_permission"];
427
+ var CHECK_RESPONSE_MODES = ["minimal", "full"];
428
+
429
+ // src/tools/query-consumer.ts
430
+ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
195
431
 
196
432
  // src/utils/windows.ts
197
433
  import { existsSync } from "fs";
198
434
  import { execSync } from "child_process";
199
- import { join, dirname, normalize } from "path";
435
+ import path from "path";
436
+ var { join, dirname, normalize } = path.win32;
200
437
  function isWindows() {
201
438
  return process.platform === "win32";
202
439
  }
@@ -268,847 +505,1227 @@ function enhanceWindowsError(errorMessage) {
268
505
  return errorMessage;
269
506
  }
270
507
 
271
- // src/tools/claude-code.ts
272
- async function executeClaudeCode(input, sessionManager, serverCwd, allowBypass = false) {
273
- const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
274
- if (typeof cwd !== "string" || cwd.trim() === "") {
508
+ // src/tools/query-consumer.ts
509
+ var MAX_TRANSIENT_RETRIES = 3;
510
+ var INITIAL_RETRY_DELAY_MS = 1e3;
511
+ function classifyError(err, abortSignal) {
512
+ if (abortSignal.aborted) return "abort";
513
+ if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
514
+ return "abort";
515
+ }
516
+ if (err instanceof Error && (err.message.includes("ECONNRESET") || err.message.includes("ETIMEDOUT") || err.message.includes("ECONNREFUSED") || err.message.includes("ENOTFOUND") || err.message.includes("EAI_AGAIN") || err.message.includes("EPIPE") || err.message.includes("stream ended unexpectedly") || err.message.includes("socket hang up"))) {
517
+ return "transient";
518
+ }
519
+ return "fatal";
520
+ }
521
+ function isSystemInitMessage(msg) {
522
+ return msg.type === "system" && msg.subtype === "init";
523
+ }
524
+ function summarizePermission(toolName, input) {
525
+ const keys = Object.keys(input ?? {}).slice(0, 5);
526
+ const suffix = keys.length > 0 ? ` (keys: ${keys.join(", ")})` : "";
527
+ return `${toolName} permission request${suffix}`;
528
+ }
529
+ function describeTool(toolName, toolCache) {
530
+ const tools = toolCache?.getTools();
531
+ const found = tools?.find((t) => t.name === toolName);
532
+ return found?.description;
533
+ }
534
+ function sdkResultToAgentResult(result) {
535
+ const base = {
536
+ sessionId: result.session_id,
537
+ durationMs: result.duration_ms,
538
+ durationApiMs: result.duration_api_ms,
539
+ numTurns: result.num_turns,
540
+ totalCostUsd: result.total_cost_usd,
541
+ stopReason: result.stop_reason,
542
+ usage: result.usage,
543
+ modelUsage: result.modelUsage,
544
+ permissionDenials: result.permission_denials
545
+ };
546
+ if (result.subtype === "success") {
275
547
  return {
276
- sessionId: "",
277
- result: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`,
278
- isError: true,
279
- durationMs: 0,
280
- numTurns: 0,
281
- totalCostUsd: 0
548
+ ...base,
549
+ result: result.result,
550
+ structuredOutput: result.structured_output,
551
+ isError: false
282
552
  };
283
553
  }
284
- let sessionId = "";
285
- let resultText = "";
286
- let isError = false;
287
- let durationMs = 0;
288
- let durationApiMs;
289
- let numTurns = 0;
290
- let totalCostUsd = 0;
291
- let sessionTotalTurns;
292
- let sessionTotalCostUsd;
293
- let structuredOutput;
294
- let stopReason;
295
- let errorSubtype;
296
- let usage;
297
- let modelUsage;
298
- let permissionDenials;
299
- let seenResult = false;
300
- let timedOut = false;
301
- if (input.permissionMode === "bypassPermissions" && !allowBypass) {
554
+ const errors = Array.isArray(result.errors) && result.errors.length > 0 ? result.errors.map(String).join("\n") : `Error [${result.subtype}]: Unknown error`;
555
+ return {
556
+ ...base,
557
+ result: errors,
558
+ isError: true,
559
+ errorSubtype: result.subtype
560
+ };
561
+ }
562
+ function errorToAgentResult(sessionId, err) {
563
+ const message = err instanceof Error ? enhanceWindowsError(err.message) : enhanceWindowsError(String(err));
564
+ return {
565
+ sessionId,
566
+ result: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`,
567
+ isError: true,
568
+ durationMs: 0,
569
+ numTurns: 0,
570
+ totalCostUsd: 0
571
+ };
572
+ }
573
+ function messageToEvent(msg) {
574
+ if (msg.type === "assistant") {
302
575
  return {
303
- sessionId: "",
304
- result: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: bypassPermissions is disabled on this server. Use the claude_code_configure tool with action 'enable_bypass' to enable it.`,
305
- isError: true,
306
- durationMs: 0,
307
- numTurns: 0,
308
- totalCostUsd: 0
576
+ type: "output",
577
+ data: {
578
+ type: "assistant",
579
+ message: msg.message,
580
+ parent_tool_use_id: msg.parent_tool_use_id,
581
+ error: msg.error
582
+ }
309
583
  };
310
584
  }
311
- const abortController = new AbortController();
312
- let timeoutId;
313
- try {
314
- if (input.timeout !== void 0) {
315
- timeoutId = setTimeout(() => {
316
- timedOut = true;
317
- abortController.abort();
318
- }, input.timeout);
585
+ if (msg.type === "tool_use_summary") {
586
+ return { type: "progress", data: { type: "tool_use_summary", summary: msg.summary } };
587
+ }
588
+ if (msg.type === "tool_progress") {
589
+ return {
590
+ type: "progress",
591
+ data: {
592
+ type: "tool_progress",
593
+ tool_use_id: msg.tool_use_id,
594
+ tool_name: msg.tool_name,
595
+ elapsed_time_seconds: msg.elapsed_time_seconds
596
+ }
597
+ };
598
+ }
599
+ if (msg.type === "auth_status") {
600
+ return {
601
+ type: "progress",
602
+ data: {
603
+ type: "auth_status",
604
+ isAuthenticating: msg.isAuthenticating,
605
+ output: msg.output,
606
+ error: msg.error
607
+ }
608
+ };
609
+ }
610
+ if (msg.type === "system" && msg.subtype === "status") {
611
+ return {
612
+ type: "progress",
613
+ data: { type: "status", status: msg.status, permissionMode: msg.permissionMode }
614
+ };
615
+ }
616
+ if (msg.type === "system" && msg.subtype === "task_notification") {
617
+ return {
618
+ type: "progress",
619
+ data: {
620
+ type: "task_notification",
621
+ task_id: msg.task_id,
622
+ status: msg.status,
623
+ summary: msg.summary,
624
+ output_file: msg.output_file
625
+ }
626
+ };
627
+ }
628
+ return null;
629
+ }
630
+ function consumeQuery(params) {
631
+ let resolveSessionId;
632
+ let rejectSessionId;
633
+ const sdkSessionIdPromise = new Promise((resolve, reject) => {
634
+ resolveSessionId = resolve;
635
+ rejectSessionId = reject;
636
+ });
637
+ const waitForInitSessionId = params.mode !== "start" ? params.waitForInitSessionId ?? false : false;
638
+ const shouldWaitForInit = params.mode === "start" || waitForInitSessionId;
639
+ let sessionIdResolved = false;
640
+ let activeSessionId = "";
641
+ if (params.mode !== "start" && !waitForInitSessionId) {
642
+ sessionIdResolved = true;
643
+ activeSessionId = params.sessionId;
644
+ resolveSessionId(activeSessionId);
645
+ }
646
+ const getSessionId = async () => {
647
+ if (activeSessionId) return activeSessionId;
648
+ activeSessionId = await sdkSessionIdPromise;
649
+ return activeSessionId;
650
+ };
651
+ let initTimeoutId;
652
+ const canUseTool = async (toolName, input, options2) => {
653
+ const sessionId = await getSessionId();
654
+ const sessionInfo = params.sessionManager.get(sessionId);
655
+ if (sessionInfo) {
656
+ if (Array.isArray(sessionInfo.disallowedTools) && sessionInfo.disallowedTools.includes(toolName)) {
657
+ return { behavior: "deny", message: `Tool '${toolName}' is disallowed by session policy.` };
658
+ }
659
+ if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
660
+ return { behavior: "allow" };
661
+ }
319
662
  }
320
- const effectivePermissionMode = input.permissionMode ?? "dontAsk";
321
- const options = {
322
- cwd,
323
- abortController,
324
- permissionMode: effectivePermissionMode
663
+ const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
664
+ const record = {
665
+ requestId,
666
+ toolName,
667
+ input,
668
+ summary: summarizePermission(toolName, input),
669
+ description: describeTool(toolName, params.toolCache),
670
+ decisionReason: options2.decisionReason,
671
+ blockedPath: options2.blockedPath,
672
+ toolUseID: options2.toolUseID,
673
+ agentID: options2.agentID,
674
+ suggestions: options2.suggestions,
675
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
325
676
  };
326
- if (input.allowedTools !== void 0) options.allowedTools = input.allowedTools;
327
- if (input.disallowedTools !== void 0) options.disallowedTools = input.disallowedTools;
328
- if (input.maxTurns !== void 0) options.maxTurns = input.maxTurns;
329
- if (input.model !== void 0) options.model = input.model;
330
- if (input.maxBudgetUsd !== void 0) options.maxBudgetUsd = input.maxBudgetUsd;
331
- if (input.agents !== void 0) options.agents = input.agents;
332
- if (input.effort !== void 0) options.effort = input.effort;
333
- if (input.betas !== void 0) options.betas = input.betas;
334
- if (input.additionalDirectories !== void 0)
335
- options.additionalDirectories = input.additionalDirectories;
336
- if (input.outputFormat !== void 0) options.outputFormat = input.outputFormat;
337
- if (input.thinking !== void 0) options.thinking = input.thinking;
338
- if (input.tools !== void 0) options.tools = input.tools;
339
- if (input.persistSession !== void 0) options.persistSession = input.persistSession;
340
- if (input.pathToClaudeCodeExecutable !== void 0)
341
- options.pathToClaudeCodeExecutable = input.pathToClaudeCodeExecutable;
342
- if (input.agent !== void 0) options.agent = input.agent;
343
- if (input.mcpServers !== void 0)
344
- options.mcpServers = input.mcpServers;
345
- if (input.sandbox !== void 0) options.sandbox = input.sandbox;
346
- if (input.fallbackModel !== void 0) options.fallbackModel = input.fallbackModel;
347
- if (input.enableFileCheckpointing !== void 0)
348
- options.enableFileCheckpointing = input.enableFileCheckpointing;
349
- if (input.includePartialMessages !== void 0)
350
- options.includePartialMessages = input.includePartialMessages;
351
- if (input.strictMcpConfig !== void 0) options.strictMcpConfig = input.strictMcpConfig;
352
- if (input.settingSources !== void 0) options.settingSources = input.settingSources;
353
- else options.settingSources = DEFAULT_SETTING_SOURCES;
354
- if (input.debug !== void 0) options.debug = input.debug;
355
- if (input.debugFile !== void 0) options.debugFile = input.debugFile;
356
- if (input.env !== void 0) options.env = { ...process.env, ...input.env };
357
- if (effectivePermissionMode === "bypassPermissions") {
358
- options.allowDangerouslySkipPermissions = true;
677
+ return await new Promise((resolve) => {
678
+ let finished = false;
679
+ const abortListener = () => {
680
+ params.sessionManager.finishRequest(
681
+ sessionId,
682
+ requestId,
683
+ { behavior: "deny", message: "Session cancelled", interrupt: true },
684
+ "signal"
685
+ );
686
+ };
687
+ const finish = (result) => {
688
+ if (finished) return;
689
+ finished = true;
690
+ options2.signal.removeEventListener("abort", abortListener);
691
+ resolve(result);
692
+ };
693
+ const registered = params.sessionManager.setPendingPermission(
694
+ sessionId,
695
+ record,
696
+ finish,
697
+ params.permissionRequestTimeoutMs
698
+ );
699
+ if (!registered) {
700
+ finish({ behavior: "deny", message: "Session no longer exists.", interrupt: true });
701
+ return;
702
+ }
703
+ options2.signal.addEventListener("abort", abortListener, { once: true });
704
+ if (options2.signal.aborted) {
705
+ abortListener();
706
+ }
707
+ });
708
+ };
709
+ const options = {
710
+ ...params.options,
711
+ abortController: params.abortController,
712
+ permissionMode: "default",
713
+ canUseTool
714
+ };
715
+ const startQuery = (opts) => query({
716
+ prompt: params.prompt,
717
+ options: opts
718
+ });
719
+ if (params.mode === "resume" || params.mode === "disk-resume") {
720
+ options.resume = params.sessionId;
721
+ }
722
+ let activeQuery = startQuery(options);
723
+ const close = () => {
724
+ try {
725
+ activeQuery.close?.();
726
+ } finally {
727
+ params.abortController.abort();
728
+ }
729
+ };
730
+ const interrupt = () => {
731
+ activeQuery.interrupt?.();
732
+ };
733
+ const done = (async () => {
734
+ const preInit = [];
735
+ if (shouldWaitForInit) {
736
+ initTimeoutId = setTimeout(() => {
737
+ close();
738
+ rejectSessionId(
739
+ new Error(
740
+ `Error [${"TIMEOUT" /* TIMEOUT */}]: session init timed out after ${params.sessionInitTimeoutMs}ms.`
741
+ )
742
+ );
743
+ }, params.sessionInitTimeoutMs);
744
+ }
745
+ let retryCount = 0;
746
+ let currentStream = activeQuery;
747
+ while (true) {
748
+ try {
749
+ for await (const message of currentStream) {
750
+ if (isSystemInitMessage(message)) {
751
+ params.toolCache?.updateFromInit(message.tools);
752
+ params.onInit?.(message);
753
+ params.sessionManager.setInitTools(message.session_id, message.tools);
754
+ activeSessionId = message.session_id;
755
+ if (!sessionIdResolved && shouldWaitForInit) {
756
+ sessionIdResolved = true;
757
+ resolveSessionId(activeSessionId);
758
+ if (initTimeoutId) clearTimeout(initTimeoutId);
759
+ for (const buffered of preInit) {
760
+ const event2 = messageToEvent(buffered);
761
+ if (!event2) continue;
762
+ params.sessionManager.pushEvent(activeSessionId, {
763
+ type: event2.type,
764
+ data: event2.data,
765
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
766
+ });
767
+ }
768
+ preInit.length = 0;
769
+ }
770
+ continue;
771
+ }
772
+ if (shouldWaitForInit && !sessionIdResolved) {
773
+ preInit.push(message);
774
+ continue;
775
+ }
776
+ if (message.type === "result") {
777
+ const sessionId2 = message.session_id ?? await getSessionId();
778
+ const agentResult = sdkResultToAgentResult(message);
779
+ const stored = {
780
+ type: agentResult.isError ? "error" : "result",
781
+ result: agentResult,
782
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
783
+ };
784
+ params.sessionManager.setResult(sessionId2, stored);
785
+ params.sessionManager.clearTerminalEvents(sessionId2);
786
+ params.sessionManager.pushEvent(sessionId2, {
787
+ type: agentResult.isError ? "error" : "result",
788
+ data: agentResult,
789
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
790
+ });
791
+ const current = params.sessionManager.get(sessionId2);
792
+ if (current && current.status !== "cancelled") {
793
+ params.sessionManager.update(sessionId2, {
794
+ status: agentResult.isError ? "error" : "idle",
795
+ totalTurns: agentResult.numTurns,
796
+ totalCostUsd: agentResult.totalCostUsd,
797
+ abortController: void 0
798
+ });
799
+ } else if (current) {
800
+ params.sessionManager.update(sessionId2, {
801
+ totalTurns: agentResult.numTurns,
802
+ totalCostUsd: agentResult.totalCostUsd,
803
+ abortController: void 0
804
+ });
805
+ }
806
+ return;
807
+ }
808
+ const sessionId = message.session_id ?? await getSessionId();
809
+ const event = messageToEvent(message);
810
+ if (event) {
811
+ params.sessionManager.pushEvent(sessionId, {
812
+ type: event.type,
813
+ data: event.data,
814
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
815
+ });
816
+ }
817
+ }
818
+ if (shouldWaitForInit && !sessionIdResolved) {
819
+ rejectSessionId(
820
+ new Error(
821
+ `Error [${"INTERNAL" /* INTERNAL */}]: query stream ended before receiving session init.`
822
+ )
823
+ );
824
+ } else if (activeSessionId) {
825
+ const sessionId = activeSessionId;
826
+ const current = params.sessionManager.get(sessionId);
827
+ if (current && current.status !== "cancelled") {
828
+ params.sessionManager.finishAllPending(
829
+ sessionId,
830
+ {
831
+ behavior: "deny",
832
+ message: "Session ended before permission was resolved.",
833
+ interrupt: true
834
+ },
835
+ "cleanup"
836
+ );
837
+ const agentResult = errorToAgentResult(
838
+ sessionId,
839
+ "No result message received from agent."
840
+ );
841
+ const stored = {
842
+ type: "error",
843
+ result: agentResult,
844
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
845
+ };
846
+ params.sessionManager.setResult(sessionId, stored);
847
+ params.sessionManager.clearTerminalEvents(sessionId);
848
+ params.sessionManager.pushEvent(sessionId, {
849
+ type: "error",
850
+ data: agentResult,
851
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
852
+ });
853
+ params.sessionManager.update(sessionId, {
854
+ status: "error",
855
+ abortController: void 0
856
+ });
857
+ }
858
+ }
859
+ return;
860
+ } catch (err) {
861
+ const errClass = classifyError(err, params.abortController.signal);
862
+ if (shouldWaitForInit && !sessionIdResolved) {
863
+ rejectSessionId(
864
+ new Error(
865
+ errClass === "abort" ? `Error [${"CANCELLED" /* CANCELLED */}]: session was cancelled before init.` : `Error [${"INTERNAL" /* INTERNAL */}]: ${enhanceWindowsError(err instanceof Error ? err.message : String(err))}`
866
+ )
867
+ );
868
+ return;
869
+ }
870
+ if (!activeSessionId) return;
871
+ const sessionId = activeSessionId;
872
+ if (errClass === "transient" && retryCount < MAX_TRANSIENT_RETRIES) {
873
+ retryCount++;
874
+ const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount - 1);
875
+ params.sessionManager.pushEvent(sessionId, {
876
+ type: "progress",
877
+ data: {
878
+ type: "retry",
879
+ attempt: retryCount,
880
+ maxRetries: MAX_TRANSIENT_RETRIES,
881
+ delayMs: delay,
882
+ error: err instanceof Error ? err.message : String(err)
883
+ },
884
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
885
+ });
886
+ await new Promise((r) => {
887
+ const timer = setTimeout(r, delay);
888
+ const onAbort = () => {
889
+ clearTimeout(timer);
890
+ r();
891
+ };
892
+ params.abortController.signal.addEventListener("abort", onAbort, { once: true });
893
+ });
894
+ if (params.abortController.signal.aborted) return;
895
+ params.sessionManager.finishAllPending(
896
+ sessionId,
897
+ { behavior: "deny", message: "Retrying after transient error.", interrupt: false },
898
+ "cleanup"
899
+ );
900
+ const retryOpts = {
901
+ ...options,
902
+ resume: sessionId
903
+ };
904
+ currentStream = startQuery(retryOpts);
905
+ activeQuery = currentStream;
906
+ continue;
907
+ }
908
+ const current = params.sessionManager.get(sessionId);
909
+ if (current && current.status !== "cancelled") {
910
+ params.sessionManager.finishAllPending(
911
+ sessionId,
912
+ {
913
+ behavior: "deny",
914
+ message: "Session failed before permission was resolved.",
915
+ interrupt: true
916
+ },
917
+ "cleanup"
918
+ );
919
+ const agentResult = errClass === "abort" ? {
920
+ sessionId,
921
+ result: `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`,
922
+ isError: true,
923
+ durationMs: 0,
924
+ numTurns: 0,
925
+ totalCostUsd: 0
926
+ } : errorToAgentResult(sessionId, err);
927
+ params.sessionManager.setResult(sessionId, {
928
+ type: "error",
929
+ result: agentResult,
930
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
931
+ });
932
+ params.sessionManager.clearTerminalEvents(sessionId);
933
+ params.sessionManager.pushEvent(sessionId, {
934
+ type: "error",
935
+ data: agentResult,
936
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
937
+ });
938
+ params.sessionManager.update(sessionId, { status: "error", abortController: void 0 });
939
+ }
940
+ return;
941
+ } finally {
942
+ if (initTimeoutId) clearTimeout(initTimeoutId);
943
+ }
944
+ }
945
+ })();
946
+ return { sdkSessionIdPromise, done, close, interrupt };
947
+ }
948
+
949
+ // src/utils/resume-token.ts
950
+ import { createHmac } from "crypto";
951
+ function getResumeSecret() {
952
+ const secret = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
953
+ if (typeof secret !== "string") return void 0;
954
+ const trimmed = secret.trim();
955
+ return trimmed.length > 0 ? trimmed : void 0;
956
+ }
957
+ function computeResumeToken(sessionId, secret) {
958
+ return createHmac("sha256", secret).update(sessionId).digest("base64url");
959
+ }
960
+
961
+ // src/utils/race-with-abort.ts
962
+ function raceWithAbort(promise, signal, onAbort) {
963
+ if (!signal) return promise;
964
+ if (signal.aborted) {
965
+ try {
966
+ onAbort();
967
+ } catch {
359
968
  }
360
- if (input.systemPrompt !== void 0) options.systemPrompt = input.systemPrompt;
361
- for await (const message of query({
969
+ return Promise.reject(new Error(`Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`));
970
+ }
971
+ return new Promise((resolve, reject) => {
972
+ const abortListener = () => {
973
+ try {
974
+ onAbort();
975
+ } catch {
976
+ }
977
+ reject(new Error(`Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`));
978
+ };
979
+ signal.addEventListener("abort", abortListener, { once: true });
980
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abortListener));
981
+ });
982
+ }
983
+
984
+ // src/utils/build-options.ts
985
+ function buildOptions(src) {
986
+ const opts = { cwd: src.cwd };
987
+ if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
988
+ if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
989
+ if (src.tools !== void 0) opts.tools = src.tools;
990
+ if (src.maxTurns !== void 0) opts.maxTurns = src.maxTurns;
991
+ if (src.model !== void 0) opts.model = src.model;
992
+ if (src.systemPrompt !== void 0) opts.systemPrompt = src.systemPrompt;
993
+ if (src.agents !== void 0) opts.agents = src.agents;
994
+ if (src.maxBudgetUsd !== void 0) opts.maxBudgetUsd = src.maxBudgetUsd;
995
+ if (src.effort !== void 0) opts.effort = src.effort;
996
+ if (src.betas !== void 0) opts.betas = src.betas;
997
+ if (src.additionalDirectories !== void 0)
998
+ opts.additionalDirectories = src.additionalDirectories;
999
+ if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
1000
+ if (src.thinking !== void 0) opts.thinking = src.thinking;
1001
+ if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
1002
+ if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
1003
+ if (src.pathToClaudeCodeExecutable !== void 0)
1004
+ opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
1005
+ if (src.agent !== void 0) opts.agent = src.agent;
1006
+ if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
1007
+ if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
1008
+ if (src.fallbackModel !== void 0) opts.fallbackModel = src.fallbackModel;
1009
+ if (src.enableFileCheckpointing !== void 0)
1010
+ opts.enableFileCheckpointing = src.enableFileCheckpointing;
1011
+ if (src.includePartialMessages !== void 0)
1012
+ opts.includePartialMessages = src.includePartialMessages;
1013
+ if (src.strictMcpConfig !== void 0) opts.strictMcpConfig = src.strictMcpConfig;
1014
+ if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
1015
+ else opts.settingSources = DEFAULT_SETTING_SOURCES;
1016
+ if (src.debug !== void 0) opts.debug = src.debug;
1017
+ if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
1018
+ if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
1019
+ return opts;
1020
+ }
1021
+
1022
+ // src/tools/claude-code.ts
1023
+ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
1024
+ const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
1025
+ if (typeof cwd !== "string" || cwd.trim() === "") {
1026
+ return {
1027
+ sessionId: "",
1028
+ status: "error",
1029
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
1030
+ };
1031
+ }
1032
+ const abortController = new AbortController();
1033
+ const adv = input.advanced ?? {};
1034
+ const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1035
+ const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
1036
+ const flat = {
1037
+ cwd,
1038
+ allowedTools: input.allowedTools,
1039
+ disallowedTools: input.disallowedTools,
1040
+ maxTurns: input.maxTurns,
1041
+ model: input.model,
1042
+ systemPrompt: input.systemPrompt,
1043
+ ...adv
1044
+ };
1045
+ try {
1046
+ const handle = consumeQuery({
1047
+ mode: "start",
362
1048
  prompt: input.prompt,
363
- options
364
- })) {
365
- if (message.type === "system" && message.subtype === "init") {
366
- if (sessionId) continue;
367
- sessionId = message.session_id;
1049
+ abortController,
1050
+ options: buildOptions(flat),
1051
+ permissionRequestTimeoutMs,
1052
+ sessionInitTimeoutMs,
1053
+ sessionManager,
1054
+ toolCache,
1055
+ onInit: (init) => {
1056
+ if (sessionManager.get(init.session_id)) return;
368
1057
  sessionManager.create({
369
- sessionId,
1058
+ sessionId: init.session_id,
370
1059
  cwd,
371
1060
  model: input.model,
372
- permissionMode: effectivePermissionMode,
1061
+ permissionMode: "default",
373
1062
  allowedTools: input.allowedTools,
374
1063
  disallowedTools: input.disallowedTools,
375
- tools: input.tools,
1064
+ tools: adv.tools,
376
1065
  maxTurns: input.maxTurns,
377
1066
  systemPrompt: input.systemPrompt,
378
- agents: input.agents,
379
- maxBudgetUsd: input.maxBudgetUsd,
380
- effort: input.effort,
381
- betas: input.betas,
382
- additionalDirectories: input.additionalDirectories,
383
- outputFormat: input.outputFormat,
384
- thinking: input.thinking,
385
- persistSession: input.persistSession,
386
- pathToClaudeCodeExecutable: input.pathToClaudeCodeExecutable,
387
- agent: input.agent,
388
- mcpServers: input.mcpServers,
389
- sandbox: input.sandbox,
390
- fallbackModel: input.fallbackModel,
391
- enableFileCheckpointing: input.enableFileCheckpointing,
392
- includePartialMessages: input.includePartialMessages,
393
- strictMcpConfig: input.strictMcpConfig,
394
- settingSources: input.settingSources ?? DEFAULT_SETTING_SOURCES,
395
- debug: input.debug,
396
- debugFile: input.debugFile,
397
- env: input.env,
1067
+ agents: adv.agents,
1068
+ maxBudgetUsd: adv.maxBudgetUsd,
1069
+ effort: adv.effort,
1070
+ betas: adv.betas,
1071
+ additionalDirectories: adv.additionalDirectories,
1072
+ outputFormat: adv.outputFormat,
1073
+ thinking: adv.thinking,
1074
+ persistSession: adv.persistSession,
1075
+ pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
1076
+ agent: adv.agent,
1077
+ mcpServers: adv.mcpServers,
1078
+ sandbox: adv.sandbox,
1079
+ fallbackModel: adv.fallbackModel,
1080
+ enableFileCheckpointing: adv.enableFileCheckpointing,
1081
+ includePartialMessages: adv.includePartialMessages,
1082
+ strictMcpConfig: adv.strictMcpConfig,
1083
+ settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
1084
+ debug: adv.debug,
1085
+ debugFile: adv.debugFile,
1086
+ env: adv.env,
398
1087
  abortController
399
1088
  });
400
1089
  }
401
- if (message.type === "result") {
402
- if (seenResult) continue;
403
- seenResult = true;
404
- const result = message;
405
- durationMs = result.duration_ms;
406
- durationApiMs = result.duration_api_ms;
407
- numTurns = result.num_turns;
408
- totalCostUsd = result.total_cost_usd;
409
- isError = result.is_error;
410
- stopReason = result.stop_reason;
411
- usage = result.usage;
412
- modelUsage = result.modelUsage;
413
- permissionDenials = result.permission_denials;
414
- if (result.subtype === "success") {
415
- resultText = result.result;
416
- structuredOutput = result.structured_output;
417
- } else {
418
- isError = true;
419
- errorSubtype = result.subtype;
420
- resultText = result.errors.map(String).join("\n") || `Error [${result.subtype}]: Unknown error`;
421
- }
422
- break;
423
- }
424
- }
1090
+ });
1091
+ const sessionId = await raceWithAbort(
1092
+ handle.sdkSessionIdPromise,
1093
+ requestSignal,
1094
+ () => abortController.abort()
1095
+ );
1096
+ const resumeSecret = getResumeSecret();
1097
+ return {
1098
+ sessionId,
1099
+ status: "running",
1100
+ pollInterval: 3e3,
1101
+ resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
1102
+ };
425
1103
  } catch (err) {
426
- isError = true;
427
- const isAborted = abortController.signal.aborted || err instanceof AbortError || err instanceof Error && err.name === "AbortError";
428
- if (isAborted) {
429
- resultText = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms.` : `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`;
430
- } else {
431
- resultText = enhanceWindowsError(err instanceof Error ? err.message : String(err));
432
- }
433
- if (sessionId) {
434
- const current = sessionManager.get(sessionId);
435
- if (current) {
436
- if (timedOut && current.status !== "cancelled" && current.status !== "error") {
437
- sessionManager.update(sessionId, { status: "error" });
438
- } else if (isAborted && current.status === "running") {
439
- sessionManager.update(sessionId, { status: "cancelled" });
440
- } else if (!isAborted && current.status !== "cancelled") {
441
- sessionManager.update(sessionId, { status: "error" });
442
- }
443
- }
444
- }
445
- } finally {
446
- if (timeoutId) clearTimeout(timeoutId);
447
- }
448
- if (!seenResult && !isError && abortController.signal.aborted) {
449
- isError = true;
450
- resultText = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms.` : `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`;
451
- }
452
- if (sessionId && !seenResult && !isError) {
453
- isError = true;
454
- errorSubtype = errorSubtype ?? "missing_result";
455
- const noResultMsg = `Error [${"INTERNAL" /* INTERNAL */}]: No result message received from agent.`;
456
- resultText = resultText ? `${noResultMsg} Original: ${resultText}` : noResultMsg;
457
- }
458
- if (sessionId) {
459
- const current = sessionManager.get(sessionId);
460
- if (current && current.status !== "cancelled") {
461
- sessionManager.update(sessionId, {
462
- status: isError ? "error" : "idle",
463
- totalTurns: numTurns,
464
- totalCostUsd,
465
- abortController: void 0
466
- });
467
- const updated = sessionManager.get(sessionId);
468
- sessionTotalTurns = updated?.totalTurns;
469
- sessionTotalCostUsd = updated?.totalCostUsd;
470
- } else if (current) {
471
- sessionManager.update(sessionId, {
472
- totalTurns: numTurns,
473
- totalCostUsd,
474
- abortController: void 0
475
- });
476
- const updated = sessionManager.get(sessionId);
477
- sessionTotalTurns = updated?.totalTurns;
478
- sessionTotalCostUsd = updated?.totalCostUsd;
479
- }
480
- } else {
481
- isError = true;
482
- const noInitMsg = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms (no session ID received).` : `Error [${"INTERNAL" /* INTERNAL */}]: No session ID received from agent.`;
483
- resultText = resultText ? `${noInitMsg} Original: ${resultText}` : noInitMsg;
1104
+ const message = err instanceof Error ? err.message : String(err);
1105
+ return {
1106
+ sessionId: "",
1107
+ status: "error",
1108
+ error: message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
1109
+ };
484
1110
  }
485
- return {
486
- sessionId,
487
- result: resultText,
488
- isError,
489
- durationMs,
490
- durationApiMs,
491
- numTurns,
492
- totalCostUsd,
493
- sessionTotalTurns,
494
- sessionTotalCostUsd,
495
- structuredOutput,
496
- stopReason,
497
- errorSubtype,
498
- usage,
499
- modelUsage,
500
- permissionDenials
501
- };
502
1111
  }
503
1112
 
504
1113
  // src/tools/claude-code-reply.ts
505
- import { query as query2, AbortError as AbortError2 } from "@anthropic-ai/claude-agent-sdk";
506
- async function executeClaudeCodeReply(input, sessionManager, allowBypass = false) {
507
- const session = sessionManager.get(input.sessionId);
508
- if (!session) {
1114
+ function toStartError(sessionId, err) {
1115
+ const message = err instanceof Error ? err.message : String(err);
1116
+ const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
1117
+ return {
1118
+ agentResult: {
1119
+ sessionId,
1120
+ result: errorText,
1121
+ isError: true,
1122
+ durationMs: 0,
1123
+ numTurns: 0,
1124
+ totalCostUsd: 0
1125
+ },
1126
+ errorText
1127
+ };
1128
+ }
1129
+ function buildOptionsFromDiskResume(dr) {
1130
+ if (dr.cwd === void 0 || typeof dr.cwd !== "string" || dr.cwd.trim() === "") {
1131
+ throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be provided for disk resume.`);
1132
+ }
1133
+ return buildOptions(dr);
1134
+ }
1135
+ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestSignal) {
1136
+ const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1137
+ const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
1138
+ const existing = sessionManager.get(input.sessionId);
1139
+ if (!existing) {
509
1140
  const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
510
1141
  if (!allowDiskResume) {
511
1142
  return {
512
1143
  sessionId: input.sessionId,
513
- result: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`,
514
- isError: true,
515
- durationMs: 0,
516
- numTurns: 0,
517
- totalCostUsd: 0
1144
+ status: "error",
1145
+ error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
1146
+ };
1147
+ }
1148
+ const resumeSecret = getResumeSecret();
1149
+ if (!resumeSecret) {
1150
+ return {
1151
+ sessionId: input.sessionId,
1152
+ status: "error",
1153
+ error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: Disk resume is enabled but CLAUDE_CODE_MCP_RESUME_SECRET is not set.`
1154
+ };
1155
+ }
1156
+ const dr = input.diskResumeConfig ?? {};
1157
+ if (typeof dr.resumeToken !== "string" || dr.resumeToken.trim() === "") {
1158
+ return {
1159
+ sessionId: input.sessionId,
1160
+ status: "error",
1161
+ error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
1162
+ };
1163
+ }
1164
+ const expectedToken = computeResumeToken(input.sessionId, resumeSecret);
1165
+ if (dr.resumeToken !== expectedToken) {
1166
+ return {
1167
+ sessionId: input.sessionId,
1168
+ status: "error",
1169
+ error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: Invalid resumeToken for session '${input.sessionId}'.`
1170
+ };
1171
+ }
1172
+ try {
1173
+ const abortController2 = new AbortController();
1174
+ const options2 = buildOptionsFromDiskResume(dr);
1175
+ sessionManager.create({
1176
+ sessionId: input.sessionId,
1177
+ cwd: options2.cwd ?? dr.cwd ?? "",
1178
+ model: dr.model,
1179
+ permissionMode: "default",
1180
+ allowedTools: dr.allowedTools,
1181
+ disallowedTools: dr.disallowedTools,
1182
+ tools: dr.tools,
1183
+ maxTurns: dr.maxTurns,
1184
+ systemPrompt: dr.systemPrompt,
1185
+ agents: dr.agents,
1186
+ maxBudgetUsd: dr.maxBudgetUsd,
1187
+ effort: dr.effort,
1188
+ betas: dr.betas,
1189
+ additionalDirectories: dr.additionalDirectories,
1190
+ outputFormat: dr.outputFormat,
1191
+ thinking: dr.thinking,
1192
+ persistSession: dr.persistSession,
1193
+ pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
1194
+ agent: dr.agent,
1195
+ mcpServers: dr.mcpServers,
1196
+ sandbox: dr.sandbox,
1197
+ fallbackModel: dr.fallbackModel,
1198
+ enableFileCheckpointing: dr.enableFileCheckpointing,
1199
+ includePartialMessages: dr.includePartialMessages,
1200
+ strictMcpConfig: dr.strictMcpConfig,
1201
+ settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
1202
+ debug: dr.debug,
1203
+ debugFile: dr.debugFile,
1204
+ env: dr.env,
1205
+ abortController: abortController2
1206
+ });
1207
+ try {
1208
+ consumeQuery({
1209
+ mode: "disk-resume",
1210
+ sessionId: input.sessionId,
1211
+ prompt: input.prompt,
1212
+ abortController: abortController2,
1213
+ options: options2,
1214
+ permissionRequestTimeoutMs,
1215
+ sessionInitTimeoutMs,
1216
+ sessionManager,
1217
+ toolCache
1218
+ });
1219
+ } catch (err) {
1220
+ const { agentResult, errorText } = toStartError(input.sessionId, err);
1221
+ sessionManager.setResult(input.sessionId, {
1222
+ type: "error",
1223
+ result: agentResult,
1224
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1225
+ });
1226
+ sessionManager.pushEvent(input.sessionId, {
1227
+ type: "error",
1228
+ data: agentResult,
1229
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1230
+ });
1231
+ sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
1232
+ return { sessionId: input.sessionId, status: "error", error: errorText };
1233
+ }
1234
+ return {
1235
+ sessionId: input.sessionId,
1236
+ status: "running",
1237
+ pollInterval: 3e3,
1238
+ resumeToken: computeResumeToken(input.sessionId, resumeSecret)
1239
+ };
1240
+ } catch (err) {
1241
+ const { agentResult, errorText } = toStartError(input.sessionId, err);
1242
+ if (sessionManager.get(input.sessionId)) {
1243
+ sessionManager.setResult(input.sessionId, {
1244
+ type: "error",
1245
+ result: agentResult,
1246
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1247
+ });
1248
+ sessionManager.pushEvent(input.sessionId, {
1249
+ type: "error",
1250
+ data: agentResult,
1251
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1252
+ });
1253
+ sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
1254
+ }
1255
+ return {
1256
+ sessionId: input.sessionId,
1257
+ status: "error",
1258
+ error: errorText
518
1259
  };
519
1260
  }
520
- return executeClaudeCodeReplyDiskResume(input, sessionManager, allowBypass);
521
- }
522
- if (session.status === "running") {
523
- return {
524
- sessionId: input.sessionId,
525
- result: `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is currently running. Wait for it to complete or cancel it.`,
526
- isError: true,
527
- durationMs: 0,
528
- numTurns: 0,
529
- totalCostUsd: 0
530
- };
531
1261
  }
532
- if (session.status === "cancelled") {
1262
+ if (existing.status === "running" || existing.status === "waiting_permission") {
533
1263
  return {
534
1264
  sessionId: input.sessionId,
535
- result: `Error [${"CANCELLED" /* CANCELLED */}]: Session '${input.sessionId}' has been cancelled and cannot be resumed.`,
536
- isError: true,
537
- durationMs: 0,
538
- numTurns: 0,
539
- totalCostUsd: 0
1265
+ status: "error",
1266
+ error: `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: ${existing.status}).`
540
1267
  };
541
1268
  }
542
- if (session.permissionMode === "bypassPermissions" && !allowBypass) {
1269
+ if (existing.status === "cancelled") {
543
1270
  return {
544
1271
  sessionId: input.sessionId,
545
- result: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: Cannot resume a bypassPermissions session while bypass is disabled. Use the claude_code_configure tool with action 'enable_bypass' first.`,
546
- isError: true,
547
- durationMs: 0,
548
- numTurns: 0,
549
- totalCostUsd: 0
1272
+ status: "error",
1273
+ error: `Error [${"CANCELLED" /* CANCELLED */}]: Session '${input.sessionId}' has been cancelled and cannot be resumed.`
550
1274
  };
551
1275
  }
552
- const originalStatus = session.status;
1276
+ const originalStatus = existing.status;
553
1277
  const abortController = new AbortController();
554
- let timedOut = false;
555
1278
  const acquired = sessionManager.tryAcquire(input.sessionId, originalStatus, abortController);
556
1279
  if (!acquired) {
557
1280
  const current = sessionManager.get(input.sessionId);
558
- if (!current) {
559
- return {
560
- sessionId: input.sessionId,
561
- result: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`,
562
- isError: true,
563
- durationMs: 0,
564
- numTurns: 0,
565
- totalCostUsd: 0
566
- };
567
- }
568
1281
  return {
569
1282
  sessionId: input.sessionId,
570
- result: `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: ${current.status}).`,
571
- isError: true,
572
- durationMs: 0,
573
- numTurns: 0,
574
- totalCostUsd: 0
1283
+ status: "error",
1284
+ error: current ? `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: ${current.status}).` : `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
575
1285
  };
576
1286
  }
577
- let timeoutId;
578
- if (input.timeout !== void 0) {
579
- timeoutId = setTimeout(() => {
580
- timedOut = true;
581
- abortController.abort();
582
- }, input.timeout);
583
- }
584
- let resultText = "";
585
- let isError = false;
586
- let durationMs = 0;
587
- let durationApiMs;
588
- let numTurns = 0;
589
- let totalCostUsd = 0;
590
- let sessionTotalTurns;
591
- let sessionTotalCostUsd;
592
- let newSessionId = input.sessionId;
593
- let structuredOutput;
594
- let stopReason;
595
- let errorSubtype;
596
- let usage;
597
- let modelUsage;
598
- let permissionDenials;
599
- let seenResult = false;
600
- let forkSessionCreated = false;
1287
+ const options = buildOptions(existing);
1288
+ if (input.forkSession) options.forkSession = true;
601
1289
  try {
602
- const options = {
603
- resume: input.sessionId,
604
- abortController,
605
- cwd: session.cwd,
606
- permissionMode: session.permissionMode
607
- };
608
- if (session.allowedTools !== void 0) options.allowedTools = session.allowedTools;
609
- if (session.disallowedTools !== void 0) options.disallowedTools = session.disallowedTools;
610
- if (session.maxTurns !== void 0) options.maxTurns = session.maxTurns;
611
- if (session.model !== void 0) options.model = session.model;
612
- if (session.maxBudgetUsd !== void 0) options.maxBudgetUsd = session.maxBudgetUsd;
613
- if (session.agents !== void 0) options.agents = session.agents;
614
- if (session.effort !== void 0) options.effort = session.effort;
615
- if (session.betas !== void 0) options.betas = session.betas;
616
- if (session.additionalDirectories !== void 0)
617
- options.additionalDirectories = session.additionalDirectories;
618
- if (session.outputFormat !== void 0) options.outputFormat = session.outputFormat;
619
- if (session.thinking !== void 0) options.thinking = session.thinking;
620
- if (session.tools !== void 0) options.tools = session.tools;
621
- if (session.systemPrompt !== void 0) options.systemPrompt = session.systemPrompt;
622
- if (session.persistSession !== void 0) options.persistSession = session.persistSession;
623
- if (session.pathToClaudeCodeExecutable !== void 0)
624
- options.pathToClaudeCodeExecutable = session.pathToClaudeCodeExecutable;
625
- if (session.agent !== void 0) options.agent = session.agent;
626
- if (session.mcpServers !== void 0)
627
- options.mcpServers = session.mcpServers;
628
- if (session.sandbox !== void 0) options.sandbox = session.sandbox;
629
- if (session.fallbackModel !== void 0) options.fallbackModel = session.fallbackModel;
630
- if (session.enableFileCheckpointing !== void 0)
631
- options.enableFileCheckpointing = session.enableFileCheckpointing;
632
- if (session.includePartialMessages !== void 0)
633
- options.includePartialMessages = session.includePartialMessages;
634
- if (session.strictMcpConfig !== void 0) options.strictMcpConfig = session.strictMcpConfig;
635
- if (session.settingSources !== void 0) options.settingSources = session.settingSources;
636
- else options.settingSources = DEFAULT_SETTING_SOURCES;
637
- if (session.debug !== void 0) options.debug = session.debug;
638
- if (session.debugFile !== void 0) options.debugFile = session.debugFile;
639
- if (session.env !== void 0) options.env = { ...process.env, ...session.env };
640
- if (session.permissionMode === "bypassPermissions") {
641
- options.allowDangerouslySkipPermissions = true;
642
- }
643
- if (input.forkSession) {
644
- options.forkSession = true;
645
- }
646
- for await (const message of query2({
1290
+ const handle = consumeQuery({
1291
+ mode: "resume",
1292
+ sessionId: input.sessionId,
647
1293
  prompt: input.prompt,
648
- options
649
- })) {
650
- if (input.forkSession && message.type === "system" && message.subtype === "init") {
651
- newSessionId = message.session_id;
652
- if (!forkSessionCreated && newSessionId !== input.sessionId) {
1294
+ abortController,
1295
+ options,
1296
+ permissionRequestTimeoutMs,
1297
+ sessionInitTimeoutMs,
1298
+ waitForInitSessionId: !!input.forkSession,
1299
+ sessionManager,
1300
+ toolCache,
1301
+ onInit: (init) => {
1302
+ if (!input.forkSession) return;
1303
+ if (init.session_id === input.sessionId) return;
1304
+ if (!sessionManager.get(init.session_id)) {
653
1305
  sessionManager.create({
654
- sessionId: newSessionId,
655
- cwd: session.cwd,
656
- model: session.model,
657
- permissionMode: session.permissionMode,
658
- allowedTools: session.allowedTools,
659
- disallowedTools: session.disallowedTools,
660
- tools: session.tools,
661
- maxTurns: session.maxTurns,
662
- systemPrompt: session.systemPrompt,
663
- agents: session.agents,
664
- maxBudgetUsd: session.maxBudgetUsd,
665
- effort: session.effort,
666
- betas: session.betas,
667
- additionalDirectories: session.additionalDirectories,
668
- outputFormat: session.outputFormat,
669
- thinking: session.thinking,
670
- persistSession: session.persistSession,
671
- pathToClaudeCodeExecutable: session.pathToClaudeCodeExecutable,
672
- agent: session.agent,
673
- mcpServers: session.mcpServers,
674
- sandbox: session.sandbox,
675
- fallbackModel: session.fallbackModel,
676
- enableFileCheckpointing: session.enableFileCheckpointing,
677
- includePartialMessages: session.includePartialMessages,
678
- strictMcpConfig: session.strictMcpConfig,
679
- settingSources: session.settingSources ?? DEFAULT_SETTING_SOURCES,
680
- debug: session.debug,
681
- debugFile: session.debugFile,
682
- env: session.env,
1306
+ sessionId: init.session_id,
1307
+ cwd: existing.cwd,
1308
+ model: existing.model,
1309
+ permissionMode: "default",
1310
+ allowedTools: existing.allowedTools,
1311
+ disallowedTools: existing.disallowedTools,
1312
+ tools: existing.tools,
1313
+ maxTurns: existing.maxTurns,
1314
+ systemPrompt: existing.systemPrompt,
1315
+ agents: existing.agents,
1316
+ maxBudgetUsd: existing.maxBudgetUsd,
1317
+ effort: existing.effort,
1318
+ betas: existing.betas,
1319
+ additionalDirectories: existing.additionalDirectories,
1320
+ outputFormat: existing.outputFormat,
1321
+ thinking: existing.thinking,
1322
+ persistSession: existing.persistSession,
1323
+ pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
1324
+ agent: existing.agent,
1325
+ mcpServers: existing.mcpServers,
1326
+ sandbox: existing.sandbox,
1327
+ fallbackModel: existing.fallbackModel,
1328
+ enableFileCheckpointing: existing.enableFileCheckpointing,
1329
+ includePartialMessages: existing.includePartialMessages,
1330
+ strictMcpConfig: existing.strictMcpConfig,
1331
+ settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
1332
+ debug: existing.debug,
1333
+ debugFile: existing.debugFile,
1334
+ env: existing.env,
683
1335
  abortController
684
1336
  });
685
- forkSessionCreated = true;
686
- }
687
- }
688
- if (message.type === "result") {
689
- if (seenResult) continue;
690
- seenResult = true;
691
- const result = message;
692
- durationMs = result.duration_ms;
693
- durationApiMs = result.duration_api_ms;
694
- numTurns = result.num_turns;
695
- totalCostUsd = result.total_cost_usd;
696
- isError = result.is_error;
697
- stopReason = result.stop_reason;
698
- usage = result.usage;
699
- modelUsage = result.modelUsage;
700
- permissionDenials = result.permission_denials;
701
- if (result.subtype === "success") {
702
- resultText = result.result;
703
- structuredOutput = result.structured_output;
704
- } else {
705
- isError = true;
706
- errorSubtype = result.subtype;
707
- resultText = result.errors.map(String).join("\n") || `Error [${result.subtype}]: Unknown error`;
708
1337
  }
709
- break;
1338
+ sessionManager.update(input.sessionId, {
1339
+ status: originalStatus,
1340
+ abortController: void 0
1341
+ });
710
1342
  }
1343
+ });
1344
+ const sessionId = input.forkSession ? await raceWithAbort(
1345
+ handle.sdkSessionIdPromise,
1346
+ requestSignal,
1347
+ () => abortController.abort()
1348
+ ) : input.sessionId;
1349
+ if (input.forkSession && sessionId === input.sessionId) {
1350
+ return {
1351
+ sessionId: input.sessionId,
1352
+ status: "error",
1353
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: Fork requested but no new session ID received from agent.`
1354
+ };
711
1355
  }
1356
+ const resumeSecret = getResumeSecret();
1357
+ return {
1358
+ sessionId,
1359
+ status: "running",
1360
+ pollInterval: 3e3,
1361
+ resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
1362
+ };
712
1363
  } catch (err) {
713
- isError = true;
714
- const isAborted = abortController.signal.aborted || err instanceof AbortError2 || err instanceof Error && err.name === "AbortError";
715
- if (isAborted) {
716
- resultText = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms.` : `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`;
717
- } else {
718
- resultText = enhanceWindowsError(err instanceof Error ? err.message : String(err));
719
- }
720
- } finally {
721
- if (timeoutId) clearTimeout(timeoutId);
722
- }
723
- if (!seenResult && !isError && abortController.signal.aborted) {
724
- isError = true;
725
- resultText = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms.` : `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`;
726
- }
727
- if (!seenResult && !isError) {
728
- isError = true;
729
- errorSubtype = errorSubtype ?? "missing_result";
730
- const noResultMsg = `Error [${"INTERNAL" /* INTERNAL */}]: No result message received from agent.`;
731
- resultText = resultText ? `${noResultMsg} Original: ${resultText}` : noResultMsg;
732
- }
733
- if (input.forkSession && newSessionId !== input.sessionId) {
734
- const forkedCurrent = sessionManager.get(newSessionId);
735
- if (forkedCurrent && forkedCurrent.status !== "cancelled") {
736
- sessionManager.update(newSessionId, {
737
- status: isError ? "error" : "idle",
738
- totalTurns: numTurns,
739
- totalCostUsd,
740
- abortController: void 0
741
- });
742
- const updatedFork = sessionManager.get(newSessionId);
743
- sessionTotalTurns = updatedFork?.totalTurns;
744
- sessionTotalCostUsd = updatedFork?.totalCostUsd;
745
- } else if (forkedCurrent) {
746
- sessionManager.update(newSessionId, {
747
- totalTurns: numTurns,
748
- totalCostUsd,
749
- abortController: void 0
750
- });
751
- const updatedFork = sessionManager.get(newSessionId);
752
- sessionTotalTurns = updatedFork?.totalTurns;
753
- sessionTotalCostUsd = updatedFork?.totalCostUsd;
754
- }
755
- const origCurrent = sessionManager.get(input.sessionId);
756
- if (origCurrent && origCurrent.status !== "cancelled") {
1364
+ const { agentResult, errorText } = toStartError(input.sessionId, err);
1365
+ if (input.forkSession) {
757
1366
  sessionManager.update(input.sessionId, {
758
1367
  status: originalStatus,
759
1368
  abortController: void 0
760
1369
  });
761
- } else if (origCurrent) {
762
- sessionManager.update(input.sessionId, {
763
- abortController: void 0
764
- });
765
- }
766
- } else {
767
- const current = sessionManager.get(input.sessionId);
768
- if (current && current.status !== "cancelled") {
769
- sessionManager.update(input.sessionId, {
770
- status: isError ? "error" : "idle",
771
- totalTurns: (session.totalTurns ?? 0) + numTurns,
772
- totalCostUsd: (session.totalCostUsd ?? 0) + totalCostUsd,
773
- abortController: void 0
1370
+ } else {
1371
+ sessionManager.setResult(input.sessionId, {
1372
+ type: "error",
1373
+ result: agentResult,
1374
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
774
1375
  });
775
- const updated = sessionManager.get(input.sessionId);
776
- sessionTotalTurns = updated?.totalTurns;
777
- sessionTotalCostUsd = updated?.totalCostUsd;
778
- } else if (current) {
779
- sessionManager.update(input.sessionId, {
780
- totalTurns: (session.totalTurns ?? 0) + numTurns,
781
- totalCostUsd: (session.totalCostUsd ?? 0) + totalCostUsd,
782
- abortController: void 0
1376
+ sessionManager.pushEvent(input.sessionId, {
1377
+ type: "error",
1378
+ data: agentResult,
1379
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
783
1380
  });
784
- const updated = sessionManager.get(input.sessionId);
785
- sessionTotalTurns = updated?.totalTurns;
786
- sessionTotalCostUsd = updated?.totalCostUsd;
1381
+ sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
787
1382
  }
1383
+ return {
1384
+ sessionId: input.sessionId,
1385
+ status: "error",
1386
+ error: errorText
1387
+ };
788
1388
  }
789
- const targetSessionId = input.forkSession ? newSessionId : input.sessionId;
790
- if (input.forkSession && newSessionId === input.sessionId && !isError) {
791
- isError = true;
792
- const noForkMsg = `Error [${"INTERNAL" /* INTERNAL */}]: Fork requested but no new session ID received from agent.`;
793
- resultText = resultText ? `${noForkMsg} Original: ${resultText}` : noForkMsg;
1389
+ }
1390
+
1391
+ // src/tools/tool-discovery.ts
1392
+ var TOOL_CATALOG = {
1393
+ Bash: {
1394
+ description: "Run shell commands (e.g. npm install, git commit, ls) in the project directory.",
1395
+ category: "execute"
1396
+ },
1397
+ Read: { description: "Read the contents of a file given its path.", category: "file_read" },
1398
+ Write: {
1399
+ description: "Create a new file or completely replace an existing file's contents.",
1400
+ category: "file_write"
1401
+ },
1402
+ Edit: {
1403
+ description: "Make targeted changes to specific parts of an existing file without rewriting the whole file.",
1404
+ category: "file_write"
1405
+ },
1406
+ Glob: {
1407
+ description: "Find files by name pattern (e.g. '**/*.ts' finds all TypeScript files).",
1408
+ category: "file_read"
1409
+ },
1410
+ Grep: {
1411
+ description: "Search inside files for text or regex patterns (like grep/ripgrep).",
1412
+ category: "file_read"
1413
+ },
1414
+ NotebookEdit: {
1415
+ description: "Edit individual cells in Jupyter notebooks (.ipynb files).",
1416
+ category: "file_write"
1417
+ },
1418
+ WebFetch: {
1419
+ description: "Download and read the content of a web page or API endpoint.",
1420
+ category: "network"
1421
+ },
1422
+ WebSearch: { description: "Search the web and return relevant results.", category: "network" },
1423
+ Task: {
1424
+ description: "Spawn a subagent to handle a subtask independently (requires this tool to be in allowedTools).",
1425
+ category: "agent"
1426
+ },
1427
+ TaskOutput: { description: "Get the output from a background subagent task.", category: "agent" },
1428
+ TaskStop: { description: "Cancel a running background subagent task.", category: "agent" },
1429
+ TodoWrite: {
1430
+ description: "Create and update a structured task/todo checklist.",
1431
+ category: "agent"
1432
+ },
1433
+ AskUserQuestion: {
1434
+ description: "Ask the user a question and wait for their answer before continuing.",
1435
+ category: "interaction"
794
1436
  }
1437
+ };
1438
+ function uniq(items) {
1439
+ return Array.from(new Set(items));
1440
+ }
1441
+ function discoverToolsFromInit(initTools) {
1442
+ const names = uniq(initTools.filter((t) => typeof t === "string" && t.trim() !== ""));
1443
+ return names.map((name) => ({
1444
+ name,
1445
+ description: TOOL_CATALOG[name]?.description ?? name,
1446
+ category: TOOL_CATALOG[name]?.category
1447
+ }));
1448
+ }
1449
+ function defaultCatalogTools() {
1450
+ return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => ({ name, ...TOOL_CATALOG[name] }));
1451
+ }
1452
+ var ToolDiscoveryCache = class {
1453
+ cached;
1454
+ constructor(initial) {
1455
+ this.cached = initial ?? defaultCatalogTools();
1456
+ }
1457
+ getTools() {
1458
+ return this.cached;
1459
+ }
1460
+ updateFromInit(initTools) {
1461
+ const discovered = discoverToolsFromInit(initTools);
1462
+ const next = mergeToolLists(discovered, defaultCatalogTools());
1463
+ const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
1464
+ if (updated) this.cached = next;
1465
+ return { updated, tools: this.cached };
1466
+ }
1467
+ };
1468
+ function mergeToolLists(primary, fallback) {
1469
+ const byName = /* @__PURE__ */ new Map();
1470
+ for (const t of fallback) byName.set(t.name, t);
1471
+ for (const t of primary) byName.set(t.name, t);
1472
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
1473
+ }
1474
+ function groupByCategory(tools) {
1475
+ const grouped = {};
1476
+ for (const tool of tools) {
1477
+ const category = tool.category ?? "other";
1478
+ grouped[category] ??= [];
1479
+ grouped[category].push(tool);
1480
+ }
1481
+ for (const category of Object.keys(grouped)) {
1482
+ grouped[category].sort((a, b) => a.name.localeCompare(b.name));
1483
+ }
1484
+ return grouped;
1485
+ }
1486
+ function buildInternalToolsDescription(tools) {
1487
+ const grouped = groupByCategory(tools);
1488
+ const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
1489
+ let desc = 'Start a new Claude Code agent session.\n\nLaunches an autonomous coding agent that can read/write files, run shell commands, search code, manage git, access the web, and more. Returns immediately with a sessionId \u2014 the agent runs asynchronously in the background.\n\nWorkflow:\n1. Call claude_code with a prompt \u2192 returns { sessionId, status: "running", pollInterval }\n2. Poll with claude_code_check (action="poll") to receive progress events and the final result\n3. If the agent needs permission for a tool call, approve or deny via claude_code_check (action="respond_permission")\n\n';
1490
+ desc += "Defaults:\n- settingSources: ['user', 'project', 'local'] (loads ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md)\n- persistSession: true\n- sessionInitTimeoutMs: 10000\n- permissionRequestTimeoutMs: 60000\n- allowedTools/disallowedTools: [] (none)\n- resumeToken: omitted unless CLAUDE_CODE_MCP_RESUME_SECRET is set on the server\n\n";
1491
+ desc += "Internal tools available to the agent (use allowedTools/disallowedTools to control approval policy; authoritative list returned by claude_code_check with includeTools=true):\n";
1492
+ for (const category of categories) {
1493
+ desc += `
1494
+ [${category}]
1495
+ `;
1496
+ for (const tool of grouped[category]) {
1497
+ desc += `- ${tool.name}: ${tool.description}
1498
+ `;
1499
+ }
1500
+ }
1501
+ desc += '\nUse `allowedTools` to pre-approve tools (no permission prompts). Use `disallowedTools` to permanently block specific tools. Any tool not in either list will pause the session (status: "waiting_permission") until approved or denied via claude_code_check.\n';
1502
+ return desc;
1503
+ }
1504
+
1505
+ // src/tools/claude-code-check.ts
1506
+ function pollIntervalForStatus(status) {
1507
+ if (status === "waiting_permission") return 1e3;
1508
+ if (status === "running") return 3e3;
1509
+ return void 0;
1510
+ }
1511
+ function toPermissionResult(params) {
1512
+ if (params.decision === "allow") {
1513
+ return {
1514
+ behavior: "allow",
1515
+ updatedInput: params.updatedInput,
1516
+ updatedPermissions: params.updatedPermissions
1517
+ };
1518
+ }
1519
+ return {
1520
+ behavior: "deny",
1521
+ message: params.denyMessage ?? "Permission denied by caller",
1522
+ interrupt: params.interrupt
1523
+ };
1524
+ }
1525
+ function slimAssistantData(data) {
1526
+ if (!data || typeof data !== "object") return data;
1527
+ const d = data;
1528
+ if (d.type !== "assistant") return data;
1529
+ const msg = d.message;
1530
+ if (!msg || typeof msg !== "object") return data;
1531
+ const m = msg;
1532
+ const slimmed = {};
1533
+ if (m.role !== void 0) slimmed.role = m.role;
1534
+ if (m.stop_reason !== void 0) slimmed.stop_reason = m.stop_reason;
1535
+ if (Array.isArray(m.content)) {
1536
+ slimmed.content = m.content.map((block) => {
1537
+ const { cache_control, ...rest } = block;
1538
+ return rest;
1539
+ });
1540
+ }
1541
+ return {
1542
+ type: d.type,
1543
+ message: slimmed,
1544
+ ...d.parent_tool_use_id ? { parent_tool_use_id: d.parent_tool_use_id } : {},
1545
+ ...d.error ? { error: d.error } : {}
1546
+ };
1547
+ }
1548
+ function toEvents(events, opts) {
1549
+ return events.map((e) => {
1550
+ if ((e.type === "result" || e.type === "error") && isAgentResult(e.data)) {
1551
+ const redacted = redactAgentResult(e.data, opts);
1552
+ return { id: e.id, type: e.type, data: redacted, timestamp: e.timestamp };
1553
+ }
1554
+ if (opts.slim && e.type === "output") {
1555
+ return { id: e.id, type: e.type, data: slimAssistantData(e.data), timestamp: e.timestamp };
1556
+ }
1557
+ return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
1558
+ });
1559
+ }
1560
+ function buildResult(sessionManager, toolCache, input) {
1561
+ const responseMode = input.responseMode ?? "minimal";
1562
+ const po = input.pollOptions ?? {};
1563
+ const includeTools = po.includeTools;
1564
+ const includeEvents = po.includeEvents ?? true;
1565
+ const includeActions = po.includeActions ?? true;
1566
+ const includeResult = po.includeResult ?? true;
1567
+ const includeUsage = po.includeUsage ?? responseMode === "full";
1568
+ const includeModelUsage = po.includeModelUsage ?? responseMode === "full";
1569
+ const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
1570
+ const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
1571
+ const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
1572
+ const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
1573
+ const sessionId = input.sessionId;
1574
+ const session = sessionManager.get(sessionId);
1575
+ const status = session?.status ?? "error";
1576
+ const {
1577
+ events: rawEvents,
1578
+ nextCursor: rawNextCursor,
1579
+ cursorResetTo
1580
+ } = sessionManager.readEvents(sessionId, input.cursor);
1581
+ let truncated = false;
1582
+ const truncatedFields = [];
1583
+ const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
1584
+ const nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
1585
+ if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
1586
+ truncated = true;
1587
+ truncatedFields.push("events");
1588
+ }
1589
+ const outputEvents = (() => {
1590
+ if (!includeEvents) return [];
1591
+ let filtered = windowEvents;
1592
+ if (!includeTerminalEvents && includeResult && (status === "idle" || status === "error")) {
1593
+ filtered = filtered.filter((e) => e.type !== "result" && e.type !== "error");
1594
+ }
1595
+ if (!includeProgressEvents) {
1596
+ filtered = filtered.filter((e) => {
1597
+ if (e.type !== "progress") return true;
1598
+ const d = e.data;
1599
+ const progressType = d?.type;
1600
+ return progressType !== "tool_progress" && progressType !== "auth_status";
1601
+ });
1602
+ }
1603
+ return filtered;
1604
+ })();
1605
+ const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
1606
+ const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
1607
+ const initTools = includeTools ? sessionManager.getInitTools(sessionId) : void 0;
1608
+ const availableTools = includeTools && initTools ? discoverToolsFromInit(initTools) : void 0;
795
1609
  return {
796
- sessionId: targetSessionId,
797
- result: resultText,
798
- isError,
799
- durationMs,
1610
+ sessionId,
1611
+ status,
1612
+ pollInterval: pollIntervalForStatus(status),
1613
+ cursorResetTo,
1614
+ truncated: truncated ? true : void 0,
1615
+ truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
1616
+ events: toEvents(outputEvents, {
1617
+ includeUsage,
1618
+ includeModelUsage,
1619
+ includeStructuredOutput,
1620
+ slim: responseMode === "minimal"
1621
+ }),
1622
+ nextCursor,
1623
+ availableTools,
1624
+ actions: includeActions && status === "waiting_permission" ? pending.map((req) => ({
1625
+ type: "permission",
1626
+ requestId: req.requestId,
1627
+ toolName: req.toolName,
1628
+ input: req.input,
1629
+ summary: req.summary,
1630
+ decisionReason: req.decisionReason,
1631
+ blockedPath: req.blockedPath,
1632
+ toolUseID: req.toolUseID,
1633
+ agentID: req.agentID,
1634
+ suggestions: req.suggestions,
1635
+ description: req.description,
1636
+ createdAt: req.createdAt
1637
+ })) : void 0,
1638
+ result: includeResult && stored?.result ? redactAgentResult(stored.result, {
1639
+ includeUsage,
1640
+ includeModelUsage,
1641
+ includeStructuredOutput,
1642
+ slim: responseMode === "minimal"
1643
+ }) : void 0,
1644
+ cancelledAt: session?.cancelledAt,
1645
+ cancelledReason: session?.cancelledReason,
1646
+ cancelledSource: session?.cancelledSource,
1647
+ lastEventId: responseMode === "full" ? sessionManager.getLastEventId(sessionId) : void 0,
1648
+ lastToolUseId: responseMode === "full" ? session?.lastToolUseId : void 0
1649
+ };
1650
+ }
1651
+ function isAgentResult(value) {
1652
+ if (!value || typeof value !== "object") return false;
1653
+ const v = value;
1654
+ return typeof v.sessionId === "string" && typeof v.result === "string" && typeof v.isError === "boolean" && typeof v.durationMs === "number" && typeof v.numTurns === "number" && typeof v.totalCostUsd === "number";
1655
+ }
1656
+ function redactAgentResult(result, opts) {
1657
+ const {
1658
+ usage,
1659
+ modelUsage,
1660
+ structuredOutput,
800
1661
  durationApiMs,
801
- numTurns,
802
- totalCostUsd,
803
1662
  sessionTotalTurns,
804
1663
  sessionTotalCostUsd,
805
- structuredOutput,
806
- stopReason,
807
- errorSubtype,
808
- usage,
809
- modelUsage,
810
- permissionDenials
1664
+ ...rest
1665
+ } = result;
1666
+ return {
1667
+ ...rest,
1668
+ durationApiMs: opts.slim ? void 0 : durationApiMs,
1669
+ sessionTotalTurns: opts.slim ? void 0 : sessionTotalTurns,
1670
+ sessionTotalCostUsd: opts.slim ? void 0 : sessionTotalCostUsd,
1671
+ usage: opts.includeUsage ? usage : void 0,
1672
+ modelUsage: opts.includeModelUsage ? modelUsage : void 0,
1673
+ structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
811
1674
  };
812
1675
  }
813
- async function executeClaudeCodeReplyDiskResume(input, sessionManager, allowBypass) {
814
- if (input.cwd !== void 0 && (typeof input.cwd !== "string" || input.cwd.trim() === "")) {
1676
+ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
1677
+ if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
815
1678
  return {
816
- sessionId: input.sessionId,
817
- result: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`,
818
- isError: true,
819
- durationMs: 0,
820
- numTurns: 0,
821
- totalCostUsd: 0
1679
+ sessionId: "",
1680
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: sessionId must be a non-empty string.`,
1681
+ isError: true
822
1682
  };
823
1683
  }
824
- const effectivePermissionMode = input.permissionMode ?? "dontAsk";
825
- if (effectivePermissionMode === "bypassPermissions" && !allowBypass) {
1684
+ const session = sessionManager.get(input.sessionId);
1685
+ if (!session) {
826
1686
  return {
827
1687
  sessionId: input.sessionId,
828
- result: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: bypassPermissions is disabled on this server. Use the claude_code_configure tool with action 'enable_bypass' to enable it.`,
829
- isError: true,
830
- durationMs: 0,
831
- numTurns: 0,
832
- totalCostUsd: 0
1688
+ error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`,
1689
+ isError: true
833
1690
  };
834
1691
  }
835
- const abortController = new AbortController();
836
- let timedOut = false;
837
- let timeoutId;
838
- if (input.timeout !== void 0) {
839
- timeoutId = setTimeout(() => {
840
- timedOut = true;
841
- abortController.abort();
842
- }, input.timeout);
843
- }
844
- let resultText = "";
845
- let isError = false;
846
- let durationMs = 0;
847
- let durationApiMs;
848
- let numTurns = 0;
849
- let totalCostUsd = 0;
850
- let sessionTotalTurns;
851
- let sessionTotalCostUsd;
852
- let newSessionId = input.sessionId;
853
- let structuredOutput;
854
- let stopReason;
855
- let errorSubtype;
856
- let usage;
857
- let modelUsage;
858
- let permissionDenials;
859
- let seenResult = false;
860
- if (!input.forkSession) {
861
- const existing = sessionManager.get(input.sessionId);
862
- if (!existing) {
863
- const cwd = input.cwd ?? process.cwd();
864
- if (typeof cwd !== "string" || cwd.trim() === "") {
865
- return {
866
- sessionId: input.sessionId,
867
- result: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`,
868
- isError: true,
869
- durationMs: 0,
870
- numTurns: 0,
871
- totalCostUsd: 0
872
- };
873
- }
874
- try {
875
- sessionManager.create({
876
- sessionId: input.sessionId,
877
- cwd,
878
- model: input.model,
879
- permissionMode: effectivePermissionMode,
880
- allowedTools: input.allowedTools,
881
- disallowedTools: input.disallowedTools,
882
- tools: input.tools,
883
- maxTurns: input.maxTurns,
884
- systemPrompt: input.systemPrompt,
885
- agents: input.agents,
886
- maxBudgetUsd: input.maxBudgetUsd,
887
- effort: input.effort,
888
- betas: input.betas,
889
- additionalDirectories: input.additionalDirectories,
890
- outputFormat: input.outputFormat,
891
- thinking: input.thinking,
892
- persistSession: input.persistSession,
893
- pathToClaudeCodeExecutable: input.pathToClaudeCodeExecutable,
894
- agent: input.agent,
895
- mcpServers: input.mcpServers,
896
- sandbox: input.sandbox,
897
- fallbackModel: input.fallbackModel,
898
- enableFileCheckpointing: input.enableFileCheckpointing,
899
- includePartialMessages: input.includePartialMessages,
900
- strictMcpConfig: input.strictMcpConfig,
901
- settingSources: input.settingSources ?? DEFAULT_SETTING_SOURCES,
902
- debug: input.debug,
903
- debugFile: input.debugFile,
904
- env: input.env,
905
- abortController
906
- });
907
- } catch {
908
- return executeClaudeCodeReply(input, sessionManager, allowBypass);
909
- }
910
- }
1692
+ if (input.action === "poll") {
1693
+ return buildResult(sessionManager, toolCache, input);
911
1694
  }
912
- try {
913
- const options = {
914
- resume: input.sessionId,
915
- abortController,
916
- permissionMode: effectivePermissionMode
1695
+ if (typeof input.requestId !== "string" || input.requestId.trim() === "") {
1696
+ return {
1697
+ sessionId: input.sessionId,
1698
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: requestId is required for respond_permission.`,
1699
+ isError: true
917
1700
  };
918
- if (input.cwd !== void 0) options.cwd = input.cwd;
919
- if (input.allowedTools !== void 0) options.allowedTools = input.allowedTools;
920
- if (input.disallowedTools !== void 0) options.disallowedTools = input.disallowedTools;
921
- if (input.maxTurns !== void 0) options.maxTurns = input.maxTurns;
922
- if (input.model !== void 0) options.model = input.model;
923
- if (input.maxBudgetUsd !== void 0) options.maxBudgetUsd = input.maxBudgetUsd;
924
- if (input.agents !== void 0) options.agents = input.agents;
925
- if (input.effort !== void 0) options.effort = input.effort;
926
- if (input.betas !== void 0) options.betas = input.betas;
927
- if (input.additionalDirectories !== void 0)
928
- options.additionalDirectories = input.additionalDirectories;
929
- if (input.outputFormat !== void 0) options.outputFormat = input.outputFormat;
930
- if (input.thinking !== void 0) options.thinking = input.thinking;
931
- if (input.tools !== void 0) options.tools = input.tools;
932
- if (input.systemPrompt !== void 0) options.systemPrompt = input.systemPrompt;
933
- if (input.persistSession !== void 0) options.persistSession = input.persistSession;
934
- if (input.resumeSessionAt !== void 0) options.resumeSessionAt = input.resumeSessionAt;
935
- if (input.pathToClaudeCodeExecutable !== void 0)
936
- options.pathToClaudeCodeExecutable = input.pathToClaudeCodeExecutable;
937
- if (input.agent !== void 0) options.agent = input.agent;
938
- if (input.mcpServers !== void 0)
939
- options.mcpServers = input.mcpServers;
940
- if (input.sandbox !== void 0) options.sandbox = input.sandbox;
941
- if (input.fallbackModel !== void 0) options.fallbackModel = input.fallbackModel;
942
- if (input.enableFileCheckpointing !== void 0)
943
- options.enableFileCheckpointing = input.enableFileCheckpointing;
944
- if (input.includePartialMessages !== void 0)
945
- options.includePartialMessages = input.includePartialMessages;
946
- if (input.strictMcpConfig !== void 0) options.strictMcpConfig = input.strictMcpConfig;
947
- if (input.settingSources !== void 0) options.settingSources = input.settingSources;
948
- else options.settingSources = DEFAULT_SETTING_SOURCES;
949
- if (input.debug !== void 0) options.debug = input.debug;
950
- if (input.debugFile !== void 0) options.debugFile = input.debugFile;
951
- if (input.env !== void 0) options.env = { ...process.env, ...input.env };
952
- if (effectivePermissionMode === "bypassPermissions") {
953
- options.allowDangerouslySkipPermissions = true;
954
- }
955
- if (input.forkSession) {
956
- options.forkSession = true;
957
- }
958
- for await (const message of query2({
959
- prompt: input.prompt,
960
- options
961
- })) {
962
- if (message.type === "system" && message.subtype === "init") {
963
- if (message.permissionMode === "bypassPermissions" && !allowBypass) {
964
- isError = true;
965
- resultText = `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: Cannot resume a bypassPermissions session while bypass is disabled. Use the claude_code_configure tool with action 'enable_bypass' first.`;
966
- abortController.abort();
967
- break;
968
- }
969
- if (input.forkSession) {
970
- newSessionId = message.session_id;
971
- if (newSessionId !== input.sessionId && !sessionManager.get(newSessionId)) {
972
- try {
973
- sessionManager.create({
974
- sessionId: newSessionId,
975
- cwd: message.cwd,
976
- model: message.model,
977
- permissionMode: effectivePermissionMode,
978
- allowedTools: input.allowedTools,
979
- disallowedTools: input.disallowedTools,
980
- tools: input.tools ?? message.tools,
981
- maxTurns: input.maxTurns,
982
- systemPrompt: input.systemPrompt,
983
- agents: input.agents,
984
- maxBudgetUsd: input.maxBudgetUsd,
985
- effort: input.effort,
986
- betas: input.betas ?? message.betas,
987
- additionalDirectories: input.additionalDirectories,
988
- outputFormat: input.outputFormat,
989
- thinking: input.thinking,
990
- persistSession: input.persistSession,
991
- pathToClaudeCodeExecutable: input.pathToClaudeCodeExecutable,
992
- agent: input.agent,
993
- mcpServers: input.mcpServers,
994
- sandbox: input.sandbox,
995
- fallbackModel: input.fallbackModel,
996
- enableFileCheckpointing: input.enableFileCheckpointing,
997
- includePartialMessages: input.includePartialMessages,
998
- strictMcpConfig: input.strictMcpConfig,
999
- settingSources: input.settingSources ?? DEFAULT_SETTING_SOURCES,
1000
- debug: input.debug,
1001
- debugFile: input.debugFile,
1002
- env: input.env,
1003
- abortController
1004
- });
1005
- } catch {
1006
- isError = true;
1007
- resultText = `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: running).`;
1008
- abortController.abort();
1009
- break;
1010
- }
1011
- }
1012
- } else {
1013
- sessionManager.update(input.sessionId, {
1014
- cwd: message.cwd,
1015
- model: message.model,
1016
- permissionMode: effectivePermissionMode,
1017
- betas: input.betas ?? message.betas,
1018
- tools: input.tools ?? message.tools
1019
- });
1020
- }
1021
- }
1022
- if (message.type === "result") {
1023
- if (seenResult) continue;
1024
- seenResult = true;
1025
- const result = message;
1026
- durationMs = result.duration_ms;
1027
- durationApiMs = result.duration_api_ms;
1028
- numTurns = result.num_turns;
1029
- totalCostUsd = result.total_cost_usd;
1030
- isError = result.is_error;
1031
- stopReason = result.stop_reason;
1032
- usage = result.usage;
1033
- modelUsage = result.modelUsage;
1034
- permissionDenials = result.permission_denials;
1035
- if (result.subtype === "success") {
1036
- resultText = result.result;
1037
- structuredOutput = result.structured_output;
1038
- } else {
1039
- isError = true;
1040
- errorSubtype = result.subtype;
1041
- resultText = result.errors.map(String).join("\n") || `Error [${result.subtype}]: Unknown error`;
1042
- }
1043
- break;
1044
- }
1045
- }
1046
- } catch (err) {
1047
- isError = true;
1048
- const isAborted = abortController.signal.aborted || err instanceof AbortError2 || err instanceof Error && err.name === "AbortError";
1049
- if (isAborted) {
1050
- resultText = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms.` : `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`;
1051
- } else {
1052
- resultText = enhanceWindowsError(err instanceof Error ? err.message : String(err));
1053
- }
1054
- } finally {
1055
- if (timeoutId) clearTimeout(timeoutId);
1056
- }
1057
- if (!seenResult && !isError && abortController.signal.aborted) {
1058
- isError = true;
1059
- resultText = timedOut ? `Error [${"TIMEOUT" /* TIMEOUT */}]: Session timed out after ${input.timeout}ms.` : `Error [${"CANCELLED" /* CANCELLED */}]: Session was cancelled.`;
1060
- }
1061
- if (!seenResult && !isError) {
1062
- isError = true;
1063
- errorSubtype = errorSubtype ?? "missing_result";
1064
- const noResultMsg = `Error [${"INTERNAL" /* INTERNAL */}]: No result message received from agent.`;
1065
- resultText = resultText ? `${noResultMsg} Original: ${resultText}` : noResultMsg;
1066
- }
1067
- const targetSessionId = input.forkSession ? newSessionId : input.sessionId;
1068
- const tracked = sessionManager.get(targetSessionId);
1069
- if (tracked) {
1070
- const nextTurns = input.forkSession ? numTurns : (tracked.totalTurns ?? 0) + numTurns;
1071
- const nextCost = input.forkSession ? totalCostUsd : (tracked.totalCostUsd ?? 0) + totalCostUsd;
1072
- if (tracked.status !== "cancelled") {
1073
- sessionManager.update(targetSessionId, {
1074
- status: isError ? "error" : "idle",
1075
- totalTurns: nextTurns,
1076
- totalCostUsd: nextCost,
1077
- abortController: void 0
1078
- });
1079
- } else {
1080
- sessionManager.update(targetSessionId, {
1081
- totalTurns: nextTurns,
1082
- totalCostUsd: nextCost,
1083
- abortController: void 0
1084
- });
1085
- }
1086
- const updated = sessionManager.get(targetSessionId);
1087
- sessionTotalTurns = updated?.totalTurns;
1088
- sessionTotalCostUsd = updated?.totalCostUsd;
1089
1701
  }
1090
- if (input.forkSession && newSessionId === input.sessionId && !isError) {
1091
- isError = true;
1092
- const noForkMsg = `Error [${"INTERNAL" /* INTERNAL */}]: Fork requested but no new session ID received from agent.`;
1093
- resultText = resultText ? `${noForkMsg} Original: ${resultText}` : noForkMsg;
1702
+ if (input.decision !== "allow" && input.decision !== "deny") {
1703
+ return {
1704
+ sessionId: input.sessionId,
1705
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow' or 'deny'.`,
1706
+ isError: true
1707
+ };
1094
1708
  }
1095
- return {
1096
- sessionId: targetSessionId,
1097
- result: resultText,
1098
- isError,
1099
- durationMs,
1100
- durationApiMs,
1101
- numTurns,
1102
- totalCostUsd,
1103
- sessionTotalTurns,
1104
- sessionTotalCostUsd,
1105
- structuredOutput,
1106
- stopReason,
1107
- errorSubtype,
1108
- usage,
1109
- modelUsage,
1110
- permissionDenials
1111
- };
1709
+ const ok = sessionManager.finishRequest(
1710
+ input.sessionId,
1711
+ input.requestId,
1712
+ toPermissionResult({
1713
+ decision: input.decision,
1714
+ updatedInput: input.permissionOptions?.updatedInput,
1715
+ updatedPermissions: input.permissionOptions?.updatedPermissions,
1716
+ denyMessage: input.denyMessage,
1717
+ interrupt: input.interrupt
1718
+ }),
1719
+ "respond"
1720
+ );
1721
+ if (!ok) {
1722
+ return {
1723
+ sessionId: input.sessionId,
1724
+ error: `Error [${"PERMISSION_REQUEST_NOT_FOUND" /* PERMISSION_REQUEST_NOT_FOUND */}]: requestId '${input.requestId}' not found (already finished or expired).`,
1725
+ isError: true
1726
+ };
1727
+ }
1728
+ return buildResult(sessionManager, toolCache, input);
1112
1729
  }
1113
1730
 
1114
1731
  // src/tools/claude-code-session.ts
@@ -1145,7 +1762,10 @@ function executeClaudeCodeSession(input, sessionManager) {
1145
1762
  isError: true
1146
1763
  };
1147
1764
  }
1148
- const cancelled = sessionManager.cancel(input.sessionId);
1765
+ const cancelled = sessionManager.cancel(input.sessionId, {
1766
+ reason: "Cancelled by caller",
1767
+ source: "claude_code_session"
1768
+ });
1149
1769
  if (!cancelled) {
1150
1770
  const session = sessionManager.get(input.sessionId);
1151
1771
  if (!session) {
@@ -1176,164 +1796,117 @@ function executeClaudeCodeSession(input, sessionManager) {
1176
1796
  }
1177
1797
  }
1178
1798
 
1179
- // src/tools/claude-code-configure.ts
1180
- function executeClaudeCodeConfigure(input, config) {
1181
- switch (input.action) {
1182
- case "enable_bypass":
1183
- config.setAllowBypass(true);
1184
- return {
1185
- allowBypass: true,
1186
- message: "bypassPermissions mode is now enabled for this server session. Use with caution."
1187
- };
1188
- case "disable_bypass":
1189
- config.setAllowBypass(false);
1190
- return {
1191
- allowBypass: false,
1192
- message: "bypassPermissions mode is now disabled."
1193
- };
1194
- case "get_config":
1195
- return {
1196
- allowBypass: config.getAllowBypass(),
1197
- message: `Current config: bypassPermissions ${config.getAllowBypass() ? "enabled" : "disabled"}.`
1198
- };
1199
- default:
1200
- return {
1201
- allowBypass: config.getAllowBypass(),
1202
- message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'enable_bypass', 'disable_bypass', or 'get_config'.`,
1203
- isError: true
1204
- };
1205
- }
1206
- }
1207
-
1208
1799
  // src/server.ts
1209
- var SERVER_VERSION = true ? "1.6.0" : "0.0.0-dev";
1210
- function createServer(serverCwd, opts) {
1211
- const parsePositiveInt = (value) => {
1212
- if (value === void 0 || value.trim() === "") return void 0;
1213
- const parsed = Number.parseInt(value, 10);
1214
- if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
1215
- return parsed;
1216
- };
1217
- const sessionManager = new SessionManager({
1218
- sessionTtlMs: parsePositiveInt(process.env.CLAUDE_CODE_MCP_SESSION_TTL_MS),
1219
- runningSessionMaxMs: parsePositiveInt(process.env.CLAUDE_CODE_MCP_RUNNING_SESSION_MAX_MS),
1220
- cleanupIntervalMs: parsePositiveInt(process.env.CLAUDE_CODE_MCP_CLEANUP_INTERVAL_MS)
1221
- });
1222
- let allowBypass = opts?.allowBypass ?? false;
1223
- const config = {
1224
- getAllowBypass: () => allowBypass,
1225
- setAllowBypass: (v) => {
1226
- allowBypass = v;
1227
- }
1228
- };
1800
+ var SERVER_VERSION = true ? "2.0.0" : "0.0.0-dev";
1801
+ function createServer(serverCwd) {
1802
+ const sessionManager = new SessionManager();
1803
+ const toolCache = new ToolDiscoveryCache();
1229
1804
  const server = new McpServer({
1230
1805
  name: "claude-code-mcp",
1231
1806
  version: SERVER_VERSION
1232
1807
  });
1808
+ const agentDefinitionSchema = z.object({
1809
+ description: z.string(),
1810
+ prompt: z.string(),
1811
+ tools: z.array(z.string()).optional(),
1812
+ disallowedTools: z.array(z.string()).optional(),
1813
+ model: z.enum(AGENT_MODELS).optional(),
1814
+ maxTurns: z.number().int().positive().optional(),
1815
+ mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
1816
+ skills: z.array(z.string()).optional(),
1817
+ criticalSystemReminder_EXPERIMENTAL: z.string().optional()
1818
+ });
1819
+ const systemPromptSchema = z.union([
1820
+ z.string(),
1821
+ z.object({
1822
+ type: z.literal("preset"),
1823
+ preset: z.literal("claude_code"),
1824
+ append: z.string().optional().describe("Additional instructions to append to the preset")
1825
+ })
1826
+ ]);
1827
+ const toolsConfigSchema = z.union([
1828
+ z.array(z.string()),
1829
+ z.object({
1830
+ type: z.literal("preset"),
1831
+ preset: z.literal("claude_code")
1832
+ })
1833
+ ]);
1834
+ const thinkingSchema = z.union([
1835
+ z.object({ type: z.literal("adaptive") }),
1836
+ z.object({
1837
+ type: z.literal("enabled"),
1838
+ budgetTokens: z.number().int().positive().describe("Token budget for thinking")
1839
+ }),
1840
+ z.object({ type: z.literal("disabled") })
1841
+ ]);
1842
+ const outputFormatSchema = z.object({
1843
+ type: z.literal("json_schema"),
1844
+ schema: z.record(z.string(), z.unknown()).describe("JSON Schema for structured output")
1845
+ });
1846
+ const advancedOptionsSchema = z.object({
1847
+ tools: toolsConfigSchema.optional().describe(
1848
+ "Define the base tool set visible to the agent. Default: omitted (SDK/Claude Code default). Pass an array of tool names, or {type: 'preset', preset: 'claude_code'} for the default set."
1849
+ ),
1850
+ persistSession: z.boolean().optional().describe("Persist session history to disk (~/.claude/projects). Default: true."),
1851
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("How long to wait (in ms) for the agent process to initialize. Default: 10000."),
1852
+ agents: z.record(z.string(), agentDefinitionSchema).optional().describe(
1853
+ "Define custom sub-agents the main agent can delegate tasks to. Each key is the agent name."
1854
+ ),
1855
+ agent: z.string().optional().describe("Name of a custom agent (defined in 'agents') to use as the primary agent."),
1856
+ maxBudgetUsd: z.number().positive().optional().describe("Maximum budget in USD for this session."),
1857
+ effort: z.enum(EFFORT_LEVELS).optional().describe("Effort level: 'low' | 'medium' | 'high' | 'max'."),
1858
+ betas: z.array(z.string()).optional().describe("Beta features to enable."),
1859
+ additionalDirectories: z.array(z.string()).optional().describe("Additional directories the agent can access beyond cwd."),
1860
+ outputFormat: outputFormatSchema.optional().describe("Structured output format with JSON Schema."),
1861
+ thinking: thinkingSchema.optional().describe("Thinking mode: 'adaptive' | 'enabled' (with budget) | 'disabled'."),
1862
+ pathToClaudeCodeExecutable: z.string().optional().describe("Path to the Claude Code executable. Default: SDK-bundled."),
1863
+ mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("MCP server configurations (key: server name, value: server config)."),
1864
+ sandbox: z.record(z.string(), z.unknown()).optional().describe("Sandbox configuration for isolating shell command execution."),
1865
+ fallbackModel: z.string().optional().describe("Fallback model if the primary model fails or is unavailable."),
1866
+ enableFileCheckpointing: z.boolean().optional().describe("Enable file checkpointing to track file changes. Default: false."),
1867
+ includePartialMessages: z.boolean().optional().describe("Include intermediate messages as events in claude_code_check. Default: false."),
1868
+ strictMcpConfig: z.boolean().optional().describe("Enforce strict validation of MCP server configurations. Default: false."),
1869
+ settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe(
1870
+ "Which local config files to load. Default: ['user', 'project', 'local']. Pass [] to disable all."
1871
+ ),
1872
+ debug: z.boolean().optional().describe("Enable debug mode. Default: false."),
1873
+ debugFile: z.string().optional().describe("Write debug logs to a file path (implicitly enables debug mode)."),
1874
+ env: z.record(z.string(), z.string().optional()).optional().describe("Environment variables to merge with process.env.")
1875
+ }).optional().describe(
1876
+ "Low-frequency SDK options. All fields are optional with sensible defaults. Most callers can omit this entirely."
1877
+ );
1233
1878
  server.tool(
1234
1879
  "claude_code",
1235
- `Start a new Claude Code session. The agent autonomously performs coding tasks: reading/writing files, running shell commands, searching code, managing git, and interacting with APIs.
1236
- Returns a sessionId that can be passed to claude_code_reply for multi-turn conversations.
1237
- Defaults: permissionMode="dontAsk" (auto-approves allowed tools without prompting), loads all local Claude settings (user, project, local) including CLAUDE.md.`,
1880
+ buildInternalToolsDescription(toolCache.getTools()),
1238
1881
  {
1239
1882
  prompt: z.string().describe("The task or question for Claude Code"),
1240
- cwd: z.string().optional().describe("Working directory (defaults to server cwd)"),
1883
+ cwd: z.string().optional().describe("Working directory. Default: server cwd."),
1241
1884
  allowedTools: z.array(z.string()).optional().describe(
1242
- "List of tool names the agent can use without permission prompts. In the default 'dontAsk' mode, only tools in this list are available. Example: ['Bash', 'Read', 'Write', 'Edit']"
1885
+ "Tools the agent can use without asking for permission. Default: [] (no auto-approvals). Example: ['Bash', 'Read', 'Write', 'Edit']. Tools not listed here or in disallowedTools will trigger a permission request via claude_code_check."
1243
1886
  ),
1244
1887
  disallowedTools: z.array(z.string()).optional().describe(
1245
- "List of tool names the agent is forbidden from using. Takes precedence over allowedTools."
1246
- ),
1247
- tools: z.union([
1248
- z.array(z.string()),
1249
- z.object({
1250
- type: z.literal("preset"),
1251
- preset: z.literal("claude_code")
1252
- })
1253
- ]).optional().describe(
1254
- "Define the base tool set for the session. Pass an array of tool name strings, or use {type: 'preset', preset: 'claude_code'} for the default Claude Code toolset. allowedTools/disallowedTools further filter on top of this base set."
1255
- ),
1256
- persistSession: z.boolean().optional().describe(
1257
- "Persist session history to disk (~/.claude/projects). Default: true. Set false to disable persistence."
1888
+ "Tools the agent is forbidden from using. Default: [] (none). Takes priority over allowedTools."
1258
1889
  ),
1259
- permissionMode: z.enum(PERMISSION_MODES).optional().describe(
1260
- "Controls how the agent handles tool permissions. 'dontAsk' (default): auto-approve tools in allowedTools without prompting. 'bypassPermissions': skip all permission checks (requires enable_bypass via claude_code_configure). 'plan': require approval before executing."
1890
+ maxTurns: z.number().int().positive().optional().describe("Maximum number of reasoning steps the agent can take."),
1891
+ model: z.string().optional().describe("Model to use, e.g. 'claude-sonnet-4-5-20250929'."),
1892
+ systemPrompt: systemPromptSchema.optional().describe(
1893
+ "Override the agent's system prompt. Pass a string for full replacement, or use {type: 'preset', preset: 'claude_code', append: '...'} to extend the default."
1261
1894
  ),
1262
- maxTurns: z.number().int().positive().optional().describe(
1263
- "Maximum number of agent reasoning steps. Each step may involve one or more tool calls. Limits how many iterations the agent performs before stopping."
1895
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe(
1896
+ "How long to wait (in ms) for a permission decision via claude_code_check before auto-denying. Default: 60000."
1264
1897
  ),
1265
- model: z.string().optional().describe("Model to use, e.g. 'claude-sonnet-4-5-20250929'"),
1266
- systemPrompt: z.union([
1267
- z.string(),
1268
- z.object({
1269
- type: z.literal("preset"),
1270
- preset: z.literal("claude_code"),
1271
- append: z.string().optional().describe("Additional instructions to append to the preset")
1272
- })
1273
- ]).optional().describe(
1274
- "Override the agent's system prompt. Pass a string for full replacement, or use {type: 'preset', preset: 'claude_code', append: '...'} to extend the default Claude Code prompt with additional instructions."
1275
- ),
1276
- agents: z.record(
1277
- z.string(),
1278
- z.object({
1279
- description: z.string(),
1280
- prompt: z.string(),
1281
- tools: z.array(z.string()).optional(),
1282
- disallowedTools: z.array(z.string()).optional(),
1283
- model: z.enum(AGENT_MODELS).optional(),
1284
- maxTurns: z.number().int().positive().optional(),
1285
- mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
1286
- skills: z.array(z.string()).optional(),
1287
- criticalSystemReminder_EXPERIMENTAL: z.string().optional()
1288
- })
1289
- ).optional().describe(
1290
- "Define custom sub-agents that the main agent can delegate tasks to. Each key is the agent name; the value specifies its system prompt, available tools, model, and other constraints."
1291
- ),
1292
- maxBudgetUsd: z.number().positive().optional().describe("Maximum budget in USD for this session"),
1293
- effort: z.enum(EFFORT_LEVELS).optional().describe(
1294
- "Effort level: 'low' (fast), 'medium' (balanced), 'high' (thorough), 'max' (maximum)"
1295
- ),
1296
- betas: z.array(z.string()).optional().describe("Beta features to enable (e.g. ['context-1m-2025-08-07'])"),
1297
- additionalDirectories: z.array(z.string()).optional().describe("Additional directories the agent can access beyond cwd"),
1298
- outputFormat: z.object({
1299
- type: z.literal("json_schema"),
1300
- schema: z.record(z.string(), z.unknown()).describe("JSON Schema for structured output")
1301
- }).optional().describe("Structured output format with JSON Schema (omit for plain text output)"),
1302
- thinking: z.union([
1303
- z.object({ type: z.literal("adaptive") }),
1304
- z.object({
1305
- type: z.literal("enabled"),
1306
- budgetTokens: z.number().int().positive().describe("Token budget for thinking")
1307
- }),
1308
- z.object({ type: z.literal("disabled") })
1309
- ]).optional().describe("Thinking mode: 'adaptive' (auto), 'enabled' (with budget), or 'disabled'"),
1310
- timeout: z.number().int().positive().optional().describe("Timeout in milliseconds for this session"),
1311
- pathToClaudeCodeExecutable: z.string().optional().describe("Path to the Claude Code executable"),
1312
- agent: z.string().optional().describe(
1313
- "Name of a custom agent (defined in 'agents' parameter) to use as the primary agent for this session, applying its system prompt, tool restrictions, and model override."
1314
- ),
1315
- mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("MCP server configurations (key: server name, value: server config)"),
1316
- sandbox: z.record(z.string(), z.unknown()).optional().describe(
1317
- "Sandbox configuration for isolating shell command execution (e.g., Docker container settings). Controls the execution environment for Bash tool calls."
1318
- ),
1319
- fallbackModel: z.string().optional().describe("Fallback model if the primary model fails or is unavailable"),
1320
- enableFileCheckpointing: z.boolean().optional().describe("Enable file checkpointing to track file changes during the session"),
1321
- includePartialMessages: z.boolean().optional().describe(
1322
- "When true, includes intermediate streaming messages in the response (e.g., partial tool outputs as they arrive). Useful for real-time progress monitoring. Default: false."
1323
- ),
1324
- strictMcpConfig: z.boolean().optional().describe("Enforce strict validation of MCP server configurations"),
1325
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe(
1326
- 'Control which filesystem settings are loaded. Defaults to ["user", "project", "local"] (loads all settings including ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md). Pass an empty array [] to disable all settings (SDK isolation mode).'
1327
- ),
1328
- debug: z.boolean().optional().describe("Enable debug mode for verbose logging"),
1329
- debugFile: z.string().optional().describe("Write debug logs to a specific file path (implicitly enables debug mode)"),
1330
- env: z.record(z.string(), z.string().optional()).optional().describe(
1331
- "Environment variables to merge with process.env and pass to the Claude Code process (user-provided values take precedence)"
1332
- )
1898
+ advanced: advancedOptionsSchema
1333
1899
  },
1334
- async (args) => {
1900
+ async (args, extra) => {
1335
1901
  try {
1336
- const result = await executeClaudeCode(args, sessionManager, serverCwd, allowBypass);
1902
+ const result = await executeClaudeCode(
1903
+ args,
1904
+ sessionManager,
1905
+ serverCwd,
1906
+ toolCache,
1907
+ extra.signal
1908
+ );
1909
+ const isError = typeof result.error === "string";
1337
1910
  return {
1338
1911
  content: [
1339
1912
  {
@@ -1341,7 +1914,7 @@ Defaults: permissionMode="dontAsk" (auto-approves allowed tools without promptin
1341
1914
  text: JSON.stringify(result, null, 2)
1342
1915
  }
1343
1916
  ],
1344
- isError: result.isError
1917
+ isError
1345
1918
  };
1346
1919
  } catch (err) {
1347
1920
  const message = err instanceof Error ? err.message : String(err);
@@ -1359,102 +1932,69 @@ Defaults: permissionMode="dontAsk" (auto-approves allowed tools without promptin
1359
1932
  );
1360
1933
  server.tool(
1361
1934
  "claude_code_reply",
1362
- `Continue an existing Claude Code session by sending a follow-up message. The agent retains full context from previous turns including files read, code analysis, and conversation history. Requires a sessionId returned by a previous claude_code call.
1363
- Note: When CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 is set and the in-memory session has expired, the agent can resume from disk-persisted history. Parameters marked "(disk resume fallback)" are only used in this scenario to reconstruct the session.`,
1935
+ `Send a follow-up message to an existing Claude Code session.
1936
+
1937
+ The agent retains full context from previous turns (files read, code analyzed, conversation history). Returns immediately \u2014 use claude_code_check to poll for the result.
1938
+
1939
+ Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
1940
+
1941
+ Defaults:
1942
+ - forkSession: false
1943
+ - sessionInitTimeoutMs: 10000 (only used when forkSession=true)
1944
+ - permissionRequestTimeoutMs: 60000
1945
+ - Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
1946
+
1947
+ Disk resume: If the server restarted and the session is no longer in memory, set CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 to let the agent resume from its on-disk transcript. Pass diskResumeConfig with resumeToken and session parameters.`,
1364
1948
  {
1365
1949
  sessionId: z.string().describe("The session ID to continue (from a previous claude_code call)"),
1366
1950
  prompt: z.string().describe("Follow-up prompt or instruction"),
1367
1951
  forkSession: z.boolean().optional().describe(
1368
- "Create a branched copy of this session. The original session remains unchanged; the new session starts with the same context but diverges from this point. Useful for exploring alternative approaches."
1369
- ),
1370
- timeout: z.number().int().positive().optional().describe("Timeout in milliseconds for this reply"),
1371
- // Optional disk-resume overrides (only used when CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
1372
- // and the in-memory session metadata is missing)
1373
- cwd: z.string().optional().describe("Working directory (disk resume fallback)"),
1374
- allowedTools: z.array(z.string()).optional().describe(
1375
- "Auto-approved tool names (disk resume fallback). See claude_code tool for details."
1952
+ "Branch this session into a new copy that diverges from the current point. The original session remains unchanged. Default: false."
1376
1953
  ),
1377
- disallowedTools: z.array(z.string()).optional().describe("Forbidden tool names (disk resume fallback). See claude_code tool for details."),
1378
- tools: z.union([
1379
- z.array(z.string()),
1380
- z.object({
1381
- type: z.literal("preset"),
1382
- preset: z.literal("claude_code")
1383
- })
1384
- ]).optional().describe("Base tool set (disk resume fallback). See claude_code tool for details."),
1385
- persistSession: z.boolean().optional().describe("Persist session history to disk (~/.claude/projects). Default: true."),
1386
- permissionMode: z.enum(PERMISSION_MODES).optional().describe("Permission mode (disk resume fallback). See claude_code tool for details."),
1387
- maxTurns: z.number().int().positive().optional().describe("Maximum number of agent reasoning steps for this reply."),
1388
- model: z.string().optional().describe("Model to use, e.g. 'claude-sonnet-4-5-20250929'"),
1389
- systemPrompt: z.union([
1390
- z.string(),
1391
- z.object({
1392
- type: z.literal("preset"),
1393
- preset: z.literal("claude_code"),
1394
- append: z.string().optional().describe("Additional instructions to append to the preset")
1395
- })
1396
- ]).optional().describe("Override the agent's system prompt. See claude_code tool for details."),
1397
- agents: z.record(
1398
- z.string(),
1399
- z.object({
1400
- description: z.string(),
1401
- prompt: z.string(),
1402
- tools: z.array(z.string()).optional(),
1403
- disallowedTools: z.array(z.string()).optional(),
1404
- model: z.enum(AGENT_MODELS).optional(),
1405
- maxTurns: z.number().int().positive().optional(),
1406
- mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
1407
- skills: z.array(z.string()).optional(),
1408
- criticalSystemReminder_EXPERIMENTAL: z.string().optional()
1409
- })
1410
- ).optional().describe("Define custom sub-agents. See claude_code tool for details."),
1411
- maxBudgetUsd: z.number().positive().optional().describe("Maximum budget in USD for this reply"),
1412
- effort: z.enum(EFFORT_LEVELS).optional().describe(
1413
- "Effort level: 'low' (fast), 'medium' (balanced), 'high' (thorough), 'max' (maximum)"
1954
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("How long to wait (in ms) for a forked session to initialize. Default: 10000."),
1955
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe(
1956
+ "How long to wait (in ms) for a permission decision via claude_code_check before auto-denying. Default: 60000."
1414
1957
  ),
1415
- betas: z.array(z.string()).optional().describe("Beta features to enable"),
1416
- additionalDirectories: z.array(z.string()).optional().describe("Additional directories the agent can access beyond cwd"),
1417
- outputFormat: z.object({
1418
- type: z.literal("json_schema"),
1419
- schema: z.record(z.string(), z.unknown()).describe("JSON Schema for structured output")
1420
- }).optional().describe("Structured output format with JSON Schema (omit for plain text output)"),
1421
- thinking: z.union([
1422
- z.object({ type: z.literal("adaptive") }),
1423
- z.object({
1424
- type: z.literal("enabled"),
1425
- budgetTokens: z.number().int().positive().describe("Token budget for thinking")
1426
- }),
1427
- z.object({ type: z.literal("disabled") })
1428
- ]).optional().describe("Thinking mode: 'adaptive' (auto), 'enabled' (with budget), or 'disabled'"),
1429
- resumeSessionAt: z.string().optional().describe(
1430
- "Resume only up to and including a specific message UUID (disk resume fallback only)"
1431
- ),
1432
- pathToClaudeCodeExecutable: z.string().optional().describe("Path to the Claude Code executable"),
1433
- agent: z.string().optional().describe(
1434
- "Name of a custom agent (defined in 'agents') to use as the primary agent. See claude_code tool for details."
1435
- ),
1436
- mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("MCP server configurations (key: server name, value: server config)"),
1437
- sandbox: z.record(z.string(), z.unknown()).optional().describe(
1438
- "Sandbox configuration for isolating shell command execution. See claude_code tool for details."
1439
- ),
1440
- fallbackModel: z.string().optional().describe("Fallback model if the primary model fails or is unavailable"),
1441
- enableFileCheckpointing: z.boolean().optional().describe("Enable file checkpointing to track file changes during the session"),
1442
- includePartialMessages: z.boolean().optional().describe(
1443
- "When true, includes intermediate streaming messages in the response (e.g., partial tool outputs as they arrive). Useful for real-time progress monitoring. Default: false."
1444
- ),
1445
- strictMcpConfig: z.boolean().optional().describe("Enforce strict validation of MCP server configurations"),
1446
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe(
1447
- 'Control which filesystem settings are loaded. Defaults to ["user", "project", "local"] (loads all settings including ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md). Pass an empty array [] to disable all settings (SDK isolation mode).'
1448
- ),
1449
- debug: z.boolean().optional().describe("Enable debug mode for verbose logging"),
1450
- debugFile: z.string().optional().describe("Write debug logs to a specific file path (implicitly enables debug mode)"),
1451
- env: z.record(z.string(), z.string().optional()).optional().describe(
1452
- "Environment variables to merge with process.env and pass to the Claude Code process (user-provided values take precedence)"
1958
+ diskResumeConfig: z.object({
1959
+ resumeToken: z.string().optional().describe(
1960
+ "Resume token returned by claude_code / claude_code_reply. Required for disk resume."
1961
+ ),
1962
+ cwd: z.string().optional().describe("Working directory. Required for disk resume."),
1963
+ allowedTools: z.array(z.string()).optional().describe("Tools the agent can use without permission."),
1964
+ disallowedTools: z.array(z.string()).optional().describe("Tools the agent is forbidden from using."),
1965
+ tools: toolsConfigSchema.optional().describe("Which tools the agent can see."),
1966
+ persistSession: z.boolean().optional().describe("Persist session history to disk. Default: true."),
1967
+ maxTurns: z.number().int().positive().optional().describe("Maximum reasoning steps."),
1968
+ model: z.string().optional().describe("Model to use."),
1969
+ systemPrompt: systemPromptSchema.optional().describe("Override the agent's system prompt."),
1970
+ agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Define custom sub-agents."),
1971
+ agent: z.string().optional().describe("Name of a custom agent to use as primary."),
1972
+ maxBudgetUsd: z.number().positive().optional().describe("Maximum budget in USD."),
1973
+ effort: z.enum(EFFORT_LEVELS).optional().describe("Effort level."),
1974
+ betas: z.array(z.string()).optional().describe("Beta features to enable."),
1975
+ additionalDirectories: z.array(z.string()).optional().describe("Additional accessible directories."),
1976
+ outputFormat: outputFormatSchema.optional().describe("Structured output format."),
1977
+ thinking: thinkingSchema.optional().describe("Thinking mode configuration."),
1978
+ resumeSessionAt: z.string().optional().describe("Resume only up to a specific message UUID."),
1979
+ pathToClaudeCodeExecutable: z.string().optional().describe("Path to the Claude Code executable."),
1980
+ mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("MCP server configurations."),
1981
+ sandbox: z.record(z.string(), z.unknown()).optional().describe("Sandbox configuration."),
1982
+ fallbackModel: z.string().optional().describe("Fallback model."),
1983
+ enableFileCheckpointing: z.boolean().optional().describe("Enable file checkpointing. Default: false."),
1984
+ includePartialMessages: z.boolean().optional().describe("Include intermediate messages as events. Default: false."),
1985
+ strictMcpConfig: z.boolean().optional().describe("Enforce strict MCP validation. Default: false."),
1986
+ settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Which local config files to load."),
1987
+ debug: z.boolean().optional().describe("Enable debug mode. Default: false."),
1988
+ debugFile: z.string().optional().describe("Write debug logs to a file path."),
1989
+ env: z.record(z.string(), z.string().optional()).optional().describe("Environment variables to merge with process.env.")
1990
+ }).optional().describe(
1991
+ "Disk resume fallback configuration. Only needed when CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 and the in-memory session is missing. Contains resumeToken + all session config overrides."
1453
1992
  )
1454
1993
  },
1455
- async (args) => {
1994
+ async (args, extra) => {
1456
1995
  try {
1457
- const result = await executeClaudeCodeReply(args, sessionManager, allowBypass);
1996
+ const result = await executeClaudeCodeReply(args, sessionManager, toolCache, extra.signal);
1997
+ const isError = typeof result.error === "string";
1458
1998
  return {
1459
1999
  content: [
1460
2000
  {
@@ -1462,7 +2002,7 @@ Note: When CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 is set and the in-memory session
1462
2002
  text: JSON.stringify(result, null, 2)
1463
2003
  }
1464
2004
  ],
1465
- isError: result.isError
2005
+ isError
1466
2006
  };
1467
2007
  } catch (err) {
1468
2008
  const message = err instanceof Error ? err.message : String(err);
@@ -1480,32 +2020,19 @@ Note: When CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 is set and the in-memory session
1480
2020
  );
1481
2021
  server.tool(
1482
2022
  "claude_code_session",
1483
- `Manage Claude Code sessions. Actions: 'list' returns all sessions with status and metadata; 'get' returns detailed info for a specific session (requires sessionId); 'cancel' terminates a running session (requires sessionId).`,
2023
+ `List, inspect, or cancel Claude Code sessions.
2024
+
2025
+ - action="list": Get all sessions with their status, cost, turn count, and settings.
2026
+ - action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
2027
+ - action="cancel": Stop a running session immediately (pass sessionId).`,
1484
2028
  {
1485
2029
  action: z.enum(SESSION_ACTIONS).describe("Action to perform: 'list', 'get', or 'cancel'"),
1486
2030
  sessionId: z.string().optional().describe("Session ID (required for 'get' and 'cancel')"),
1487
2031
  includeSensitive: z.boolean().optional().describe(
1488
- "When true, includes sensitive fields (cwd, systemPrompt, agents, additionalDirectories) in the response. Requires CLAUDE_CODE_MCP_ALLOW_SENSITIVE_SESSION_DETAILS=1 env var. Default: false."
2032
+ "When true, includes sensitive fields (cwd, systemPrompt, agents, additionalDirectories) in the response. Default: false."
1489
2033
  )
1490
2034
  },
1491
2035
  async (args) => {
1492
- const allowSensitive = process.env.CLAUDE_CODE_MCP_ALLOW_SENSITIVE_SESSION_DETAILS === "1";
1493
- if (args.includeSensitive && !allowSensitive) {
1494
- const result2 = {
1495
- sessions: [],
1496
- message: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: includeSensitive is disabled. Set CLAUDE_CODE_MCP_ALLOW_SENSITIVE_SESSION_DETAILS=1 to enable.`,
1497
- isError: true
1498
- };
1499
- return {
1500
- content: [
1501
- {
1502
- type: "text",
1503
- text: JSON.stringify(result2, null, 2)
1504
- }
1505
- ],
1506
- isError: true
1507
- };
1508
- }
1509
2036
  const result = executeClaudeCodeSession(args, sessionManager);
1510
2037
  return {
1511
2038
  content: [
@@ -1519,16 +2046,82 @@ Note: When CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 is set and the in-memory session
1519
2046
  }
1520
2047
  );
1521
2048
  server.tool(
1522
- "claude_code_configure",
1523
- `Configure the Claude Code MCP server at runtime.
1524
- Actions: 'enable_bypass' allows sessions to use permissionMode='bypassPermissions' (skips all tool permission checks \u2014 use with caution); 'disable_bypass' revokes this ability; 'get_config' returns the current server configuration.`,
2049
+ "claude_code_check",
2050
+ `Query a running session for new events, retrieve the final result, or respond to permission requests.
2051
+
2052
+ Two actions:
2053
+
2054
+ Defaults (poll):
2055
+ - responseMode: "minimal"
2056
+ - minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
2057
+ - maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
2058
+
2059
+ action="poll" \u2014 Retrieve events since the last poll.
2060
+ Returns events (agent output, progress updates, permission requests, errors, final result).
2061
+ Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
2062
+ If the agent is waiting for permission, the response includes an "actions" array with pending requests.
2063
+
2064
+ action="respond_permission" \u2014 Approve or deny a pending permission request.
2065
+ Pass the requestId from the actions array, plus decision="allow" or decision="deny".
2066
+ Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
2067
+ The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
1525
2068
  {
1526
- action: z.enum(CONFIGURE_ACTIONS).describe(
1527
- "Action to perform: 'enable_bypass' | 'disable_bypass' | 'get_config'. See tool description for details."
2069
+ action: z.enum(CHECK_ACTIONS).describe('Action to perform: "poll" or "respond_permission"'),
2070
+ sessionId: z.string().describe("Session ID to check"),
2071
+ cursor: z.number().int().nonnegative().optional().describe(
2072
+ "Event cursor for incremental polling. Pass nextCursor from the previous poll response."
2073
+ ),
2074
+ responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Response shaping preset. 'minimal' reduces payload size. Default: 'minimal'."),
2075
+ maxEvents: z.number().int().positive().optional().describe(
2076
+ "Max number of events to return per poll (pagination via nextCursor). Default: 200 in minimal mode."
2077
+ ),
2078
+ requestId: z.string().optional().describe(
2079
+ "The permission request ID to respond to (from the actions array). Required for respond_permission."
2080
+ ),
2081
+ decision: z.enum(["allow", "deny"]).optional().describe(
2082
+ "Whether to approve or reject the permission request. Required for respond_permission."
2083
+ ),
2084
+ denyMessage: z.string().optional().describe(
2085
+ "Reason for denying, shown to the agent. Only used with decision='deny'. Default: 'Permission denied by caller'."
2086
+ ),
2087
+ interrupt: z.boolean().optional().describe(
2088
+ "When true with decision='deny', stops the entire agent session. Default: false."
2089
+ ),
2090
+ pollOptions: z.object({
2091
+ includeTools: z.boolean().optional().describe("Include availableTools array from session init. Default: false."),
2092
+ includeEvents: z.boolean().optional().describe(
2093
+ "When false, omits the events array (nextCursor still advances). Default: true."
2094
+ ),
2095
+ includeActions: z.boolean().optional().describe("When false, omits actions[] even if waiting_permission. Default: true."),
2096
+ includeResult: z.boolean().optional().describe("When false, omits the top-level result when idle/error. Default: true."),
2097
+ includeUsage: z.boolean().optional().describe("Include AgentResult.usage. Default: true in full mode, false in minimal."),
2098
+ includeModelUsage: z.boolean().optional().describe(
2099
+ "Include AgentResult.modelUsage. Default: true in full mode, false in minimal."
2100
+ ),
2101
+ includeStructuredOutput: z.boolean().optional().describe(
2102
+ "Include AgentResult.structuredOutput. Default: true in full mode, false in minimal."
2103
+ ),
2104
+ includeTerminalEvents: z.boolean().optional().describe(
2105
+ "Include terminal result/error events in events stream. Default: true in full, false in minimal."
2106
+ ),
2107
+ includeProgressEvents: z.boolean().optional().describe(
2108
+ "Include progress events (tool_progress, auth_status). Default: true in full, false in minimal."
2109
+ )
2110
+ }).optional().describe(
2111
+ "Fine-grained poll control. Overrides responseMode defaults for individual fields. Most callers can omit this."
2112
+ ),
2113
+ permissionOptions: z.object({
2114
+ updatedInput: z.record(z.string(), z.unknown()).optional().describe(
2115
+ "Modified tool input to use instead of the original. Only with decision='allow'."
2116
+ ),
2117
+ updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Permission rule updates to apply. Only with decision='allow'.")
2118
+ }).optional().describe(
2119
+ "Advanced permission response options. Only used with respond_permission + decision='allow'."
1528
2120
  )
1529
2121
  },
1530
2122
  async (args) => {
1531
- const result = executeClaudeCodeConfigure(args, config);
2123
+ const result = executeClaudeCodeCheck(args, sessionManager, toolCache);
2124
+ const isError = result.isError === true;
1532
2125
  return {
1533
2126
  content: [
1534
2127
  {
@@ -1536,7 +2129,7 @@ Actions: 'enable_bypass' allows sessions to use permissionMode='bypassPermission
1536
2129
  text: JSON.stringify(result, null, 2)
1537
2130
  }
1538
2131
  ],
1539
- isError: result.isError ?? false
2132
+ isError
1540
2133
  };
1541
2134
  }
1542
2135
  );