happy-coder 0.8.0 → 0.9.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +558 -408
- package/dist/index.mjs +559 -409
- package/dist/lib.d.cts +77 -72
- package/dist/lib.d.mts +77 -72
- package/package.json +11 -9
package/dist/index.cjs
CHANGED
|
@@ -20,18 +20,18 @@ require('node:events');
|
|
|
20
20
|
require('socket.io-client');
|
|
21
21
|
var tweetnacl = require('tweetnacl');
|
|
22
22
|
require('expo-server-sdk');
|
|
23
|
-
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
24
|
-
var node_http = require('node:http');
|
|
25
|
-
var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
26
|
-
var z = require('zod');
|
|
27
23
|
var child_process = require('child_process');
|
|
28
24
|
var util = require('util');
|
|
29
25
|
var crypto = require('crypto');
|
|
26
|
+
var z = require('zod');
|
|
30
27
|
var fastify = require('fastify');
|
|
31
28
|
var fastifyTypeProviderZod = require('fastify-type-provider-zod');
|
|
32
29
|
var os$1 = require('os');
|
|
33
30
|
var qrcode = require('qrcode-terminal');
|
|
34
31
|
var open = require('open');
|
|
32
|
+
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
33
|
+
var node_http = require('node:http');
|
|
34
|
+
var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
35
35
|
var fs = require('fs');
|
|
36
36
|
|
|
37
37
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -923,16 +923,19 @@ async function streamToStdin(stream, stdin, abort) {
|
|
|
923
923
|
}
|
|
924
924
|
|
|
925
925
|
class Query {
|
|
926
|
-
constructor(childStdin, childStdout, processExitPromise) {
|
|
926
|
+
constructor(childStdin, childStdout, processExitPromise, canCallTool) {
|
|
927
927
|
this.childStdin = childStdin;
|
|
928
928
|
this.childStdout = childStdout;
|
|
929
929
|
this.processExitPromise = processExitPromise;
|
|
930
|
+
this.canCallTool = canCallTool;
|
|
930
931
|
this.readMessages();
|
|
931
932
|
this.sdkMessages = this.readSdkMessages();
|
|
932
933
|
}
|
|
933
934
|
pendingControlResponses = /* @__PURE__ */ new Map();
|
|
935
|
+
cancelControllers = /* @__PURE__ */ new Map();
|
|
934
936
|
sdkMessages;
|
|
935
937
|
inputStream = new Stream();
|
|
938
|
+
canCallTool;
|
|
936
939
|
/**
|
|
937
940
|
* Set an error on the stream
|
|
938
941
|
*/
|
|
@@ -977,6 +980,12 @@ class Query {
|
|
|
977
980
|
handler(controlResponse.response);
|
|
978
981
|
}
|
|
979
982
|
continue;
|
|
983
|
+
} else if (message.type === "control_request") {
|
|
984
|
+
await this.handleControlRequest(message);
|
|
985
|
+
continue;
|
|
986
|
+
} else if (message.type === "control_cancel_request") {
|
|
987
|
+
this.handleControlCancelRequest(message);
|
|
988
|
+
continue;
|
|
980
989
|
}
|
|
981
990
|
this.inputStream.enqueue(message);
|
|
982
991
|
} catch (e) {
|
|
@@ -989,6 +998,7 @@ class Query {
|
|
|
989
998
|
this.inputStream.error(error);
|
|
990
999
|
} finally {
|
|
991
1000
|
this.inputStream.done();
|
|
1001
|
+
this.cleanupControllers();
|
|
992
1002
|
rl.close();
|
|
993
1003
|
}
|
|
994
1004
|
}
|
|
@@ -1032,6 +1042,77 @@ class Query {
|
|
|
1032
1042
|
childStdin.write(JSON.stringify(sdkRequest) + "\n");
|
|
1033
1043
|
});
|
|
1034
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Handle incoming control requests for tool permissions
|
|
1047
|
+
* Replicates the exact logic from the SDK's handleControlRequest method
|
|
1048
|
+
*/
|
|
1049
|
+
async handleControlRequest(request) {
|
|
1050
|
+
if (!this.childStdin) {
|
|
1051
|
+
logDebug("Cannot handle control request - no stdin available");
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const controller = new AbortController();
|
|
1055
|
+
this.cancelControllers.set(request.request_id, controller);
|
|
1056
|
+
try {
|
|
1057
|
+
const response = await this.processControlRequest(request, controller.signal);
|
|
1058
|
+
const controlResponse = {
|
|
1059
|
+
type: "control_response",
|
|
1060
|
+
response: {
|
|
1061
|
+
subtype: "success",
|
|
1062
|
+
request_id: request.request_id,
|
|
1063
|
+
response
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
this.childStdin.write(JSON.stringify(controlResponse) + "\n");
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
const controlErrorResponse = {
|
|
1069
|
+
type: "control_response",
|
|
1070
|
+
response: {
|
|
1071
|
+
subtype: "error",
|
|
1072
|
+
request_id: request.request_id,
|
|
1073
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
this.childStdin.write(JSON.stringify(controlErrorResponse) + "\n");
|
|
1077
|
+
} finally {
|
|
1078
|
+
this.cancelControllers.delete(request.request_id);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Handle control cancel requests
|
|
1083
|
+
* Replicates the exact logic from the SDK's handleControlCancelRequest method
|
|
1084
|
+
*/
|
|
1085
|
+
handleControlCancelRequest(request) {
|
|
1086
|
+
const controller = this.cancelControllers.get(request.request_id);
|
|
1087
|
+
if (controller) {
|
|
1088
|
+
controller.abort();
|
|
1089
|
+
this.cancelControllers.delete(request.request_id);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Process control requests based on subtype
|
|
1094
|
+
* Replicates the exact logic from the SDK's processControlRequest method
|
|
1095
|
+
*/
|
|
1096
|
+
async processControlRequest(request, signal) {
|
|
1097
|
+
if (request.request.subtype === "can_use_tool") {
|
|
1098
|
+
if (!this.canCallTool) {
|
|
1099
|
+
throw new Error("canCallTool callback is not provided.");
|
|
1100
|
+
}
|
|
1101
|
+
return this.canCallTool(request.request.tool_name, request.request.input, {
|
|
1102
|
+
signal
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
throw new Error("Unsupported control request subtype: " + request.request.subtype);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Cleanup method to abort all pending control requests
|
|
1109
|
+
*/
|
|
1110
|
+
cleanupControllers() {
|
|
1111
|
+
for (const [requestId, controller] of this.cancelControllers.entries()) {
|
|
1112
|
+
controller.abort();
|
|
1113
|
+
this.cancelControllers.delete(requestId);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1035
1116
|
}
|
|
1036
1117
|
function query(config) {
|
|
1037
1118
|
const {
|
|
@@ -1048,12 +1129,12 @@ function query(config) {
|
|
|
1048
1129
|
mcpServers,
|
|
1049
1130
|
pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
|
|
1050
1131
|
permissionMode = "default",
|
|
1051
|
-
permissionPromptToolName,
|
|
1052
1132
|
continue: continueConversation,
|
|
1053
1133
|
resume,
|
|
1054
1134
|
model,
|
|
1055
1135
|
fallbackModel,
|
|
1056
|
-
strictMcpConfig
|
|
1136
|
+
strictMcpConfig,
|
|
1137
|
+
canCallTool
|
|
1057
1138
|
} = {}
|
|
1058
1139
|
} = config;
|
|
1059
1140
|
if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
@@ -1064,7 +1145,12 @@ function query(config) {
|
|
|
1064
1145
|
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
1065
1146
|
if (maxTurns) args.push("--max-turns", maxTurns.toString());
|
|
1066
1147
|
if (model) args.push("--model", model);
|
|
1067
|
-
if (
|
|
1148
|
+
if (canCallTool) {
|
|
1149
|
+
if (typeof prompt === "string") {
|
|
1150
|
+
throw new Error("canCallTool callback requires --input-format stream-json. Please set prompt as an AsyncIterable.");
|
|
1151
|
+
}
|
|
1152
|
+
args.push("--permission-prompt-tool", "stdio");
|
|
1153
|
+
}
|
|
1068
1154
|
if (continueConversation) args.push("--continue");
|
|
1069
1155
|
if (resume) args.push("--resume", resume);
|
|
1070
1156
|
if (allowedTools.length > 0) args.push("--allowedTools", allowedTools.join(","));
|
|
@@ -1128,7 +1214,7 @@ function query(config) {
|
|
|
1128
1214
|
}
|
|
1129
1215
|
});
|
|
1130
1216
|
});
|
|
1131
|
-
const query2 = new Query(childStdin, child.stdout, processExitPromise);
|
|
1217
|
+
const query2 = new Query(childStdin, child.stdout, processExitPromise, canCallTool);
|
|
1132
1218
|
child.on("error", (error) => {
|
|
1133
1219
|
if (config.options?.abort?.aborted) {
|
|
1134
1220
|
query2.setError(new AbortError("Claude Code process aborted by user"));
|
|
@@ -1146,17 +1232,48 @@ function query(config) {
|
|
|
1146
1232
|
return query2;
|
|
1147
1233
|
}
|
|
1148
1234
|
|
|
1149
|
-
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1156
|
-
await types$1.delay(1e3);
|
|
1157
|
-
}
|
|
1235
|
+
function parseCompact(message) {
|
|
1236
|
+
const trimmed = message.trim();
|
|
1237
|
+
if (trimmed === "/compact") {
|
|
1238
|
+
return {
|
|
1239
|
+
isCompact: true,
|
|
1240
|
+
originalMessage: trimmed
|
|
1241
|
+
};
|
|
1158
1242
|
}
|
|
1159
|
-
|
|
1243
|
+
if (trimmed.startsWith("/compact ")) {
|
|
1244
|
+
return {
|
|
1245
|
+
isCompact: true,
|
|
1246
|
+
originalMessage: trimmed
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
return {
|
|
1250
|
+
isCompact: false,
|
|
1251
|
+
originalMessage: message
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
function parseClear(message) {
|
|
1255
|
+
const trimmed = message.trim();
|
|
1256
|
+
return {
|
|
1257
|
+
isClear: trimmed === "/clear"
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function parseSpecialCommand(message) {
|
|
1261
|
+
const compactResult = parseCompact(message);
|
|
1262
|
+
if (compactResult.isCompact) {
|
|
1263
|
+
return {
|
|
1264
|
+
type: "compact",
|
|
1265
|
+
originalMessage: compactResult.originalMessage
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
const clearResult = parseClear(message);
|
|
1269
|
+
if (clearResult.isClear) {
|
|
1270
|
+
return {
|
|
1271
|
+
type: "clear"
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
type: null
|
|
1276
|
+
};
|
|
1160
1277
|
}
|
|
1161
1278
|
|
|
1162
1279
|
class PushableAsyncIterable {
|
|
@@ -1285,48 +1402,17 @@ class PushableAsyncIterable {
|
|
|
1285
1402
|
}
|
|
1286
1403
|
}
|
|
1287
1404
|
|
|
1288
|
-
function
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
return {
|
|
1298
|
-
isCompact: true,
|
|
1299
|
-
originalMessage: trimmed
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
return {
|
|
1303
|
-
isCompact: false,
|
|
1304
|
-
originalMessage: message
|
|
1305
|
-
};
|
|
1306
|
-
}
|
|
1307
|
-
function parseClear(message) {
|
|
1308
|
-
const trimmed = message.trim();
|
|
1309
|
-
return {
|
|
1310
|
-
isClear: trimmed === "/clear"
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
function parseSpecialCommand(message) {
|
|
1314
|
-
const compactResult = parseCompact(message);
|
|
1315
|
-
if (compactResult.isCompact) {
|
|
1316
|
-
return {
|
|
1317
|
-
type: "compact",
|
|
1318
|
-
originalMessage: compactResult.originalMessage
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
const clearResult = parseClear(message);
|
|
1322
|
-
if (clearResult.isClear) {
|
|
1323
|
-
return {
|
|
1324
|
-
type: "clear"
|
|
1325
|
-
};
|
|
1405
|
+
async function awaitFileExist(file, timeout = 1e4) {
|
|
1406
|
+
const startTime = Date.now();
|
|
1407
|
+
while (Date.now() - startTime < timeout) {
|
|
1408
|
+
try {
|
|
1409
|
+
await promises.access(file);
|
|
1410
|
+
return true;
|
|
1411
|
+
} catch (e) {
|
|
1412
|
+
await types$1.delay(1e3);
|
|
1413
|
+
}
|
|
1326
1414
|
}
|
|
1327
|
-
return
|
|
1328
|
-
type: null
|
|
1329
|
-
};
|
|
1415
|
+
return false;
|
|
1330
1416
|
}
|
|
1331
1417
|
|
|
1332
1418
|
async function claudeRemote(opts) {
|
|
@@ -1339,32 +1425,12 @@ async function claudeRemote(opts) {
|
|
|
1339
1425
|
process.env[key] = value;
|
|
1340
1426
|
});
|
|
1341
1427
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
resume: startFrom ?? void 0,
|
|
1346
|
-
mcpServers: opts.mcpServers,
|
|
1347
|
-
permissionPromptToolName: opts.permissionPromptToolName,
|
|
1348
|
-
permissionMode: opts.permissionMode,
|
|
1349
|
-
model: opts.model,
|
|
1350
|
-
fallbackModel: opts.fallbackModel,
|
|
1351
|
-
customSystemPrompt: opts.customSystemPrompt,
|
|
1352
|
-
appendSystemPrompt: opts.appendSystemPrompt,
|
|
1353
|
-
allowedTools: opts.allowedTools,
|
|
1354
|
-
disallowedTools: opts.disallowedTools,
|
|
1355
|
-
executable: "node",
|
|
1356
|
-
abort: opts.signal,
|
|
1357
|
-
pathToClaudeCodeExecutable: (() => {
|
|
1358
|
-
return node_path.resolve(node_path.join(projectPath(), "scripts", "claude_remote_launcher.cjs"));
|
|
1359
|
-
})()
|
|
1360
|
-
};
|
|
1361
|
-
if (opts.claudeArgs && opts.claudeArgs.length > 0) {
|
|
1362
|
-
sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
|
|
1428
|
+
const initial = await opts.nextMessage();
|
|
1429
|
+
if (!initial) {
|
|
1430
|
+
return;
|
|
1363
1431
|
}
|
|
1364
|
-
|
|
1365
|
-
const specialCommand = parseSpecialCommand(opts.message);
|
|
1432
|
+
const specialCommand = parseSpecialCommand(initial.message);
|
|
1366
1433
|
if (specialCommand.type === "clear") {
|
|
1367
|
-
types$1.logger.debug("[claudeRemote] /clear command detected - should not reach here, handled in start.ts");
|
|
1368
1434
|
if (opts.onCompletionEvent) {
|
|
1369
1435
|
opts.onCompletionEvent("Context was reset");
|
|
1370
1436
|
}
|
|
@@ -1373,29 +1439,33 @@ async function claudeRemote(opts) {
|
|
|
1373
1439
|
}
|
|
1374
1440
|
return;
|
|
1375
1441
|
}
|
|
1442
|
+
let isCompactCommand = false;
|
|
1376
1443
|
if (specialCommand.type === "compact") {
|
|
1377
1444
|
types$1.logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
|
|
1378
|
-
|
|
1379
|
-
const isCompactCommand = specialCommand.type === "compact";
|
|
1380
|
-
if (isCompactCommand) {
|
|
1381
|
-
types$1.logger.debug("[claudeRemote] Compaction started");
|
|
1445
|
+
isCompactCommand = true;
|
|
1382
1446
|
if (opts.onCompletionEvent) {
|
|
1383
1447
|
opts.onCompletionEvent("Compaction started");
|
|
1384
1448
|
}
|
|
1385
1449
|
}
|
|
1386
|
-
let
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1450
|
+
let mode = initial.mode;
|
|
1451
|
+
const sdkOptions = {
|
|
1452
|
+
cwd: opts.path,
|
|
1453
|
+
resume: startFrom ?? void 0,
|
|
1454
|
+
mcpServers: opts.mcpServers,
|
|
1455
|
+
permissionMode: initial.mode.permissionMode === "plan" ? "plan" : "default",
|
|
1456
|
+
model: initial.mode.model,
|
|
1457
|
+
fallbackModel: initial.mode.fallbackModel,
|
|
1458
|
+
customSystemPrompt: initial.mode.customSystemPrompt ? initial.mode.customSystemPrompt + "\n\n" + systemPrompt : void 0,
|
|
1459
|
+
appendSystemPrompt: initial.mode.appendSystemPrompt ? initial.mode.appendSystemPrompt + "\n\n" + systemPrompt : systemPrompt,
|
|
1460
|
+
allowedTools: initial.mode.allowedTools ? initial.mode.allowedTools.concat(opts.allowedTools) : opts.allowedTools,
|
|
1461
|
+
disallowedTools: initial.mode.disallowedTools,
|
|
1462
|
+
canCallTool: (toolName, input, options) => opts.canCallTool(toolName, input, mode, options),
|
|
1463
|
+
executable: "node",
|
|
1464
|
+
abort: opts.signal,
|
|
1465
|
+
pathToClaudeCodeExecutable: (() => {
|
|
1466
|
+
return node_path.resolve(node_path.join(projectPath(), "scripts", "claude_remote_launcher.cjs"));
|
|
1467
|
+
})()
|
|
1468
|
+
};
|
|
1399
1469
|
let thinking = false;
|
|
1400
1470
|
const updateThinking = (newThinking) => {
|
|
1401
1471
|
if (thinking !== newThinking) {
|
|
@@ -1406,15 +1476,27 @@ async function claudeRemote(opts) {
|
|
|
1406
1476
|
}
|
|
1407
1477
|
}
|
|
1408
1478
|
};
|
|
1479
|
+
let messages = new PushableAsyncIterable();
|
|
1480
|
+
messages.push({
|
|
1481
|
+
type: "user",
|
|
1482
|
+
message: {
|
|
1483
|
+
role: "user",
|
|
1484
|
+
content: initial.message
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
const response = query({
|
|
1488
|
+
prompt: messages,
|
|
1489
|
+
options: sdkOptions
|
|
1490
|
+
});
|
|
1409
1491
|
updateThinking(true);
|
|
1410
1492
|
try {
|
|
1411
1493
|
types$1.logger.debug(`[claudeRemote] Starting to iterate over response`);
|
|
1412
|
-
for await (const
|
|
1413
|
-
types$1.logger.debugLargeJson(`[claudeRemote] Message ${
|
|
1414
|
-
opts.onMessage(
|
|
1415
|
-
if (
|
|
1494
|
+
for await (const message of response) {
|
|
1495
|
+
types$1.logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
|
|
1496
|
+
opts.onMessage(message);
|
|
1497
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
1416
1498
|
updateThinking(true);
|
|
1417
|
-
const systemInit =
|
|
1499
|
+
const systemInit = message;
|
|
1418
1500
|
if (systemInit.session_id) {
|
|
1419
1501
|
types$1.logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
|
|
1420
1502
|
const projectDir = getProjectPath(opts.path);
|
|
@@ -1423,7 +1505,7 @@ async function claudeRemote(opts) {
|
|
|
1423
1505
|
opts.onSessionFound(systemInit.session_id);
|
|
1424
1506
|
}
|
|
1425
1507
|
}
|
|
1426
|
-
if (
|
|
1508
|
+
if (message.type === "result") {
|
|
1427
1509
|
updateThinking(false);
|
|
1428
1510
|
types$1.logger.debug("[claudeRemote] Result received, exiting claudeRemote");
|
|
1429
1511
|
if (isCompactCommand) {
|
|
@@ -1431,26 +1513,28 @@ async function claudeRemote(opts) {
|
|
|
1431
1513
|
if (opts.onCompletionEvent) {
|
|
1432
1514
|
opts.onCompletionEvent("Compaction completed");
|
|
1433
1515
|
}
|
|
1516
|
+
isCompactCommand = false;
|
|
1434
1517
|
}
|
|
1435
|
-
|
|
1518
|
+
const next = await opts.nextMessage();
|
|
1519
|
+
if (!next) {
|
|
1520
|
+
messages.end();
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
mode = next.mode;
|
|
1524
|
+
messages.push({ type: "user", message: { role: "user", content: next.message } });
|
|
1436
1525
|
}
|
|
1437
|
-
if (
|
|
1438
|
-
const msg =
|
|
1526
|
+
if (message.type === "user") {
|
|
1527
|
+
const msg = message;
|
|
1439
1528
|
if (msg.message.role === "user" && Array.isArray(msg.message.content)) {
|
|
1440
1529
|
for (let c of msg.message.content) {
|
|
1441
|
-
if (c.type === "tool_result" &&
|
|
1442
|
-
types$1.logger.debug("[claudeRemote]
|
|
1443
|
-
return;
|
|
1444
|
-
}
|
|
1445
|
-
if (c.type === "tool_result" && c.tool_use_id && opts.responses.has(c.tool_use_id) && !opts.responses.get(c.tool_use_id).approved) {
|
|
1446
|
-
types$1.logger.debug("[claudeRemote] Tool rejected, exiting claudeRemote");
|
|
1530
|
+
if (c.type === "tool_result" && c.tool_use_id && opts.isAborted(c.tool_use_id)) {
|
|
1531
|
+
types$1.logger.debug("[claudeRemote] Tool aborted, exiting claudeRemote");
|
|
1447
1532
|
return;
|
|
1448
1533
|
}
|
|
1449
1534
|
}
|
|
1450
1535
|
}
|
|
1451
1536
|
}
|
|
1452
1537
|
}
|
|
1453
|
-
types$1.logger.debug(`[claudeRemote] Finished iterating over response`);
|
|
1454
1538
|
} catch (e) {
|
|
1455
1539
|
if (e instanceof AbortError) {
|
|
1456
1540
|
types$1.logger.debug(`[claudeRemote] Aborted`);
|
|
@@ -1460,71 +1544,11 @@ async function claudeRemote(opts) {
|
|
|
1460
1544
|
} finally {
|
|
1461
1545
|
updateThinking(false);
|
|
1462
1546
|
}
|
|
1463
|
-
types$1.logger.debug(`[claudeRemote] Function completed`);
|
|
1464
1547
|
}
|
|
1465
1548
|
|
|
1466
1549
|
const PLAN_FAKE_REJECT = `User approved plan, but you need to be restarted. STOP IMMEDIATELY TO SWITCH FROM PLAN MODE. DO NOT REPLY TO THIS MESSAGE.`;
|
|
1467
1550
|
const PLAN_FAKE_RESTART = `PlEaZe Continue with plan.`;
|
|
1468
1551
|
|
|
1469
|
-
async function startPermissionServerV2(handler) {
|
|
1470
|
-
const mcp = new mcp_js.McpServer({
|
|
1471
|
-
name: "Permission Server",
|
|
1472
|
-
version: "1.0.0",
|
|
1473
|
-
description: "A server that allows you to request permissions from the user"
|
|
1474
|
-
});
|
|
1475
|
-
mcp.registerTool("ask_permission", {
|
|
1476
|
-
description: "Request permission to execute a tool",
|
|
1477
|
-
title: "Request Permission",
|
|
1478
|
-
inputSchema: {
|
|
1479
|
-
tool_name: z.z.string().describe("The tool that needs permission"),
|
|
1480
|
-
input: z.z.any().describe("The arguments for the tool")
|
|
1481
|
-
}
|
|
1482
|
-
}, async (args) => {
|
|
1483
|
-
const response = await handler({ name: args.tool_name, arguments: args.input });
|
|
1484
|
-
types$1.logger.debugLargeJson("[permissionServerV2] Response", response);
|
|
1485
|
-
const result = response.approved ? { behavior: "allow", updatedInput: args.input || {} } : { behavior: "deny", message: response.reason || `The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.` };
|
|
1486
|
-
return {
|
|
1487
|
-
content: [
|
|
1488
|
-
{
|
|
1489
|
-
type: "text",
|
|
1490
|
-
text: JSON.stringify(result)
|
|
1491
|
-
}
|
|
1492
|
-
],
|
|
1493
|
-
isError: false
|
|
1494
|
-
};
|
|
1495
|
-
});
|
|
1496
|
-
const transport = new streamableHttp_js.StreamableHTTPServerTransport({
|
|
1497
|
-
// NOTE: Returning session id here will result in claude
|
|
1498
|
-
// sdk spawn to fail with `Invalid Request: Server already initialized`
|
|
1499
|
-
sessionIdGenerator: void 0
|
|
1500
|
-
});
|
|
1501
|
-
await mcp.connect(transport);
|
|
1502
|
-
const server = node_http.createServer(async (req, res) => {
|
|
1503
|
-
try {
|
|
1504
|
-
await transport.handleRequest(req, res);
|
|
1505
|
-
} catch (error) {
|
|
1506
|
-
types$1.logger.debug("Error handling request:", error);
|
|
1507
|
-
if (!res.headersSent) {
|
|
1508
|
-
res.writeHead(500).end();
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
});
|
|
1512
|
-
const baseUrl = await new Promise((resolve) => {
|
|
1513
|
-
server.listen(0, "127.0.0.1", () => {
|
|
1514
|
-
const addr = server.address();
|
|
1515
|
-
resolve(new URL(`http://127.0.0.1:${addr.port}`));
|
|
1516
|
-
});
|
|
1517
|
-
});
|
|
1518
|
-
return {
|
|
1519
|
-
url: baseUrl.toString(),
|
|
1520
|
-
toolName: "ask_permission",
|
|
1521
|
-
stop: () => {
|
|
1522
|
-
mcp.close();
|
|
1523
|
-
server.close();
|
|
1524
|
-
}
|
|
1525
|
-
};
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
1552
|
function deepEqual(a, b) {
|
|
1529
1553
|
if (a === b) return true;
|
|
1530
1554
|
if (a == null || b == null) return false;
|
|
@@ -1539,133 +1563,178 @@ function deepEqual(a, b) {
|
|
|
1539
1563
|
return true;
|
|
1540
1564
|
}
|
|
1541
1565
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1566
|
+
const STANDARD_TOOLS = {
|
|
1567
|
+
// File operations
|
|
1568
|
+
"Read": "Read File",
|
|
1569
|
+
"Write": "Write File",
|
|
1570
|
+
"Edit": "Edit File",
|
|
1571
|
+
"MultiEdit": "Edit File",
|
|
1572
|
+
"NotebookEdit": "Edit Notebook",
|
|
1573
|
+
// Search and navigation
|
|
1574
|
+
"Glob": "Find Files",
|
|
1575
|
+
"Grep": "Search in Files",
|
|
1576
|
+
"LS": "List Directory",
|
|
1577
|
+
// Command execution
|
|
1578
|
+
"Bash": "Run Command",
|
|
1579
|
+
"BashOutput": "Check Command Output",
|
|
1580
|
+
"KillBash": "Stop Command",
|
|
1581
|
+
// Task management
|
|
1582
|
+
"TodoWrite": "Update Tasks",
|
|
1583
|
+
"TodoRead": "Read Tasks",
|
|
1584
|
+
"Task": "Launch Agent",
|
|
1585
|
+
// Web tools
|
|
1586
|
+
"WebFetch": "Fetch Web Page",
|
|
1587
|
+
"WebSearch": "Search Web",
|
|
1588
|
+
// Special cases
|
|
1589
|
+
"exit_plan_mode": "Execute Plan",
|
|
1590
|
+
"ExitPlanMode": "Execute Plan"
|
|
1591
|
+
};
|
|
1592
|
+
function toTitleCase(str) {
|
|
1593
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1594
|
+
}
|
|
1595
|
+
function getToolName(toolName) {
|
|
1596
|
+
if (STANDARD_TOOLS[toolName]) {
|
|
1597
|
+
return STANDARD_TOOLS[toolName];
|
|
1598
|
+
}
|
|
1599
|
+
if (toolName.startsWith("mcp__")) {
|
|
1600
|
+
const parts = toolName.split("__");
|
|
1601
|
+
if (parts.length >= 3) {
|
|
1602
|
+
const server = toTitleCase(parts[1]);
|
|
1603
|
+
const action = toTitleCase(parts.slice(2).join("_"));
|
|
1604
|
+
return `${server}: ${action}`;
|
|
1561
1605
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
types$1.logger.debug("Permission request" + id + " " + JSON.stringify(request));
|
|
1609
|
-
session.api.push().sendToAllDevices(
|
|
1610
|
-
"Permission Request",
|
|
1611
|
-
`Claude wants to use ${request.name}`,
|
|
1612
|
-
{
|
|
1613
|
-
sessionId: session.client.sessionId,
|
|
1614
|
-
requestId: id,
|
|
1615
|
-
tool: request.name,
|
|
1616
|
-
type: "permission_request"
|
|
1617
|
-
}
|
|
1618
|
-
);
|
|
1619
|
-
session.client.updateAgentState((currentState) => ({
|
|
1620
|
-
...currentState,
|
|
1621
|
-
requests: {
|
|
1622
|
-
...currentState.requests,
|
|
1623
|
-
[id]: {
|
|
1624
|
-
tool: request.name,
|
|
1625
|
-
arguments: request.arguments,
|
|
1626
|
-
createdAt: Date.now()
|
|
1606
|
+
}
|
|
1607
|
+
return toTitleCase(toolName);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function getToolDescriptor(toolName) {
|
|
1611
|
+
if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
|
|
1612
|
+
return { edit: false, exitPlan: true };
|
|
1613
|
+
}
|
|
1614
|
+
if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
|
|
1615
|
+
return { edit: true, exitPlan: false };
|
|
1616
|
+
}
|
|
1617
|
+
return { edit: false, exitPlan: false };
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
class PermissionHandler {
|
|
1621
|
+
toolCalls = [];
|
|
1622
|
+
responses = /* @__PURE__ */ new Map();
|
|
1623
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
1624
|
+
session;
|
|
1625
|
+
allowedTools = /* @__PURE__ */ new Set();
|
|
1626
|
+
permissionMode = "default";
|
|
1627
|
+
constructor(session) {
|
|
1628
|
+
this.session = session;
|
|
1629
|
+
this.setupClientHandler();
|
|
1630
|
+
}
|
|
1631
|
+
handleModeChange(mode) {
|
|
1632
|
+
this.permissionMode = mode;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Handler response
|
|
1636
|
+
*/
|
|
1637
|
+
handlePermissionResponse(response, pending) {
|
|
1638
|
+
if (response.allowTools && response.allowTools.length > 0) {
|
|
1639
|
+
response.allowTools.forEach((tool) => this.allowedTools.add(tool));
|
|
1640
|
+
}
|
|
1641
|
+
if (response.mode) {
|
|
1642
|
+
this.permissionMode = response.mode;
|
|
1643
|
+
}
|
|
1644
|
+
if (pending.toolName === "exit_plan_mode" || pending.toolName === "ExitPlanMode") {
|
|
1645
|
+
types$1.logger.debug("Plan mode result received", response);
|
|
1646
|
+
if (response.approved) {
|
|
1647
|
+
types$1.logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
|
|
1648
|
+
if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
|
|
1649
|
+
this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
|
|
1650
|
+
} else {
|
|
1651
|
+
this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
|
|
1627
1652
|
}
|
|
1653
|
+
pending.resolve({ behavior: "deny", message: PLAN_FAKE_REJECT });
|
|
1654
|
+
} else {
|
|
1655
|
+
pending.resolve({ behavior: "deny", message: response.reason || "Plan rejected" });
|
|
1628
1656
|
}
|
|
1629
|
-
}));
|
|
1630
|
-
promise.then(() => clearTimeout(timeout)).catch(() => clearTimeout(timeout));
|
|
1631
|
-
return promise;
|
|
1632
|
-
}
|
|
1633
|
-
session.client.setHandler("permission", async (message) => {
|
|
1634
|
-
types$1.logger.debug("Permission response" + JSON.stringify(message));
|
|
1635
|
-
const id = message.id;
|
|
1636
|
-
const resolve = requests.get(id);
|
|
1637
|
-
if (resolve) {
|
|
1638
|
-
responses.set(id, message);
|
|
1639
|
-
resolve({ approved: message.approved, reason: message.reason, mode: message.mode });
|
|
1640
|
-
requests.delete(id);
|
|
1641
1657
|
} else {
|
|
1642
|
-
|
|
1643
|
-
|
|
1658
|
+
const result = response.approved ? { behavior: "allow", updatedInput: pending.input || {} } : { behavior: "deny", message: response.reason || `The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.` };
|
|
1659
|
+
pending.resolve(result);
|
|
1644
1660
|
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
return {
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Creates the canCallTool callback for the SDK
|
|
1664
|
+
*/
|
|
1665
|
+
handleToolCall = async (toolName, input, mode, options) => {
|
|
1666
|
+
if (this.allowedTools.has(toolName)) {
|
|
1667
|
+
return { behavior: "allow", updatedInput: input };
|
|
1668
|
+
}
|
|
1669
|
+
const descriptor = getToolDescriptor(toolName);
|
|
1670
|
+
if (this.permissionMode === "bypassPermissions") {
|
|
1671
|
+
return { behavior: "allow", updatedInput: input };
|
|
1672
|
+
}
|
|
1673
|
+
if (this.permissionMode === "acceptEdits" && descriptor.edit) {
|
|
1674
|
+
return { behavior: "allow", updatedInput: input };
|
|
1675
|
+
}
|
|
1676
|
+
let toolCallId = this.resolveToolCallId(toolName, input);
|
|
1677
|
+
if (!toolCallId) {
|
|
1678
|
+
await types$1.delay(1e3);
|
|
1679
|
+
toolCallId = this.resolveToolCallId(toolName, input);
|
|
1680
|
+
if (!toolCallId) {
|
|
1681
|
+
throw new Error(`Could not resolve tool call ID for ${toolName}`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return this.handlePermissionRequest(toolCallId, toolName, input, options.signal);
|
|
1685
|
+
};
|
|
1686
|
+
/**
|
|
1687
|
+
* Handles individual permission requests
|
|
1688
|
+
*/
|
|
1689
|
+
async handlePermissionRequest(id, toolName, input, signal) {
|
|
1690
|
+
return new Promise((resolve, reject) => {
|
|
1691
|
+
const abortHandler = () => {
|
|
1692
|
+
this.pendingRequests.delete(id);
|
|
1693
|
+
reject(new Error("Permission request aborted"));
|
|
1694
|
+
};
|
|
1695
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1696
|
+
this.pendingRequests.set(id, {
|
|
1697
|
+
resolve: (result) => {
|
|
1698
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1699
|
+
resolve(result);
|
|
1700
|
+
},
|
|
1701
|
+
reject: (error) => {
|
|
1702
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1703
|
+
reject(error);
|
|
1704
|
+
},
|
|
1705
|
+
toolName,
|
|
1706
|
+
input
|
|
1707
|
+
});
|
|
1708
|
+
this.session.api.push().sendToAllDevices(
|
|
1709
|
+
"Permission Request",
|
|
1710
|
+
`Claude wants to ${getToolName(toolName)}`,
|
|
1711
|
+
{
|
|
1712
|
+
sessionId: this.session.client.sessionId,
|
|
1713
|
+
requestId: id,
|
|
1714
|
+
tool: toolName,
|
|
1715
|
+
type: "permission_request"
|
|
1716
|
+
}
|
|
1717
|
+
);
|
|
1718
|
+
this.session.client.updateAgentState((currentState) => ({
|
|
1652
1719
|
...currentState,
|
|
1653
|
-
requests:
|
|
1654
|
-
|
|
1655
|
-
...currentState.completedRequests,
|
|
1720
|
+
requests: {
|
|
1721
|
+
...currentState.requests,
|
|
1656
1722
|
[id]: {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
reason: isExitPlanModeSuccess ? "Plan approved" : message.reason
|
|
1723
|
+
tool: toolName,
|
|
1724
|
+
arguments: input,
|
|
1725
|
+
createdAt: Date.now()
|
|
1661
1726
|
}
|
|
1662
1727
|
}
|
|
1663
|
-
};
|
|
1728
|
+
}));
|
|
1729
|
+
types$1.logger.debug(`Permission request sent for tool call ${id}: ${toolName}`);
|
|
1664
1730
|
});
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Resolves tool call ID based on tool name and input
|
|
1734
|
+
*/
|
|
1735
|
+
resolveToolCallId(name, args) {
|
|
1736
|
+
for (let i = this.toolCalls.length - 1; i >= 0; i--) {
|
|
1737
|
+
const call = this.toolCalls[i];
|
|
1669
1738
|
if (call.name === name && deepEqual(call.input, args)) {
|
|
1670
1739
|
if (call.used) {
|
|
1671
1740
|
return null;
|
|
@@ -1675,59 +1744,22 @@ async function startPermissionResolver(session) {
|
|
|
1675
1744
|
}
|
|
1676
1745
|
}
|
|
1677
1746
|
return null;
|
|
1678
|
-
};
|
|
1679
|
-
function reset() {
|
|
1680
|
-
toolCalls = [];
|
|
1681
|
-
requests.clear();
|
|
1682
|
-
responses.clear();
|
|
1683
|
-
for (const pending of pendingPermissionRequests) {
|
|
1684
|
-
clearTimeout(pending.timeout);
|
|
1685
|
-
}
|
|
1686
|
-
pendingPermissionRequests = [];
|
|
1687
|
-
session.client.updateAgentState((currentState) => {
|
|
1688
|
-
const pendingRequests = currentState.requests || {};
|
|
1689
|
-
const completedRequests = { ...currentState.completedRequests };
|
|
1690
|
-
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
1691
|
-
completedRequests[id] = {
|
|
1692
|
-
...request,
|
|
1693
|
-
completedAt: Date.now(),
|
|
1694
|
-
status: "canceled",
|
|
1695
|
-
reason: "Session switched to local mode"
|
|
1696
|
-
};
|
|
1697
|
-
}
|
|
1698
|
-
return {
|
|
1699
|
-
...currentState,
|
|
1700
|
-
requests: {},
|
|
1701
|
-
// Clear all pending requests
|
|
1702
|
-
completedRequests
|
|
1703
|
-
};
|
|
1704
|
-
});
|
|
1705
1747
|
}
|
|
1706
|
-
|
|
1748
|
+
/**
|
|
1749
|
+
* Handles messages to track tool calls
|
|
1750
|
+
*/
|
|
1751
|
+
onMessage(message) {
|
|
1707
1752
|
if (message.type === "assistant") {
|
|
1708
1753
|
const assistantMsg = message;
|
|
1709
1754
|
if (assistantMsg.message && assistantMsg.message.content) {
|
|
1710
1755
|
for (const block of assistantMsg.message.content) {
|
|
1711
1756
|
if (block.type === "tool_use") {
|
|
1712
|
-
toolCalls.push({
|
|
1757
|
+
this.toolCalls.push({
|
|
1713
1758
|
id: block.id,
|
|
1714
1759
|
name: block.name,
|
|
1715
1760
|
input: block.input,
|
|
1716
1761
|
used: false
|
|
1717
1762
|
});
|
|
1718
|
-
for (let i = pendingPermissionRequests.length - 1; i >= 0; i--) {
|
|
1719
|
-
const pending = pendingPermissionRequests[i];
|
|
1720
|
-
if (pending.request.name === block.name && deepEqual(pending.request.arguments, block.input)) {
|
|
1721
|
-
types$1.logger.debug(`Resolving pending permission request for ${block.name} with ID ${block.id}`);
|
|
1722
|
-
clearTimeout(pending.timeout);
|
|
1723
|
-
pendingPermissionRequests.splice(i, 1);
|
|
1724
|
-
handlePermissionRequest(block.id, pending.request).then(
|
|
1725
|
-
pending.resolve,
|
|
1726
|
-
pending.reject
|
|
1727
|
-
);
|
|
1728
|
-
break;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
1763
|
}
|
|
1732
1764
|
}
|
|
1733
1765
|
}
|
|
@@ -1737,7 +1769,7 @@ async function startPermissionResolver(session) {
|
|
|
1737
1769
|
if (userMsg.message && userMsg.message.content && Array.isArray(userMsg.message.content)) {
|
|
1738
1770
|
for (const block of userMsg.message.content) {
|
|
1739
1771
|
if (block.type === "tool_result" && block.tool_use_id) {
|
|
1740
|
-
const toolCall = toolCalls.find((tc) => tc.id === block.tool_use_id);
|
|
1772
|
+
const toolCall = this.toolCalls.find((tc) => tc.id === block.tool_use_id);
|
|
1741
1773
|
if (toolCall && !toolCall.used) {
|
|
1742
1774
|
toolCall.used = true;
|
|
1743
1775
|
}
|
|
@@ -1746,12 +1778,92 @@ async function startPermissionResolver(session) {
|
|
|
1746
1778
|
}
|
|
1747
1779
|
}
|
|
1748
1780
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
responses
|
|
1754
|
-
|
|
1781
|
+
/**
|
|
1782
|
+
* Checks if a tool call is rejected
|
|
1783
|
+
*/
|
|
1784
|
+
isAborted(toolCallId) {
|
|
1785
|
+
if (this.responses.get(toolCallId)?.approved === false) {
|
|
1786
|
+
return true;
|
|
1787
|
+
}
|
|
1788
|
+
const toolCall = this.toolCalls.find((tc) => tc.id === toolCallId);
|
|
1789
|
+
if (toolCall && (toolCall.name === "exit_plan_mode" || toolCall.name === "ExitPlanMode")) {
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
return false;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Resets all state for new sessions
|
|
1796
|
+
*/
|
|
1797
|
+
reset() {
|
|
1798
|
+
this.toolCalls = [];
|
|
1799
|
+
this.responses.clear();
|
|
1800
|
+
for (const [, pending] of this.pendingRequests.entries()) {
|
|
1801
|
+
pending.reject(new Error("Session reset"));
|
|
1802
|
+
}
|
|
1803
|
+
this.pendingRequests.clear();
|
|
1804
|
+
this.session.client.updateAgentState((currentState) => {
|
|
1805
|
+
const pendingRequests = currentState.requests || {};
|
|
1806
|
+
const completedRequests = { ...currentState.completedRequests };
|
|
1807
|
+
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
1808
|
+
completedRequests[id] = {
|
|
1809
|
+
...request,
|
|
1810
|
+
completedAt: Date.now(),
|
|
1811
|
+
status: "canceled",
|
|
1812
|
+
reason: "Session switched to local mode"
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
return {
|
|
1816
|
+
...currentState,
|
|
1817
|
+
requests: {},
|
|
1818
|
+
// Clear all pending requests
|
|
1819
|
+
completedRequests
|
|
1820
|
+
};
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Sets up the client handler for permission responses
|
|
1825
|
+
*/
|
|
1826
|
+
setupClientHandler() {
|
|
1827
|
+
this.session.client.setHandler("permission", async (message) => {
|
|
1828
|
+
types$1.logger.debug(`Permission response: ${JSON.stringify(message)}`);
|
|
1829
|
+
const id = message.id;
|
|
1830
|
+
const pending = this.pendingRequests.get(id);
|
|
1831
|
+
if (!pending) {
|
|
1832
|
+
types$1.logger.debug("Permission request not found or already resolved");
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
this.responses.set(id, { ...message, receivedAt: Date.now() });
|
|
1836
|
+
this.pendingRequests.delete(id);
|
|
1837
|
+
this.handlePermissionResponse(message, pending);
|
|
1838
|
+
this.session.client.updateAgentState((currentState) => {
|
|
1839
|
+
const request = currentState.requests?.[id];
|
|
1840
|
+
if (!request) return currentState;
|
|
1841
|
+
let r = { ...currentState.requests };
|
|
1842
|
+
delete r[id];
|
|
1843
|
+
return {
|
|
1844
|
+
...currentState,
|
|
1845
|
+
requests: r,
|
|
1846
|
+
completedRequests: {
|
|
1847
|
+
...currentState.completedRequests,
|
|
1848
|
+
[id]: {
|
|
1849
|
+
...request,
|
|
1850
|
+
completedAt: Date.now(),
|
|
1851
|
+
status: message.approved ? "approved" : "denied",
|
|
1852
|
+
reason: message.reason,
|
|
1853
|
+
mode: message.mode,
|
|
1854
|
+
allowTools: message.allowTools
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
});
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Gets the responses map (for compatibility with existing code)
|
|
1863
|
+
*/
|
|
1864
|
+
getResponses() {
|
|
1865
|
+
return this.responses;
|
|
1866
|
+
}
|
|
1755
1867
|
}
|
|
1756
1868
|
|
|
1757
1869
|
function formatClaudeMessageForInk(message, messageBuffer, onAssistantResult) {
|
|
@@ -2125,12 +2237,6 @@ async function claudeRemoteLauncher(session) {
|
|
|
2125
2237
|
}
|
|
2126
2238
|
process.stdin.setEncoding("utf8");
|
|
2127
2239
|
}
|
|
2128
|
-
const scanner = await createSessionScanner({
|
|
2129
|
-
sessionId: session.sessionId,
|
|
2130
|
-
workingDirectory: session.path,
|
|
2131
|
-
onMessage: (message) => {
|
|
2132
|
-
}
|
|
2133
|
-
});
|
|
2134
2240
|
let exitReason = null;
|
|
2135
2241
|
let abortController = null;
|
|
2136
2242
|
let abortFuture = null;
|
|
@@ -2153,17 +2259,17 @@ async function claudeRemoteLauncher(session) {
|
|
|
2153
2259
|
}
|
|
2154
2260
|
session.client.setHandler("abort", doAbort);
|
|
2155
2261
|
session.client.setHandler("switch", doSwitch);
|
|
2156
|
-
const
|
|
2262
|
+
const permissionHandler = new PermissionHandler(session);
|
|
2157
2263
|
const sdkToLogConverter = new SDKToLogConverter({
|
|
2158
2264
|
sessionId: session.sessionId || "unknown",
|
|
2159
2265
|
cwd: session.path,
|
|
2160
2266
|
version: process.env.npm_package_version
|
|
2161
|
-
},
|
|
2267
|
+
}, permissionHandler.getResponses());
|
|
2162
2268
|
let planModeToolCalls = /* @__PURE__ */ new Set();
|
|
2163
2269
|
let ongoingToolCalls = /* @__PURE__ */ new Map();
|
|
2164
2270
|
function onMessage(message) {
|
|
2165
2271
|
formatClaudeMessageForInk(message, messageBuffer);
|
|
2166
|
-
|
|
2272
|
+
permissionHandler.onMessage(message);
|
|
2167
2273
|
if (message.type === "assistant") {
|
|
2168
2274
|
let umessage = message;
|
|
2169
2275
|
if (umessage.message.content && Array.isArray(umessage.message.content)) {
|
|
@@ -2227,6 +2333,32 @@ async function claudeRemoteLauncher(session) {
|
|
|
2227
2333
|
}
|
|
2228
2334
|
const logMessage = sdkToLogConverter.convert(msg);
|
|
2229
2335
|
if (logMessage) {
|
|
2336
|
+
if (logMessage.type === "user" && logMessage.message?.content) {
|
|
2337
|
+
const content = Array.isArray(logMessage.message.content) ? logMessage.message.content : [];
|
|
2338
|
+
for (let i = 0; i < content.length; i++) {
|
|
2339
|
+
const c = content[i];
|
|
2340
|
+
if (c.type === "tool_result" && c.tool_use_id) {
|
|
2341
|
+
const responses = permissionHandler.getResponses();
|
|
2342
|
+
const response = responses.get(c.tool_use_id);
|
|
2343
|
+
if (response) {
|
|
2344
|
+
const permissions = {
|
|
2345
|
+
date: response.receivedAt || Date.now(),
|
|
2346
|
+
result: response.approved ? "approved" : "denied"
|
|
2347
|
+
};
|
|
2348
|
+
if (response.mode) {
|
|
2349
|
+
permissions.mode = response.mode;
|
|
2350
|
+
}
|
|
2351
|
+
if (response.allowTools && response.allowTools.length > 0) {
|
|
2352
|
+
permissions.allowedTools = response.allowTools;
|
|
2353
|
+
}
|
|
2354
|
+
content[i] = {
|
|
2355
|
+
...c,
|
|
2356
|
+
permissions
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2230
2362
|
if (logMessage.type !== "system") {
|
|
2231
2363
|
session.client.sendClaudeSessionMessage(logMessage);
|
|
2232
2364
|
}
|
|
@@ -2246,58 +2378,57 @@ async function claudeRemoteLauncher(session) {
|
|
|
2246
2378
|
}
|
|
2247
2379
|
}
|
|
2248
2380
|
try {
|
|
2381
|
+
let pending = null;
|
|
2249
2382
|
while (!exitReason) {
|
|
2250
|
-
types$1.logger.debug("[remote]: fetch next message");
|
|
2251
|
-
abortController = new AbortController();
|
|
2252
|
-
abortFuture = new Future();
|
|
2253
|
-
const messageData = await session.queue.waitForMessagesAndGetAsString(abortController.signal);
|
|
2254
|
-
if (!messageData || abortController.signal.aborted) {
|
|
2255
|
-
types$1.logger.debug("[remote]: fetch next message done: no message or aborted");
|
|
2256
|
-
abortFuture?.resolve(void 0);
|
|
2257
|
-
if (exitReason) {
|
|
2258
|
-
return exitReason;
|
|
2259
|
-
} else {
|
|
2260
|
-
continue;
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
types$1.logger.debug("[remote]: fetch next message done: message received");
|
|
2264
|
-
abortFuture?.resolve(void 0);
|
|
2265
|
-
abortFuture = null;
|
|
2266
|
-
abortController = null;
|
|
2267
2383
|
types$1.logger.debug("[remote]: launch");
|
|
2268
2384
|
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
2269
2385
|
messageBuffer.addMessage("Starting new Claude session...", "status");
|
|
2270
|
-
|
|
2386
|
+
const controller = new AbortController();
|
|
2387
|
+
abortController = controller;
|
|
2271
2388
|
abortFuture = new Future();
|
|
2272
|
-
|
|
2389
|
+
permissionHandler.reset();
|
|
2273
2390
|
sdkToLogConverter.resetParentChain();
|
|
2391
|
+
let modeHash = null;
|
|
2392
|
+
let mode = null;
|
|
2274
2393
|
try {
|
|
2275
2394
|
await claudeRemote({
|
|
2276
2395
|
sessionId: session.sessionId,
|
|
2277
2396
|
path: session.path,
|
|
2278
|
-
|
|
2279
|
-
mcpServers:
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2397
|
+
allowedTools: session.allowedTools ?? [],
|
|
2398
|
+
mcpServers: session.mcpServers,
|
|
2399
|
+
canCallTool: permissionHandler.handleToolCall,
|
|
2400
|
+
isAborted: (toolCallId) => {
|
|
2401
|
+
return permissionHandler.isAborted(toolCallId);
|
|
2402
|
+
},
|
|
2403
|
+
nextMessage: async () => {
|
|
2404
|
+
if (pending) {
|
|
2405
|
+
let p = pending;
|
|
2406
|
+
pending = null;
|
|
2407
|
+
permissionHandler.handleModeChange(p.mode.permissionMode);
|
|
2408
|
+
return p;
|
|
2284
2409
|
}
|
|
2410
|
+
let msg = await session.queue.waitForMessagesAndGetAsString(controller.signal);
|
|
2411
|
+
if (msg) {
|
|
2412
|
+
if (modeHash && msg.hash !== modeHash || msg.isolate) {
|
|
2413
|
+
types$1.logger.debug("[remote]: mode has changed, pending message");
|
|
2414
|
+
pending = msg;
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
modeHash = msg.hash;
|
|
2418
|
+
mode = msg.mode;
|
|
2419
|
+
permissionHandler.handleModeChange(mode.permissionMode);
|
|
2420
|
+
return {
|
|
2421
|
+
message: msg.message,
|
|
2422
|
+
mode: msg.mode
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
return null;
|
|
2285
2426
|
},
|
|
2286
|
-
permissionPromptToolName: "mcp__permission__" + permissions.server.toolName,
|
|
2287
|
-
permissionMode: messageData.mode.permissionMode,
|
|
2288
|
-
model: messageData.mode.model,
|
|
2289
|
-
fallbackModel: messageData.mode.fallbackModel,
|
|
2290
|
-
customSystemPrompt: messageData.mode.customSystemPrompt,
|
|
2291
|
-
appendSystemPrompt: messageData.mode.appendSystemPrompt ? messageData.mode.appendSystemPrompt + "\n" + systemPrompt : systemPrompt,
|
|
2292
|
-
allowedTools: messageData.mode.allowedTools ? [...messageData.mode.allowedTools, ...session.allowedTools ? session.allowedTools : []] : session.allowedTools ? [...session.allowedTools] : void 0,
|
|
2293
|
-
disallowedTools: messageData.mode.disallowedTools,
|
|
2294
2427
|
onSessionFound: (sessionId) => {
|
|
2295
2428
|
sdkToLogConverter.updateSessionId(sessionId);
|
|
2296
2429
|
session.onSessionFound(sessionId);
|
|
2297
|
-
scanner.onNewSession(sessionId);
|
|
2298
2430
|
},
|
|
2299
2431
|
onThinkingChange: session.onThinkingChange,
|
|
2300
|
-
message: messageData.message,
|
|
2301
2432
|
claudeEnvVars: session.claudeEnvVars,
|
|
2302
2433
|
claudeArgs: session.claudeArgs,
|
|
2303
2434
|
onMessage,
|
|
@@ -2315,11 +2446,13 @@ async function claudeRemoteLauncher(session) {
|
|
|
2315
2446
|
session.client.sendSessionEvent({ type: "message", message: "Aborted by user" });
|
|
2316
2447
|
}
|
|
2317
2448
|
} catch (e) {
|
|
2449
|
+
types$1.logger.debug("[remote]: launch error", e);
|
|
2318
2450
|
if (!exitReason) {
|
|
2319
2451
|
session.client.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
|
|
2320
2452
|
continue;
|
|
2321
2453
|
}
|
|
2322
2454
|
} finally {
|
|
2455
|
+
types$1.logger.debug("[remote]: launch finally");
|
|
2323
2456
|
for (let [toolCallId, { parentToolCallId }] of ongoingToolCalls) {
|
|
2324
2457
|
const converted = sdkToLogConverter.generateInterruptedToolResult(toolCallId, parentToolCallId);
|
|
2325
2458
|
if (converted) {
|
|
@@ -2332,11 +2465,13 @@ async function claudeRemoteLauncher(session) {
|
|
|
2332
2465
|
abortFuture?.resolve(void 0);
|
|
2333
2466
|
abortFuture = null;
|
|
2334
2467
|
types$1.logger.debug("[remote]: launch done");
|
|
2335
|
-
|
|
2468
|
+
permissionHandler.reset();
|
|
2469
|
+
modeHash = null;
|
|
2470
|
+
mode = null;
|
|
2336
2471
|
}
|
|
2337
2472
|
}
|
|
2338
2473
|
} finally {
|
|
2339
|
-
|
|
2474
|
+
permissionHandler.reset();
|
|
2340
2475
|
process.stdin.off("data", abort);
|
|
2341
2476
|
if (process.stdin.isTTY) {
|
|
2342
2477
|
process.stdin.setRawMode(false);
|
|
@@ -2348,7 +2483,6 @@ async function claudeRemoteLauncher(session) {
|
|
|
2348
2483
|
if (abortFuture) {
|
|
2349
2484
|
abortFuture.resolve(void 0);
|
|
2350
2485
|
}
|
|
2351
|
-
await scanner.cleanup();
|
|
2352
2486
|
}
|
|
2353
2487
|
return exitReason || "exit";
|
|
2354
2488
|
}
|
|
@@ -2400,7 +2534,7 @@ async function loop(opts) {
|
|
|
2400
2534
|
}
|
|
2401
2535
|
|
|
2402
2536
|
var name = "happy-coder";
|
|
2403
|
-
var version = "0.
|
|
2537
|
+
var version = "0.9.0-0";
|
|
2404
2538
|
var description = "Claude Code session sharing CLI";
|
|
2405
2539
|
var author = "Kirill Dubovitskiy";
|
|
2406
2540
|
var license = "MIT";
|
|
@@ -2450,18 +2584,14 @@ var scripts = {
|
|
|
2450
2584
|
test: "yarn build && vitest run",
|
|
2451
2585
|
"test:watch": "vitest",
|
|
2452
2586
|
"test:integration-test-env": "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
2453
|
-
dev: "
|
|
2587
|
+
dev: "yarn build && DEBUG=1 npx tsx src/index.ts",
|
|
2454
2588
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
2455
2589
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
2456
2590
|
prepublishOnly: "yarn build && yarn test",
|
|
2457
|
-
|
|
2458
|
-
"patch:publish": "yarn build && npm version patch && npm publish",
|
|
2459
|
-
"version:prerelease": "yarn build && npm version prerelease --preid=beta",
|
|
2460
|
-
"publish:prerelease": "npm publish --tag beta",
|
|
2461
|
-
"beta:publish": "yarn version:prerelease && yarn publish:prerelease"
|
|
2591
|
+
release: "release-it"
|
|
2462
2592
|
};
|
|
2463
2593
|
var dependencies = {
|
|
2464
|
-
"@anthropic-ai/claude-code": "^1.0.
|
|
2594
|
+
"@anthropic-ai/claude-code": "^1.0.89",
|
|
2465
2595
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
2466
2596
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
2467
2597
|
"@stablelib/base64": "^2.0.1",
|
|
@@ -2490,6 +2620,7 @@ var devDependencies = {
|
|
|
2490
2620
|
eslint: "^9",
|
|
2491
2621
|
"eslint-config-prettier": "^10",
|
|
2492
2622
|
pkgroll: "^2.14.2",
|
|
2623
|
+
"release-it": "^19.0.4",
|
|
2493
2624
|
shx: "^0.3.3",
|
|
2494
2625
|
"ts-node": "^10",
|
|
2495
2626
|
tsx: "^4.20.3",
|
|
@@ -2497,7 +2628,12 @@ var devDependencies = {
|
|
|
2497
2628
|
vitest: "^3.2.4"
|
|
2498
2629
|
};
|
|
2499
2630
|
var resolutions = {
|
|
2500
|
-
"whatwg-url": "14.2.0"
|
|
2631
|
+
"whatwg-url": "14.2.0",
|
|
2632
|
+
"parse-path": "7.0.3",
|
|
2633
|
+
"@types/parse-path": "7.0.3"
|
|
2634
|
+
};
|
|
2635
|
+
var publishConfig = {
|
|
2636
|
+
registry: "https://registry.npmjs.org"
|
|
2501
2637
|
};
|
|
2502
2638
|
var packageManager = "yarn@1.22.22";
|
|
2503
2639
|
var packageJson = {
|
|
@@ -2520,6 +2656,7 @@ var packageJson = {
|
|
|
2520
2656
|
dependencies: dependencies,
|
|
2521
2657
|
devDependencies: devDependencies,
|
|
2522
2658
|
resolutions: resolutions,
|
|
2659
|
+
publishConfig: publishConfig,
|
|
2523
2660
|
packageManager: packageManager
|
|
2524
2661
|
};
|
|
2525
2662
|
|
|
@@ -2906,15 +3043,17 @@ async function clearDaemonState() {
|
|
|
2906
3043
|
}
|
|
2907
3044
|
|
|
2908
3045
|
class MessageQueue2 {
|
|
2909
|
-
constructor(modeHasher) {
|
|
2910
|
-
this.modeHasher = modeHasher;
|
|
2911
|
-
types$1.logger.debug(`[MessageQueue2] Initialized`);
|
|
2912
|
-
}
|
|
2913
3046
|
queue = [];
|
|
2914
3047
|
// Made public for testing
|
|
2915
3048
|
waiter = null;
|
|
2916
3049
|
closed = false;
|
|
2917
3050
|
onMessageHandler = null;
|
|
3051
|
+
modeHasher;
|
|
3052
|
+
constructor(modeHasher, onMessageHandler = null) {
|
|
3053
|
+
this.modeHasher = modeHasher;
|
|
3054
|
+
this.onMessageHandler = onMessageHandler;
|
|
3055
|
+
types$1.logger.debug(`[MessageQueue2] Initialized`);
|
|
3056
|
+
}
|
|
2918
3057
|
/**
|
|
2919
3058
|
* Set a handler that will be called when a message arrives
|
|
2920
3059
|
*/
|
|
@@ -3089,6 +3228,7 @@ class MessageQueue2 {
|
|
|
3089
3228
|
const firstItem = this.queue[0];
|
|
3090
3229
|
const sameModeMessages = [];
|
|
3091
3230
|
let mode = firstItem.mode;
|
|
3231
|
+
let isolate = firstItem.isolate ?? false;
|
|
3092
3232
|
const targetModeHash = firstItem.modeHash;
|
|
3093
3233
|
if (firstItem.isolate) {
|
|
3094
3234
|
const item = this.queue.shift();
|
|
@@ -3104,7 +3244,9 @@ class MessageQueue2 {
|
|
|
3104
3244
|
const combinedMessage = sameModeMessages.join("\n");
|
|
3105
3245
|
return {
|
|
3106
3246
|
message: combinedMessage,
|
|
3107
|
-
mode
|
|
3247
|
+
mode,
|
|
3248
|
+
hash: targetModeHash,
|
|
3249
|
+
isolate
|
|
3108
3250
|
};
|
|
3109
3251
|
}
|
|
3110
3252
|
/**
|
|
@@ -3999,10 +4141,10 @@ async function doWebAuth(keypair) {
|
|
|
3999
4141
|
console.log("\u2713 Browser opened\n");
|
|
4000
4142
|
console.log("Complete authentication in your browser window.");
|
|
4001
4143
|
} else {
|
|
4002
|
-
console.log("Could not open browser automatically
|
|
4003
|
-
console.log("Please open this URL manually:");
|
|
4004
|
-
console.log(webUrl);
|
|
4144
|
+
console.log("Could not open browser automatically.");
|
|
4005
4145
|
}
|
|
4146
|
+
console.log("\nIf the browser did not open, please copy and paste this URL:");
|
|
4147
|
+
console.log(webUrl);
|
|
4006
4148
|
console.log("");
|
|
4007
4149
|
return await waitForAuthentication(keypair);
|
|
4008
4150
|
}
|
|
@@ -4513,7 +4655,15 @@ async function start(credentials, options = {}) {
|
|
|
4513
4655
|
if (caffeinateStarted) {
|
|
4514
4656
|
types$1.logger.infoDeveloper("Sleep prevention enabled (macOS)");
|
|
4515
4657
|
}
|
|
4516
|
-
const messageQueue = new MessageQueue2((mode) => hashObject(
|
|
4658
|
+
const messageQueue = new MessageQueue2((mode) => hashObject({
|
|
4659
|
+
isPlan: mode.permissionMode === "plan",
|
|
4660
|
+
model: mode.model,
|
|
4661
|
+
fallbackModel: mode.fallbackModel,
|
|
4662
|
+
customSystemPrompt: mode.customSystemPrompt,
|
|
4663
|
+
appendSystemPrompt: mode.appendSystemPrompt,
|
|
4664
|
+
allowedTools: mode.allowedTools,
|
|
4665
|
+
disallowedTools: mode.disallowedTools
|
|
4666
|
+
}));
|
|
4517
4667
|
registerHandlers(session);
|
|
4518
4668
|
let currentPermissionMode = options.permissionMode;
|
|
4519
4669
|
let currentModel = options.model;
|