@leo000001/claude-code-mcp 2.0.3 → 2.4.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/CHANGELOG.md +81 -4
- package/CONTRIBUTING.md +17 -1
- package/NOTICE.md +27 -0
- package/README.md +103 -34
- package/SECURITY.md +4 -1
- package/dist/index.js +2370 -490
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -7,45 +7,102 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
// src/utils/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
// src/utils/normalize-windows-path.ts
|
|
11
|
+
import path from "path";
|
|
12
|
+
import os from "os";
|
|
13
|
+
var TOOL_INPUT_PATH_FIELDS = [
|
|
14
|
+
"file_path",
|
|
15
|
+
"path",
|
|
16
|
+
"directory",
|
|
17
|
+
"folder",
|
|
18
|
+
"cwd",
|
|
19
|
+
"dest",
|
|
20
|
+
"destination",
|
|
21
|
+
"source",
|
|
22
|
+
"target"
|
|
23
|
+
];
|
|
24
|
+
function maybeAddWindowsLongPathPrefix(value) {
|
|
25
|
+
if (value.startsWith("\\\\?\\") || value.startsWith("\\\\.\\") || value.length < 240)
|
|
26
|
+
return value;
|
|
27
|
+
return path.win32.toNamespacedPath(value);
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
29
|
+
function expandTilde(value, platform) {
|
|
30
|
+
if (value === "~") return os.homedir();
|
|
31
|
+
if (!value.startsWith("~")) return value;
|
|
32
|
+
const next = value[1];
|
|
33
|
+
if (next !== "/" && next !== "\\") return value;
|
|
34
|
+
const rest = value.slice(2);
|
|
35
|
+
const join2 = platform === "win32" ? path.win32.join : path.posix.join;
|
|
36
|
+
return join2(os.homedir(), rest);
|
|
37
|
+
}
|
|
38
|
+
function convertMsysToWindowsPath(rawPath) {
|
|
39
|
+
if (/^[a-zA-Z]:[\\/]/.test(rawPath) || rawPath.startsWith("\\\\")) return void 0;
|
|
40
|
+
if (rawPath.startsWith("//")) {
|
|
41
|
+
const m2 = rawPath.match(/^\/\/([^/]{2,})\/(.*)$/);
|
|
23
42
|
if (m2) {
|
|
24
43
|
const host = m2[1];
|
|
25
44
|
const rest2 = m2[2] ?? "";
|
|
26
45
|
return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
|
|
27
46
|
}
|
|
28
47
|
}
|
|
29
|
-
const cyg =
|
|
48
|
+
const cyg = rawPath.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
|
|
30
49
|
if (cyg) {
|
|
31
50
|
const drive2 = cyg[1].toUpperCase();
|
|
32
51
|
const rest2 = cyg[2] ?? "";
|
|
33
52
|
return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
|
|
34
53
|
}
|
|
35
|
-
const m =
|
|
54
|
+
const m = rawPath.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
|
|
36
55
|
if (!m) return void 0;
|
|
37
56
|
const drive = m[1].toUpperCase();
|
|
38
57
|
const rest = m[2] ?? "";
|
|
39
58
|
return `${drive}:\\${rest.replace(/\//g, "\\")}`;
|
|
40
59
|
}
|
|
41
|
-
function
|
|
60
|
+
function normalizeMsysToWindowsPath(rawPath) {
|
|
61
|
+
const converted = convertMsysToWindowsPath(rawPath);
|
|
62
|
+
if (!converted) return void 0;
|
|
63
|
+
return path.win32.normalize(converted);
|
|
64
|
+
}
|
|
65
|
+
function isUnsupportedPosixAbsolutePath(value, platform = process.platform) {
|
|
66
|
+
if (platform !== "win32") return false;
|
|
67
|
+
if (!value.startsWith("/")) return false;
|
|
68
|
+
return convertMsysToWindowsPath(value) === void 0;
|
|
69
|
+
}
|
|
70
|
+
function extractPosixHomePathFromCommand(command) {
|
|
71
|
+
const match = command.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
|
|
72
|
+
return match?.[0];
|
|
73
|
+
}
|
|
74
|
+
function findUnsupportedPosixPathInToolInput(input, platform = process.platform) {
|
|
75
|
+
if (platform !== "win32") return void 0;
|
|
76
|
+
for (const key of TOOL_INPUT_PATH_FIELDS) {
|
|
77
|
+
const value = input[key];
|
|
78
|
+
if (typeof value !== "string") continue;
|
|
79
|
+
if (isUnsupportedPosixAbsolutePath(value, platform)) return value;
|
|
80
|
+
}
|
|
81
|
+
const command = input.command;
|
|
82
|
+
if (typeof command !== "string") return void 0;
|
|
83
|
+
const embeddedHomePath = extractPosixHomePathFromCommand(command);
|
|
84
|
+
if (!embeddedHomePath) return void 0;
|
|
85
|
+
return isUnsupportedPosixAbsolutePath(embeddedHomePath, platform) ? embeddedHomePath : void 0;
|
|
86
|
+
}
|
|
87
|
+
function normalizeWindowsPathLike(value, platform = process.platform) {
|
|
88
|
+
const expanded = expandTilde(value, platform);
|
|
89
|
+
if (platform !== "win32") return expanded;
|
|
90
|
+
const converted = normalizeMsysToWindowsPath(expanded);
|
|
91
|
+
if (!converted && expanded.startsWith("/")) return expanded;
|
|
92
|
+
const normalized = path.win32.normalize(converted ?? expanded);
|
|
93
|
+
return maybeAddWindowsLongPathPrefix(normalized);
|
|
94
|
+
}
|
|
95
|
+
function normalizeWindowsPathArray(values, platform = process.platform) {
|
|
96
|
+
return values.map((v) => normalizeWindowsPathLike(v, platform));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/utils/normalize-tool-input.ts
|
|
100
|
+
function normalizeToolInput(_toolName, input, platform = process.platform) {
|
|
42
101
|
if (platform !== "win32") return input;
|
|
43
|
-
if (toolName !== "NotebookEdit") return input;
|
|
44
102
|
const filePath = input.file_path;
|
|
45
103
|
if (typeof filePath !== "string") return input;
|
|
46
|
-
const normalized =
|
|
47
|
-
|
|
48
|
-
return { ...input, file_path: normalized };
|
|
104
|
+
const normalized = normalizeWindowsPathLike(filePath, platform);
|
|
105
|
+
return normalized === filePath ? input : { ...input, file_path: normalized };
|
|
49
106
|
}
|
|
50
107
|
|
|
51
108
|
// src/session/manager.ts
|
|
@@ -54,21 +111,91 @@ var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
|
|
|
54
111
|
var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
|
|
55
112
|
var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
|
|
56
113
|
var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
|
|
114
|
+
var DEFAULT_MAX_SESSIONS = 128;
|
|
115
|
+
var DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION = 64;
|
|
116
|
+
function parsePositiveInt(raw) {
|
|
117
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
118
|
+
const parsed = Number.parseInt(raw, 10);
|
|
119
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
function normalizePositiveNumber(value) {
|
|
123
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
124
|
+
return Math.trunc(value);
|
|
125
|
+
}
|
|
126
|
+
function normalizeToolPolicyNames(values) {
|
|
127
|
+
if (!Array.isArray(values) || values.length === 0) return [];
|
|
128
|
+
return values.filter((value) => typeof value === "string").map((value) => value.trim()).filter((value) => value !== "");
|
|
129
|
+
}
|
|
57
130
|
var SessionManager = class _SessionManager {
|
|
58
131
|
sessions = /* @__PURE__ */ new Map();
|
|
59
132
|
runtime = /* @__PURE__ */ new Map();
|
|
133
|
+
drainingSessions = /* @__PURE__ */ new Map();
|
|
60
134
|
cleanupTimer;
|
|
61
135
|
sessionTtlMs = DEFAULT_SESSION_TTL_MS;
|
|
62
136
|
runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
|
|
63
137
|
platform;
|
|
138
|
+
maxSessions;
|
|
139
|
+
maxPendingPermissionsPerSession;
|
|
140
|
+
eventBufferMaxSize;
|
|
141
|
+
eventBufferHardMaxSize;
|
|
142
|
+
destroyed = false;
|
|
64
143
|
constructor(opts) {
|
|
65
144
|
this.platform = opts?.platform ?? process.platform;
|
|
145
|
+
const envRaw = process.env.CLAUDE_CODE_MCP_MAX_SESSIONS;
|
|
146
|
+
const envParsed = typeof envRaw === "string" && envRaw.trim() !== "" ? Number.parseInt(envRaw, 10) : NaN;
|
|
147
|
+
const configured = opts?.maxSessions ?? (Number.isFinite(envParsed) ? envParsed : void 0);
|
|
148
|
+
this.maxSessions = typeof configured === "number" ? configured <= 0 ? Number.POSITIVE_INFINITY : configured : DEFAULT_MAX_SESSIONS;
|
|
149
|
+
const maxPendingEnvRaw = process.env.CLAUDE_CODE_MCP_MAX_PENDING_PERMISSIONS;
|
|
150
|
+
const maxPendingEnvParsed = typeof maxPendingEnvRaw === "string" && maxPendingEnvRaw.trim() !== "" ? Number.parseInt(maxPendingEnvRaw, 10) : NaN;
|
|
151
|
+
const maxPendingConfigured = opts?.maxPendingPermissionsPerSession ?? (Number.isFinite(maxPendingEnvParsed) ? maxPendingEnvParsed : void 0);
|
|
152
|
+
this.maxPendingPermissionsPerSession = typeof maxPendingConfigured === "number" ? maxPendingConfigured <= 0 ? Number.POSITIVE_INFINITY : maxPendingConfigured : DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION;
|
|
153
|
+
const configuredEventBufferMaxSize = normalizePositiveNumber(opts?.eventBufferMaxSize) ?? parsePositiveInt(process.env.CLAUDE_CODE_MCP_EVENT_BUFFER_MAX_SIZE);
|
|
154
|
+
const configuredEventBufferHardMaxSize = normalizePositiveNumber(opts?.eventBufferHardMaxSize) ?? parsePositiveInt(process.env.CLAUDE_CODE_MCP_EVENT_BUFFER_HARD_MAX_SIZE);
|
|
155
|
+
this.eventBufferMaxSize = configuredEventBufferMaxSize ?? DEFAULT_EVENT_BUFFER_MAX_SIZE;
|
|
156
|
+
const initialHardMaxSize = configuredEventBufferHardMaxSize ?? DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE;
|
|
157
|
+
this.eventBufferHardMaxSize = Math.max(this.eventBufferMaxSize, initialHardMaxSize);
|
|
158
|
+
if (initialHardMaxSize < this.eventBufferMaxSize) {
|
|
159
|
+
console.error(
|
|
160
|
+
`[config] CLAUDE_CODE_MCP_EVENT_BUFFER_HARD_MAX_SIZE (${initialHardMaxSize}) is smaller than maxSize (${this.eventBufferMaxSize}); clamped to ${this.eventBufferHardMaxSize}.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
66
163
|
this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
|
|
67
164
|
if (this.cleanupTimer.unref) {
|
|
68
165
|
this.cleanupTimer.unref();
|
|
69
166
|
}
|
|
70
167
|
}
|
|
168
|
+
getSessionCount() {
|
|
169
|
+
if (this.destroyed) return 0;
|
|
170
|
+
return this.sessions.size;
|
|
171
|
+
}
|
|
172
|
+
getMaxSessions() {
|
|
173
|
+
return this.maxSessions;
|
|
174
|
+
}
|
|
175
|
+
getMaxPendingPermissionsPerSession() {
|
|
176
|
+
return this.maxPendingPermissionsPerSession;
|
|
177
|
+
}
|
|
178
|
+
getEventBufferConfig() {
|
|
179
|
+
return {
|
|
180
|
+
maxSize: this.eventBufferMaxSize,
|
|
181
|
+
hardMaxSize: this.eventBufferHardMaxSize
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
getSessionTtlMs() {
|
|
185
|
+
return this.sessionTtlMs;
|
|
186
|
+
}
|
|
187
|
+
getRunningSessionMaxMs() {
|
|
188
|
+
return this.runningSessionMaxMs;
|
|
189
|
+
}
|
|
190
|
+
hasCapacityFor(additionalSessions) {
|
|
191
|
+
if (this.destroyed) return false;
|
|
192
|
+
if (additionalSessions <= 0) return true;
|
|
193
|
+
return this.sessions.size + additionalSessions <= this.maxSessions;
|
|
194
|
+
}
|
|
71
195
|
create(params) {
|
|
196
|
+
if (this.destroyed) {
|
|
197
|
+
throw new Error("SessionManager is destroyed and no longer accepts new sessions.");
|
|
198
|
+
}
|
|
72
199
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
73
200
|
const existing = this.sessions.get(params.sessionId);
|
|
74
201
|
if (existing) {
|
|
@@ -86,6 +213,7 @@ var SessionManager = class _SessionManager {
|
|
|
86
213
|
permissionMode: params.permissionMode ?? "default",
|
|
87
214
|
allowedTools: params.allowedTools,
|
|
88
215
|
disallowedTools: params.disallowedTools,
|
|
216
|
+
strictAllowedTools: params.strictAllowedTools,
|
|
89
217
|
tools: params.tools,
|
|
90
218
|
maxTurns: params.maxTurns,
|
|
91
219
|
systemPrompt: params.systemPrompt,
|
|
@@ -109,14 +237,15 @@ var SessionManager = class _SessionManager {
|
|
|
109
237
|
debug: params.debug,
|
|
110
238
|
debugFile: params.debugFile,
|
|
111
239
|
env: params.env,
|
|
112
|
-
abortController: params.abortController
|
|
240
|
+
abortController: params.abortController,
|
|
241
|
+
queryInterrupt: params.queryInterrupt
|
|
113
242
|
};
|
|
114
243
|
this.sessions.set(params.sessionId, info);
|
|
115
244
|
this.runtime.set(params.sessionId, {
|
|
116
245
|
buffer: {
|
|
117
246
|
events: [],
|
|
118
|
-
maxSize:
|
|
119
|
-
hardMaxSize:
|
|
247
|
+
maxSize: this.eventBufferMaxSize,
|
|
248
|
+
hardMaxSize: this.eventBufferHardMaxSize,
|
|
120
249
|
nextId: 0
|
|
121
250
|
},
|
|
122
251
|
pendingPermissions: /* @__PURE__ */ new Map()
|
|
@@ -124,46 +253,62 @@ var SessionManager = class _SessionManager {
|
|
|
124
253
|
return info;
|
|
125
254
|
}
|
|
126
255
|
get(sessionId) {
|
|
256
|
+
if (this.destroyed) return void 0;
|
|
127
257
|
return this.sessions.get(sessionId);
|
|
128
258
|
}
|
|
129
259
|
updateStatus(sessionId, status) {
|
|
130
260
|
return this.update(sessionId, { status });
|
|
131
261
|
}
|
|
132
262
|
list() {
|
|
263
|
+
if (this.destroyed) return [];
|
|
133
264
|
return Array.from(this.sessions.values());
|
|
134
265
|
}
|
|
135
266
|
update(sessionId, patch) {
|
|
267
|
+
if (this.destroyed) return void 0;
|
|
136
268
|
const info = this.sessions.get(sessionId);
|
|
137
269
|
if (!info) return void 0;
|
|
138
270
|
Object.assign(info, patch, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
139
271
|
return info;
|
|
140
272
|
}
|
|
141
273
|
/**
|
|
142
|
-
* Atomically transition a session from
|
|
274
|
+
* Atomically transition a session's status from expectedStatus to "running".
|
|
275
|
+
* This only covers synchronous state mutation within SessionManager.
|
|
143
276
|
* Returns the session if successful, undefined if the session doesn't exist
|
|
144
277
|
* or its current status doesn't match `expectedStatus`.
|
|
145
278
|
*/
|
|
146
279
|
tryAcquire(sessionId, expectedStatus, abortController) {
|
|
280
|
+
if (this.destroyed) return void 0;
|
|
147
281
|
if (expectedStatus !== "idle" && expectedStatus !== "error") return void 0;
|
|
148
282
|
const info = this.sessions.get(sessionId);
|
|
149
283
|
if (!info || info.status !== expectedStatus) return void 0;
|
|
150
284
|
info.status = "running";
|
|
151
285
|
info.abortController = abortController;
|
|
286
|
+
info.queryInterrupt = void 0;
|
|
152
287
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
153
288
|
this.clearTerminalEvents(sessionId);
|
|
154
289
|
return info;
|
|
155
290
|
}
|
|
156
291
|
cancel(sessionId, opts) {
|
|
292
|
+
if (this.destroyed) return false;
|
|
157
293
|
const info = this.sessions.get(sessionId);
|
|
158
294
|
if (!info) return false;
|
|
159
295
|
if (info.status !== "running" && info.status !== "waiting_permission") return false;
|
|
160
296
|
if (info.status === "waiting_permission") {
|
|
161
297
|
this.finishAllPending(
|
|
162
298
|
sessionId,
|
|
163
|
-
|
|
299
|
+
// `cancel()` already aborts the running query below. Avoid sending an additional
|
|
300
|
+
// interrupt=true control response while the stream is being torn down, which can race
|
|
301
|
+
// into SDK-level "Operation aborted" write errors on stdio clients.
|
|
302
|
+
{ behavior: "deny", message: "Session cancelled", interrupt: false },
|
|
164
303
|
"cancel"
|
|
165
304
|
);
|
|
166
305
|
}
|
|
306
|
+
if (info.queryInterrupt) {
|
|
307
|
+
try {
|
|
308
|
+
info.queryInterrupt();
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
}
|
|
167
312
|
if (info.abortController) {
|
|
168
313
|
info.abortController.abort();
|
|
169
314
|
}
|
|
@@ -171,35 +316,77 @@ var SessionManager = class _SessionManager {
|
|
|
171
316
|
info.cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
172
317
|
info.cancelledReason = opts?.reason ?? "Session cancelled";
|
|
173
318
|
info.cancelledSource = opts?.source ?? "cancel";
|
|
319
|
+
info.queryInterrupt = void 0;
|
|
320
|
+
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
interrupt(sessionId, opts) {
|
|
324
|
+
if (this.destroyed) return false;
|
|
325
|
+
const info = this.sessions.get(sessionId);
|
|
326
|
+
if (!info) return false;
|
|
327
|
+
if (info.status !== "running" && info.status !== "waiting_permission") return false;
|
|
328
|
+
if (info.status === "waiting_permission") {
|
|
329
|
+
this.finishAllPending(
|
|
330
|
+
sessionId,
|
|
331
|
+
{ behavior: "deny", message: opts?.reason ?? "Session interrupted", interrupt: true },
|
|
332
|
+
"interrupt"
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (info.queryInterrupt) {
|
|
336
|
+
try {
|
|
337
|
+
info.queryInterrupt();
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (info.abortController) {
|
|
342
|
+
info.abortController.abort();
|
|
343
|
+
}
|
|
344
|
+
this.pushEvent(sessionId, {
|
|
345
|
+
type: "progress",
|
|
346
|
+
data: {
|
|
347
|
+
type: "interrupted",
|
|
348
|
+
reason: opts?.reason ?? "Session interrupted",
|
|
349
|
+
source: opts?.source ?? "interrupt"
|
|
350
|
+
},
|
|
351
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
352
|
+
});
|
|
174
353
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
175
354
|
return true;
|
|
176
355
|
}
|
|
177
356
|
delete(sessionId) {
|
|
357
|
+
if (this.destroyed) return false;
|
|
178
358
|
this.finishAllPending(
|
|
179
359
|
sessionId,
|
|
180
360
|
{ behavior: "deny", message: "Session deleted", interrupt: true },
|
|
181
|
-
"cleanup"
|
|
361
|
+
"cleanup",
|
|
362
|
+
{ restoreRunning: false }
|
|
182
363
|
);
|
|
364
|
+
this.drainingSessions.delete(sessionId);
|
|
183
365
|
this.runtime.delete(sessionId);
|
|
184
366
|
return this.sessions.delete(sessionId);
|
|
185
367
|
}
|
|
186
368
|
setResult(sessionId, result) {
|
|
369
|
+
if (this.destroyed) return;
|
|
187
370
|
const state = this.runtime.get(sessionId);
|
|
188
371
|
if (!state) return;
|
|
189
372
|
state.storedResult = result;
|
|
190
373
|
}
|
|
191
374
|
getResult(sessionId) {
|
|
375
|
+
if (this.destroyed) return void 0;
|
|
192
376
|
return this.runtime.get(sessionId)?.storedResult;
|
|
193
377
|
}
|
|
194
378
|
setInitTools(sessionId, tools) {
|
|
379
|
+
if (this.destroyed) return;
|
|
195
380
|
const state = this.runtime.get(sessionId);
|
|
196
381
|
if (!state) return;
|
|
197
382
|
state.initTools = tools;
|
|
198
383
|
}
|
|
199
384
|
getInitTools(sessionId) {
|
|
385
|
+
if (this.destroyed) return void 0;
|
|
200
386
|
return this.runtime.get(sessionId)?.initTools;
|
|
201
387
|
}
|
|
202
388
|
pushEvent(sessionId, event) {
|
|
389
|
+
if (this.destroyed) return void 0;
|
|
203
390
|
const state = this.runtime.get(sessionId);
|
|
204
391
|
if (!state) return void 0;
|
|
205
392
|
const full = _SessionManager.pushEvent(
|
|
@@ -217,25 +404,75 @@ var SessionManager = class _SessionManager {
|
|
|
217
404
|
return full;
|
|
218
405
|
}
|
|
219
406
|
getLastEventId(sessionId) {
|
|
407
|
+
if (this.destroyed) return void 0;
|
|
220
408
|
const state = this.runtime.get(sessionId);
|
|
221
409
|
if (!state) return void 0;
|
|
222
410
|
return state.buffer.nextId > 0 ? state.buffer.nextId - 1 : void 0;
|
|
223
411
|
}
|
|
224
412
|
readEvents(sessionId, cursor) {
|
|
413
|
+
if (this.destroyed) return { events: [], nextCursor: cursor ?? 0 };
|
|
225
414
|
const state = this.runtime.get(sessionId);
|
|
226
415
|
if (!state) return { events: [], nextCursor: cursor ?? 0 };
|
|
227
416
|
return _SessionManager.readEvents(state.buffer, cursor);
|
|
228
417
|
}
|
|
229
418
|
clearTerminalEvents(sessionId) {
|
|
419
|
+
if (this.destroyed) return;
|
|
230
420
|
const state = this.runtime.get(sessionId);
|
|
231
421
|
if (!state) return;
|
|
232
422
|
_SessionManager.clearTerminalEvents(state.buffer);
|
|
233
423
|
}
|
|
234
424
|
setPendingPermission(sessionId, req, finish, timeoutMs) {
|
|
425
|
+
if (this.destroyed) return false;
|
|
235
426
|
const state = this.runtime.get(sessionId);
|
|
236
427
|
const info = this.sessions.get(sessionId);
|
|
237
428
|
if (!state || !info) return false;
|
|
429
|
+
if (info.status !== "running" && info.status !== "waiting_permission") {
|
|
430
|
+
try {
|
|
431
|
+
finish({
|
|
432
|
+
behavior: "deny",
|
|
433
|
+
message: `Session is not accepting permission requests (status: ${info.status}).`,
|
|
434
|
+
interrupt: true
|
|
435
|
+
});
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
if (this.isDraining(sessionId)) {
|
|
441
|
+
try {
|
|
442
|
+
finish({
|
|
443
|
+
behavior: "deny",
|
|
444
|
+
message: "Session is finishing pending permission requests.",
|
|
445
|
+
interrupt: true
|
|
446
|
+
});
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
238
451
|
if (!state.pendingPermissions.has(req.requestId)) {
|
|
452
|
+
if (state.pendingPermissions.size >= this.maxPendingPermissionsPerSession) {
|
|
453
|
+
const deny = {
|
|
454
|
+
behavior: "deny",
|
|
455
|
+
message: "Too many pending permission requests for this session.",
|
|
456
|
+
interrupt: false
|
|
457
|
+
};
|
|
458
|
+
this.pushEvent(sessionId, {
|
|
459
|
+
type: "permission_result",
|
|
460
|
+
data: {
|
|
461
|
+
requestId: req.requestId,
|
|
462
|
+
toolName: req.toolName,
|
|
463
|
+
behavior: "deny",
|
|
464
|
+
source: "policy",
|
|
465
|
+
message: deny.message,
|
|
466
|
+
interrupt: deny.interrupt
|
|
467
|
+
},
|
|
468
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
469
|
+
});
|
|
470
|
+
try {
|
|
471
|
+
finish(deny);
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
239
476
|
const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
240
477
|
const record = {
|
|
241
478
|
...req,
|
|
@@ -267,14 +504,79 @@ var SessionManager = class _SessionManager {
|
|
|
267
504
|
return false;
|
|
268
505
|
}
|
|
269
506
|
getPendingPermissionCount(sessionId) {
|
|
507
|
+
if (this.destroyed) return 0;
|
|
270
508
|
return this.runtime.get(sessionId)?.pendingPermissions.size ?? 0;
|
|
271
509
|
}
|
|
510
|
+
getEventCount(sessionId) {
|
|
511
|
+
if (this.destroyed) return 0;
|
|
512
|
+
return this.runtime.get(sessionId)?.buffer.events.length ?? 0;
|
|
513
|
+
}
|
|
514
|
+
getCurrentCursor(sessionId) {
|
|
515
|
+
if (this.destroyed) return void 0;
|
|
516
|
+
const state = this.runtime.get(sessionId);
|
|
517
|
+
if (!state) return void 0;
|
|
518
|
+
return state.buffer.nextId;
|
|
519
|
+
}
|
|
520
|
+
getRemainingTtlMs(sessionId) {
|
|
521
|
+
if (this.destroyed) return void 0;
|
|
522
|
+
const session = this.sessions.get(sessionId);
|
|
523
|
+
if (!session) return void 0;
|
|
524
|
+
const lastActiveMs = Date.parse(session.lastActiveAt);
|
|
525
|
+
if (!Number.isFinite(lastActiveMs)) return void 0;
|
|
526
|
+
const limitMs = session.status === "running" || session.status === "waiting_permission" ? this.runningSessionMaxMs : this.sessionTtlMs;
|
|
527
|
+
return Math.max(0, limitMs - (Date.now() - lastActiveMs));
|
|
528
|
+
}
|
|
529
|
+
getRuntimeToolStats() {
|
|
530
|
+
if (this.destroyed) {
|
|
531
|
+
return { sessionsWithInitTools: 0, runtimeDiscoveredUniqueCount: 0 };
|
|
532
|
+
}
|
|
533
|
+
const unique = /* @__PURE__ */ new Set();
|
|
534
|
+
let sessionsWithInitTools = 0;
|
|
535
|
+
for (const state of this.runtime.values()) {
|
|
536
|
+
if (!Array.isArray(state.initTools) || state.initTools.length === 0) continue;
|
|
537
|
+
sessionsWithInitTools += 1;
|
|
538
|
+
for (const tool of state.initTools) {
|
|
539
|
+
if (typeof tool === "string" && tool.trim() !== "") {
|
|
540
|
+
unique.add(tool);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
sessionsWithInitTools,
|
|
546
|
+
runtimeDiscoveredUniqueCount: unique.size
|
|
547
|
+
};
|
|
548
|
+
}
|
|
272
549
|
listPendingPermissions(sessionId) {
|
|
550
|
+
if (this.destroyed) return [];
|
|
273
551
|
const state = this.runtime.get(sessionId);
|
|
274
552
|
if (!state) return [];
|
|
275
553
|
return Array.from(state.pendingPermissions.values()).map((p) => p.record).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
276
554
|
}
|
|
555
|
+
getPendingPermission(sessionId, requestId) {
|
|
556
|
+
if (this.destroyed) return void 0;
|
|
557
|
+
const state = this.runtime.get(sessionId);
|
|
558
|
+
return state?.pendingPermissions.get(requestId)?.record;
|
|
559
|
+
}
|
|
560
|
+
allowToolForSession(sessionId, toolName) {
|
|
561
|
+
if (this.destroyed) return false;
|
|
562
|
+
const info = this.sessions.get(sessionId);
|
|
563
|
+
if (!info) return false;
|
|
564
|
+
const normalized = toolName.trim();
|
|
565
|
+
if (normalized === "") return false;
|
|
566
|
+
const disallowed = normalizeToolPolicyNames(info.disallowedTools);
|
|
567
|
+
if (disallowed.includes(normalized)) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
const allowed = Array.isArray(info.allowedTools) ? [...info.allowedTools] : [];
|
|
571
|
+
if (!allowed.includes(normalized)) {
|
|
572
|
+
allowed.push(normalized);
|
|
573
|
+
info.allowedTools = allowed;
|
|
574
|
+
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
277
578
|
finishRequest(sessionId, requestId, result, source) {
|
|
579
|
+
if (this.destroyed) return false;
|
|
278
580
|
const state = this.runtime.get(sessionId);
|
|
279
581
|
const info = this.sessions.get(sessionId);
|
|
280
582
|
if (!state || !info) return false;
|
|
@@ -282,11 +584,12 @@ var SessionManager = class _SessionManager {
|
|
|
282
584
|
if (!pending) return false;
|
|
283
585
|
let finalResult = result;
|
|
284
586
|
if (finalResult.behavior === "allow") {
|
|
285
|
-
const disallowed = info.disallowedTools;
|
|
286
|
-
|
|
587
|
+
const disallowed = normalizeToolPolicyNames(info.disallowedTools);
|
|
588
|
+
const pendingToolName = pending.record.toolName.trim();
|
|
589
|
+
if (pendingToolName !== "" && disallowed.includes(pendingToolName)) {
|
|
287
590
|
finalResult = {
|
|
288
591
|
behavior: "deny",
|
|
289
|
-
message: `Tool '${
|
|
592
|
+
message: `Tool '${pendingToolName}' is disallowed by session policy.`,
|
|
290
593
|
interrupt: false
|
|
291
594
|
};
|
|
292
595
|
}
|
|
@@ -294,19 +597,25 @@ var SessionManager = class _SessionManager {
|
|
|
294
597
|
if (finalResult.behavior === "allow") {
|
|
295
598
|
const updatedInput = finalResult.updatedInput;
|
|
296
599
|
const validRecord = updatedInput !== null && updatedInput !== void 0 && typeof updatedInput === "object" && !Array.isArray(updatedInput);
|
|
297
|
-
|
|
600
|
+
const normalizedUpdatedInput = validRecord ? normalizeToolInput(
|
|
601
|
+
pending.record.toolName,
|
|
602
|
+
updatedInput,
|
|
603
|
+
this.platform
|
|
604
|
+
) : normalizeToolInput(pending.record.toolName, pending.record.input, this.platform);
|
|
605
|
+
const unsupportedPath = findUnsupportedPosixPathInToolInput(
|
|
606
|
+
normalizedUpdatedInput,
|
|
607
|
+
this.platform
|
|
608
|
+
);
|
|
609
|
+
if (unsupportedPath) {
|
|
298
610
|
finalResult = {
|
|
299
|
-
|
|
300
|
-
|
|
611
|
+
behavior: "deny",
|
|
612
|
+
message: `Tool '${pending.record.toolName}' attempted to allow an unsupported POSIX path '${unsupportedPath}' on Windows.`,
|
|
613
|
+
interrupt: false
|
|
301
614
|
};
|
|
302
615
|
} else {
|
|
303
616
|
finalResult = {
|
|
304
617
|
...finalResult,
|
|
305
|
-
updatedInput:
|
|
306
|
-
pending.record.toolName,
|
|
307
|
-
updatedInput,
|
|
308
|
-
this.platform
|
|
309
|
-
)
|
|
618
|
+
updatedInput: normalizedUpdatedInput
|
|
310
619
|
};
|
|
311
620
|
}
|
|
312
621
|
}
|
|
@@ -321,6 +630,11 @@ var SessionManager = class _SessionManager {
|
|
|
321
630
|
if (finalResult.behavior === "deny") {
|
|
322
631
|
eventData.message = finalResult.message;
|
|
323
632
|
eventData.interrupt = finalResult.interrupt;
|
|
633
|
+
} else {
|
|
634
|
+
const allow = finalResult;
|
|
635
|
+
if (allow.updatedInput !== void 0) eventData.updatedInput = allow.updatedInput;
|
|
636
|
+
if (allow.updatedPermissions !== void 0)
|
|
637
|
+
eventData.updatedPermissions = allow.updatedPermissions;
|
|
324
638
|
}
|
|
325
639
|
this.pushEvent(sessionId, {
|
|
326
640
|
type: "permission_result",
|
|
@@ -331,21 +645,47 @@ var SessionManager = class _SessionManager {
|
|
|
331
645
|
pending.finish(finalResult);
|
|
332
646
|
} catch {
|
|
333
647
|
}
|
|
334
|
-
if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
|
|
648
|
+
if (info.status === "waiting_permission" && state.pendingPermissions.size === 0 && !this.isDraining(sessionId)) {
|
|
335
649
|
info.status = "running";
|
|
336
650
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
337
651
|
}
|
|
338
652
|
return true;
|
|
339
653
|
}
|
|
340
|
-
finishAllPending(sessionId, result, source) {
|
|
654
|
+
finishAllPending(sessionId, result, source, opts) {
|
|
655
|
+
if (this.destroyed) return;
|
|
341
656
|
const state = this.runtime.get(sessionId);
|
|
342
657
|
if (!state) return;
|
|
343
|
-
|
|
344
|
-
|
|
658
|
+
if (state.pendingPermissions.size === 0) return;
|
|
659
|
+
this.beginDraining(sessionId);
|
|
660
|
+
try {
|
|
661
|
+
while (state.pendingPermissions.size > 0) {
|
|
662
|
+
const next = state.pendingPermissions.keys().next();
|
|
663
|
+
if (next.done || typeof next.value !== "string") break;
|
|
664
|
+
const requestId = next.value;
|
|
665
|
+
const handled = this.finishRequest(sessionId, requestId, result, source);
|
|
666
|
+
if (!handled) {
|
|
667
|
+
const pending = state.pendingPermissions.get(requestId);
|
|
668
|
+
if (pending?.timeoutId) clearTimeout(pending.timeoutId);
|
|
669
|
+
state.pendingPermissions.delete(requestId);
|
|
670
|
+
try {
|
|
671
|
+
pending?.finish(result);
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
} finally {
|
|
677
|
+
this.endDraining(sessionId);
|
|
678
|
+
}
|
|
679
|
+
const restoreRunning = opts?.restoreRunning ?? true;
|
|
680
|
+
const info = this.sessions.get(sessionId);
|
|
681
|
+
if (restoreRunning && info && info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
|
|
682
|
+
info.status = "running";
|
|
683
|
+
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
345
684
|
}
|
|
346
685
|
}
|
|
347
686
|
/** Remove sessions that have been idle for too long, or stuck running too long */
|
|
348
687
|
cleanup() {
|
|
688
|
+
if (this.destroyed) return;
|
|
349
689
|
const now = Date.now();
|
|
350
690
|
for (const [id, info] of this.sessions) {
|
|
351
691
|
const lastActive = new Date(info.lastActiveAt).getTime();
|
|
@@ -353,29 +693,42 @@ var SessionManager = class _SessionManager {
|
|
|
353
693
|
this.finishAllPending(
|
|
354
694
|
id,
|
|
355
695
|
{ behavior: "deny", message: "Session expired", interrupt: true },
|
|
356
|
-
"cleanup"
|
|
696
|
+
"cleanup",
|
|
697
|
+
{ restoreRunning: false }
|
|
357
698
|
);
|
|
699
|
+
this.drainingSessions.delete(id);
|
|
358
700
|
this.sessions.delete(id);
|
|
359
701
|
this.runtime.delete(id);
|
|
360
702
|
} else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
|
|
361
703
|
if (info.abortController) info.abortController.abort();
|
|
362
|
-
info.
|
|
704
|
+
info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
705
|
+
info.cancelledReason = info.cancelledReason ?? "Session timed out";
|
|
706
|
+
info.cancelledSource = info.cancelledSource ?? "cleanup";
|
|
707
|
+
info.queryInterrupt = void 0;
|
|
708
|
+
info.status = "cancelled";
|
|
363
709
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
364
710
|
} else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
|
|
365
711
|
this.finishAllPending(
|
|
366
712
|
id,
|
|
367
713
|
{ behavior: "deny", message: "Session timed out", interrupt: true },
|
|
368
|
-
"cleanup"
|
|
714
|
+
"cleanup",
|
|
715
|
+
{ restoreRunning: false }
|
|
369
716
|
);
|
|
370
717
|
if (info.abortController) info.abortController.abort();
|
|
371
|
-
info.
|
|
718
|
+
info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
719
|
+
info.cancelledReason = info.cancelledReason ?? "Session timed out";
|
|
720
|
+
info.cancelledSource = info.cancelledSource ?? "cleanup";
|
|
721
|
+
info.queryInterrupt = void 0;
|
|
722
|
+
info.status = "cancelled";
|
|
372
723
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
373
724
|
} else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
|
|
374
725
|
this.finishAllPending(
|
|
375
726
|
id,
|
|
376
727
|
{ behavior: "deny", message: "Session expired", interrupt: true },
|
|
377
|
-
"cleanup"
|
|
728
|
+
"cleanup",
|
|
729
|
+
{ restoreRunning: false }
|
|
378
730
|
);
|
|
731
|
+
this.drainingSessions.delete(id);
|
|
379
732
|
this.sessions.delete(id);
|
|
380
733
|
this.runtime.delete(id);
|
|
381
734
|
}
|
|
@@ -404,6 +757,7 @@ var SessionManager = class _SessionManager {
|
|
|
404
757
|
toPublicJSON(info) {
|
|
405
758
|
const {
|
|
406
759
|
abortController: _abortController,
|
|
760
|
+
queryInterrupt: _queryInterrupt,
|
|
407
761
|
cwd: _cwd,
|
|
408
762
|
systemPrompt: _systemPrompt,
|
|
409
763
|
agents: _agents,
|
|
@@ -419,22 +773,29 @@ var SessionManager = class _SessionManager {
|
|
|
419
773
|
return rest;
|
|
420
774
|
}
|
|
421
775
|
destroy() {
|
|
776
|
+
if (this.destroyed) return;
|
|
422
777
|
clearInterval(this.cleanupTimer);
|
|
423
778
|
for (const info of this.sessions.values()) {
|
|
424
779
|
this.finishAllPending(
|
|
425
780
|
info.sessionId,
|
|
426
781
|
{ behavior: "deny", message: "Server shutting down", interrupt: true },
|
|
427
|
-
"destroy"
|
|
782
|
+
"destroy",
|
|
783
|
+
{ restoreRunning: false }
|
|
428
784
|
);
|
|
429
785
|
if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
|
|
430
786
|
info.abortController.abort();
|
|
431
787
|
}
|
|
788
|
+
info.queryInterrupt = void 0;
|
|
432
789
|
info.status = "cancelled";
|
|
433
790
|
info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
434
791
|
info.cancelledReason = info.cancelledReason ?? "Server shutting down";
|
|
435
792
|
info.cancelledSource = info.cancelledSource ?? "destroy";
|
|
436
793
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
437
794
|
}
|
|
795
|
+
this.drainingSessions.clear();
|
|
796
|
+
this.runtime.clear();
|
|
797
|
+
this.sessions.clear();
|
|
798
|
+
this.destroyed = true;
|
|
438
799
|
}
|
|
439
800
|
static pushEvent(buffer, event, isActivePermissionRequest) {
|
|
440
801
|
const pinned = event.pinned ?? (event.type === "permission_request" || event.type === "permission_result" || event.type === "result" || event.type === "error");
|
|
@@ -446,38 +807,61 @@ var SessionManager = class _SessionManager {
|
|
|
446
807
|
pinned
|
|
447
808
|
};
|
|
448
809
|
buffer.events.push(full);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
810
|
+
_SessionManager.evictEventsToLimits(buffer, isActivePermissionRequest);
|
|
811
|
+
return full;
|
|
812
|
+
}
|
|
813
|
+
static evictEventsToLimits(buffer, isActivePermissionRequest) {
|
|
814
|
+
if (buffer.events.length <= buffer.maxSize && buffer.events.length <= buffer.hardMaxSize) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const droppedIds = /* @__PURE__ */ new Set();
|
|
818
|
+
let remaining = buffer.events.length;
|
|
819
|
+
const dropOldestMatching = (count, predicate) => {
|
|
820
|
+
if (count <= 0) return 0;
|
|
821
|
+
let dropped = 0;
|
|
822
|
+
for (const event of buffer.events) {
|
|
823
|
+
if (dropped >= count) break;
|
|
824
|
+
if (droppedIds.has(event.id)) continue;
|
|
825
|
+
if (!predicate(event)) continue;
|
|
826
|
+
droppedIds.add(event.id);
|
|
827
|
+
dropped += 1;
|
|
454
828
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
829
|
+
if (dropped > 0) {
|
|
830
|
+
remaining -= dropped;
|
|
831
|
+
}
|
|
832
|
+
return dropped;
|
|
833
|
+
};
|
|
834
|
+
const isDroppablePermissionEvent = (event) => {
|
|
835
|
+
if (event.type === "permission_result") return true;
|
|
836
|
+
if (event.type !== "permission_request") return false;
|
|
837
|
+
const requestId = event.data?.requestId;
|
|
838
|
+
if (typeof requestId !== "string") return true;
|
|
839
|
+
return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
|
|
840
|
+
};
|
|
841
|
+
let toDropForSoftLimit = remaining - buffer.maxSize;
|
|
842
|
+
if (toDropForSoftLimit > 0) {
|
|
843
|
+
toDropForSoftLimit -= dropOldestMatching(toDropForSoftLimit, (event) => !event.pinned);
|
|
844
|
+
}
|
|
845
|
+
if (toDropForSoftLimit > 0) {
|
|
846
|
+
toDropForSoftLimit -= dropOldestMatching(toDropForSoftLimit, isDroppablePermissionEvent);
|
|
847
|
+
}
|
|
848
|
+
let toDropForHardLimit = remaining - buffer.hardMaxSize;
|
|
849
|
+
if (toDropForHardLimit > 0) {
|
|
850
|
+
toDropForHardLimit -= dropOldestMatching(toDropForHardLimit, isDroppablePermissionEvent);
|
|
851
|
+
}
|
|
852
|
+
if (droppedIds.size > 0) {
|
|
853
|
+
buffer.events = buffer.events.filter((event) => !droppedIds.has(event.id));
|
|
479
854
|
}
|
|
480
|
-
|
|
855
|
+
}
|
|
856
|
+
static lowerBoundByEventId(events, startFrom) {
|
|
857
|
+
let left = 0;
|
|
858
|
+
let right = events.length;
|
|
859
|
+
while (left < right) {
|
|
860
|
+
const mid = Math.floor((left + right) / 2);
|
|
861
|
+
if (events[mid].id < startFrom) left = mid + 1;
|
|
862
|
+
else right = mid;
|
|
863
|
+
}
|
|
864
|
+
return left;
|
|
481
865
|
}
|
|
482
866
|
static readEvents(buffer, cursor) {
|
|
483
867
|
let cursorResetTo;
|
|
@@ -487,22 +871,52 @@ var SessionManager = class _SessionManager {
|
|
|
487
871
|
if (earliest == null && buffer.nextId > cursor) cursorResetTo = buffer.nextId;
|
|
488
872
|
}
|
|
489
873
|
const startFrom = cursorResetTo ?? cursor ?? 0;
|
|
490
|
-
const
|
|
874
|
+
const startIndex = _SessionManager.lowerBoundByEventId(buffer.events, startFrom);
|
|
875
|
+
const filtered = buffer.events.slice(startIndex);
|
|
491
876
|
const nextCursor = filtered.length > 0 ? filtered[filtered.length - 1].id + 1 : startFrom;
|
|
492
877
|
return { events: filtered, nextCursor, cursorResetTo };
|
|
493
878
|
}
|
|
494
879
|
static clearTerminalEvents(buffer) {
|
|
495
880
|
buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
|
|
496
881
|
}
|
|
882
|
+
isDraining(sessionId) {
|
|
883
|
+
return (this.drainingSessions.get(sessionId) ?? 0) > 0;
|
|
884
|
+
}
|
|
885
|
+
beginDraining(sessionId) {
|
|
886
|
+
this.drainingSessions.set(sessionId, (this.drainingSessions.get(sessionId) ?? 0) + 1);
|
|
887
|
+
}
|
|
888
|
+
endDraining(sessionId) {
|
|
889
|
+
const current = this.drainingSessions.get(sessionId) ?? 0;
|
|
890
|
+
if (current <= 1) {
|
|
891
|
+
this.drainingSessions.delete(sessionId);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
this.drainingSessions.set(sessionId, current - 1);
|
|
895
|
+
}
|
|
497
896
|
};
|
|
498
897
|
|
|
898
|
+
// src/tools/claude-code.ts
|
|
899
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
900
|
+
|
|
499
901
|
// src/types.ts
|
|
500
902
|
var EFFORT_LEVELS = ["low", "medium", "high", "max"];
|
|
501
903
|
var AGENT_MODELS = ["sonnet", "opus", "haiku", "inherit"];
|
|
502
|
-
var SESSION_ACTIONS = ["list", "get", "cancel"];
|
|
904
|
+
var SESSION_ACTIONS = ["list", "get", "cancel", "interrupt"];
|
|
503
905
|
var DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
|
|
504
906
|
var CHECK_ACTIONS = ["poll", "respond_permission"];
|
|
505
|
-
var CHECK_RESPONSE_MODES = ["minimal", "full"];
|
|
907
|
+
var CHECK_RESPONSE_MODES = ["minimal", "full", "delta_compact"];
|
|
908
|
+
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
909
|
+
ErrorCode2["INVALID_ARGUMENT"] = "INVALID_ARGUMENT";
|
|
910
|
+
ErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
|
|
911
|
+
ErrorCode2["SESSION_BUSY"] = "SESSION_BUSY";
|
|
912
|
+
ErrorCode2["PERMISSION_REQUEST_NOT_FOUND"] = "PERMISSION_REQUEST_NOT_FOUND";
|
|
913
|
+
ErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
|
|
914
|
+
ErrorCode2["RESOURCE_EXHAUSTED"] = "RESOURCE_EXHAUSTED";
|
|
915
|
+
ErrorCode2["TIMEOUT"] = "TIMEOUT";
|
|
916
|
+
ErrorCode2["CANCELLED"] = "CANCELLED";
|
|
917
|
+
ErrorCode2["INTERNAL"] = "INTERNAL";
|
|
918
|
+
return ErrorCode2;
|
|
919
|
+
})(ErrorCode || {});
|
|
506
920
|
|
|
507
921
|
// src/tools/query-consumer.ts
|
|
508
922
|
import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -510,23 +924,98 @@ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
510
924
|
// src/utils/windows.ts
|
|
511
925
|
import { existsSync } from "fs";
|
|
512
926
|
import { execSync } from "child_process";
|
|
513
|
-
import
|
|
514
|
-
var { join, dirname, normalize } =
|
|
927
|
+
import path2 from "path";
|
|
928
|
+
var { join, dirname, normalize, isAbsolute } = path2.win32;
|
|
929
|
+
var LONG_PATH_PREFIX = "\\\\?\\";
|
|
930
|
+
var UNC_LONG_PATH_PREFIX = "\\\\?\\UNC\\";
|
|
515
931
|
function isWindows() {
|
|
516
932
|
return process.platform === "win32";
|
|
517
933
|
}
|
|
934
|
+
function normalizeMaybeQuotedPath(raw) {
|
|
935
|
+
return normalize(raw.trim().replace(/^"|"$/g, ""));
|
|
936
|
+
}
|
|
937
|
+
function existsSyncSafe(candidate) {
|
|
938
|
+
try {
|
|
939
|
+
return existsSync(candidate);
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function ensureLongPathPrefix(normalized) {
|
|
945
|
+
if (!normalized || normalized.startsWith(LONG_PATH_PREFIX)) return normalized;
|
|
946
|
+
if (!isAbsolute(normalized)) return normalized;
|
|
947
|
+
if (normalized.startsWith("\\\\")) {
|
|
948
|
+
return `${UNC_LONG_PATH_PREFIX}${normalized.slice(2)}`;
|
|
949
|
+
}
|
|
950
|
+
return `${LONG_PATH_PREFIX}${normalized}`;
|
|
951
|
+
}
|
|
952
|
+
function existsPath(raw) {
|
|
953
|
+
if (!raw) return false;
|
|
954
|
+
const normalized = normalizeMaybeQuotedPath(raw);
|
|
955
|
+
if (existsSyncSafe(normalized)) return true;
|
|
956
|
+
const longPath = ensureLongPathPrefix(normalized);
|
|
957
|
+
if (longPath === normalized) return false;
|
|
958
|
+
return existsSyncSafe(longPath);
|
|
959
|
+
}
|
|
960
|
+
function tryWhere(exe) {
|
|
961
|
+
try {
|
|
962
|
+
const output = execSync(`where ${exe}`, {
|
|
963
|
+
encoding: "utf8",
|
|
964
|
+
timeout: 2e3,
|
|
965
|
+
windowsHide: true
|
|
966
|
+
});
|
|
967
|
+
return output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
|
|
968
|
+
} catch {
|
|
969
|
+
return [];
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function pathEntries() {
|
|
973
|
+
const raw = process.env.PATH;
|
|
974
|
+
if (typeof raw !== "string" || raw.trim() === "") return [];
|
|
975
|
+
return raw.split(path2.delimiter).map((p) => p.trim().replace(/^"|"$/g, "")).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
|
|
976
|
+
}
|
|
977
|
+
function tryFromPath(exeNames) {
|
|
978
|
+
const results = [];
|
|
979
|
+
for (const dir of pathEntries()) {
|
|
980
|
+
for (const exe of exeNames) {
|
|
981
|
+
const full = normalize(path2.win32.join(dir, exe));
|
|
982
|
+
if (existsPath(full)) results.push(full);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return results;
|
|
986
|
+
}
|
|
987
|
+
function firstExisting(candidates) {
|
|
988
|
+
for (const p of candidates) {
|
|
989
|
+
if (p && existsPath(p)) return p;
|
|
990
|
+
}
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
function isWindowsSystemBash(pathLike) {
|
|
994
|
+
const p = normalizeMaybeQuotedPath(pathLike).toLowerCase();
|
|
995
|
+
return p.endsWith("\\windows\\system32\\bash.exe") || p.endsWith("\\windows\\syswow64\\bash.exe") || p.includes("\\windows\\system32\\bash.exe") || p.includes("\\windows\\syswow64\\bash.exe");
|
|
996
|
+
}
|
|
518
997
|
function findGitBash() {
|
|
519
998
|
const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
|
|
520
999
|
if (envPathRaw && envPathRaw.trim() !== "") {
|
|
521
|
-
const envPath =
|
|
522
|
-
if (
|
|
523
|
-
|
|
1000
|
+
const envPath = normalizeMaybeQuotedPath(envPathRaw);
|
|
1001
|
+
if (existsPath(envPath)) return envPath;
|
|
1002
|
+
}
|
|
1003
|
+
const programFilesRoots = [
|
|
1004
|
+
process.env.ProgramW6432,
|
|
1005
|
+
process.env.ProgramFiles,
|
|
1006
|
+
process.env["ProgramFiles(x86)"]
|
|
1007
|
+
].filter((v) => typeof v === "string" && v.trim() !== "");
|
|
1008
|
+
const defaultCandidates = [];
|
|
1009
|
+
for (const root of programFilesRoots) {
|
|
1010
|
+
const base = join(normalizeMaybeQuotedPath(root), "Git");
|
|
1011
|
+
defaultCandidates.push(join(base, "bin", "bash.exe"));
|
|
1012
|
+
defaultCandidates.push(join(base, "usr", "bin", "bash.exe"));
|
|
524
1013
|
}
|
|
1014
|
+
const fromDefault = firstExisting(defaultCandidates);
|
|
1015
|
+
if (fromDefault) return fromDefault;
|
|
525
1016
|
try {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
for (const gitPathRaw of gitCandidates) {
|
|
529
|
-
const gitPath = normalize(gitPathRaw.replace(/^"|"$/g, ""));
|
|
1017
|
+
const gitCandidates = [...tryFromPath(["git.exe", "git.cmd", "git.bat"]), ...tryWhere("git")];
|
|
1018
|
+
for (const gitPath of gitCandidates) {
|
|
530
1019
|
if (!gitPath) continue;
|
|
531
1020
|
const gitDir = dirname(gitPath);
|
|
532
1021
|
const gitDirLower = gitDir.toLowerCase();
|
|
@@ -548,19 +1037,38 @@ function findGitBash() {
|
|
|
548
1037
|
bashCandidates.push(join(root, "mingw64", "bin", "bash.exe"));
|
|
549
1038
|
}
|
|
550
1039
|
for (const bashPath of bashCandidates) {
|
|
551
|
-
const
|
|
552
|
-
if (
|
|
1040
|
+
const normalizedPath = normalize(bashPath);
|
|
1041
|
+
if (existsPath(normalizedPath)) return normalizedPath;
|
|
553
1042
|
}
|
|
554
1043
|
}
|
|
555
1044
|
} catch {
|
|
556
1045
|
}
|
|
1046
|
+
const fromWhereBash = firstExisting(
|
|
1047
|
+
[...tryFromPath(["bash.exe"]), ...tryWhere("bash")].filter(
|
|
1048
|
+
(p) => p.toLowerCase().endsWith("\\bash.exe") && !isWindowsSystemBash(p)
|
|
1049
|
+
)
|
|
1050
|
+
);
|
|
1051
|
+
if (fromWhereBash) return fromWhereBash;
|
|
557
1052
|
return null;
|
|
558
1053
|
}
|
|
559
1054
|
function checkWindowsBashAvailability() {
|
|
560
1055
|
if (!isWindows()) return;
|
|
1056
|
+
const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
|
|
1057
|
+
const envPath = envPathRaw && envPathRaw.trim() !== "" ? normalizeMaybeQuotedPath(envPathRaw) : null;
|
|
1058
|
+
const envValid = !!(envPath && existsPath(envPath));
|
|
561
1059
|
const bashPath = findGitBash();
|
|
562
1060
|
if (bashPath) {
|
|
563
|
-
|
|
1061
|
+
if (!envValid) {
|
|
1062
|
+
process.env.CLAUDE_CODE_GIT_BASH_PATH = bashPath;
|
|
1063
|
+
if (envPathRaw && envPathRaw.trim() !== "") {
|
|
1064
|
+
console.error(
|
|
1065
|
+
`[windows] WARNING: CLAUDE_CODE_GIT_BASH_PATH is set to "${envPathRaw}" but the file does not exist.`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
console.error(`[windows] Git Bash detected: ${bashPath} (set CLAUDE_CODE_GIT_BASH_PATH)`);
|
|
1069
|
+
} else {
|
|
1070
|
+
console.error(`[windows] Git Bash detected: ${bashPath}`);
|
|
1071
|
+
}
|
|
564
1072
|
return;
|
|
565
1073
|
}
|
|
566
1074
|
const hint = process.env.CLAUDE_CODE_GIT_BASH_PATH ? `CLAUDE_CODE_GIT_BASH_PATH is set to "${process.env.CLAUDE_CODE_GIT_BASH_PATH}" but the file does not exist.` : "CLAUDE_CODE_GIT_BASH_PATH is not set and git was not found in PATH.";
|
|
@@ -577,15 +1085,32 @@ function checkWindowsBashAvailability() {
|
|
|
577
1085
|
var WINDOWS_BASH_HINT = '\n\n[Windows] The Claude Code CLI requires Git Bash. Set CLAUDE_CODE_GIT_BASH_PATH in your MCP server config or system environment. See README.md "Windows Support" section for details.';
|
|
578
1086
|
function enhanceWindowsError(errorMessage) {
|
|
579
1087
|
if (!isWindows()) return errorMessage;
|
|
580
|
-
|
|
1088
|
+
const lower = errorMessage.toLowerCase();
|
|
1089
|
+
const looksLikeMissingBash = lower.includes("enoent") && (lower.includes("bash") || lower.includes("git-bash")) || lower.includes("claude code on windows requires git-bash");
|
|
1090
|
+
if (looksLikeMissingBash || lower.includes("claude_code_git_bash_path")) {
|
|
581
1091
|
return errorMessage + WINDOWS_BASH_HINT;
|
|
582
1092
|
}
|
|
583
1093
|
return errorMessage;
|
|
584
1094
|
}
|
|
585
1095
|
|
|
1096
|
+
// src/utils/permission-updated-input.ts
|
|
1097
|
+
function normalizePermissionUpdatedInput(input) {
|
|
1098
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
1099
|
+
return input;
|
|
1100
|
+
}
|
|
1101
|
+
return { input };
|
|
1102
|
+
}
|
|
1103
|
+
|
|
586
1104
|
// src/tools/query-consumer.ts
|
|
587
1105
|
var MAX_TRANSIENT_RETRIES = 3;
|
|
588
1106
|
var INITIAL_RETRY_DELAY_MS = 1e3;
|
|
1107
|
+
var DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS = 6e4;
|
|
1108
|
+
var MAX_PERMISSION_REQUEST_TIMEOUT_MS = 5 * 6e4;
|
|
1109
|
+
var MAX_PREINIT_BUFFER_MESSAGES = 200;
|
|
1110
|
+
function clampPermissionRequestTimeoutMs(ms) {
|
|
1111
|
+
if (!Number.isFinite(ms) || ms <= 0) return DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS;
|
|
1112
|
+
return Math.min(ms, MAX_PERMISSION_REQUEST_TIMEOUT_MS);
|
|
1113
|
+
}
|
|
589
1114
|
function classifyError(err, abortSignal) {
|
|
590
1115
|
if (abortSignal.aborted) return "abort";
|
|
591
1116
|
if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
|
|
@@ -609,13 +1134,21 @@ function describeTool(toolName, toolCache) {
|
|
|
609
1134
|
const found = tools?.find((t) => t.name === toolName);
|
|
610
1135
|
return found?.description;
|
|
611
1136
|
}
|
|
1137
|
+
function normalizePolicyToolNames(tools) {
|
|
1138
|
+
if (!Array.isArray(tools) || tools.length === 0) return [];
|
|
1139
|
+
return tools.filter((tool) => typeof tool === "string").map((tool) => tool.trim()).filter((tool) => tool !== "");
|
|
1140
|
+
}
|
|
612
1141
|
function sdkResultToAgentResult(result) {
|
|
1142
|
+
const sessionTotalTurns = result.session_total_turns;
|
|
1143
|
+
const sessionTotalCostUsd = result.session_total_cost_usd;
|
|
613
1144
|
const base = {
|
|
614
1145
|
sessionId: result.session_id,
|
|
615
1146
|
durationMs: result.duration_ms,
|
|
616
1147
|
durationApiMs: result.duration_api_ms,
|
|
617
1148
|
numTurns: result.num_turns,
|
|
618
1149
|
totalCostUsd: result.total_cost_usd,
|
|
1150
|
+
sessionTotalTurns: typeof sessionTotalTurns === "number" ? sessionTotalTurns : void 0,
|
|
1151
|
+
sessionTotalCostUsd: typeof sessionTotalCostUsd === "number" ? sessionTotalCostUsd : void 0,
|
|
619
1152
|
stopReason: result.stop_reason,
|
|
620
1153
|
usage: result.usage,
|
|
621
1154
|
modelUsage: result.modelUsage,
|
|
@@ -727,15 +1260,46 @@ function consumeQuery(params) {
|
|
|
727
1260
|
return activeSessionId;
|
|
728
1261
|
};
|
|
729
1262
|
let initTimeoutId;
|
|
1263
|
+
const permissionRequestTimeoutMs = clampPermissionRequestTimeoutMs(
|
|
1264
|
+
params.permissionRequestTimeoutMs
|
|
1265
|
+
);
|
|
730
1266
|
const canUseTool = async (toolName, input, options2) => {
|
|
731
1267
|
const sessionId = await getSessionId();
|
|
1268
|
+
const normalizedToolName = toolName.trim();
|
|
732
1269
|
const normalizedInput = normalizeToolInput(toolName, input, params.platform);
|
|
1270
|
+
const unsupportedPath = findUnsupportedPosixPathInToolInput(normalizedInput, params.platform);
|
|
1271
|
+
if (unsupportedPath) {
|
|
1272
|
+
return {
|
|
1273
|
+
behavior: "deny",
|
|
1274
|
+
message: `Tool '${normalizedToolName || toolName}' requested unsupported POSIX path '${unsupportedPath}' on Windows.`,
|
|
1275
|
+
interrupt: false
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
if (typeof options2.blockedPath === "string" && isUnsupportedPosixAbsolutePath(options2.blockedPath, params.platform)) {
|
|
1279
|
+
return {
|
|
1280
|
+
behavior: "deny",
|
|
1281
|
+
message: `Tool '${normalizedToolName || toolName}' requested blocked path '${options2.blockedPath}' that is unsupported on Windows.`,
|
|
1282
|
+
interrupt: false
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
733
1285
|
const sessionInfo = params.sessionManager.get(sessionId);
|
|
734
1286
|
if (sessionInfo) {
|
|
735
|
-
|
|
736
|
-
|
|
1287
|
+
const disallowedTools = normalizePolicyToolNames(sessionInfo.disallowedTools);
|
|
1288
|
+
const allowedTools = normalizePolicyToolNames(sessionInfo.allowedTools);
|
|
1289
|
+
if (normalizedToolName !== "" && disallowedTools.includes(normalizedToolName)) {
|
|
1290
|
+
return {
|
|
1291
|
+
behavior: "deny",
|
|
1292
|
+
message: `Tool '${normalizedToolName}' is disallowed by session policy.`
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
if (sessionInfo.strictAllowedTools === true && normalizedToolName !== "" && allowedTools.length > 0 && !allowedTools.includes(normalizedToolName)) {
|
|
1296
|
+
return {
|
|
1297
|
+
behavior: "deny",
|
|
1298
|
+
message: `Tool '${normalizedToolName}' is not in allowedTools under strictAllowedTools policy.`,
|
|
1299
|
+
interrupt: false
|
|
1300
|
+
};
|
|
737
1301
|
}
|
|
738
|
-
if (!options2.blockedPath &&
|
|
1302
|
+
if (!options2.blockedPath && normalizedToolName !== "" && allowedTools.includes(normalizedToolName)) {
|
|
739
1303
|
return {
|
|
740
1304
|
behavior: "allow",
|
|
741
1305
|
updatedInput: normalizePermissionUpdatedInput(normalizedInput)
|
|
@@ -744,7 +1308,7 @@ function consumeQuery(params) {
|
|
|
744
1308
|
}
|
|
745
1309
|
const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
|
746
1310
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
747
|
-
const timeoutMs =
|
|
1311
|
+
const timeoutMs = permissionRequestTimeoutMs;
|
|
748
1312
|
const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
749
1313
|
const record = {
|
|
750
1314
|
requestId,
|
|
@@ -781,10 +1345,14 @@ function consumeQuery(params) {
|
|
|
781
1345
|
sessionId,
|
|
782
1346
|
record,
|
|
783
1347
|
finish,
|
|
784
|
-
|
|
1348
|
+
permissionRequestTimeoutMs
|
|
785
1349
|
);
|
|
786
1350
|
if (!registered) {
|
|
787
|
-
finish({
|
|
1351
|
+
finish({
|
|
1352
|
+
behavior: "deny",
|
|
1353
|
+
message: "Session no longer exists.",
|
|
1354
|
+
interrupt: true
|
|
1355
|
+
});
|
|
788
1356
|
return;
|
|
789
1357
|
}
|
|
790
1358
|
options2.signal.addEventListener("abort", abortListener, { once: true });
|
|
@@ -819,6 +1387,7 @@ function consumeQuery(params) {
|
|
|
819
1387
|
};
|
|
820
1388
|
const done = (async () => {
|
|
821
1389
|
const preInit = [];
|
|
1390
|
+
let preInitDropped = 0;
|
|
822
1391
|
if (shouldWaitForInit) {
|
|
823
1392
|
initTimeoutId = setTimeout(() => {
|
|
824
1393
|
close();
|
|
@@ -843,6 +1412,13 @@ function consumeQuery(params) {
|
|
|
843
1412
|
sessionIdResolved = true;
|
|
844
1413
|
resolveSessionId(activeSessionId);
|
|
845
1414
|
if (initTimeoutId) clearTimeout(initTimeoutId);
|
|
1415
|
+
if (preInitDropped > 0) {
|
|
1416
|
+
params.sessionManager.pushEvent(activeSessionId, {
|
|
1417
|
+
type: "progress",
|
|
1418
|
+
data: { type: "pre_init_dropped", dropped: preInitDropped },
|
|
1419
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
846
1422
|
for (const buffered of preInit) {
|
|
847
1423
|
const event2 = messageToEvent(buffered);
|
|
848
1424
|
if (!event2) continue;
|
|
@@ -858,36 +1434,53 @@ function consumeQuery(params) {
|
|
|
858
1434
|
}
|
|
859
1435
|
if (shouldWaitForInit && !sessionIdResolved) {
|
|
860
1436
|
preInit.push(message);
|
|
1437
|
+
if (preInit.length > MAX_PREINIT_BUFFER_MESSAGES) {
|
|
1438
|
+
preInit.shift();
|
|
1439
|
+
preInitDropped++;
|
|
1440
|
+
}
|
|
861
1441
|
continue;
|
|
862
1442
|
}
|
|
863
1443
|
if (message.type === "result") {
|
|
864
1444
|
const sessionId2 = message.session_id ?? await getSessionId();
|
|
865
1445
|
const agentResult = sdkResultToAgentResult(message);
|
|
1446
|
+
const current = params.sessionManager.get(sessionId2);
|
|
1447
|
+
const previousTotalTurns = current?.totalTurns ?? 0;
|
|
1448
|
+
const previousTotalCostUsd = current?.totalCostUsd ?? 0;
|
|
1449
|
+
const computedTotalTurns = previousTotalTurns + agentResult.numTurns;
|
|
1450
|
+
const computedTotalCostUsd = previousTotalCostUsd + agentResult.totalCostUsd;
|
|
1451
|
+
const sessionTotalTurns = typeof agentResult.sessionTotalTurns === "number" ? Math.max(agentResult.sessionTotalTurns, computedTotalTurns) : computedTotalTurns;
|
|
1452
|
+
const sessionTotalCostUsd = typeof agentResult.sessionTotalCostUsd === "number" ? Math.max(agentResult.sessionTotalCostUsd, computedTotalCostUsd) : computedTotalCostUsd;
|
|
1453
|
+
const resultWithSessionTotals = {
|
|
1454
|
+
...agentResult,
|
|
1455
|
+
sessionTotalTurns,
|
|
1456
|
+
sessionTotalCostUsd
|
|
1457
|
+
};
|
|
866
1458
|
const stored = {
|
|
867
1459
|
type: agentResult.isError ? "error" : "result",
|
|
868
|
-
result:
|
|
1460
|
+
result: resultWithSessionTotals,
|
|
869
1461
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
870
1462
|
};
|
|
871
1463
|
params.sessionManager.setResult(sessionId2, stored);
|
|
872
1464
|
params.sessionManager.clearTerminalEvents(sessionId2);
|
|
873
1465
|
params.sessionManager.pushEvent(sessionId2, {
|
|
874
1466
|
type: agentResult.isError ? "error" : "result",
|
|
875
|
-
data:
|
|
1467
|
+
data: resultWithSessionTotals,
|
|
876
1468
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
877
1469
|
});
|
|
878
|
-
const current = params.sessionManager.get(sessionId2);
|
|
879
1470
|
if (current && current.status !== "cancelled") {
|
|
880
1471
|
params.sessionManager.update(sessionId2, {
|
|
881
1472
|
status: agentResult.isError ? "error" : "idle",
|
|
882
|
-
totalTurns:
|
|
883
|
-
totalCostUsd:
|
|
884
|
-
abortController: void 0
|
|
1473
|
+
totalTurns: sessionTotalTurns,
|
|
1474
|
+
totalCostUsd: sessionTotalCostUsd,
|
|
1475
|
+
abortController: void 0,
|
|
1476
|
+
queryInterrupt: void 0
|
|
885
1477
|
});
|
|
886
1478
|
} else if (current) {
|
|
887
1479
|
params.sessionManager.update(sessionId2, {
|
|
888
|
-
totalTurns:
|
|
889
|
-
totalCostUsd:
|
|
890
|
-
abortController: void 0
|
|
1480
|
+
totalTurns: sessionTotalTurns,
|
|
1481
|
+
totalCostUsd: sessionTotalCostUsd,
|
|
1482
|
+
abortController: void 0,
|
|
1483
|
+
queryInterrupt: void 0
|
|
891
1484
|
});
|
|
892
1485
|
}
|
|
893
1486
|
return;
|
|
@@ -939,7 +1532,8 @@ function consumeQuery(params) {
|
|
|
939
1532
|
});
|
|
940
1533
|
params.sessionManager.update(sessionId, {
|
|
941
1534
|
status: "error",
|
|
942
|
-
abortController: void 0
|
|
1535
|
+
abortController: void 0,
|
|
1536
|
+
queryInterrupt: void 0
|
|
943
1537
|
});
|
|
944
1538
|
}
|
|
945
1539
|
}
|
|
@@ -1022,7 +1616,11 @@ function consumeQuery(params) {
|
|
|
1022
1616
|
data: agentResult,
|
|
1023
1617
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1024
1618
|
});
|
|
1025
|
-
params.sessionManager.update(sessionId, {
|
|
1619
|
+
params.sessionManager.update(sessionId, {
|
|
1620
|
+
status: "error",
|
|
1621
|
+
abortController: void 0,
|
|
1622
|
+
queryInterrupt: void 0
|
|
1623
|
+
});
|
|
1026
1624
|
}
|
|
1027
1625
|
return;
|
|
1028
1626
|
} finally {
|
|
@@ -1034,16 +1632,33 @@ function consumeQuery(params) {
|
|
|
1034
1632
|
}
|
|
1035
1633
|
|
|
1036
1634
|
// src/utils/resume-token.ts
|
|
1037
|
-
import { createHmac } from "crypto";
|
|
1635
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
1636
|
+
function getResumeSecrets() {
|
|
1637
|
+
const raw = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
|
|
1638
|
+
if (typeof raw !== "string") return [];
|
|
1639
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1640
|
+
}
|
|
1038
1641
|
function getResumeSecret() {
|
|
1039
|
-
|
|
1040
|
-
if (typeof secret !== "string") return void 0;
|
|
1041
|
-
const trimmed = secret.trim();
|
|
1042
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1642
|
+
return getResumeSecrets()[0];
|
|
1043
1643
|
}
|
|
1044
1644
|
function computeResumeToken(sessionId, secret) {
|
|
1045
1645
|
return createHmac("sha256", secret).update(sessionId).digest("base64url");
|
|
1046
1646
|
}
|
|
1647
|
+
function timingSafeEqualText(a, b) {
|
|
1648
|
+
const left = Buffer.from(a, "utf8");
|
|
1649
|
+
const rightRaw = Buffer.from(b, "utf8");
|
|
1650
|
+
const right = Buffer.alloc(left.length);
|
|
1651
|
+
rightRaw.copy(right, 0, 0, left.length);
|
|
1652
|
+
const sameLength = rightRaw.length === left.length;
|
|
1653
|
+
return timingSafeEqual(left, right) && sameLength;
|
|
1654
|
+
}
|
|
1655
|
+
function isValidResumeToken(sessionId, token, secrets) {
|
|
1656
|
+
for (const secret of secrets) {
|
|
1657
|
+
const computed = computeResumeToken(sessionId, secret);
|
|
1658
|
+
if (timingSafeEqualText(computed, token)) return true;
|
|
1659
|
+
}
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1047
1662
|
|
|
1048
1663
|
// src/utils/race-with-abort.ts
|
|
1049
1664
|
function raceWithAbort(promise, signal, onAbort) {
|
|
@@ -1070,7 +1685,7 @@ function raceWithAbort(promise, signal, onAbort) {
|
|
|
1070
1685
|
|
|
1071
1686
|
// src/utils/build-options.ts
|
|
1072
1687
|
function buildOptions(src) {
|
|
1073
|
-
const opts = { cwd: src.cwd };
|
|
1688
|
+
const opts = { cwd: normalizeWindowsPathLike(src.cwd) };
|
|
1074
1689
|
if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
|
|
1075
1690
|
if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
|
|
1076
1691
|
if (src.tools !== void 0) opts.tools = src.tools;
|
|
@@ -1082,13 +1697,13 @@ function buildOptions(src) {
|
|
|
1082
1697
|
if (src.effort !== void 0) opts.effort = src.effort;
|
|
1083
1698
|
if (src.betas !== void 0) opts.betas = src.betas;
|
|
1084
1699
|
if (src.additionalDirectories !== void 0)
|
|
1085
|
-
opts.additionalDirectories = src.additionalDirectories;
|
|
1700
|
+
opts.additionalDirectories = normalizeWindowsPathArray(src.additionalDirectories);
|
|
1086
1701
|
if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
|
|
1087
1702
|
if (src.thinking !== void 0) opts.thinking = src.thinking;
|
|
1088
1703
|
if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
|
|
1089
1704
|
if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
|
|
1090
1705
|
if (src.pathToClaudeCodeExecutable !== void 0)
|
|
1091
|
-
opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
|
|
1706
|
+
opts.pathToClaudeCodeExecutable = normalizeWindowsPathLike(src.pathToClaudeCodeExecutable);
|
|
1092
1707
|
if (src.agent !== void 0) opts.agent = src.agent;
|
|
1093
1708
|
if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
|
|
1094
1709
|
if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
|
|
@@ -1101,14 +1716,54 @@ function buildOptions(src) {
|
|
|
1101
1716
|
if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
|
|
1102
1717
|
else opts.settingSources = DEFAULT_SETTING_SOURCES;
|
|
1103
1718
|
if (src.debug !== void 0) opts.debug = src.debug;
|
|
1104
|
-
if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
|
|
1719
|
+
if (src.debugFile !== void 0) opts.debugFile = normalizeWindowsPathLike(src.debugFile);
|
|
1105
1720
|
if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
|
|
1106
1721
|
return opts;
|
|
1107
1722
|
}
|
|
1108
1723
|
|
|
1724
|
+
// src/utils/session-create.ts
|
|
1725
|
+
function toSessionCreateParams(input) {
|
|
1726
|
+
const src = input.source;
|
|
1727
|
+
return {
|
|
1728
|
+
sessionId: input.sessionId,
|
|
1729
|
+
cwd: src.cwd,
|
|
1730
|
+
model: src.model,
|
|
1731
|
+
permissionMode: input.permissionMode,
|
|
1732
|
+
allowedTools: src.allowedTools,
|
|
1733
|
+
disallowedTools: src.disallowedTools,
|
|
1734
|
+
strictAllowedTools: src.strictAllowedTools,
|
|
1735
|
+
tools: src.tools,
|
|
1736
|
+
maxTurns: src.maxTurns,
|
|
1737
|
+
systemPrompt: src.systemPrompt,
|
|
1738
|
+
agents: src.agents,
|
|
1739
|
+
maxBudgetUsd: src.maxBudgetUsd,
|
|
1740
|
+
effort: src.effort,
|
|
1741
|
+
betas: src.betas,
|
|
1742
|
+
additionalDirectories: src.additionalDirectories,
|
|
1743
|
+
outputFormat: src.outputFormat,
|
|
1744
|
+
thinking: src.thinking,
|
|
1745
|
+
persistSession: src.persistSession,
|
|
1746
|
+
pathToClaudeCodeExecutable: src.pathToClaudeCodeExecutable,
|
|
1747
|
+
agent: src.agent,
|
|
1748
|
+
mcpServers: src.mcpServers,
|
|
1749
|
+
sandbox: src.sandbox,
|
|
1750
|
+
fallbackModel: src.fallbackModel,
|
|
1751
|
+
enableFileCheckpointing: src.enableFileCheckpointing,
|
|
1752
|
+
includePartialMessages: src.includePartialMessages,
|
|
1753
|
+
strictMcpConfig: src.strictMcpConfig,
|
|
1754
|
+
settingSources: src.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1755
|
+
debug: src.debug,
|
|
1756
|
+
debugFile: src.debugFile,
|
|
1757
|
+
env: src.env,
|
|
1758
|
+
abortController: input.abortController,
|
|
1759
|
+
queryInterrupt: input.queryInterrupt
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1109
1763
|
// src/tools/claude-code.ts
|
|
1110
1764
|
async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
|
|
1111
|
-
const
|
|
1765
|
+
const cwdProvided = input.cwd !== void 0;
|
|
1766
|
+
const cwd = cwdProvided ? input.cwd : serverCwd;
|
|
1112
1767
|
if (typeof cwd !== "string" || cwd.trim() === "") {
|
|
1113
1768
|
return {
|
|
1114
1769
|
sessionId: "",
|
|
@@ -1116,63 +1771,96 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1116
1771
|
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
|
|
1117
1772
|
};
|
|
1118
1773
|
}
|
|
1774
|
+
const normalizedCwd = normalizeWindowsPathLike(cwd);
|
|
1775
|
+
if (cwdProvided && !existsSync2(normalizedCwd)) {
|
|
1776
|
+
return {
|
|
1777
|
+
sessionId: "",
|
|
1778
|
+
status: "error",
|
|
1779
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd path does not exist: ${normalizedCwd}`
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
if (cwdProvided) {
|
|
1783
|
+
try {
|
|
1784
|
+
if (!statSync(normalizedCwd).isDirectory()) {
|
|
1785
|
+
return {
|
|
1786
|
+
sessionId: "",
|
|
1787
|
+
status: "error",
|
|
1788
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a directory: ${normalizedCwd}`
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
} catch (err) {
|
|
1792
|
+
const detail = err instanceof Error ? ` (${err.message})` : "";
|
|
1793
|
+
return {
|
|
1794
|
+
sessionId: "",
|
|
1795
|
+
status: "error",
|
|
1796
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd is not accessible: ${normalizedCwd}${detail}`
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (!sessionManager.hasCapacityFor(1)) {
|
|
1801
|
+
return {
|
|
1802
|
+
sessionId: "",
|
|
1803
|
+
status: "error",
|
|
1804
|
+
error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1119
1807
|
const abortController = new AbortController();
|
|
1120
1808
|
const adv = input.advanced ?? {};
|
|
1121
1809
|
const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
|
|
1122
|
-
const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
|
|
1810
|
+
const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? input.sessionInitTimeoutMs ?? 1e4;
|
|
1811
|
+
const compatWarnings = [];
|
|
1812
|
+
if (input.sessionInitTimeoutMs !== void 0) {
|
|
1813
|
+
compatWarnings.push(
|
|
1814
|
+
"Top-level sessionInitTimeoutMs for claude_code is a compatibility alias; prefer advanced.sessionInitTimeoutMs."
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
if (input.sessionInitTimeoutMs !== void 0 && adv.sessionInitTimeoutMs !== void 0 && input.sessionInitTimeoutMs !== adv.sessionInitTimeoutMs) {
|
|
1818
|
+
compatWarnings.push(
|
|
1819
|
+
`Both advanced.sessionInitTimeoutMs (${adv.sessionInitTimeoutMs}) and top-level sessionInitTimeoutMs (${input.sessionInitTimeoutMs}) were provided; using advanced.sessionInitTimeoutMs.`
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1123
1822
|
const flat = {
|
|
1124
|
-
cwd,
|
|
1823
|
+
cwd: normalizedCwd,
|
|
1125
1824
|
allowedTools: input.allowedTools,
|
|
1126
1825
|
disallowedTools: input.disallowedTools,
|
|
1826
|
+
strictAllowedTools: input.strictAllowedTools ?? adv.strictAllowedTools,
|
|
1127
1827
|
maxTurns: input.maxTurns,
|
|
1128
1828
|
model: input.model,
|
|
1129
1829
|
systemPrompt: input.systemPrompt,
|
|
1130
|
-
...adv
|
|
1830
|
+
...adv,
|
|
1831
|
+
effort: input.effort ?? adv.effort,
|
|
1832
|
+
thinking: input.thinking ?? adv.thinking
|
|
1833
|
+
};
|
|
1834
|
+
const normalizedFlat = {
|
|
1835
|
+
...flat,
|
|
1836
|
+
cwd: normalizeWindowsPathLike(flat.cwd),
|
|
1837
|
+
additionalDirectories: flat.additionalDirectories !== void 0 ? normalizeWindowsPathArray(flat.additionalDirectories) : void 0,
|
|
1838
|
+
debugFile: flat.debugFile !== void 0 ? normalizeWindowsPathLike(flat.debugFile) : void 0,
|
|
1839
|
+
pathToClaudeCodeExecutable: flat.pathToClaudeCodeExecutable !== void 0 ? normalizeWindowsPathLike(flat.pathToClaudeCodeExecutable) : void 0
|
|
1131
1840
|
};
|
|
1132
1841
|
try {
|
|
1133
1842
|
const handle = consumeQuery({
|
|
1134
1843
|
mode: "start",
|
|
1135
1844
|
prompt: input.prompt,
|
|
1136
1845
|
abortController,
|
|
1137
|
-
options: buildOptions(
|
|
1846
|
+
options: buildOptions(normalizedFlat),
|
|
1138
1847
|
permissionRequestTimeoutMs,
|
|
1139
1848
|
sessionInitTimeoutMs,
|
|
1140
1849
|
sessionManager,
|
|
1141
1850
|
toolCache,
|
|
1142
1851
|
onInit: (init) => {
|
|
1143
1852
|
if (sessionManager.get(init.session_id)) return;
|
|
1144
|
-
sessionManager.create(
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
maxBudgetUsd: adv.maxBudgetUsd,
|
|
1156
|
-
effort: adv.effort,
|
|
1157
|
-
betas: adv.betas,
|
|
1158
|
-
additionalDirectories: adv.additionalDirectories,
|
|
1159
|
-
outputFormat: adv.outputFormat,
|
|
1160
|
-
thinking: adv.thinking,
|
|
1161
|
-
persistSession: adv.persistSession,
|
|
1162
|
-
pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
|
|
1163
|
-
agent: adv.agent,
|
|
1164
|
-
mcpServers: adv.mcpServers,
|
|
1165
|
-
sandbox: adv.sandbox,
|
|
1166
|
-
fallbackModel: adv.fallbackModel,
|
|
1167
|
-
enableFileCheckpointing: adv.enableFileCheckpointing,
|
|
1168
|
-
includePartialMessages: adv.includePartialMessages,
|
|
1169
|
-
strictMcpConfig: adv.strictMcpConfig,
|
|
1170
|
-
settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1171
|
-
debug: adv.debug,
|
|
1172
|
-
debugFile: adv.debugFile,
|
|
1173
|
-
env: adv.env,
|
|
1174
|
-
abortController
|
|
1175
|
-
});
|
|
1853
|
+
sessionManager.create(
|
|
1854
|
+
toSessionCreateParams({
|
|
1855
|
+
sessionId: init.session_id,
|
|
1856
|
+
source: normalizedFlat,
|
|
1857
|
+
permissionMode: "default",
|
|
1858
|
+
abortController,
|
|
1859
|
+
queryInterrupt: () => {
|
|
1860
|
+
handle.interrupt();
|
|
1861
|
+
}
|
|
1862
|
+
})
|
|
1863
|
+
);
|
|
1176
1864
|
}
|
|
1177
1865
|
});
|
|
1178
1866
|
const sessionId = await raceWithAbort(
|
|
@@ -1185,7 +1873,8 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1185
1873
|
sessionId,
|
|
1186
1874
|
status: "running",
|
|
1187
1875
|
pollInterval: 3e3,
|
|
1188
|
-
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
|
|
1876
|
+
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
|
|
1877
|
+
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
|
|
1189
1878
|
};
|
|
1190
1879
|
} catch (err) {
|
|
1191
1880
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1198,9 +1887,45 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1198
1887
|
}
|
|
1199
1888
|
|
|
1200
1889
|
// src/tools/claude-code-reply.ts
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1890
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
1891
|
+
import os2 from "os";
|
|
1892
|
+
import path3 from "path";
|
|
1893
|
+
function normalizeAndAssertCwd(cwd, contextLabel) {
|
|
1894
|
+
const normalizedCwd = normalizeWindowsPathLike(cwd);
|
|
1895
|
+
const resolvedCwd = resolvePortableTmpAlias(normalizedCwd);
|
|
1896
|
+
if (!existsSync3(resolvedCwd)) {
|
|
1897
|
+
throw new Error(
|
|
1898
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} path does not exist: ${resolvedCwd}`
|
|
1899
|
+
);
|
|
1900
|
+
}
|
|
1901
|
+
try {
|
|
1902
|
+
const stat = statSync2(resolvedCwd);
|
|
1903
|
+
if (!stat.isDirectory()) {
|
|
1904
|
+
throw new Error(
|
|
1905
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} must be a directory: ${resolvedCwd}`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
} catch (err) {
|
|
1909
|
+
if (err instanceof Error && err.message.includes("Error [")) throw err;
|
|
1910
|
+
const detail = err instanceof Error ? ` (${err.message})` : "";
|
|
1911
|
+
throw new Error(
|
|
1912
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} is not accessible: ${resolvedCwd}${detail}`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
return resolvedCwd;
|
|
1916
|
+
}
|
|
1917
|
+
function resolvePortableTmpAlias(cwd) {
|
|
1918
|
+
if (process.platform !== "win32") return cwd;
|
|
1919
|
+
const normalized = cwd.replace(/\\/g, "/");
|
|
1920
|
+
if (normalized === "/tmp") return os2.tmpdir();
|
|
1921
|
+
if (normalized.startsWith("/tmp/")) {
|
|
1922
|
+
return path3.join(os2.tmpdir(), normalized.slice("/tmp/".length));
|
|
1923
|
+
}
|
|
1924
|
+
return cwd;
|
|
1925
|
+
}
|
|
1926
|
+
function toStartError(sessionId, err) {
|
|
1927
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1928
|
+
const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
|
|
1204
1929
|
return {
|
|
1205
1930
|
agentResult: {
|
|
1206
1931
|
sessionId,
|
|
@@ -1217,13 +1942,24 @@ function buildOptionsFromDiskResume(dr) {
|
|
|
1217
1942
|
if (dr.cwd === void 0 || typeof dr.cwd !== "string" || dr.cwd.trim() === "") {
|
|
1218
1943
|
throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be provided for disk resume.`);
|
|
1219
1944
|
}
|
|
1220
|
-
|
|
1945
|
+
const normalizedCwd = normalizeAndAssertCwd(dr.cwd, "disk resume cwd");
|
|
1946
|
+
return buildOptions({
|
|
1947
|
+
...dr,
|
|
1948
|
+
cwd: normalizedCwd
|
|
1949
|
+
});
|
|
1221
1950
|
}
|
|
1222
1951
|
async function executeClaudeCodeReply(input, sessionManager, toolCache, requestSignal) {
|
|
1223
1952
|
const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
|
|
1224
1953
|
const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
|
|
1225
1954
|
const existing = sessionManager.get(input.sessionId);
|
|
1226
1955
|
if (!existing) {
|
|
1956
|
+
if (!sessionManager.hasCapacityFor(1)) {
|
|
1957
|
+
return {
|
|
1958
|
+
sessionId: input.sessionId,
|
|
1959
|
+
status: "error",
|
|
1960
|
+
error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1227
1963
|
const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
|
|
1228
1964
|
if (!allowDiskResume) {
|
|
1229
1965
|
return {
|
|
@@ -1232,7 +1968,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1232
1968
|
error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
|
|
1233
1969
|
};
|
|
1234
1970
|
}
|
|
1235
|
-
const
|
|
1971
|
+
const resumeSecrets = getResumeSecrets();
|
|
1972
|
+
const resumeSecret = resumeSecrets[0];
|
|
1236
1973
|
if (!resumeSecret) {
|
|
1237
1974
|
return {
|
|
1238
1975
|
sessionId: input.sessionId,
|
|
@@ -1248,8 +1985,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1248
1985
|
error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
|
|
1249
1986
|
};
|
|
1250
1987
|
}
|
|
1251
|
-
|
|
1252
|
-
if (dr.resumeToken !== expectedToken) {
|
|
1988
|
+
if (!isValidResumeToken(input.sessionId, dr.resumeToken, resumeSecrets)) {
|
|
1253
1989
|
return {
|
|
1254
1990
|
sessionId: input.sessionId,
|
|
1255
1991
|
status: "error",
|
|
@@ -1259,40 +1995,29 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1259
1995
|
try {
|
|
1260
1996
|
const abortController2 = new AbortController();
|
|
1261
1997
|
const options2 = buildOptionsFromDiskResume(dr);
|
|
1262
|
-
|
|
1263
|
-
|
|
1998
|
+
if (input.effort !== void 0) options2.effort = input.effort;
|
|
1999
|
+
if (input.thinking !== void 0) options2.thinking = input.thinking;
|
|
2000
|
+
const { resumeToken: _resumeToken, ...rest } = dr;
|
|
2001
|
+
void _resumeToken;
|
|
2002
|
+
const source = {
|
|
2003
|
+
...rest,
|
|
1264
2004
|
cwd: options2.cwd ?? dr.cwd ?? "",
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
persistSession: dr.persistSession,
|
|
1280
|
-
pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
|
|
1281
|
-
agent: dr.agent,
|
|
1282
|
-
mcpServers: dr.mcpServers,
|
|
1283
|
-
sandbox: dr.sandbox,
|
|
1284
|
-
fallbackModel: dr.fallbackModel,
|
|
1285
|
-
enableFileCheckpointing: dr.enableFileCheckpointing,
|
|
1286
|
-
includePartialMessages: dr.includePartialMessages,
|
|
1287
|
-
strictMcpConfig: dr.strictMcpConfig,
|
|
1288
|
-
settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1289
|
-
debug: dr.debug,
|
|
1290
|
-
debugFile: dr.debugFile,
|
|
1291
|
-
env: dr.env,
|
|
1292
|
-
abortController: abortController2
|
|
1293
|
-
});
|
|
2005
|
+
additionalDirectories: options2.additionalDirectories ?? rest.additionalDirectories,
|
|
2006
|
+
debugFile: options2.debugFile ?? rest.debugFile,
|
|
2007
|
+
pathToClaudeCodeExecutable: options2.pathToClaudeCodeExecutable ?? rest.pathToClaudeCodeExecutable,
|
|
2008
|
+
effort: input.effort ?? rest.effort,
|
|
2009
|
+
thinking: input.thinking ?? rest.thinking
|
|
2010
|
+
};
|
|
2011
|
+
sessionManager.create(
|
|
2012
|
+
toSessionCreateParams({
|
|
2013
|
+
sessionId: input.sessionId,
|
|
2014
|
+
source,
|
|
2015
|
+
permissionMode: "default",
|
|
2016
|
+
abortController: abortController2
|
|
2017
|
+
})
|
|
2018
|
+
);
|
|
1294
2019
|
try {
|
|
1295
|
-
consumeQuery({
|
|
2020
|
+
const handle = consumeQuery({
|
|
1296
2021
|
mode: "disk-resume",
|
|
1297
2022
|
sessionId: input.sessionId,
|
|
1298
2023
|
prompt: input.prompt,
|
|
@@ -1303,6 +2028,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1303
2028
|
sessionManager,
|
|
1304
2029
|
toolCache
|
|
1305
2030
|
});
|
|
2031
|
+
sessionManager.update(input.sessionId, {
|
|
2032
|
+
queryInterrupt: () => {
|
|
2033
|
+
handle.interrupt();
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
1306
2036
|
} catch (err) {
|
|
1307
2037
|
const { agentResult, errorText } = toStartError(input.sessionId, err);
|
|
1308
2038
|
sessionManager.setResult(input.sessionId, {
|
|
@@ -1315,7 +2045,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1315
2045
|
data: agentResult,
|
|
1316
2046
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1317
2047
|
});
|
|
1318
|
-
sessionManager.update(input.sessionId, {
|
|
2048
|
+
sessionManager.update(input.sessionId, {
|
|
2049
|
+
status: "error",
|
|
2050
|
+
abortController: void 0,
|
|
2051
|
+
queryInterrupt: void 0
|
|
2052
|
+
});
|
|
1319
2053
|
return { sessionId: input.sessionId, status: "error", error: errorText };
|
|
1320
2054
|
}
|
|
1321
2055
|
return {
|
|
@@ -1337,7 +2071,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1337
2071
|
data: agentResult,
|
|
1338
2072
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1339
2073
|
});
|
|
1340
|
-
sessionManager.update(input.sessionId, {
|
|
2074
|
+
sessionManager.update(input.sessionId, {
|
|
2075
|
+
status: "error",
|
|
2076
|
+
abortController: void 0,
|
|
2077
|
+
queryInterrupt: void 0
|
|
2078
|
+
});
|
|
1341
2079
|
}
|
|
1342
2080
|
return {
|
|
1343
2081
|
sessionId: input.sessionId,
|
|
@@ -1371,8 +2109,31 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1371
2109
|
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.`
|
|
1372
2110
|
};
|
|
1373
2111
|
}
|
|
1374
|
-
const
|
|
2112
|
+
const session = acquired;
|
|
2113
|
+
const normalizedCwd = normalizeAndAssertCwd(session.cwd, "session cwd");
|
|
2114
|
+
const options = buildOptions(session);
|
|
2115
|
+
options.cwd = normalizedCwd;
|
|
1375
2116
|
if (input.forkSession) options.forkSession = true;
|
|
2117
|
+
if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
|
|
2118
|
+
sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
|
|
2119
|
+
return {
|
|
2120
|
+
sessionId: input.sessionId,
|
|
2121
|
+
status: "error",
|
|
2122
|
+
error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
const sourceOverrides = {
|
|
2126
|
+
effort: input.effort ?? session.effort,
|
|
2127
|
+
thinking: input.thinking ?? session.thinking
|
|
2128
|
+
};
|
|
2129
|
+
if (input.effort !== void 0) options.effort = input.effort;
|
|
2130
|
+
if (input.thinking !== void 0) options.thinking = input.thinking;
|
|
2131
|
+
if (!input.forkSession && (input.effort !== void 0 || input.thinking !== void 0)) {
|
|
2132
|
+
const patch = {};
|
|
2133
|
+
if (input.effort !== void 0) patch.effort = input.effort;
|
|
2134
|
+
if (input.thinking !== void 0) patch.thinking = input.thinking;
|
|
2135
|
+
sessionManager.update(input.sessionId, patch);
|
|
2136
|
+
}
|
|
1376
2137
|
try {
|
|
1377
2138
|
const handle = consumeQuery({
|
|
1378
2139
|
mode: "resume",
|
|
@@ -1388,46 +2149,33 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1388
2149
|
onInit: (init) => {
|
|
1389
2150
|
if (!input.forkSession) return;
|
|
1390
2151
|
if (init.session_id === input.sessionId) return;
|
|
1391
|
-
if (!sessionManager.get(init.session_id)) {
|
|
1392
|
-
sessionManager.create({
|
|
1393
|
-
sessionId: init.session_id,
|
|
1394
|
-
cwd: existing.cwd,
|
|
1395
|
-
model: existing.model,
|
|
1396
|
-
permissionMode: "default",
|
|
1397
|
-
allowedTools: existing.allowedTools,
|
|
1398
|
-
disallowedTools: existing.disallowedTools,
|
|
1399
|
-
tools: existing.tools,
|
|
1400
|
-
maxTurns: existing.maxTurns,
|
|
1401
|
-
systemPrompt: existing.systemPrompt,
|
|
1402
|
-
agents: existing.agents,
|
|
1403
|
-
maxBudgetUsd: existing.maxBudgetUsd,
|
|
1404
|
-
effort: existing.effort,
|
|
1405
|
-
betas: existing.betas,
|
|
1406
|
-
additionalDirectories: existing.additionalDirectories,
|
|
1407
|
-
outputFormat: existing.outputFormat,
|
|
1408
|
-
thinking: existing.thinking,
|
|
1409
|
-
persistSession: existing.persistSession,
|
|
1410
|
-
pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
|
|
1411
|
-
agent: existing.agent,
|
|
1412
|
-
mcpServers: existing.mcpServers,
|
|
1413
|
-
sandbox: existing.sandbox,
|
|
1414
|
-
fallbackModel: existing.fallbackModel,
|
|
1415
|
-
enableFileCheckpointing: existing.enableFileCheckpointing,
|
|
1416
|
-
includePartialMessages: existing.includePartialMessages,
|
|
1417
|
-
strictMcpConfig: existing.strictMcpConfig,
|
|
1418
|
-
settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1419
|
-
debug: existing.debug,
|
|
1420
|
-
debugFile: existing.debugFile,
|
|
1421
|
-
env: existing.env,
|
|
1422
|
-
abortController
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
2152
|
sessionManager.update(input.sessionId, {
|
|
1426
2153
|
status: originalStatus,
|
|
1427
|
-
abortController: void 0
|
|
2154
|
+
abortController: void 0,
|
|
2155
|
+
queryInterrupt: void 0
|
|
1428
2156
|
});
|
|
2157
|
+
if (!sessionManager.get(init.session_id)) {
|
|
2158
|
+
sessionManager.create(
|
|
2159
|
+
toSessionCreateParams({
|
|
2160
|
+
sessionId: init.session_id,
|
|
2161
|
+
source: { ...session, ...sourceOverrides },
|
|
2162
|
+
permissionMode: "default",
|
|
2163
|
+
abortController,
|
|
2164
|
+
queryInterrupt: () => {
|
|
2165
|
+
handle.interrupt();
|
|
2166
|
+
}
|
|
2167
|
+
})
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
1429
2170
|
}
|
|
1430
2171
|
});
|
|
2172
|
+
if (!input.forkSession) {
|
|
2173
|
+
sessionManager.update(input.sessionId, {
|
|
2174
|
+
queryInterrupt: () => {
|
|
2175
|
+
handle.interrupt();
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
1431
2179
|
const sessionId = input.forkSession ? await raceWithAbort(
|
|
1432
2180
|
handle.sdkSessionIdPromise,
|
|
1433
2181
|
requestSignal,
|
|
@@ -1452,7 +2200,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1452
2200
|
if (input.forkSession) {
|
|
1453
2201
|
sessionManager.update(input.sessionId, {
|
|
1454
2202
|
status: originalStatus,
|
|
1455
|
-
abortController: void 0
|
|
2203
|
+
abortController: void 0,
|
|
2204
|
+
queryInterrupt: void 0
|
|
1456
2205
|
});
|
|
1457
2206
|
} else {
|
|
1458
2207
|
sessionManager.setResult(input.sessionId, {
|
|
@@ -1465,7 +2214,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1465
2214
|
data: agentResult,
|
|
1466
2215
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1467
2216
|
});
|
|
1468
|
-
sessionManager.update(input.sessionId, {
|
|
2217
|
+
sessionManager.update(input.sessionId, {
|
|
2218
|
+
status: "error",
|
|
2219
|
+
abortController: void 0,
|
|
2220
|
+
queryInterrupt: void 0
|
|
2221
|
+
});
|
|
1469
2222
|
}
|
|
1470
2223
|
return {
|
|
1471
2224
|
sessionId: input.sessionId,
|
|
@@ -1476,77 +2229,97 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1476
2229
|
}
|
|
1477
2230
|
|
|
1478
2231
|
// src/tools/tool-discovery.ts
|
|
2232
|
+
var DEFAULT_PERMISSION_MODEL = "policy_controlled";
|
|
2233
|
+
var DEFAULT_SCHEMA_AVAILABILITY = "none";
|
|
1479
2234
|
var TOOL_CATALOG = {
|
|
1480
2235
|
Bash: {
|
|
1481
|
-
description: "Run shell commands
|
|
1482
|
-
category: "execute"
|
|
2236
|
+
description: "Run shell commands",
|
|
2237
|
+
category: "execute",
|
|
2238
|
+
notes: ["Execution may require permission approval depending on session policy."]
|
|
1483
2239
|
},
|
|
1484
2240
|
Read: {
|
|
1485
|
-
description: "Read
|
|
1486
|
-
category: "file_read"
|
|
2241
|
+
description: "Read file contents (large files: use offset/limit or Grep)",
|
|
2242
|
+
category: "file_read",
|
|
2243
|
+
notes: ["Large reads are often capped by the backend; use offset/limit when needed."]
|
|
1487
2244
|
},
|
|
1488
2245
|
Write: {
|
|
1489
|
-
description: "Create
|
|
2246
|
+
description: "Create or overwrite files",
|
|
1490
2247
|
category: "file_write"
|
|
1491
2248
|
},
|
|
1492
2249
|
Edit: {
|
|
1493
|
-
description: "
|
|
2250
|
+
description: "Targeted edits (replace_all is substring-based)",
|
|
1494
2251
|
category: "file_write"
|
|
1495
2252
|
},
|
|
1496
2253
|
Glob: {
|
|
1497
|
-
description: "Find files by
|
|
2254
|
+
description: "Find files by glob pattern",
|
|
1498
2255
|
category: "file_read"
|
|
1499
2256
|
},
|
|
1500
2257
|
Grep: {
|
|
1501
|
-
description: "Search
|
|
2258
|
+
description: "Search file contents (regex)",
|
|
1502
2259
|
category: "file_read"
|
|
1503
2260
|
},
|
|
1504
2261
|
NotebookEdit: {
|
|
1505
|
-
description: "Edit
|
|
2262
|
+
description: "Edit Jupyter notebook cells (Windows paths normalized)",
|
|
1506
2263
|
category: "file_write"
|
|
1507
2264
|
},
|
|
1508
2265
|
WebFetch: {
|
|
1509
|
-
description: "
|
|
2266
|
+
description: "Fetch web page or API content",
|
|
1510
2267
|
category: "network"
|
|
1511
2268
|
},
|
|
1512
|
-
WebSearch: { description: "
|
|
2269
|
+
WebSearch: { description: "Web search", category: "network" },
|
|
1513
2270
|
Task: {
|
|
1514
|
-
description: "Spawn
|
|
1515
|
-
category: "agent"
|
|
2271
|
+
description: "Spawn subagent (must be in allowedTools)",
|
|
2272
|
+
category: "agent",
|
|
2273
|
+
availabilityConditions: ["Requires Task to be visible and approved by session policy."]
|
|
1516
2274
|
},
|
|
1517
|
-
TaskOutput: { description: "Get
|
|
1518
|
-
TaskStop: { description: "Cancel
|
|
2275
|
+
TaskOutput: { description: "Get subagent output", category: "agent" },
|
|
2276
|
+
TaskStop: { description: "Cancel subagent", category: "agent" },
|
|
1519
2277
|
TodoWrite: {
|
|
1520
|
-
description: "
|
|
2278
|
+
description: "Task/todo checklist",
|
|
1521
2279
|
category: "agent"
|
|
1522
2280
|
},
|
|
1523
2281
|
AskUserQuestion: {
|
|
1524
|
-
description: "Ask
|
|
2282
|
+
description: "Ask user a question",
|
|
1525
2283
|
category: "interaction"
|
|
1526
2284
|
},
|
|
1527
2285
|
TeamDelete: {
|
|
1528
|
-
description: "Delete
|
|
1529
|
-
category: "agent"
|
|
2286
|
+
description: "Delete team (may need shutdown_approved first)",
|
|
2287
|
+
category: "agent",
|
|
2288
|
+
notes: ["Team cleanup can be asynchronous during shutdown."]
|
|
1530
2289
|
}
|
|
1531
2290
|
};
|
|
1532
2291
|
function uniq(items) {
|
|
1533
2292
|
return Array.from(new Set(items));
|
|
1534
2293
|
}
|
|
2294
|
+
function withToolDefaults(tool) {
|
|
2295
|
+
return {
|
|
2296
|
+
...tool,
|
|
2297
|
+
permissionModel: tool.permissionModel ?? DEFAULT_PERMISSION_MODEL,
|
|
2298
|
+
schemaAvailability: tool.schemaAvailability ?? DEFAULT_SCHEMA_AVAILABILITY
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
1535
2301
|
function discoverToolsFromInit(initTools) {
|
|
1536
2302
|
const names = uniq(initTools.filter((t) => typeof t === "string" && t.trim() !== ""));
|
|
1537
|
-
return names.map(
|
|
1538
|
-
name
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2303
|
+
return names.map(
|
|
2304
|
+
(name) => withToolDefaults({
|
|
2305
|
+
name,
|
|
2306
|
+
description: TOOL_CATALOG[name]?.description ?? name,
|
|
2307
|
+
category: TOOL_CATALOG[name]?.category,
|
|
2308
|
+
availabilityConditions: TOOL_CATALOG[name]?.availabilityConditions,
|
|
2309
|
+
platformConstraints: TOOL_CATALOG[name]?.platformConstraints,
|
|
2310
|
+
notes: TOOL_CATALOG[name]?.notes
|
|
2311
|
+
})
|
|
2312
|
+
);
|
|
1542
2313
|
}
|
|
1543
2314
|
function defaultCatalogTools() {
|
|
1544
|
-
return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => ({ name, ...TOOL_CATALOG[name] }));
|
|
2315
|
+
return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => withToolDefaults({ name, ...TOOL_CATALOG[name] }));
|
|
1545
2316
|
}
|
|
1546
2317
|
var ToolDiscoveryCache = class {
|
|
1547
2318
|
cached;
|
|
1548
|
-
|
|
2319
|
+
onUpdated;
|
|
2320
|
+
constructor(initial, onUpdated) {
|
|
1549
2321
|
this.cached = initial ?? defaultCatalogTools();
|
|
2322
|
+
this.onUpdated = onUpdated;
|
|
1550
2323
|
}
|
|
1551
2324
|
getTools() {
|
|
1552
2325
|
return this.cached;
|
|
@@ -1555,7 +2328,13 @@ var ToolDiscoveryCache = class {
|
|
|
1555
2328
|
const discovered = discoverToolsFromInit(initTools);
|
|
1556
2329
|
const next = mergeToolLists(discovered, defaultCatalogTools());
|
|
1557
2330
|
const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
|
|
1558
|
-
if (updated)
|
|
2331
|
+
if (updated) {
|
|
2332
|
+
this.cached = next;
|
|
2333
|
+
try {
|
|
2334
|
+
this.onUpdated?.(this.cached);
|
|
2335
|
+
} catch {
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
1559
2338
|
return { updated, tools: this.cached };
|
|
1560
2339
|
}
|
|
1561
2340
|
};
|
|
@@ -1580,9 +2359,8 @@ function groupByCategory(tools) {
|
|
|
1580
2359
|
function buildInternalToolsDescription(tools) {
|
|
1581
2360
|
const grouped = groupByCategory(tools);
|
|
1582
2361
|
const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
|
|
1583
|
-
let desc =
|
|
1584
|
-
desc += "
|
|
1585
|
-
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";
|
|
2362
|
+
let desc = "Start a Claude Code session and return sessionId.\nUse claude_code_check to poll events/results and handle permissions.\n\n";
|
|
2363
|
+
desc += "Internal tools (authoritative list: includeTools=true in claude_code_check):\n";
|
|
1586
2364
|
for (const category of categories) {
|
|
1587
2365
|
desc += `
|
|
1588
2366
|
[${category}]
|
|
@@ -1592,8 +2370,7 @@ function buildInternalToolsDescription(tools) {
|
|
|
1592
2370
|
`;
|
|
1593
2371
|
}
|
|
1594
2372
|
}
|
|
1595
|
-
desc += "\
|
|
1596
|
-
desc += 'Use `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';
|
|
2373
|
+
desc += "\nPermission control: allowedTools auto-approves; disallowedTools always denies; others require approval.\n";
|
|
1597
2374
|
return desc;
|
|
1598
2375
|
}
|
|
1599
2376
|
|
|
@@ -1604,7 +2381,7 @@ function pollIntervalForStatus(status) {
|
|
|
1604
2381
|
return void 0;
|
|
1605
2382
|
}
|
|
1606
2383
|
function toPermissionResult(params) {
|
|
1607
|
-
if (params.decision === "allow") {
|
|
2384
|
+
if (params.decision === "allow" || params.decision === "allow_for_session") {
|
|
1608
2385
|
return {
|
|
1609
2386
|
behavior: "allow",
|
|
1610
2387
|
updatedInput: params.updatedInput,
|
|
@@ -1617,6 +2394,19 @@ function toPermissionResult(params) {
|
|
|
1617
2394
|
interrupt: params.interrupt
|
|
1618
2395
|
};
|
|
1619
2396
|
}
|
|
2397
|
+
function appendAllowForSessionUpdate(updates, toolName) {
|
|
2398
|
+
const normalizedToolName = toolName?.trim();
|
|
2399
|
+
if (!normalizedToolName) return updates;
|
|
2400
|
+
return [
|
|
2401
|
+
...updates ?? [],
|
|
2402
|
+
{
|
|
2403
|
+
type: "addRules",
|
|
2404
|
+
behavior: "allow",
|
|
2405
|
+
destination: "session",
|
|
2406
|
+
rules: [{ toolName: normalizedToolName }]
|
|
2407
|
+
}
|
|
2408
|
+
];
|
|
2409
|
+
}
|
|
1620
2410
|
function slimAssistantData(data) {
|
|
1621
2411
|
if (!data || typeof data !== "object") return data;
|
|
1622
2412
|
const d = data;
|
|
@@ -1652,18 +2442,169 @@ function toEvents(events, opts) {
|
|
|
1652
2442
|
return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
|
|
1653
2443
|
});
|
|
1654
2444
|
}
|
|
2445
|
+
function uniqSorted(values) {
|
|
2446
|
+
if (!Array.isArray(values) || values.length === 0) return [];
|
|
2447
|
+
const filtered = values.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean);
|
|
2448
|
+
return Array.from(new Set(filtered)).sort((a, b) => a.localeCompare(b));
|
|
2449
|
+
}
|
|
2450
|
+
function detectPathCompatibilityWarnings(session) {
|
|
2451
|
+
if (!session) return [];
|
|
2452
|
+
const cwd = session.cwd;
|
|
2453
|
+
if (typeof cwd !== "string" || cwd.trim() === "") return [];
|
|
2454
|
+
const warnings = [];
|
|
2455
|
+
if (process.platform === "win32" && cwd.startsWith("/") && !cwd.startsWith("//")) {
|
|
2456
|
+
warnings.push(
|
|
2457
|
+
`cwd '${cwd}' looks POSIX-style on Windows. Consider using a Windows path (e.g. C:\\\\repo) to avoid path compatibility issues.`
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
if (process.platform !== "win32" && /^[a-zA-Z]:[\\/]/.test(cwd)) {
|
|
2461
|
+
warnings.push(
|
|
2462
|
+
`cwd '${cwd}' looks Windows-style on ${process.platform}. This may indicate cross-platform path mismatch (for example WSL boundary).`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
if (process.platform !== "win32" && cwd.startsWith("\\\\")) {
|
|
2466
|
+
warnings.push(
|
|
2467
|
+
`cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
if (process.platform === "win32" && Array.isArray(session.additionalDirectories)) {
|
|
2471
|
+
for (const dir of session.additionalDirectories) {
|
|
2472
|
+
if (typeof dir === "string" && dir.startsWith("/") && !dir.startsWith("//")) {
|
|
2473
|
+
warnings.push(
|
|
2474
|
+
`additionalDirectories contains POSIX-style path '${dir}' on Windows. Consider using a Windows path to avoid path compatibility issues.`
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
return warnings;
|
|
2480
|
+
}
|
|
2481
|
+
function isPosixHomePath(value) {
|
|
2482
|
+
return /^\/home\/[^/\s]+(?:\/|$)/.test(value);
|
|
2483
|
+
}
|
|
2484
|
+
function extractPosixHomePath(value) {
|
|
2485
|
+
const match = value.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
|
|
2486
|
+
return match?.[0];
|
|
2487
|
+
}
|
|
2488
|
+
function detectPendingPermissionPathWarnings(pending, session) {
|
|
2489
|
+
if (process.platform !== "win32" || pending.length === 0) return [];
|
|
2490
|
+
const cwd = session?.cwd;
|
|
2491
|
+
const cwdHint = typeof cwd === "string" && cwd.trim() !== "" ? ` under cwd '${cwd}'` : " under the current cwd";
|
|
2492
|
+
const warnings = [];
|
|
2493
|
+
for (const req of pending) {
|
|
2494
|
+
const candidates = [];
|
|
2495
|
+
if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
|
|
2496
|
+
const filePath = req.input.file_path;
|
|
2497
|
+
if (typeof filePath === "string") candidates.push(filePath);
|
|
2498
|
+
const pathFields = [
|
|
2499
|
+
"path",
|
|
2500
|
+
"directory",
|
|
2501
|
+
"folder",
|
|
2502
|
+
"cwd",
|
|
2503
|
+
"dest",
|
|
2504
|
+
"destination",
|
|
2505
|
+
"source",
|
|
2506
|
+
"target"
|
|
2507
|
+
];
|
|
2508
|
+
for (const field of pathFields) {
|
|
2509
|
+
const val = req.input[field];
|
|
2510
|
+
if (typeof val === "string") candidates.push(val);
|
|
2511
|
+
}
|
|
2512
|
+
const command = req.input.command;
|
|
2513
|
+
if (typeof command === "string") {
|
|
2514
|
+
const embeddedPath = extractPosixHomePath(command);
|
|
2515
|
+
if (embeddedPath) candidates.push(embeddedPath);
|
|
2516
|
+
}
|
|
2517
|
+
const badPath = candidates.find((p) => isPosixHomePath(p));
|
|
2518
|
+
if (!badPath) continue;
|
|
2519
|
+
warnings.push(
|
|
2520
|
+
`Permission request '${req.requestId}' uses POSIX home path '${badPath}' on Windows. Prefer an absolute Windows path${cwdHint} to avoid out-of-bounds permission prompts.`
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
return warnings;
|
|
2524
|
+
}
|
|
2525
|
+
function computeToolValidation(session, initTools) {
|
|
2526
|
+
if (!session) return { summary: void 0, warnings: [] };
|
|
2527
|
+
const allowedTools = uniqSorted(session.allowedTools);
|
|
2528
|
+
const disallowedTools = uniqSorted(session.disallowedTools);
|
|
2529
|
+
const warnings = [];
|
|
2530
|
+
if (allowedTools.length > 0 && session.strictAllowedTools !== true) {
|
|
2531
|
+
warnings.push(
|
|
2532
|
+
"allowedTools currently acts as pre-approval only. Set strictAllowedTools=true to enforce a strict allowlist."
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
if (allowedTools.length === 0 && disallowedTools.length === 0) {
|
|
2536
|
+
return { summary: void 0, warnings };
|
|
2537
|
+
}
|
|
2538
|
+
if (!Array.isArray(initTools) || initTools.length === 0) {
|
|
2539
|
+
return {
|
|
2540
|
+
summary: {
|
|
2541
|
+
runtimeToolsKnown: false,
|
|
2542
|
+
unknownAllowedTools: [],
|
|
2543
|
+
unknownDisallowedTools: []
|
|
2544
|
+
},
|
|
2545
|
+
warnings: [
|
|
2546
|
+
...warnings,
|
|
2547
|
+
"Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
|
|
2548
|
+
]
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
const runtime = new Set(
|
|
2552
|
+
initTools.filter((name) => typeof name === "string").map((name) => name.trim()).filter(Boolean)
|
|
2553
|
+
);
|
|
2554
|
+
const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
|
|
2555
|
+
const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
|
|
2556
|
+
if (unknownAllowedTools.length > 0) {
|
|
2557
|
+
warnings.push(
|
|
2558
|
+
`Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
if (unknownDisallowedTools.length > 0) {
|
|
2562
|
+
warnings.push(
|
|
2563
|
+
`Unknown disallowedTools (not present in runtime tools): ${unknownDisallowedTools.join(", ")}.`
|
|
2564
|
+
);
|
|
2565
|
+
}
|
|
2566
|
+
return {
|
|
2567
|
+
summary: {
|
|
2568
|
+
runtimeToolsKnown: true,
|
|
2569
|
+
unknownAllowedTools,
|
|
2570
|
+
unknownDisallowedTools
|
|
2571
|
+
},
|
|
2572
|
+
warnings
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
function byteLength(value) {
|
|
2576
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
2577
|
+
}
|
|
2578
|
+
function capEventsByBytes(events, maxBytes) {
|
|
2579
|
+
if (!Number.isFinite(maxBytes) || typeof maxBytes !== "number" || maxBytes <= 0) {
|
|
2580
|
+
return { events, truncated: false };
|
|
2581
|
+
}
|
|
2582
|
+
const budget = Math.floor(maxBytes);
|
|
2583
|
+
const kept = [];
|
|
2584
|
+
let bytes = 2;
|
|
2585
|
+
for (const evt of events) {
|
|
2586
|
+
const evtBytes = byteLength(evt);
|
|
2587
|
+
const separatorBytes = kept.length === 0 ? 0 : 1;
|
|
2588
|
+
if (bytes + separatorBytes + evtBytes > budget) break;
|
|
2589
|
+
kept.push(evt);
|
|
2590
|
+
bytes += separatorBytes + evtBytes;
|
|
2591
|
+
}
|
|
2592
|
+
return { events: kept, truncated: kept.length < events.length };
|
|
2593
|
+
}
|
|
1655
2594
|
function buildResult(sessionManager, toolCache, input) {
|
|
1656
2595
|
const responseMode = input.responseMode ?? "minimal";
|
|
2596
|
+
const compactMode = responseMode === "delta_compact";
|
|
1657
2597
|
const po = input.pollOptions ?? {};
|
|
1658
2598
|
const includeTools = po.includeTools;
|
|
1659
|
-
const includeEvents = po.includeEvents ??
|
|
2599
|
+
const includeEvents = po.includeEvents ?? !compactMode;
|
|
1660
2600
|
const includeActions = po.includeActions ?? true;
|
|
1661
|
-
const includeResult = po.includeResult ??
|
|
2601
|
+
const includeResult = po.includeResult ?? !compactMode;
|
|
1662
2602
|
const includeUsage = po.includeUsage ?? responseMode === "full";
|
|
1663
2603
|
const includeModelUsage = po.includeModelUsage ?? responseMode === "full";
|
|
1664
2604
|
const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
|
|
1665
2605
|
const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
|
|
1666
2606
|
const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
|
|
2607
|
+
const maxBytes = po.maxBytes;
|
|
1667
2608
|
const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
|
|
1668
2609
|
const sessionId = input.sessionId;
|
|
1669
2610
|
const session = sessionManager.get(sessionId);
|
|
@@ -1676,7 +2617,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1676
2617
|
let truncated = false;
|
|
1677
2618
|
const truncatedFields = [];
|
|
1678
2619
|
const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
|
|
1679
|
-
|
|
2620
|
+
let nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
|
|
1680
2621
|
if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
|
|
1681
2622
|
truncated = true;
|
|
1682
2623
|
truncatedFields.push("events");
|
|
@@ -1699,8 +2640,28 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1699
2640
|
})();
|
|
1700
2641
|
const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
|
|
1701
2642
|
const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
|
|
1702
|
-
const initTools =
|
|
2643
|
+
const initTools = sessionManager.getInitTools(sessionId);
|
|
1703
2644
|
const availableTools = includeTools && initTools ? discoverToolsFromInit(initTools) : void 0;
|
|
2645
|
+
const toolValidation = computeToolValidation(session, initTools);
|
|
2646
|
+
const compatWarnings = Array.from(
|
|
2647
|
+
/* @__PURE__ */ new Set([
|
|
2648
|
+
...toolValidation.warnings,
|
|
2649
|
+
...detectPathCompatibilityWarnings(session),
|
|
2650
|
+
...detectPendingPermissionPathWarnings(pending, session)
|
|
2651
|
+
])
|
|
2652
|
+
);
|
|
2653
|
+
const shapedEvents = toEvents(outputEvents, {
|
|
2654
|
+
includeUsage,
|
|
2655
|
+
includeModelUsage,
|
|
2656
|
+
includeStructuredOutput,
|
|
2657
|
+
slim: responseMode === "minimal" || responseMode === "delta_compact"
|
|
2658
|
+
});
|
|
2659
|
+
const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
|
|
2660
|
+
if (cappedEvents.truncated) {
|
|
2661
|
+
truncated = true;
|
|
2662
|
+
truncatedFields.push("events_bytes");
|
|
2663
|
+
nextCursor = cappedEvents.events.length > 0 ? cappedEvents.events[cappedEvents.events.length - 1].id + 1 : cursorResetTo ?? input.cursor ?? 0;
|
|
2664
|
+
}
|
|
1704
2665
|
return {
|
|
1705
2666
|
sessionId,
|
|
1706
2667
|
status,
|
|
@@ -1708,14 +2669,11 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1708
2669
|
cursorResetTo,
|
|
1709
2670
|
truncated: truncated ? true : void 0,
|
|
1710
2671
|
truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
|
|
1711
|
-
events:
|
|
1712
|
-
includeUsage,
|
|
1713
|
-
includeModelUsage,
|
|
1714
|
-
includeStructuredOutput,
|
|
1715
|
-
slim: responseMode === "minimal"
|
|
1716
|
-
}),
|
|
2672
|
+
events: cappedEvents.events,
|
|
1717
2673
|
nextCursor,
|
|
1718
2674
|
availableTools,
|
|
2675
|
+
toolValidation: toolValidation.summary,
|
|
2676
|
+
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0,
|
|
1719
2677
|
actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
|
|
1720
2678
|
const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
|
|
1721
2679
|
const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
|
|
@@ -1741,7 +2699,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1741
2699
|
includeUsage,
|
|
1742
2700
|
includeModelUsage,
|
|
1743
2701
|
includeStructuredOutput,
|
|
1744
|
-
slim: responseMode === "minimal"
|
|
2702
|
+
slim: responseMode === "minimal" || responseMode === "delta_compact"
|
|
1745
2703
|
}) : void 0,
|
|
1746
2704
|
cancelledAt: session?.cancelledAt,
|
|
1747
2705
|
cancelledReason: session?.cancelledReason,
|
|
@@ -1775,7 +2733,14 @@ function redactAgentResult(result, opts) {
|
|
|
1775
2733
|
structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
|
|
1776
2734
|
};
|
|
1777
2735
|
}
|
|
1778
|
-
function executeClaudeCodeCheck(input, sessionManager, toolCache) {
|
|
2736
|
+
function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal) {
|
|
2737
|
+
if (requestSignal?.aborted) {
|
|
2738
|
+
return {
|
|
2739
|
+
sessionId: input.sessionId ?? "",
|
|
2740
|
+
error: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
|
|
2741
|
+
isError: true
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
1779
2744
|
if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
|
|
1780
2745
|
return {
|
|
1781
2746
|
sessionId: "",
|
|
@@ -1801,20 +2766,25 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
|
|
|
1801
2766
|
isError: true
|
|
1802
2767
|
};
|
|
1803
2768
|
}
|
|
1804
|
-
if (input.decision !== "allow" && input.decision !== "deny") {
|
|
2769
|
+
if (input.decision !== "allow" && input.decision !== "deny" && input.decision !== "allow_for_session") {
|
|
1805
2770
|
return {
|
|
1806
2771
|
sessionId: input.sessionId,
|
|
1807
|
-
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow' or '
|
|
2772
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow', 'deny', or 'allow_for_session'.`,
|
|
1808
2773
|
isError: true
|
|
1809
2774
|
};
|
|
1810
2775
|
}
|
|
2776
|
+
const pendingRequest = input.decision === "allow_for_session" ? sessionManager.getPendingPermission(input.sessionId, input.requestId) : void 0;
|
|
2777
|
+
const updatedPermissions = input.decision === "allow_for_session" ? appendAllowForSessionUpdate(
|
|
2778
|
+
input.permissionOptions?.updatedPermissions,
|
|
2779
|
+
pendingRequest?.toolName
|
|
2780
|
+
) : input.permissionOptions?.updatedPermissions;
|
|
1811
2781
|
const ok = sessionManager.finishRequest(
|
|
1812
2782
|
input.sessionId,
|
|
1813
2783
|
input.requestId,
|
|
1814
2784
|
toPermissionResult({
|
|
1815
2785
|
decision: input.decision,
|
|
1816
2786
|
updatedInput: input.permissionOptions?.updatedInput,
|
|
1817
|
-
updatedPermissions
|
|
2787
|
+
updatedPermissions,
|
|
1818
2788
|
denyMessage: input.denyMessage,
|
|
1819
2789
|
interrupt: input.interrupt
|
|
1820
2790
|
}),
|
|
@@ -1827,12 +2797,63 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
|
|
|
1827
2797
|
isError: true
|
|
1828
2798
|
};
|
|
1829
2799
|
}
|
|
2800
|
+
if (input.decision === "allow_for_session" && pendingRequest?.toolName) {
|
|
2801
|
+
sessionManager.allowToolForSession(input.sessionId, pendingRequest.toolName);
|
|
2802
|
+
}
|
|
1830
2803
|
return buildResult(sessionManager, toolCache, input);
|
|
1831
2804
|
}
|
|
1832
2805
|
|
|
1833
2806
|
// src/tools/claude-code-session.ts
|
|
1834
|
-
|
|
1835
|
-
|
|
2807
|
+
var ALWAYS_REDACTED_FIELDS = [
|
|
2808
|
+
"env",
|
|
2809
|
+
"mcpServers",
|
|
2810
|
+
"sandbox",
|
|
2811
|
+
"debugFile",
|
|
2812
|
+
"pathToClaudeCodeExecutable"
|
|
2813
|
+
];
|
|
2814
|
+
var CONDITIONAL_REDACTED_FIELDS = [
|
|
2815
|
+
"cwd",
|
|
2816
|
+
"systemPrompt",
|
|
2817
|
+
"agents",
|
|
2818
|
+
"additionalDirectories"
|
|
2819
|
+
];
|
|
2820
|
+
function buildRedactions(includeSensitive) {
|
|
2821
|
+
const redactions = [];
|
|
2822
|
+
for (const field of ALWAYS_REDACTED_FIELDS) {
|
|
2823
|
+
redactions?.push({ field, reason: "secret_or_internal" });
|
|
2824
|
+
}
|
|
2825
|
+
if (!includeSensitive) {
|
|
2826
|
+
for (const field of CONDITIONAL_REDACTED_FIELDS) {
|
|
2827
|
+
redactions?.push({ field, reason: "sensitive_by_default" });
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
return redactions;
|
|
2831
|
+
}
|
|
2832
|
+
function executeClaudeCodeSession(input, sessionManager, requestSignal) {
|
|
2833
|
+
if (requestSignal?.aborted) {
|
|
2834
|
+
return {
|
|
2835
|
+
sessions: [],
|
|
2836
|
+
message: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
|
|
2837
|
+
isError: true
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
const toSessionJson = (s) => {
|
|
2841
|
+
const base = input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
|
|
2842
|
+
const stored = sessionManager.getResult(s.sessionId);
|
|
2843
|
+
const lastError = stored?.type === "error" ? stored.result.result : void 0;
|
|
2844
|
+
const lastErrorAt = stored?.type === "error" ? stored.createdAt : void 0;
|
|
2845
|
+
return {
|
|
2846
|
+
...base,
|
|
2847
|
+
pendingPermissionCount: sessionManager.getPendingPermissionCount(s.sessionId),
|
|
2848
|
+
eventCount: sessionManager.getEventCount(s.sessionId),
|
|
2849
|
+
currentCursor: sessionManager.getCurrentCursor(s.sessionId),
|
|
2850
|
+
lastEventId: sessionManager.getLastEventId(s.sessionId),
|
|
2851
|
+
ttlMs: sessionManager.getRemainingTtlMs(s.sessionId),
|
|
2852
|
+
lastError,
|
|
2853
|
+
lastErrorAt,
|
|
2854
|
+
redactions: buildRedactions(input.includeSensitive)
|
|
2855
|
+
};
|
|
2856
|
+
};
|
|
1836
2857
|
switch (input.action) {
|
|
1837
2858
|
case "list": {
|
|
1838
2859
|
const sessions = sessionManager.list().map((s) => toSessionJson(s));
|
|
@@ -1889,21 +2910,64 @@ function executeClaudeCodeSession(input, sessionManager) {
|
|
|
1889
2910
|
message: `Session '${input.sessionId}' cancelled.`
|
|
1890
2911
|
};
|
|
1891
2912
|
}
|
|
2913
|
+
case "interrupt": {
|
|
2914
|
+
if (!input.sessionId) {
|
|
2915
|
+
return {
|
|
2916
|
+
sessions: [],
|
|
2917
|
+
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: sessionId is required for 'interrupt' action.`,
|
|
2918
|
+
isError: true
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
const interrupted = sessionManager.interrupt(input.sessionId, {
|
|
2922
|
+
reason: "Interrupted by caller",
|
|
2923
|
+
source: "claude_code_session"
|
|
2924
|
+
});
|
|
2925
|
+
if (!interrupted) {
|
|
2926
|
+
const session = sessionManager.get(input.sessionId);
|
|
2927
|
+
if (!session) {
|
|
2928
|
+
return {
|
|
2929
|
+
sessions: [],
|
|
2930
|
+
message: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found.`,
|
|
2931
|
+
isError: true
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
return {
|
|
2935
|
+
sessions: [toSessionJson(session)],
|
|
2936
|
+
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Session '${input.sessionId}' is not running (status: ${session.status}).`,
|
|
2937
|
+
isError: true
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
const updated = sessionManager.get(input.sessionId);
|
|
2941
|
+
return {
|
|
2942
|
+
sessions: updated ? [toSessionJson(updated)] : [],
|
|
2943
|
+
message: `Session '${input.sessionId}' interrupted.`
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
1892
2946
|
default:
|
|
1893
2947
|
return {
|
|
1894
2948
|
sessions: [],
|
|
1895
|
-
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', or '
|
|
2949
|
+
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', 'cancel', or 'interrupt'.`,
|
|
1896
2950
|
isError: true
|
|
1897
2951
|
};
|
|
1898
2952
|
}
|
|
1899
2953
|
}
|
|
1900
2954
|
|
|
1901
2955
|
// src/resources/register-resources.ts
|
|
2956
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2957
|
+
import { createHash } from "crypto";
|
|
1902
2958
|
var RESOURCE_SCHEME = "claude-code-mcp";
|
|
1903
2959
|
var RESOURCE_URIS = {
|
|
1904
2960
|
serverInfo: `${RESOURCE_SCHEME}:///server-info`,
|
|
1905
2961
|
internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
|
|
1906
|
-
gotchas: `${RESOURCE_SCHEME}:///gotchas
|
|
2962
|
+
gotchas: `${RESOURCE_SCHEME}:///gotchas`,
|
|
2963
|
+
quickstart: `${RESOURCE_SCHEME}:///quickstart`,
|
|
2964
|
+
errors: `${RESOURCE_SCHEME}:///errors`,
|
|
2965
|
+
compatReport: `${RESOURCE_SCHEME}:///compat-report`
|
|
2966
|
+
};
|
|
2967
|
+
var RESOURCE_TEMPLATES = {
|
|
2968
|
+
sessionById: `${RESOURCE_SCHEME}:///session/{sessionId}`,
|
|
2969
|
+
runtimeTools: `${RESOURCE_SCHEME}:///tools/runtime{?sessionId}`,
|
|
2970
|
+
compatDiff: `${RESOURCE_SCHEME}:///compat/diff{?client}`
|
|
1907
2971
|
};
|
|
1908
2972
|
function asTextResource(uri, text, mimeType) {
|
|
1909
2973
|
return {
|
|
@@ -1916,31 +2980,189 @@ function asTextResource(uri, text, mimeType) {
|
|
|
1916
2980
|
]
|
|
1917
2981
|
};
|
|
1918
2982
|
}
|
|
2983
|
+
function asJsonResource(uri, value) {
|
|
2984
|
+
return asTextResource(uri, JSON.stringify(value, null, 2), "application/json");
|
|
2985
|
+
}
|
|
2986
|
+
function serializeLimit(limit) {
|
|
2987
|
+
return Number.isFinite(limit) ? limit : "unlimited";
|
|
2988
|
+
}
|
|
2989
|
+
function computeEtag(value) {
|
|
2990
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
2991
|
+
}
|
|
2992
|
+
function extractSingleVariable(value) {
|
|
2993
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
2994
|
+
if (Array.isArray(value) && typeof value[0] === "string" && value[0].trim() !== "")
|
|
2995
|
+
return value[0];
|
|
2996
|
+
return void 0;
|
|
2997
|
+
}
|
|
2998
|
+
function buildSessionRedactions(includeSensitive) {
|
|
2999
|
+
const redactions = [
|
|
3000
|
+
{ field: "env", reason: "secret_or_internal" },
|
|
3001
|
+
{ field: "mcpServers", reason: "secret_or_internal" },
|
|
3002
|
+
{ field: "sandbox", reason: "secret_or_internal" },
|
|
3003
|
+
{ field: "debugFile", reason: "secret_or_internal" },
|
|
3004
|
+
{ field: "pathToClaudeCodeExecutable", reason: "secret_or_internal" }
|
|
3005
|
+
];
|
|
3006
|
+
if (!includeSensitive) {
|
|
3007
|
+
redactions.push(
|
|
3008
|
+
{ field: "cwd", reason: "sensitive_by_default" },
|
|
3009
|
+
{ field: "systemPrompt", reason: "sensitive_by_default" },
|
|
3010
|
+
{ field: "agents", reason: "sensitive_by_default" },
|
|
3011
|
+
{ field: "additionalDirectories", reason: "sensitive_by_default" }
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
return redactions;
|
|
3015
|
+
}
|
|
3016
|
+
function buildGotchasEntries() {
|
|
3017
|
+
return [
|
|
3018
|
+
{
|
|
3019
|
+
id: "permission-timeout",
|
|
3020
|
+
title: "Permission requests auto-deny on timeout",
|
|
3021
|
+
severity: "high",
|
|
3022
|
+
appliesTo: ["claude_code_check.actions", "permission workflow"],
|
|
3023
|
+
symptom: "Session waits for approval, then tool call is denied without explicit caller decision.",
|
|
3024
|
+
detection: "Observe actions[].expiresAt/remainingMs and permission_result with source=timeout.",
|
|
3025
|
+
remedy: "Poll more frequently and respond before timeout; increase permissionRequestTimeoutMs if needed.",
|
|
3026
|
+
example: "Default timeout is 60000ms and server-clamped to 300000ms."
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
id: "read-size-cap",
|
|
3030
|
+
title: "Read may cap response size",
|
|
3031
|
+
severity: "medium",
|
|
3032
|
+
appliesTo: ["Read tool"],
|
|
3033
|
+
symptom: "Large file reads are truncated or fail.",
|
|
3034
|
+
detection: "Read responses are incomplete for large files.",
|
|
3035
|
+
remedy: "Use offset/limit paging or chunk with Grep."
|
|
3036
|
+
},
|
|
3037
|
+
{
|
|
3038
|
+
id: "edit-replace-all",
|
|
3039
|
+
title: "Edit replace_all uses substring matching",
|
|
3040
|
+
severity: "medium",
|
|
3041
|
+
appliesTo: ["Edit tool"],
|
|
3042
|
+
symptom: "replace_all=true fails unexpectedly.",
|
|
3043
|
+
detection: "Tool returns error when no exact substring match is found.",
|
|
3044
|
+
remedy: "Validate exact match text first; prefer smaller targeted replacements."
|
|
3045
|
+
},
|
|
3046
|
+
{
|
|
3047
|
+
id: "windows-path-normalization",
|
|
3048
|
+
title: "Windows path normalization applies to common MSYS-style paths",
|
|
3049
|
+
severity: "medium",
|
|
3050
|
+
appliesTo: ["Windows", "cwd", "additionalDirectories", "file_path"],
|
|
3051
|
+
symptom: "Permission/path behavior differs from raw input path text.",
|
|
3052
|
+
detection: "Compare submitted path with effective path in logs/permission prompts.",
|
|
3053
|
+
remedy: "Use absolute native Windows paths when possible; avoid relying on implicit conversion behavior.",
|
|
3054
|
+
example: "/d/... /mnt/c/... /cygdrive/c/... //server/share/... are normalized on Windows."
|
|
3055
|
+
},
|
|
3056
|
+
{
|
|
3057
|
+
id: "team-delete-async-cleanup",
|
|
3058
|
+
title: "TeamDelete cleanup can be asynchronous",
|
|
3059
|
+
severity: "low",
|
|
3060
|
+
appliesTo: ["TeamDelete"],
|
|
3061
|
+
symptom: "Immediate follow-up calls still see active members.",
|
|
3062
|
+
detection: "TeamDelete reports shutdown_approved or active member transitions.",
|
|
3063
|
+
remedy: "Retry after short delay; wait for shutdown state to settle."
|
|
3064
|
+
},
|
|
3065
|
+
{
|
|
3066
|
+
id: "skills-late-availability",
|
|
3067
|
+
title: "Skills may become available later in a session",
|
|
3068
|
+
severity: "low",
|
|
3069
|
+
appliesTo: ["Skills"],
|
|
3070
|
+
symptom: "Early calls show unknown skill/tool errors.",
|
|
3071
|
+
detection: "Later calls in same session succeed without config changes.",
|
|
3072
|
+
remedy: "Retry after initialization events are complete."
|
|
3073
|
+
},
|
|
3074
|
+
{
|
|
3075
|
+
id: "tool-count-sources-differ",
|
|
3076
|
+
title: "toolCatalogCount and availableTools have different sources",
|
|
3077
|
+
severity: "medium",
|
|
3078
|
+
appliesTo: ["internal-tools", "claude_code_check.availableTools", "compat-report"],
|
|
3079
|
+
symptom: "Tool counts appear inconsistent (for example 15 vs 28).",
|
|
3080
|
+
detection: "Compare compat-report.toolCounts and session-level availableTools.",
|
|
3081
|
+
remedy: "Treat catalog count as server-known baseline and availableTools as session runtime view from system/init.tools."
|
|
3082
|
+
},
|
|
3083
|
+
{
|
|
3084
|
+
id: "available-tools-not-exhaustive",
|
|
3085
|
+
title: "availableTools may omit internal features",
|
|
3086
|
+
severity: "low",
|
|
3087
|
+
appliesTo: ["claude_code_check.availableTools"],
|
|
3088
|
+
symptom: "A known feature is callable but not listed in availableTools.",
|
|
3089
|
+
detection: "Feature works while missing from availableTools list.",
|
|
3090
|
+
remedy: "Use availableTools as runtime hint, not exhaustive capability proof.",
|
|
3091
|
+
example: "Some internal features (e.g. ToolSearch) may not appear."
|
|
3092
|
+
}
|
|
3093
|
+
];
|
|
3094
|
+
}
|
|
3095
|
+
function asVersionedPayload(params) {
|
|
3096
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3097
|
+
return {
|
|
3098
|
+
...params.payload,
|
|
3099
|
+
schemaVersion: params.schemaVersion,
|
|
3100
|
+
updatedAt,
|
|
3101
|
+
etag: computeEtag(params.payload),
|
|
3102
|
+
stability: params.stability
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
1919
3105
|
function registerResources(server, deps) {
|
|
3106
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3107
|
+
const resourceSchemaVersion = "1.3";
|
|
3108
|
+
const mcpProtocolVersion = "2025-03-26";
|
|
3109
|
+
const gotchasEntries = buildGotchasEntries();
|
|
3110
|
+
const catalogToolNames = new Set(defaultCatalogTools().map((tool) => tool.name));
|
|
3111
|
+
const staticResourceUris = Object.values(RESOURCE_URIS);
|
|
3112
|
+
const templateUris = Object.values(RESOURCE_TEMPLATES);
|
|
1920
3113
|
const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
|
|
1921
3114
|
server.registerResource(
|
|
1922
3115
|
"server_info",
|
|
1923
3116
|
serverInfoUri.toString(),
|
|
1924
3117
|
{
|
|
1925
3118
|
title: "Server Info",
|
|
1926
|
-
description: "
|
|
3119
|
+
description: "Server metadata (version/platform/runtime).",
|
|
1927
3120
|
mimeType: "application/json"
|
|
1928
3121
|
},
|
|
1929
|
-
() =>
|
|
3122
|
+
() => asJsonResource(
|
|
1930
3123
|
serverInfoUri,
|
|
1931
|
-
|
|
1932
|
-
{
|
|
3124
|
+
(() => {
|
|
3125
|
+
const base = {
|
|
1933
3126
|
name: "claude-code-mcp",
|
|
3127
|
+
version: deps.version,
|
|
1934
3128
|
node: process.version,
|
|
1935
3129
|
platform: process.platform,
|
|
1936
3130
|
arch: process.arch,
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
3131
|
+
mcpProtocolVersion,
|
|
3132
|
+
startedAt,
|
|
3133
|
+
uptimeSec: Math.floor(process.uptime()),
|
|
3134
|
+
resources: staticResourceUris,
|
|
3135
|
+
resourceTemplates: templateUris,
|
|
3136
|
+
toolCatalogCount: deps.toolCache.getTools().length,
|
|
3137
|
+
capabilities: {
|
|
3138
|
+
resources: true,
|
|
3139
|
+
toolsListChanged: true,
|
|
3140
|
+
resourcesListChanged: true,
|
|
3141
|
+
prompts: false,
|
|
3142
|
+
completions: false
|
|
3143
|
+
},
|
|
3144
|
+
limits: {
|
|
3145
|
+
maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
|
|
3146
|
+
maxPendingPermissionsPerSession: serializeLimit(
|
|
3147
|
+
deps.sessionManager.getMaxPendingPermissionsPerSession()
|
|
3148
|
+
),
|
|
3149
|
+
eventBuffer: deps.sessionManager.getEventBufferConfig(),
|
|
3150
|
+
pollDefaults: {
|
|
3151
|
+
runningMs: 3e3,
|
|
3152
|
+
waitingPermissionMs: 1e3
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
if (typeof process.env.CLAUDE_CODE_MCP_BUILD_COMMIT === "string") {
|
|
3157
|
+
const commit = process.env.CLAUDE_CODE_MCP_BUILD_COMMIT.trim();
|
|
3158
|
+
if (commit !== "") base.buildCommit = commit;
|
|
3159
|
+
}
|
|
3160
|
+
return asVersionedPayload({
|
|
3161
|
+
schemaVersion: resourceSchemaVersion,
|
|
3162
|
+
stability: "stable",
|
|
3163
|
+
payload: base
|
|
3164
|
+
});
|
|
3165
|
+
})()
|
|
1944
3166
|
)
|
|
1945
3167
|
);
|
|
1946
3168
|
const toolsUri = new URL(RESOURCE_URIS.internalTools);
|
|
@@ -1949,13 +3171,22 @@ function registerResources(server, deps) {
|
|
|
1949
3171
|
toolsUri.toString(),
|
|
1950
3172
|
{
|
|
1951
3173
|
title: "Internal Tools",
|
|
1952
|
-
description: "Claude Code internal tool catalog (
|
|
3174
|
+
description: "Claude Code internal tool catalog (runtime-aware).",
|
|
1953
3175
|
mimeType: "application/json"
|
|
1954
3176
|
},
|
|
1955
|
-
() =>
|
|
3177
|
+
() => asJsonResource(
|
|
1956
3178
|
toolsUri,
|
|
1957
|
-
|
|
1958
|
-
|
|
3179
|
+
(() => {
|
|
3180
|
+
const base = {
|
|
3181
|
+
tools: deps.toolCache.getTools(),
|
|
3182
|
+
toolCatalogCount: deps.toolCache.getTools().length
|
|
3183
|
+
};
|
|
3184
|
+
return asVersionedPayload({
|
|
3185
|
+
schemaVersion: resourceSchemaVersion,
|
|
3186
|
+
stability: "stable",
|
|
3187
|
+
payload: base
|
|
3188
|
+
});
|
|
3189
|
+
})()
|
|
1959
3190
|
)
|
|
1960
3191
|
);
|
|
1961
3192
|
const gotchasUri = new URL(RESOURCE_URIS.gotchas);
|
|
@@ -1972,46 +3203,429 @@ function registerResources(server, deps) {
|
|
|
1972
3203
|
[
|
|
1973
3204
|
"# claude-code-mcp: gotchas",
|
|
1974
3205
|
"",
|
|
1975
|
-
|
|
1976
|
-
"- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
|
|
1977
|
-
"- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
|
|
1978
|
-
"- `NotebookEdit` expects native Windows paths; this server normalizes MSYS paths like `/d/...` when possible.",
|
|
1979
|
-
"- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
|
|
1980
|
-
'- Skills may become available later in the same session (early calls may show "Unknown").',
|
|
1981
|
-
"- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
|
|
3206
|
+
...gotchasEntries.map((entry) => `- ${entry.title}. ${entry.remedy}`),
|
|
1982
3207
|
""
|
|
1983
3208
|
].join("\n"),
|
|
1984
3209
|
"text/markdown"
|
|
1985
3210
|
)
|
|
1986
3211
|
);
|
|
3212
|
+
const compatReportUri = new URL(RESOURCE_URIS.compatReport);
|
|
3213
|
+
const quickstartUri = new URL(RESOURCE_URIS.quickstart);
|
|
3214
|
+
server.registerResource(
|
|
3215
|
+
"quickstart",
|
|
3216
|
+
quickstartUri.toString(),
|
|
3217
|
+
{
|
|
3218
|
+
title: "Quickstart",
|
|
3219
|
+
description: "Minimal async polling flow for claude_code / claude_code_check.",
|
|
3220
|
+
mimeType: "text/markdown"
|
|
3221
|
+
},
|
|
3222
|
+
() => asTextResource(
|
|
3223
|
+
quickstartUri,
|
|
3224
|
+
[
|
|
3225
|
+
"# claude-code-mcp quickstart",
|
|
3226
|
+
"",
|
|
3227
|
+
"1. Call `claude_code` with `{ prompt }` and keep `sessionId`.",
|
|
3228
|
+
"2. Poll with `claude_code_check(action='poll')` using `nextCursor`.",
|
|
3229
|
+
"3. If actions are returned, respond with `claude_code_check(action='respond_permission')`.",
|
|
3230
|
+
"4. Continue polling until status becomes `idle` / `error` / `cancelled`.",
|
|
3231
|
+
"",
|
|
3232
|
+
"Notes:",
|
|
3233
|
+
"- `respond_user_input` is not supported on this backend.",
|
|
3234
|
+
"- `allowedTools` is pre-approval by default; set `strictAllowedTools=true` for strict allowlist behavior.",
|
|
3235
|
+
"- Prefer `responseMode='delta_compact'` for high-frequency polling."
|
|
3236
|
+
].join("\n"),
|
|
3237
|
+
"text/markdown"
|
|
3238
|
+
)
|
|
3239
|
+
);
|
|
3240
|
+
const errorsUri = new URL(RESOURCE_URIS.errors);
|
|
3241
|
+
server.registerResource(
|
|
3242
|
+
"errors",
|
|
3243
|
+
errorsUri.toString(),
|
|
3244
|
+
{
|
|
3245
|
+
title: "Errors",
|
|
3246
|
+
description: "Structured error codes and remediation hints.",
|
|
3247
|
+
mimeType: "application/json"
|
|
3248
|
+
},
|
|
3249
|
+
() => {
|
|
3250
|
+
const codes = Object.values(ErrorCode);
|
|
3251
|
+
const hints = {
|
|
3252
|
+
["INVALID_ARGUMENT" /* INVALID_ARGUMENT */]: "Validate required fields and enum values.",
|
|
3253
|
+
["SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */]: "Session may be expired or server-restarted.",
|
|
3254
|
+
["SESSION_BUSY" /* SESSION_BUSY */]: "Wait for running/waiting_permission session to settle.",
|
|
3255
|
+
["PERMISSION_REQUEST_NOT_FOUND" /* PERMISSION_REQUEST_NOT_FOUND */]: "The permission request was already finished/expired.",
|
|
3256
|
+
["PERMISSION_DENIED" /* PERMISSION_DENIED */]: "Check token/secrets/policy restrictions.",
|
|
3257
|
+
["RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */]: "Reduce session count or increase server limits.",
|
|
3258
|
+
["TIMEOUT" /* TIMEOUT */]: "Increase timeout or poll/respond more frequently.",
|
|
3259
|
+
["CANCELLED" /* CANCELLED */]: "Request/session was cancelled by caller or shutdown.",
|
|
3260
|
+
["INTERNAL" /* INTERNAL */]: "Inspect server logs and runtime environment."
|
|
3261
|
+
};
|
|
3262
|
+
return asJsonResource(
|
|
3263
|
+
errorsUri,
|
|
3264
|
+
asVersionedPayload({
|
|
3265
|
+
schemaVersion: resourceSchemaVersion,
|
|
3266
|
+
stability: "stable",
|
|
3267
|
+
payload: {
|
|
3268
|
+
codes,
|
|
3269
|
+
hints
|
|
3270
|
+
}
|
|
3271
|
+
})
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
);
|
|
3275
|
+
server.registerResource(
|
|
3276
|
+
"compat_report",
|
|
3277
|
+
compatReportUri.toString(),
|
|
3278
|
+
{
|
|
3279
|
+
title: "Compatibility Report",
|
|
3280
|
+
description: "Compatibility diagnostics for MCP clients and local runtime assumptions.",
|
|
3281
|
+
mimeType: "application/json"
|
|
3282
|
+
},
|
|
3283
|
+
() => {
|
|
3284
|
+
const runtimeWarnings = [];
|
|
3285
|
+
if (process.platform === "win32" && !process.env.CLAUDE_CODE_GIT_BASH_PATH) {
|
|
3286
|
+
runtimeWarnings.push(
|
|
3287
|
+
"CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
|
|
3288
|
+
);
|
|
3289
|
+
}
|
|
3290
|
+
const diskResumeEnabled = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
|
|
3291
|
+
const resumeSecretConfigured = typeof process.env.CLAUDE_CODE_MCP_RESUME_SECRET === "string" && process.env.CLAUDE_CODE_MCP_RESUME_SECRET.trim() !== "";
|
|
3292
|
+
const runtimeToolStats = deps.sessionManager.getRuntimeToolStats();
|
|
3293
|
+
const toolCatalogCount = deps.toolCache.getTools().length;
|
|
3294
|
+
const detectedMismatches = [];
|
|
3295
|
+
if (runtimeToolStats.sessionsWithInitTools > 0 && runtimeToolStats.runtimeDiscoveredUniqueCount < toolCatalogCount) {
|
|
3296
|
+
detectedMismatches.push(
|
|
3297
|
+
"Runtime discovered tools are fewer than catalog tools; some features may be hidden or not surfaced in system/init.tools."
|
|
3298
|
+
);
|
|
3299
|
+
}
|
|
3300
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3301
|
+
const base = {
|
|
3302
|
+
transport: "stdio",
|
|
3303
|
+
samePlatformRequired: true,
|
|
3304
|
+
packageVersion: deps.version,
|
|
3305
|
+
runtime: {
|
|
3306
|
+
node: process.version,
|
|
3307
|
+
platform: process.platform,
|
|
3308
|
+
arch: process.arch
|
|
3309
|
+
},
|
|
3310
|
+
limits: {
|
|
3311
|
+
maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
|
|
3312
|
+
maxPendingPermissionsPerSession: serializeLimit(
|
|
3313
|
+
deps.sessionManager.getMaxPendingPermissionsPerSession()
|
|
3314
|
+
),
|
|
3315
|
+
eventBuffer: deps.sessionManager.getEventBufferConfig()
|
|
3316
|
+
},
|
|
3317
|
+
diskResume: {
|
|
3318
|
+
enabled: diskResumeEnabled,
|
|
3319
|
+
resumeSecretConfigured
|
|
3320
|
+
},
|
|
3321
|
+
features: {
|
|
3322
|
+
resources: true,
|
|
3323
|
+
resourceTemplates: true,
|
|
3324
|
+
toolsListChanged: true,
|
|
3325
|
+
resourcesListChanged: true,
|
|
3326
|
+
sessionInterrupt: true,
|
|
3327
|
+
allowForSessionDecision: true,
|
|
3328
|
+
respondUserInput: false,
|
|
3329
|
+
prompts: false,
|
|
3330
|
+
completions: false
|
|
3331
|
+
},
|
|
3332
|
+
recommendedSettings: {
|
|
3333
|
+
responseMode: "delta_compact",
|
|
3334
|
+
poll: {
|
|
3335
|
+
runningMs: 3e3,
|
|
3336
|
+
waitingPermissionMs: 1e3,
|
|
3337
|
+
cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
|
|
3338
|
+
},
|
|
3339
|
+
timeouts: {
|
|
3340
|
+
sessionInitTimeoutMs: 1e4,
|
|
3341
|
+
permissionRequestTimeoutMs: 6e4,
|
|
3342
|
+
permissionRequestTimeoutMaxMs: 3e5
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
guidance: [
|
|
3346
|
+
"Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
|
|
3347
|
+
"Use allowedTools/disallowedTools only with exact runtime tool names.",
|
|
3348
|
+
"Set strictAllowedTools=true when you need allowedTools to behave as a strict allowlist.",
|
|
3349
|
+
"This server assumes MCP client and server run on the same machine/platform.",
|
|
3350
|
+
"For high-frequency status checks, prefer responseMode='delta_compact'.",
|
|
3351
|
+
"respond_user_input is not supported on this backend; use poll/respond_permission flow."
|
|
3352
|
+
],
|
|
3353
|
+
toolCounts: {
|
|
3354
|
+
catalogCount: toolCatalogCount,
|
|
3355
|
+
sessionsWithInitTools: runtimeToolStats.sessionsWithInitTools,
|
|
3356
|
+
runtimeDiscoveredUniqueCount: runtimeToolStats.runtimeDiscoveredUniqueCount,
|
|
3357
|
+
explain: "catalogCount is server catalog size; runtimeDiscoveredUniqueCount is union of system/init.tools across active sessions."
|
|
3358
|
+
},
|
|
3359
|
+
toolCatalogCount,
|
|
3360
|
+
detectedMismatches,
|
|
3361
|
+
runtimeWarnings,
|
|
3362
|
+
resourceTemplates: templateUris
|
|
3363
|
+
};
|
|
3364
|
+
const healthScore = Math.max(
|
|
3365
|
+
0,
|
|
3366
|
+
100 - runtimeWarnings.length * 10 - detectedMismatches.length * 15
|
|
3367
|
+
);
|
|
3368
|
+
return asJsonResource(
|
|
3369
|
+
compatReportUri,
|
|
3370
|
+
asVersionedPayload({
|
|
3371
|
+
schemaVersion: resourceSchemaVersion,
|
|
3372
|
+
stability: "stable",
|
|
3373
|
+
payload: {
|
|
3374
|
+
...base,
|
|
3375
|
+
healthScore,
|
|
3376
|
+
updatedAt
|
|
3377
|
+
}
|
|
3378
|
+
})
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
);
|
|
3382
|
+
server.registerResource(
|
|
3383
|
+
"session_snapshot_template",
|
|
3384
|
+
new ResourceTemplate(RESOURCE_TEMPLATES.sessionById, { list: void 0 }),
|
|
3385
|
+
{
|
|
3386
|
+
title: "Session Snapshot Template",
|
|
3387
|
+
description: "Read lightweight session diagnostics by sessionId.",
|
|
3388
|
+
mimeType: "application/json"
|
|
3389
|
+
},
|
|
3390
|
+
(uri, variables) => {
|
|
3391
|
+
const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
|
|
3392
|
+
const payload = typeof sessionId !== "string" || sessionId.trim() === "" ? {
|
|
3393
|
+
found: false,
|
|
3394
|
+
message: "sessionId is required in URI template variable."
|
|
3395
|
+
} : (() => {
|
|
3396
|
+
const session = deps.sessionManager.get(sessionId);
|
|
3397
|
+
if (!session) {
|
|
3398
|
+
return {
|
|
3399
|
+
sessionId,
|
|
3400
|
+
found: false,
|
|
3401
|
+
message: `Session '${sessionId}' not found.`
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
const base = deps.sessionManager.toPublicJSON(session);
|
|
3405
|
+
const stored = deps.sessionManager.getResult(sessionId);
|
|
3406
|
+
return {
|
|
3407
|
+
sessionId,
|
|
3408
|
+
found: true,
|
|
3409
|
+
session: {
|
|
3410
|
+
...base,
|
|
3411
|
+
pendingPermissionCount: deps.sessionManager.getPendingPermissionCount(sessionId),
|
|
3412
|
+
eventCount: deps.sessionManager.getEventCount(sessionId),
|
|
3413
|
+
currentCursor: deps.sessionManager.getCurrentCursor(sessionId),
|
|
3414
|
+
lastEventId: deps.sessionManager.getLastEventId(sessionId),
|
|
3415
|
+
ttlMs: deps.sessionManager.getRemainingTtlMs(sessionId),
|
|
3416
|
+
lastError: stored?.type === "error" ? stored.result.result : void 0,
|
|
3417
|
+
lastErrorAt: stored?.type === "error" ? stored.createdAt : void 0,
|
|
3418
|
+
redactions: buildSessionRedactions(false)
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
})();
|
|
3422
|
+
return asJsonResource(
|
|
3423
|
+
uri,
|
|
3424
|
+
asVersionedPayload({
|
|
3425
|
+
schemaVersion: resourceSchemaVersion,
|
|
3426
|
+
stability: "stable",
|
|
3427
|
+
payload
|
|
3428
|
+
})
|
|
3429
|
+
);
|
|
3430
|
+
}
|
|
3431
|
+
);
|
|
3432
|
+
server.registerResource(
|
|
3433
|
+
"runtime_tools_template",
|
|
3434
|
+
new ResourceTemplate(RESOURCE_TEMPLATES.runtimeTools, { list: void 0 }),
|
|
3435
|
+
{
|
|
3436
|
+
title: "Runtime Tools Template",
|
|
3437
|
+
description: "Read runtime tool view globally or for a specific sessionId.",
|
|
3438
|
+
mimeType: "application/json"
|
|
3439
|
+
},
|
|
3440
|
+
(uri, variables) => {
|
|
3441
|
+
const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
|
|
3442
|
+
const internalToolCount = catalogToolNames.size;
|
|
3443
|
+
const payload = (() => {
|
|
3444
|
+
if (typeof sessionId === "string" && sessionId.trim() !== "") {
|
|
3445
|
+
const session = deps.sessionManager.get(sessionId);
|
|
3446
|
+
if (!session) {
|
|
3447
|
+
return {
|
|
3448
|
+
sessionId,
|
|
3449
|
+
source: "session_not_found",
|
|
3450
|
+
availableTools: [],
|
|
3451
|
+
toolCounts: {
|
|
3452
|
+
internalToolCount,
|
|
3453
|
+
runtimeAvailableCount: 0,
|
|
3454
|
+
sessionAugmentedToolCount: 0
|
|
3455
|
+
}
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
const initTools = deps.sessionManager.getInitTools(sessionId) ?? [];
|
|
3459
|
+
const discovered = discoverToolsFromInit(initTools);
|
|
3460
|
+
const sessionAugmentedToolCount2 = discovered.filter(
|
|
3461
|
+
(tool) => !catalogToolNames.has(tool.name)
|
|
3462
|
+
).length;
|
|
3463
|
+
return {
|
|
3464
|
+
sessionId,
|
|
3465
|
+
source: initTools.length > 0 ? "session_runtime" : "session_without_init",
|
|
3466
|
+
availableTools: discovered,
|
|
3467
|
+
toolCounts: {
|
|
3468
|
+
internalToolCount,
|
|
3469
|
+
runtimeAvailableCount: discovered.length,
|
|
3470
|
+
sessionAugmentedToolCount: sessionAugmentedToolCount2
|
|
3471
|
+
}
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
const catalog = deps.toolCache.getTools();
|
|
3475
|
+
const sessionAugmentedToolCount = catalog.filter(
|
|
3476
|
+
(tool) => !catalogToolNames.has(tool.name)
|
|
3477
|
+
).length;
|
|
3478
|
+
return {
|
|
3479
|
+
source: "catalog",
|
|
3480
|
+
availableTools: catalog,
|
|
3481
|
+
toolCounts: {
|
|
3482
|
+
internalToolCount,
|
|
3483
|
+
runtimeAvailableCount: catalog.length,
|
|
3484
|
+
sessionAugmentedToolCount
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
})();
|
|
3488
|
+
return asJsonResource(
|
|
3489
|
+
uri,
|
|
3490
|
+
asVersionedPayload({
|
|
3491
|
+
schemaVersion: resourceSchemaVersion,
|
|
3492
|
+
stability: "stable",
|
|
3493
|
+
payload
|
|
3494
|
+
})
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
);
|
|
3498
|
+
server.registerResource(
|
|
3499
|
+
"compat_diff_template",
|
|
3500
|
+
new ResourceTemplate(RESOURCE_TEMPLATES.compatDiff, { list: void 0 }),
|
|
3501
|
+
{
|
|
3502
|
+
title: "Compatibility Diff Template",
|
|
3503
|
+
description: "Returns client-specific compatibility guidance and recommended settings.",
|
|
3504
|
+
mimeType: "application/json"
|
|
3505
|
+
},
|
|
3506
|
+
(uri, variables) => {
|
|
3507
|
+
const clientRaw = extractSingleVariable(variables.client) ?? extractSingleVariable(uri.searchParams.get("client"));
|
|
3508
|
+
const clientFingerprint = (clientRaw ?? "unknown").trim();
|
|
3509
|
+
const key = clientFingerprint.toLowerCase();
|
|
3510
|
+
const profile = (() => {
|
|
3511
|
+
if (key.includes("codex")) {
|
|
3512
|
+
return {
|
|
3513
|
+
clientFamily: "codex",
|
|
3514
|
+
detectedMismatches: [],
|
|
3515
|
+
recommendations: [
|
|
3516
|
+
"Prefer responseMode='delta_compact' for fast status loops.",
|
|
3517
|
+
"Enable pollOptions.includeTools=true when exact runtime tool names are required."
|
|
3518
|
+
]
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
if (key.includes("claude")) {
|
|
3522
|
+
return {
|
|
3523
|
+
clientFamily: "claude",
|
|
3524
|
+
detectedMismatches: [],
|
|
3525
|
+
recommendations: [
|
|
3526
|
+
"Use resources and resource templates for low-latency diagnostics.",
|
|
3527
|
+
"Use allowedTools/disallowedTools with exact runtime names.",
|
|
3528
|
+
"Enable strictAllowedTools when running in locked-down governance mode."
|
|
3529
|
+
]
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
if (key.includes("cursor")) {
|
|
3533
|
+
return {
|
|
3534
|
+
clientFamily: "cursor",
|
|
3535
|
+
detectedMismatches: [
|
|
3536
|
+
"Verify that resources/list and resourceTemplates/list are refreshed after list_changed notifications."
|
|
3537
|
+
],
|
|
3538
|
+
recommendations: [
|
|
3539
|
+
"Prefer claude_code_check polling as source of truth for runtime state.",
|
|
3540
|
+
"Fallback to tool calls if resource template support is partial."
|
|
3541
|
+
]
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
return {
|
|
3545
|
+
clientFamily: "generic",
|
|
3546
|
+
detectedMismatches: [],
|
|
3547
|
+
recommendations: [
|
|
3548
|
+
"Persist nextCursor and de-duplicate by event.id.",
|
|
3549
|
+
"Use responseMode='delta_compact' for high-frequency polling, full mode only for diagnostics."
|
|
3550
|
+
]
|
|
3551
|
+
};
|
|
3552
|
+
})();
|
|
3553
|
+
return asJsonResource(
|
|
3554
|
+
uri,
|
|
3555
|
+
asVersionedPayload({
|
|
3556
|
+
schemaVersion: resourceSchemaVersion,
|
|
3557
|
+
stability: "experimental",
|
|
3558
|
+
payload: {
|
|
3559
|
+
clientFingerprint,
|
|
3560
|
+
...profile,
|
|
3561
|
+
recommendedSettings: {
|
|
3562
|
+
responseMode: "delta_compact",
|
|
3563
|
+
poll: {
|
|
3564
|
+
runningMs: 3e3,
|
|
3565
|
+
waitingPermissionMs: 1e3,
|
|
3566
|
+
cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
})
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
);
|
|
1987
3574
|
}
|
|
1988
3575
|
|
|
1989
3576
|
// src/server.ts
|
|
1990
|
-
var SERVER_VERSION = true ? "2.0
|
|
1991
|
-
function
|
|
3577
|
+
var SERVER_VERSION = true ? "2.4.0" : "0.0.0-dev";
|
|
3578
|
+
function createServerContext(serverCwd) {
|
|
1992
3579
|
const sessionManager = new SessionManager();
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
3580
|
+
const server = new McpServer(
|
|
3581
|
+
{
|
|
3582
|
+
name: "claude-code-mcp",
|
|
3583
|
+
version: SERVER_VERSION,
|
|
3584
|
+
title: "Claude Code MCP",
|
|
3585
|
+
description: "MCP server that runs Claude Code via the Claude Agent SDK with async polling and interactive permissions.",
|
|
3586
|
+
websiteUrl: "https://github.com/xihuai18/claude-code-mcp",
|
|
3587
|
+
icons: []
|
|
3588
|
+
},
|
|
3589
|
+
{
|
|
3590
|
+
capabilities: {
|
|
3591
|
+
logging: {},
|
|
3592
|
+
tools: { listChanged: true },
|
|
3593
|
+
resources: { listChanged: true }
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
);
|
|
3597
|
+
const claudeCodeToolRef = {};
|
|
3598
|
+
const notifyInternalToolsResourceChanged = () => {
|
|
3599
|
+
if (!server.isConnected()) return;
|
|
3600
|
+
server.sendResourceListChanged();
|
|
3601
|
+
};
|
|
3602
|
+
const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
|
|
3603
|
+
try {
|
|
3604
|
+
claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
|
|
3605
|
+
if (server.isConnected()) {
|
|
3606
|
+
server.sendToolListChanged();
|
|
3607
|
+
}
|
|
3608
|
+
notifyInternalToolsResourceChanged();
|
|
3609
|
+
} catch {
|
|
3610
|
+
}
|
|
1997
3611
|
});
|
|
1998
3612
|
const agentDefinitionSchema = z.object({
|
|
1999
3613
|
description: z.string(),
|
|
2000
3614
|
prompt: z.string(),
|
|
2001
|
-
tools: z.array(z.string()).optional(),
|
|
2002
|
-
disallowedTools: z.array(z.string()).optional(),
|
|
2003
|
-
model: z.enum(AGENT_MODELS).optional(),
|
|
2004
|
-
maxTurns: z.number().int().positive().optional(),
|
|
2005
|
-
mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
|
|
2006
|
-
skills: z.array(z.string()).optional(),
|
|
2007
|
-
criticalSystemReminder_EXPERIMENTAL: z.string().optional()
|
|
3615
|
+
tools: z.array(z.string()).optional().describe("Default: inherit"),
|
|
3616
|
+
disallowedTools: z.array(z.string()).optional().describe("Default: none"),
|
|
3617
|
+
model: z.enum(AGENT_MODELS).optional().describe("Default: inherit"),
|
|
3618
|
+
maxTurns: z.number().int().positive().optional().describe("Default: none"),
|
|
3619
|
+
mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional().describe("Default: inherit"),
|
|
3620
|
+
skills: z.array(z.string()).optional().describe("Default: none"),
|
|
3621
|
+
criticalSystemReminder_EXPERIMENTAL: z.string().optional().describe("Default: none")
|
|
2008
3622
|
});
|
|
2009
3623
|
const systemPromptSchema = z.union([
|
|
2010
3624
|
z.string(),
|
|
2011
3625
|
z.object({
|
|
2012
3626
|
type: z.literal("preset"),
|
|
2013
3627
|
preset: z.literal("claude_code"),
|
|
2014
|
-
append: z.string().optional().describe("
|
|
3628
|
+
append: z.string().optional().describe("Default: none")
|
|
2015
3629
|
})
|
|
2016
3630
|
]);
|
|
2017
3631
|
const toolsConfigSchema = z.union([
|
|
@@ -2029,47 +3643,137 @@ function createServer(serverCwd) {
|
|
|
2029
3643
|
}),
|
|
2030
3644
|
z.object({ type: z.literal("disabled") })
|
|
2031
3645
|
]);
|
|
3646
|
+
const effortOptionSchema = z.enum(EFFORT_LEVELS).optional();
|
|
3647
|
+
const thinkingOptionSchema = thinkingSchema.optional();
|
|
2032
3648
|
const outputFormatSchema = z.object({
|
|
2033
3649
|
type: z.literal("json_schema"),
|
|
2034
3650
|
schema: z.record(z.string(), z.unknown())
|
|
2035
3651
|
});
|
|
2036
|
-
const
|
|
2037
|
-
tools: toolsConfigSchema.optional().describe("
|
|
3652
|
+
const sharedOptionFieldsSchemaShape = {
|
|
3653
|
+
tools: toolsConfigSchema.optional().describe("Tool set. Default: SDK"),
|
|
2038
3654
|
persistSession: z.boolean().optional().describe("Default: true"),
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
agent: z.string().optional().describe("Primary agent name (from 'agents'). Default: none"),
|
|
3655
|
+
agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
|
|
3656
|
+
agent: z.string().optional().describe("Default: none"),
|
|
2042
3657
|
maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
|
|
2043
|
-
effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
|
|
2044
3658
|
betas: z.array(z.string()).optional().describe("Default: none"),
|
|
2045
3659
|
additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
|
|
2046
|
-
outputFormat: outputFormatSchema.optional().describe("Default: none
|
|
2047
|
-
thinking: thinkingSchema.optional().describe("Default: SDK"),
|
|
3660
|
+
outputFormat: outputFormatSchema.optional().describe("Default: none"),
|
|
2048
3661
|
pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
|
|
2049
3662
|
mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
|
|
2050
3663
|
sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
2051
3664
|
fallbackModel: z.string().optional().describe("Default: none"),
|
|
2052
3665
|
enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
|
|
2053
|
-
includePartialMessages: z.boolean().optional().describe("
|
|
3666
|
+
includePartialMessages: z.boolean().optional().describe("Default: false"),
|
|
2054
3667
|
strictMcpConfig: z.boolean().optional().describe("Default: false"),
|
|
2055
|
-
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. []
|
|
3668
|
+
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. []=isolation"),
|
|
2056
3669
|
debug: z.boolean().optional().describe("Default: false"),
|
|
2057
|
-
debugFile: z.string().optional().describe("
|
|
2058
|
-
env: z.record(z.string(), z.string().optional()).optional().describe("
|
|
2059
|
-
}
|
|
2060
|
-
|
|
3670
|
+
debugFile: z.string().optional().describe("Default: none"),
|
|
3671
|
+
env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
|
|
3672
|
+
};
|
|
3673
|
+
const advancedOptionFieldsSchemaShape = {
|
|
3674
|
+
...sharedOptionFieldsSchemaShape,
|
|
3675
|
+
effort: effortOptionSchema.describe("Deprecated, use top-level. Default: SDK"),
|
|
3676
|
+
thinking: thinkingOptionSchema.describe("Deprecated, use top-level. Default: SDK")
|
|
3677
|
+
};
|
|
3678
|
+
const diskResumeOptionFieldsSchemaShape = {
|
|
3679
|
+
...sharedOptionFieldsSchemaShape,
|
|
3680
|
+
effort: effortOptionSchema.describe("Default: SDK"),
|
|
3681
|
+
thinking: thinkingOptionSchema.describe("Default: SDK")
|
|
3682
|
+
};
|
|
3683
|
+
const advancedOptionsSchema = z.object({
|
|
3684
|
+
...advancedOptionFieldsSchemaShape,
|
|
3685
|
+
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000")
|
|
3686
|
+
}).optional().describe("Default: none");
|
|
3687
|
+
const diskResumeConfigSchema = z.object({
|
|
3688
|
+
resumeToken: z.string(),
|
|
3689
|
+
cwd: z.string(),
|
|
3690
|
+
allowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
3691
|
+
disallowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
3692
|
+
strictAllowedTools: z.boolean().optional().describe("Default: false"),
|
|
3693
|
+
maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
|
|
3694
|
+
model: z.string().optional().describe("Default: SDK"),
|
|
3695
|
+
systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
|
|
3696
|
+
resumeSessionAt: z.string().optional().describe("Default: none"),
|
|
3697
|
+
...diskResumeOptionFieldsSchemaShape
|
|
3698
|
+
}).optional().describe("Default: none");
|
|
3699
|
+
const startResultSchema = z.object({
|
|
3700
|
+
sessionId: z.string(),
|
|
3701
|
+
status: z.enum(["running", "error"]),
|
|
3702
|
+
pollInterval: z.number().optional(),
|
|
3703
|
+
resumeToken: z.string().optional(),
|
|
3704
|
+
compatWarnings: z.array(z.string()).optional(),
|
|
3705
|
+
error: z.string().optional()
|
|
3706
|
+
}).passthrough();
|
|
3707
|
+
const sessionResultSchema = z.object({
|
|
3708
|
+
sessions: z.array(z.record(z.string(), z.unknown())),
|
|
3709
|
+
message: z.string().optional(),
|
|
3710
|
+
isError: z.boolean().optional()
|
|
3711
|
+
}).passthrough();
|
|
3712
|
+
const checkEventSchema = z.object({
|
|
3713
|
+
id: z.number().int().nonnegative(),
|
|
3714
|
+
type: z.string(),
|
|
3715
|
+
data: z.unknown(),
|
|
3716
|
+
timestamp: z.string()
|
|
3717
|
+
}).passthrough();
|
|
3718
|
+
const checkActionSchema = z.object({
|
|
3719
|
+
type: z.string(),
|
|
3720
|
+
requestId: z.string().optional(),
|
|
3721
|
+
toolName: z.string().optional(),
|
|
3722
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
3723
|
+
summary: z.string().optional()
|
|
3724
|
+
}).passthrough();
|
|
3725
|
+
const checkResultSchema = z.object({
|
|
3726
|
+
sessionId: z.string(),
|
|
3727
|
+
status: z.string(),
|
|
3728
|
+
pollInterval: z.number().optional(),
|
|
3729
|
+
cursorResetTo: z.number().optional(),
|
|
3730
|
+
truncated: z.boolean().optional(),
|
|
3731
|
+
truncatedFields: z.array(z.string()).optional(),
|
|
3732
|
+
events: z.array(checkEventSchema),
|
|
3733
|
+
nextCursor: z.number().optional(),
|
|
3734
|
+
availableTools: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
3735
|
+
toolValidation: z.object({
|
|
3736
|
+
runtimeToolsKnown: z.boolean(),
|
|
3737
|
+
unknownAllowedTools: z.array(z.string()),
|
|
3738
|
+
unknownDisallowedTools: z.array(z.string())
|
|
3739
|
+
}).optional(),
|
|
3740
|
+
compatWarnings: z.array(z.string()).optional(),
|
|
3741
|
+
actions: z.array(checkActionSchema).optional(),
|
|
3742
|
+
result: z.unknown().optional(),
|
|
3743
|
+
cancelledAt: z.string().optional(),
|
|
3744
|
+
cancelledReason: z.string().optional(),
|
|
3745
|
+
cancelledSource: z.string().optional(),
|
|
3746
|
+
lastEventId: z.number().optional(),
|
|
3747
|
+
lastToolUseId: z.string().optional(),
|
|
3748
|
+
isError: z.boolean().optional(),
|
|
3749
|
+
error: z.string().optional()
|
|
3750
|
+
}).passthrough();
|
|
3751
|
+
claudeCodeToolRef.current = server.registerTool(
|
|
2061
3752
|
"claude_code",
|
|
2062
|
-
buildInternalToolsDescription(toolCache.getTools()),
|
|
2063
3753
|
{
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
3754
|
+
description: buildInternalToolsDescription(toolCache.getTools()),
|
|
3755
|
+
inputSchema: {
|
|
3756
|
+
prompt: z.string().describe("Prompt"),
|
|
3757
|
+
cwd: z.string().optional().describe("Working dir. Default: server cwd"),
|
|
3758
|
+
allowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
3759
|
+
disallowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
3760
|
+
strictAllowedTools: z.boolean().optional().describe("Default: false"),
|
|
3761
|
+
maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
|
|
3762
|
+
model: z.string().optional().describe("Default: SDK"),
|
|
3763
|
+
effort: effortOptionSchema.describe("Default: SDK"),
|
|
3764
|
+
thinking: thinkingOptionSchema.describe("Default: SDK"),
|
|
3765
|
+
systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
|
|
3766
|
+
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
|
|
3767
|
+
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
|
|
3768
|
+
advanced: advancedOptionsSchema
|
|
3769
|
+
},
|
|
3770
|
+
outputSchema: startResultSchema,
|
|
3771
|
+
annotations: {
|
|
3772
|
+
readOnlyHint: false,
|
|
3773
|
+
destructiveHint: true,
|
|
3774
|
+
idempotentHint: false,
|
|
3775
|
+
openWorldHint: true
|
|
3776
|
+
}
|
|
2073
3777
|
},
|
|
2074
3778
|
async (args, extra) => {
|
|
2075
3779
|
try {
|
|
@@ -2088,76 +3792,50 @@ function createServer(serverCwd) {
|
|
|
2088
3792
|
text: JSON.stringify(result, null, 2)
|
|
2089
3793
|
}
|
|
2090
3794
|
],
|
|
3795
|
+
structuredContent: result,
|
|
2091
3796
|
isError
|
|
2092
3797
|
};
|
|
2093
3798
|
} catch (err) {
|
|
2094
3799
|
const message = err instanceof Error ? err.message : String(err);
|
|
3800
|
+
const errorResult = {
|
|
3801
|
+
sessionId: "",
|
|
3802
|
+
status: "error",
|
|
3803
|
+
error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
|
|
3804
|
+
};
|
|
2095
3805
|
return {
|
|
2096
3806
|
content: [
|
|
2097
3807
|
{
|
|
2098
3808
|
type: "text",
|
|
2099
|
-
text:
|
|
3809
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
2100
3810
|
}
|
|
2101
3811
|
],
|
|
3812
|
+
structuredContent: errorResult,
|
|
2102
3813
|
isError: true
|
|
2103
3814
|
};
|
|
2104
3815
|
}
|
|
2105
3816
|
}
|
|
2106
3817
|
);
|
|
2107
|
-
server.
|
|
3818
|
+
server.registerTool(
|
|
2108
3819
|
"claude_code_reply",
|
|
2109
|
-
`Send a follow-up message to an existing Claude Code session.
|
|
2110
|
-
|
|
2111
|
-
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.
|
|
2112
|
-
|
|
2113
|
-
Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
|
|
2114
|
-
|
|
2115
|
-
Defaults:
|
|
2116
|
-
- forkSession: false
|
|
2117
|
-
- sessionInitTimeoutMs: 10000 (only used when forkSession=true)
|
|
2118
|
-
- permissionRequestTimeoutMs: 60000
|
|
2119
|
-
- Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
|
|
2120
|
-
|
|
2121
|
-
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.`,
|
|
2122
3820
|
{
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
|
|
2142
|
-
betas: z.array(z.string()).optional().describe("Default: none"),
|
|
2143
|
-
additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
|
|
2144
|
-
outputFormat: outputFormatSchema.optional().describe("Default: none"),
|
|
2145
|
-
thinking: thinkingSchema.optional().describe("Default: SDK"),
|
|
2146
|
-
resumeSessionAt: z.string().optional().describe("Resume to specific message UUID. Default: none"),
|
|
2147
|
-
pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
|
|
2148
|
-
mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
|
|
2149
|
-
sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
2150
|
-
fallbackModel: z.string().optional().describe("Default: none"),
|
|
2151
|
-
enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
|
|
2152
|
-
includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
|
|
2153
|
-
strictMcpConfig: z.boolean().optional().describe("Default: false"),
|
|
2154
|
-
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
|
|
2155
|
-
debug: z.boolean().optional().describe("Default: false"),
|
|
2156
|
-
debugFile: z.string().optional().describe("Enables debug. Default: none"),
|
|
2157
|
-
env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
|
|
2158
|
-
}).optional().describe(
|
|
2159
|
-
"Disk resume config (needs CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1). Requires resumeToken + cwd."
|
|
2160
|
-
)
|
|
3821
|
+
description: "Send a follow-up to an existing session. Returns immediately; use claude_code_check to poll.",
|
|
3822
|
+
inputSchema: {
|
|
3823
|
+
sessionId: z.string().describe("Session ID"),
|
|
3824
|
+
prompt: z.string().describe("Prompt"),
|
|
3825
|
+
forkSession: z.boolean().optional().describe("Default: false"),
|
|
3826
|
+
effort: effortOptionSchema.describe("Default: SDK"),
|
|
3827
|
+
thinking: thinkingOptionSchema.describe("Default: SDK"),
|
|
3828
|
+
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
|
|
3829
|
+
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
|
|
3830
|
+
diskResumeConfig: diskResumeConfigSchema
|
|
3831
|
+
},
|
|
3832
|
+
outputSchema: startResultSchema,
|
|
3833
|
+
annotations: {
|
|
3834
|
+
readOnlyHint: false,
|
|
3835
|
+
destructiveHint: true,
|
|
3836
|
+
idempotentHint: false,
|
|
3837
|
+
openWorldHint: true
|
|
3838
|
+
}
|
|
2161
3839
|
},
|
|
2162
3840
|
async (args, extra) => {
|
|
2163
3841
|
try {
|
|
@@ -2170,137 +3848,339 @@ Disk resume: If the server restarted and the session is no longer in memory, set
|
|
|
2170
3848
|
text: JSON.stringify(result, null, 2)
|
|
2171
3849
|
}
|
|
2172
3850
|
],
|
|
3851
|
+
structuredContent: result,
|
|
2173
3852
|
isError
|
|
2174
3853
|
};
|
|
2175
3854
|
} catch (err) {
|
|
2176
3855
|
const message = err instanceof Error ? err.message : String(err);
|
|
3856
|
+
const errorResult = {
|
|
3857
|
+
sessionId: "",
|
|
3858
|
+
status: "error",
|
|
3859
|
+
error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
|
|
3860
|
+
};
|
|
2177
3861
|
return {
|
|
2178
3862
|
content: [
|
|
2179
3863
|
{
|
|
2180
3864
|
type: "text",
|
|
2181
|
-
text:
|
|
3865
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
2182
3866
|
}
|
|
2183
3867
|
],
|
|
3868
|
+
structuredContent: errorResult,
|
|
2184
3869
|
isError: true
|
|
2185
3870
|
};
|
|
2186
3871
|
}
|
|
2187
3872
|
}
|
|
2188
3873
|
);
|
|
2189
|
-
server.
|
|
3874
|
+
server.registerTool(
|
|
2190
3875
|
"claude_code_session",
|
|
2191
|
-
`List, inspect, or cancel Claude Code sessions.
|
|
2192
|
-
|
|
2193
|
-
- action="list": Get all sessions with their status, cost, turn count, and settings.
|
|
2194
|
-
- action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
|
|
2195
|
-
- action="cancel": Stop a running session immediately (pass sessionId).`,
|
|
2196
3876
|
{
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
3877
|
+
description: "List, inspect, cancel, or interrupt sessions.",
|
|
3878
|
+
inputSchema: {
|
|
3879
|
+
action: z.enum(SESSION_ACTIONS),
|
|
3880
|
+
sessionId: z.string().optional().describe("Required for get/cancel/interrupt"),
|
|
3881
|
+
includeSensitive: z.boolean().optional().describe("Default: false")
|
|
3882
|
+
},
|
|
3883
|
+
outputSchema: sessionResultSchema,
|
|
3884
|
+
annotations: {
|
|
3885
|
+
readOnlyHint: false,
|
|
3886
|
+
destructiveHint: true,
|
|
3887
|
+
idempotentHint: false,
|
|
3888
|
+
openWorldHint: false
|
|
3889
|
+
}
|
|
2200
3890
|
},
|
|
2201
|
-
async (args) => {
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
3891
|
+
async (args, extra) => {
|
|
3892
|
+
try {
|
|
3893
|
+
const result = executeClaudeCodeSession(args, sessionManager, extra.signal);
|
|
3894
|
+
return {
|
|
3895
|
+
content: [
|
|
3896
|
+
{
|
|
3897
|
+
type: "text",
|
|
3898
|
+
text: JSON.stringify(result, null, 2)
|
|
3899
|
+
}
|
|
3900
|
+
],
|
|
3901
|
+
structuredContent: result,
|
|
3902
|
+
isError: result.isError ?? false
|
|
3903
|
+
};
|
|
3904
|
+
} catch (err) {
|
|
3905
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3906
|
+
const errorResult = {
|
|
3907
|
+
sessions: [],
|
|
3908
|
+
message: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`,
|
|
3909
|
+
isError: true
|
|
3910
|
+
};
|
|
3911
|
+
return {
|
|
3912
|
+
content: [
|
|
3913
|
+
{
|
|
3914
|
+
type: "text",
|
|
3915
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
3916
|
+
}
|
|
3917
|
+
],
|
|
3918
|
+
structuredContent: errorResult,
|
|
3919
|
+
isError: true
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
2212
3922
|
}
|
|
2213
3923
|
);
|
|
2214
|
-
server.
|
|
3924
|
+
server.registerTool(
|
|
2215
3925
|
"claude_code_check",
|
|
2216
|
-
`Query a running session for new events, retrieve the final result, or respond to permission requests.
|
|
2217
|
-
|
|
2218
|
-
Two actions:
|
|
2219
|
-
|
|
2220
|
-
Defaults (poll):
|
|
2221
|
-
- responseMode: "minimal"
|
|
2222
|
-
- minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
|
|
2223
|
-
- maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
|
|
2224
|
-
|
|
2225
|
-
action="poll" \u2014 Retrieve events since the last poll.
|
|
2226
|
-
Returns events (agent output, progress updates, permission requests, errors, final result).
|
|
2227
|
-
Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
|
|
2228
|
-
If the agent is waiting for permission, the response includes an "actions" array with pending requests.
|
|
2229
|
-
|
|
2230
|
-
action="respond_permission" \u2014 Approve or deny a pending permission request.
|
|
2231
|
-
Pass the requestId from the actions array, plus decision="allow" or decision="deny".
|
|
2232
|
-
Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
|
|
2233
|
-
The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
|
|
2234
3926
|
{
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
3927
|
+
description: "Poll session events or respond to permission requests.",
|
|
3928
|
+
inputSchema: {
|
|
3929
|
+
action: z.enum(CHECK_ACTIONS),
|
|
3930
|
+
sessionId: z.string().describe("Session ID"),
|
|
3931
|
+
cursor: z.number().int().nonnegative().optional().describe("Default: 0"),
|
|
3932
|
+
responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'. Use 'delta_compact' for lightweight polling."),
|
|
3933
|
+
maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full/delta_compact)"),
|
|
3934
|
+
requestId: z.string().optional().describe("Default: none"),
|
|
3935
|
+
decision: z.enum(["allow", "deny", "allow_for_session"]).optional().describe("Default: none"),
|
|
3936
|
+
denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
|
|
3937
|
+
interrupt: z.boolean().optional().describe("Default: false"),
|
|
3938
|
+
pollOptions: z.object({
|
|
3939
|
+
includeTools: z.boolean().optional().describe("Default: false"),
|
|
3940
|
+
includeEvents: z.boolean().optional().describe("Default: true"),
|
|
3941
|
+
includeActions: z.boolean().optional().describe("Default: true"),
|
|
3942
|
+
includeResult: z.boolean().optional().describe("Default: true"),
|
|
3943
|
+
includeUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3944
|
+
includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3945
|
+
includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3946
|
+
includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3947
|
+
includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3948
|
+
maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
|
|
3949
|
+
}).optional().describe("Default: none"),
|
|
3950
|
+
permissionOptions: z.object({
|
|
3951
|
+
updatedInput: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
3952
|
+
updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Default: none")
|
|
3953
|
+
}).optional().describe("Default: none")
|
|
3954
|
+
},
|
|
3955
|
+
outputSchema: checkResultSchema,
|
|
3956
|
+
annotations: {
|
|
3957
|
+
readOnlyHint: false,
|
|
3958
|
+
destructiveHint: true,
|
|
3959
|
+
idempotentHint: false,
|
|
3960
|
+
openWorldHint: false
|
|
3961
|
+
}
|
|
2259
3962
|
},
|
|
2260
|
-
async (args) => {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
3963
|
+
async (args, extra) => {
|
|
3964
|
+
try {
|
|
3965
|
+
const result = executeClaudeCodeCheck(args, sessionManager, toolCache, extra.signal);
|
|
3966
|
+
const isError = result.isError === true;
|
|
3967
|
+
return {
|
|
3968
|
+
content: [
|
|
3969
|
+
{
|
|
3970
|
+
type: "text",
|
|
3971
|
+
text: JSON.stringify(result, null, 2)
|
|
3972
|
+
}
|
|
3973
|
+
],
|
|
3974
|
+
structuredContent: result,
|
|
3975
|
+
isError
|
|
3976
|
+
};
|
|
3977
|
+
} catch (err) {
|
|
3978
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3979
|
+
const errorResult = {
|
|
3980
|
+
sessionId: args.sessionId ?? "",
|
|
3981
|
+
status: "error",
|
|
3982
|
+
events: [],
|
|
3983
|
+
isError: true,
|
|
3984
|
+
error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
|
|
3985
|
+
};
|
|
3986
|
+
return {
|
|
3987
|
+
content: [
|
|
3988
|
+
{
|
|
3989
|
+
type: "text",
|
|
3990
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
3991
|
+
}
|
|
3992
|
+
],
|
|
3993
|
+
structuredContent: errorResult,
|
|
3994
|
+
isError: true
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
2272
3997
|
}
|
|
2273
3998
|
);
|
|
2274
|
-
registerResources(server, { toolCache });
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
3999
|
+
registerResources(server, { toolCache, version: SERVER_VERSION, sessionManager });
|
|
4000
|
+
return { server, sessionManager, toolCache };
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
// src/utils/runtime-errors.ts
|
|
4004
|
+
var BENIGN_CODES = /* @__PURE__ */ new Set([
|
|
4005
|
+
"EPIPE",
|
|
4006
|
+
"ECONNRESET",
|
|
4007
|
+
"ERR_STREAM_WRITE_AFTER_END",
|
|
4008
|
+
"ERR_STREAM_DESTROYED",
|
|
4009
|
+
"ABORT_ERR"
|
|
4010
|
+
]);
|
|
4011
|
+
var BENIGN_MESSAGE_PATTERNS = [
|
|
4012
|
+
"operation aborted",
|
|
4013
|
+
"request was cancelled",
|
|
4014
|
+
"transport closed",
|
|
4015
|
+
"write after end",
|
|
4016
|
+
"stream was destroyed",
|
|
4017
|
+
"cannot call write after a stream was destroyed",
|
|
4018
|
+
"the pipe is being closed"
|
|
4019
|
+
];
|
|
4020
|
+
function isBenignRuntimeError(error) {
|
|
4021
|
+
if (!(error instanceof Error)) return false;
|
|
4022
|
+
const withCode = error;
|
|
4023
|
+
const code = typeof withCode.code === "string" ? withCode.code : void 0;
|
|
4024
|
+
if (code && BENIGN_CODES.has(code)) return true;
|
|
4025
|
+
if (error.name === "AbortError") return true;
|
|
4026
|
+
const message = error.message.toLowerCase();
|
|
4027
|
+
return BENIGN_MESSAGE_PATTERNS.some((pattern) => message.includes(pattern));
|
|
2281
4028
|
}
|
|
2282
4029
|
|
|
2283
4030
|
// src/index.ts
|
|
4031
|
+
var STDIN_SHUTDOWN_CHECK_MS = 750;
|
|
4032
|
+
var STDIN_SHUTDOWN_MAX_WAIT_MS = process.platform === "win32" ? 15e3 : 1e4;
|
|
4033
|
+
function summarizeSessions(ctx) {
|
|
4034
|
+
const sessions = ctx.sessionManager.list();
|
|
4035
|
+
const running = sessions.filter((s) => s.status === "running").length;
|
|
4036
|
+
const waitingPermission = sessions.filter((s) => s.status === "waiting_permission").length;
|
|
4037
|
+
return {
|
|
4038
|
+
total: sessions.length,
|
|
4039
|
+
running,
|
|
4040
|
+
waitingPermission,
|
|
4041
|
+
terminal: sessions.length - running - waitingPermission
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
2284
4044
|
async function main() {
|
|
2285
4045
|
const serverCwd = process.cwd();
|
|
2286
|
-
const
|
|
4046
|
+
const ctx = createServerContext(serverCwd);
|
|
4047
|
+
const server = ctx.server;
|
|
4048
|
+
const sessionManager = ctx.sessionManager;
|
|
2287
4049
|
const transport = new StdioServerTransport();
|
|
2288
4050
|
let closing = false;
|
|
2289
|
-
|
|
4051
|
+
let lastExitCode = 0;
|
|
4052
|
+
let stdinClosedAt;
|
|
4053
|
+
let stdinClosedReason;
|
|
4054
|
+
let stdinShutdownTimer;
|
|
4055
|
+
const onStdinEnd = () => handleStdinTerminated("end");
|
|
4056
|
+
const onStdinClose = () => handleStdinTerminated("close");
|
|
4057
|
+
const clearStdinShutdownTimer = () => {
|
|
4058
|
+
if (stdinShutdownTimer) {
|
|
4059
|
+
clearTimeout(stdinShutdownTimer);
|
|
4060
|
+
stdinShutdownTimer = void 0;
|
|
4061
|
+
}
|
|
4062
|
+
};
|
|
4063
|
+
const shutdown = async (reason = "unknown") => {
|
|
2290
4064
|
if (closing) return;
|
|
2291
4065
|
closing = true;
|
|
4066
|
+
clearStdinShutdownTimer();
|
|
4067
|
+
if (typeof process.stdin.off === "function") {
|
|
4068
|
+
process.stdin.off("error", handleStdinError);
|
|
4069
|
+
process.stdin.off("end", onStdinEnd);
|
|
4070
|
+
process.stdin.off("close", onStdinClose);
|
|
4071
|
+
}
|
|
4072
|
+
const forceExitMs = process.platform === "win32" ? 1e4 : 5e3;
|
|
4073
|
+
const forceExitTimer = setTimeout(() => process.exit(lastExitCode), forceExitMs);
|
|
4074
|
+
if (forceExitTimer.unref) forceExitTimer.unref();
|
|
4075
|
+
const sessionSummary = summarizeSessions(ctx);
|
|
2292
4076
|
try {
|
|
4077
|
+
if (server?.isConnected()) {
|
|
4078
|
+
await server.sendLoggingMessage({
|
|
4079
|
+
level: "info",
|
|
4080
|
+
data: { event: "server_stopping", reason, ...sessionSummary }
|
|
4081
|
+
});
|
|
4082
|
+
}
|
|
4083
|
+
sessionManager.destroy();
|
|
2293
4084
|
await server.close();
|
|
2294
4085
|
} catch {
|
|
2295
4086
|
}
|
|
2296
|
-
process.exitCode =
|
|
2297
|
-
|
|
4087
|
+
process.exitCode = lastExitCode;
|
|
4088
|
+
try {
|
|
4089
|
+
await new Promise((resolve) => process.stderr.write("", () => resolve()));
|
|
4090
|
+
} catch {
|
|
4091
|
+
} finally {
|
|
4092
|
+
clearTimeout(forceExitTimer);
|
|
4093
|
+
}
|
|
4094
|
+
};
|
|
4095
|
+
function handleStdinError(error) {
|
|
4096
|
+
console.error("stdin error:", error);
|
|
4097
|
+
lastExitCode = 1;
|
|
4098
|
+
void shutdown("stdin_error");
|
|
4099
|
+
}
|
|
4100
|
+
function hasActiveSessions() {
|
|
4101
|
+
return sessionManager.list().some((s) => s.status === "running" || s.status === "waiting_permission");
|
|
4102
|
+
}
|
|
4103
|
+
const evaluateStdinTermination = () => {
|
|
4104
|
+
if (closing || stdinClosedAt === void 0) return;
|
|
4105
|
+
const stdinUnavailable = process.stdin.destroyed || process.stdin.readableEnded || !process.stdin.readable;
|
|
4106
|
+
if (!stdinUnavailable) {
|
|
4107
|
+
stdinClosedAt = void 0;
|
|
4108
|
+
stdinClosedReason = void 0;
|
|
4109
|
+
return;
|
|
4110
|
+
}
|
|
4111
|
+
const elapsedMs = Date.now() - stdinClosedAt;
|
|
4112
|
+
const active = hasActiveSessions();
|
|
4113
|
+
const connected = server.isConnected();
|
|
4114
|
+
if (!active && !connected) {
|
|
4115
|
+
void shutdown(`stdin_${stdinClosedReason ?? "closed"}`);
|
|
4116
|
+
return;
|
|
4117
|
+
}
|
|
4118
|
+
if (elapsedMs >= STDIN_SHUTDOWN_MAX_WAIT_MS) {
|
|
4119
|
+
void shutdown(`stdin_${stdinClosedReason ?? "closed"}_timeout`);
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
4122
|
+
stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
|
|
4123
|
+
if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
|
|
4124
|
+
};
|
|
4125
|
+
function handleStdinTerminated(event) {
|
|
4126
|
+
if (closing) return;
|
|
4127
|
+
if (stdinClosedAt === void 0) {
|
|
4128
|
+
stdinClosedAt = Date.now();
|
|
4129
|
+
stdinClosedReason = event;
|
|
4130
|
+
console.error(`[lifecycle] stdin ${event} observed; entering guarded shutdown checks`);
|
|
4131
|
+
}
|
|
4132
|
+
clearStdinShutdownTimer();
|
|
4133
|
+
stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
|
|
4134
|
+
if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
|
|
4135
|
+
}
|
|
4136
|
+
const handleUnexpectedError = (error) => {
|
|
4137
|
+
if (isBenignRuntimeError(error)) {
|
|
4138
|
+
console.error("Ignored benign runtime abort:", error);
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
console.error("Unhandled runtime error:", error);
|
|
4142
|
+
lastExitCode = 1;
|
|
4143
|
+
void shutdown("runtime_error");
|
|
2298
4144
|
};
|
|
2299
|
-
process.on("SIGINT",
|
|
2300
|
-
|
|
4145
|
+
process.on("SIGINT", () => {
|
|
4146
|
+
void shutdown("SIGINT");
|
|
4147
|
+
});
|
|
4148
|
+
process.on("SIGTERM", () => {
|
|
4149
|
+
void shutdown("SIGTERM");
|
|
4150
|
+
});
|
|
4151
|
+
process.on("SIGHUP", () => {
|
|
4152
|
+
void shutdown("SIGHUP");
|
|
4153
|
+
});
|
|
4154
|
+
process.on("beforeExit", () => {
|
|
4155
|
+
void shutdown("beforeExit");
|
|
4156
|
+
});
|
|
4157
|
+
process.on("uncaughtException", handleUnexpectedError);
|
|
4158
|
+
process.on("unhandledRejection", handleUnexpectedError);
|
|
4159
|
+
if (process.platform === "win32") {
|
|
4160
|
+
process.on("SIGBREAK", () => {
|
|
4161
|
+
void shutdown("SIGBREAK");
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
if (typeof process.stdin.resume === "function") {
|
|
4165
|
+
process.stdin.resume();
|
|
4166
|
+
}
|
|
4167
|
+
process.stdin.on("error", handleStdinError);
|
|
4168
|
+
process.stdin.on("end", onStdinEnd);
|
|
4169
|
+
process.stdin.on("close", onStdinClose);
|
|
2301
4170
|
await server.connect(transport);
|
|
4171
|
+
server.sendToolListChanged();
|
|
4172
|
+
server.sendResourceListChanged();
|
|
2302
4173
|
checkWindowsBashAvailability();
|
|
2303
|
-
|
|
4174
|
+
try {
|
|
4175
|
+
if (transport && server) {
|
|
4176
|
+
await server.sendLoggingMessage({
|
|
4177
|
+
level: "info",
|
|
4178
|
+
data: { event: "server_started", cwd: serverCwd }
|
|
4179
|
+
});
|
|
4180
|
+
}
|
|
4181
|
+
} catch {
|
|
4182
|
+
}
|
|
4183
|
+
console.error(`claude-code-mcp server started (transport=stdio, cwd: ${serverCwd})`);
|
|
2304
4184
|
}
|
|
2305
4185
|
main().catch((err) => {
|
|
2306
4186
|
console.error("Fatal error:", err);
|