happy-coder 0.8.0 → 0.9.0-1
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 +598 -405
- package/dist/index.mjs +599 -406
- package/dist/lib.d.cts +77 -72
- package/dist/lib.d.mts +77 -72
- package/package.json +11 -9
package/dist/index.mjs
CHANGED
|
@@ -18,19 +18,19 @@ import 'node:events';
|
|
|
18
18
|
import 'socket.io-client';
|
|
19
19
|
import tweetnacl from 'tweetnacl';
|
|
20
20
|
import 'expo-server-sdk';
|
|
21
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
22
|
-
import { createServer } from 'node:http';
|
|
23
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
24
|
-
import * as z from 'zod';
|
|
25
|
-
import { z as z$1 } from 'zod';
|
|
26
21
|
import { spawn as spawn$1, exec, execSync as execSync$1 } from 'child_process';
|
|
27
22
|
import { promisify } from 'util';
|
|
28
23
|
import { createHash } from 'crypto';
|
|
24
|
+
import * as z from 'zod';
|
|
25
|
+
import { z as z$1 } from 'zod';
|
|
29
26
|
import fastify from 'fastify';
|
|
30
27
|
import { validatorCompiler, serializerCompiler } from 'fastify-type-provider-zod';
|
|
31
28
|
import os from 'os';
|
|
32
29
|
import qrcode from 'qrcode-terminal';
|
|
33
30
|
import open$1 from 'open';
|
|
31
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
32
|
+
import { createServer } from 'node:http';
|
|
33
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
34
34
|
import { existsSync as existsSync$1, writeFileSync, chmodSync, unlinkSync } from 'fs';
|
|
35
35
|
|
|
36
36
|
class Session {
|
|
@@ -902,16 +902,19 @@ async function streamToStdin(stream, stdin, abort) {
|
|
|
902
902
|
}
|
|
903
903
|
|
|
904
904
|
class Query {
|
|
905
|
-
constructor(childStdin, childStdout, processExitPromise) {
|
|
905
|
+
constructor(childStdin, childStdout, processExitPromise, canCallTool) {
|
|
906
906
|
this.childStdin = childStdin;
|
|
907
907
|
this.childStdout = childStdout;
|
|
908
908
|
this.processExitPromise = processExitPromise;
|
|
909
|
+
this.canCallTool = canCallTool;
|
|
909
910
|
this.readMessages();
|
|
910
911
|
this.sdkMessages = this.readSdkMessages();
|
|
911
912
|
}
|
|
912
913
|
pendingControlResponses = /* @__PURE__ */ new Map();
|
|
914
|
+
cancelControllers = /* @__PURE__ */ new Map();
|
|
913
915
|
sdkMessages;
|
|
914
916
|
inputStream = new Stream();
|
|
917
|
+
canCallTool;
|
|
915
918
|
/**
|
|
916
919
|
* Set an error on the stream
|
|
917
920
|
*/
|
|
@@ -956,6 +959,12 @@ class Query {
|
|
|
956
959
|
handler(controlResponse.response);
|
|
957
960
|
}
|
|
958
961
|
continue;
|
|
962
|
+
} else if (message.type === "control_request") {
|
|
963
|
+
await this.handleControlRequest(message);
|
|
964
|
+
continue;
|
|
965
|
+
} else if (message.type === "control_cancel_request") {
|
|
966
|
+
this.handleControlCancelRequest(message);
|
|
967
|
+
continue;
|
|
959
968
|
}
|
|
960
969
|
this.inputStream.enqueue(message);
|
|
961
970
|
} catch (e) {
|
|
@@ -968,6 +977,7 @@ class Query {
|
|
|
968
977
|
this.inputStream.error(error);
|
|
969
978
|
} finally {
|
|
970
979
|
this.inputStream.done();
|
|
980
|
+
this.cleanupControllers();
|
|
971
981
|
rl.close();
|
|
972
982
|
}
|
|
973
983
|
}
|
|
@@ -1011,6 +1021,77 @@ class Query {
|
|
|
1011
1021
|
childStdin.write(JSON.stringify(sdkRequest) + "\n");
|
|
1012
1022
|
});
|
|
1013
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Handle incoming control requests for tool permissions
|
|
1026
|
+
* Replicates the exact logic from the SDK's handleControlRequest method
|
|
1027
|
+
*/
|
|
1028
|
+
async handleControlRequest(request) {
|
|
1029
|
+
if (!this.childStdin) {
|
|
1030
|
+
logDebug("Cannot handle control request - no stdin available");
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const controller = new AbortController();
|
|
1034
|
+
this.cancelControllers.set(request.request_id, controller);
|
|
1035
|
+
try {
|
|
1036
|
+
const response = await this.processControlRequest(request, controller.signal);
|
|
1037
|
+
const controlResponse = {
|
|
1038
|
+
type: "control_response",
|
|
1039
|
+
response: {
|
|
1040
|
+
subtype: "success",
|
|
1041
|
+
request_id: request.request_id,
|
|
1042
|
+
response
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
this.childStdin.write(JSON.stringify(controlResponse) + "\n");
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
const controlErrorResponse = {
|
|
1048
|
+
type: "control_response",
|
|
1049
|
+
response: {
|
|
1050
|
+
subtype: "error",
|
|
1051
|
+
request_id: request.request_id,
|
|
1052
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
this.childStdin.write(JSON.stringify(controlErrorResponse) + "\n");
|
|
1056
|
+
} finally {
|
|
1057
|
+
this.cancelControllers.delete(request.request_id);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Handle control cancel requests
|
|
1062
|
+
* Replicates the exact logic from the SDK's handleControlCancelRequest method
|
|
1063
|
+
*/
|
|
1064
|
+
handleControlCancelRequest(request) {
|
|
1065
|
+
const controller = this.cancelControllers.get(request.request_id);
|
|
1066
|
+
if (controller) {
|
|
1067
|
+
controller.abort();
|
|
1068
|
+
this.cancelControllers.delete(request.request_id);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Process control requests based on subtype
|
|
1073
|
+
* Replicates the exact logic from the SDK's processControlRequest method
|
|
1074
|
+
*/
|
|
1075
|
+
async processControlRequest(request, signal) {
|
|
1076
|
+
if (request.request.subtype === "can_use_tool") {
|
|
1077
|
+
if (!this.canCallTool) {
|
|
1078
|
+
throw new Error("canCallTool callback is not provided.");
|
|
1079
|
+
}
|
|
1080
|
+
return this.canCallTool(request.request.tool_name, request.request.input, {
|
|
1081
|
+
signal
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
throw new Error("Unsupported control request subtype: " + request.request.subtype);
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Cleanup method to abort all pending control requests
|
|
1088
|
+
*/
|
|
1089
|
+
cleanupControllers() {
|
|
1090
|
+
for (const [requestId, controller] of this.cancelControllers.entries()) {
|
|
1091
|
+
controller.abort();
|
|
1092
|
+
this.cancelControllers.delete(requestId);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1014
1095
|
}
|
|
1015
1096
|
function query(config) {
|
|
1016
1097
|
const {
|
|
@@ -1027,12 +1108,12 @@ function query(config) {
|
|
|
1027
1108
|
mcpServers,
|
|
1028
1109
|
pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
|
|
1029
1110
|
permissionMode = "default",
|
|
1030
|
-
permissionPromptToolName,
|
|
1031
1111
|
continue: continueConversation,
|
|
1032
1112
|
resume,
|
|
1033
1113
|
model,
|
|
1034
1114
|
fallbackModel,
|
|
1035
|
-
strictMcpConfig
|
|
1115
|
+
strictMcpConfig,
|
|
1116
|
+
canCallTool
|
|
1036
1117
|
} = {}
|
|
1037
1118
|
} = config;
|
|
1038
1119
|
if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
@@ -1043,7 +1124,12 @@ function query(config) {
|
|
|
1043
1124
|
if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
|
|
1044
1125
|
if (maxTurns) args.push("--max-turns", maxTurns.toString());
|
|
1045
1126
|
if (model) args.push("--model", model);
|
|
1046
|
-
if (
|
|
1127
|
+
if (canCallTool) {
|
|
1128
|
+
if (typeof prompt === "string") {
|
|
1129
|
+
throw new Error("canCallTool callback requires --input-format stream-json. Please set prompt as an AsyncIterable.");
|
|
1130
|
+
}
|
|
1131
|
+
args.push("--permission-prompt-tool", "stdio");
|
|
1132
|
+
}
|
|
1047
1133
|
if (continueConversation) args.push("--continue");
|
|
1048
1134
|
if (resume) args.push("--resume", resume);
|
|
1049
1135
|
if (allowedTools.length > 0) args.push("--allowedTools", allowedTools.join(","));
|
|
@@ -1107,7 +1193,7 @@ function query(config) {
|
|
|
1107
1193
|
}
|
|
1108
1194
|
});
|
|
1109
1195
|
});
|
|
1110
|
-
const query2 = new Query(childStdin, child.stdout, processExitPromise);
|
|
1196
|
+
const query2 = new Query(childStdin, child.stdout, processExitPromise, canCallTool);
|
|
1111
1197
|
child.on("error", (error) => {
|
|
1112
1198
|
if (config.options?.abort?.aborted) {
|
|
1113
1199
|
query2.setError(new AbortError("Claude Code process aborted by user"));
|
|
@@ -1125,17 +1211,48 @@ function query(config) {
|
|
|
1125
1211
|
return query2;
|
|
1126
1212
|
}
|
|
1127
1213
|
|
|
1128
|
-
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1135
|
-
await delay(1e3);
|
|
1136
|
-
}
|
|
1214
|
+
function parseCompact(message) {
|
|
1215
|
+
const trimmed = message.trim();
|
|
1216
|
+
if (trimmed === "/compact") {
|
|
1217
|
+
return {
|
|
1218
|
+
isCompact: true,
|
|
1219
|
+
originalMessage: trimmed
|
|
1220
|
+
};
|
|
1137
1221
|
}
|
|
1138
|
-
|
|
1222
|
+
if (trimmed.startsWith("/compact ")) {
|
|
1223
|
+
return {
|
|
1224
|
+
isCompact: true,
|
|
1225
|
+
originalMessage: trimmed
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
return {
|
|
1229
|
+
isCompact: false,
|
|
1230
|
+
originalMessage: message
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function parseClear(message) {
|
|
1234
|
+
const trimmed = message.trim();
|
|
1235
|
+
return {
|
|
1236
|
+
isClear: trimmed === "/clear"
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function parseSpecialCommand(message) {
|
|
1240
|
+
const compactResult = parseCompact(message);
|
|
1241
|
+
if (compactResult.isCompact) {
|
|
1242
|
+
return {
|
|
1243
|
+
type: "compact",
|
|
1244
|
+
originalMessage: compactResult.originalMessage
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
const clearResult = parseClear(message);
|
|
1248
|
+
if (clearResult.isClear) {
|
|
1249
|
+
return {
|
|
1250
|
+
type: "clear"
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
type: null
|
|
1255
|
+
};
|
|
1139
1256
|
}
|
|
1140
1257
|
|
|
1141
1258
|
class PushableAsyncIterable {
|
|
@@ -1264,48 +1381,17 @@ class PushableAsyncIterable {
|
|
|
1264
1381
|
}
|
|
1265
1382
|
}
|
|
1266
1383
|
|
|
1267
|
-
function
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
return {
|
|
1277
|
-
isCompact: true,
|
|
1278
|
-
originalMessage: trimmed
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
return {
|
|
1282
|
-
isCompact: false,
|
|
1283
|
-
originalMessage: message
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
function parseClear(message) {
|
|
1287
|
-
const trimmed = message.trim();
|
|
1288
|
-
return {
|
|
1289
|
-
isClear: trimmed === "/clear"
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
function parseSpecialCommand(message) {
|
|
1293
|
-
const compactResult = parseCompact(message);
|
|
1294
|
-
if (compactResult.isCompact) {
|
|
1295
|
-
return {
|
|
1296
|
-
type: "compact",
|
|
1297
|
-
originalMessage: compactResult.originalMessage
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
const clearResult = parseClear(message);
|
|
1301
|
-
if (clearResult.isClear) {
|
|
1302
|
-
return {
|
|
1303
|
-
type: "clear"
|
|
1304
|
-
};
|
|
1384
|
+
async function awaitFileExist(file, timeout = 1e4) {
|
|
1385
|
+
const startTime = Date.now();
|
|
1386
|
+
while (Date.now() - startTime < timeout) {
|
|
1387
|
+
try {
|
|
1388
|
+
await access(file);
|
|
1389
|
+
return true;
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
await delay(1e3);
|
|
1392
|
+
}
|
|
1305
1393
|
}
|
|
1306
|
-
return
|
|
1307
|
-
type: null
|
|
1308
|
-
};
|
|
1394
|
+
return false;
|
|
1309
1395
|
}
|
|
1310
1396
|
|
|
1311
1397
|
async function claudeRemote(opts) {
|
|
@@ -1318,32 +1404,12 @@ async function claudeRemote(opts) {
|
|
|
1318
1404
|
process.env[key] = value;
|
|
1319
1405
|
});
|
|
1320
1406
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
resume: startFrom ?? void 0,
|
|
1325
|
-
mcpServers: opts.mcpServers,
|
|
1326
|
-
permissionPromptToolName: opts.permissionPromptToolName,
|
|
1327
|
-
permissionMode: opts.permissionMode,
|
|
1328
|
-
model: opts.model,
|
|
1329
|
-
fallbackModel: opts.fallbackModel,
|
|
1330
|
-
customSystemPrompt: opts.customSystemPrompt,
|
|
1331
|
-
appendSystemPrompt: opts.appendSystemPrompt,
|
|
1332
|
-
allowedTools: opts.allowedTools,
|
|
1333
|
-
disallowedTools: opts.disallowedTools,
|
|
1334
|
-
executable: "node",
|
|
1335
|
-
abort: opts.signal,
|
|
1336
|
-
pathToClaudeCodeExecutable: (() => {
|
|
1337
|
-
return resolve(join(projectPath(), "scripts", "claude_remote_launcher.cjs"));
|
|
1338
|
-
})()
|
|
1339
|
-
};
|
|
1340
|
-
if (opts.claudeArgs && opts.claudeArgs.length > 0) {
|
|
1341
|
-
sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
|
|
1407
|
+
const initial = await opts.nextMessage();
|
|
1408
|
+
if (!initial) {
|
|
1409
|
+
return;
|
|
1342
1410
|
}
|
|
1343
|
-
|
|
1344
|
-
const specialCommand = parseSpecialCommand(opts.message);
|
|
1411
|
+
const specialCommand = parseSpecialCommand(initial.message);
|
|
1345
1412
|
if (specialCommand.type === "clear") {
|
|
1346
|
-
logger.debug("[claudeRemote] /clear command detected - should not reach here, handled in start.ts");
|
|
1347
1413
|
if (opts.onCompletionEvent) {
|
|
1348
1414
|
opts.onCompletionEvent("Context was reset");
|
|
1349
1415
|
}
|
|
@@ -1352,29 +1418,33 @@ async function claudeRemote(opts) {
|
|
|
1352
1418
|
}
|
|
1353
1419
|
return;
|
|
1354
1420
|
}
|
|
1421
|
+
let isCompactCommand = false;
|
|
1355
1422
|
if (specialCommand.type === "compact") {
|
|
1356
1423
|
logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
|
|
1357
|
-
|
|
1358
|
-
const isCompactCommand = specialCommand.type === "compact";
|
|
1359
|
-
if (isCompactCommand) {
|
|
1360
|
-
logger.debug("[claudeRemote] Compaction started");
|
|
1424
|
+
isCompactCommand = true;
|
|
1361
1425
|
if (opts.onCompletionEvent) {
|
|
1362
1426
|
opts.onCompletionEvent("Compaction started");
|
|
1363
1427
|
}
|
|
1364
1428
|
}
|
|
1365
|
-
let
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1429
|
+
let mode = initial.mode;
|
|
1430
|
+
const sdkOptions = {
|
|
1431
|
+
cwd: opts.path,
|
|
1432
|
+
resume: startFrom ?? void 0,
|
|
1433
|
+
mcpServers: opts.mcpServers,
|
|
1434
|
+
permissionMode: initial.mode.permissionMode === "plan" ? "plan" : "default",
|
|
1435
|
+
model: initial.mode.model,
|
|
1436
|
+
fallbackModel: initial.mode.fallbackModel,
|
|
1437
|
+
customSystemPrompt: initial.mode.customSystemPrompt ? initial.mode.customSystemPrompt + "\n\n" + systemPrompt : void 0,
|
|
1438
|
+
appendSystemPrompt: initial.mode.appendSystemPrompt ? initial.mode.appendSystemPrompt + "\n\n" + systemPrompt : systemPrompt,
|
|
1439
|
+
allowedTools: initial.mode.allowedTools ? initial.mode.allowedTools.concat(opts.allowedTools) : opts.allowedTools,
|
|
1440
|
+
disallowedTools: initial.mode.disallowedTools,
|
|
1441
|
+
canCallTool: (toolName, input, options) => opts.canCallTool(toolName, input, mode, options),
|
|
1442
|
+
executable: "node",
|
|
1443
|
+
abort: opts.signal,
|
|
1444
|
+
pathToClaudeCodeExecutable: (() => {
|
|
1445
|
+
return resolve(join(projectPath(), "scripts", "claude_remote_launcher.cjs"));
|
|
1446
|
+
})()
|
|
1447
|
+
};
|
|
1378
1448
|
let thinking = false;
|
|
1379
1449
|
const updateThinking = (newThinking) => {
|
|
1380
1450
|
if (thinking !== newThinking) {
|
|
@@ -1385,15 +1455,27 @@ async function claudeRemote(opts) {
|
|
|
1385
1455
|
}
|
|
1386
1456
|
}
|
|
1387
1457
|
};
|
|
1458
|
+
let messages = new PushableAsyncIterable();
|
|
1459
|
+
messages.push({
|
|
1460
|
+
type: "user",
|
|
1461
|
+
message: {
|
|
1462
|
+
role: "user",
|
|
1463
|
+
content: initial.message
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
const response = query({
|
|
1467
|
+
prompt: messages,
|
|
1468
|
+
options: sdkOptions
|
|
1469
|
+
});
|
|
1388
1470
|
updateThinking(true);
|
|
1389
1471
|
try {
|
|
1390
1472
|
logger.debug(`[claudeRemote] Starting to iterate over response`);
|
|
1391
|
-
for await (const
|
|
1392
|
-
logger.debugLargeJson(`[claudeRemote] Message ${
|
|
1393
|
-
opts.onMessage(
|
|
1394
|
-
if (
|
|
1473
|
+
for await (const message of response) {
|
|
1474
|
+
logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
|
|
1475
|
+
opts.onMessage(message);
|
|
1476
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
1395
1477
|
updateThinking(true);
|
|
1396
|
-
const systemInit =
|
|
1478
|
+
const systemInit = message;
|
|
1397
1479
|
if (systemInit.session_id) {
|
|
1398
1480
|
logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
|
|
1399
1481
|
const projectDir = getProjectPath(opts.path);
|
|
@@ -1402,7 +1484,7 @@ async function claudeRemote(opts) {
|
|
|
1402
1484
|
opts.onSessionFound(systemInit.session_id);
|
|
1403
1485
|
}
|
|
1404
1486
|
}
|
|
1405
|
-
if (
|
|
1487
|
+
if (message.type === "result") {
|
|
1406
1488
|
updateThinking(false);
|
|
1407
1489
|
logger.debug("[claudeRemote] Result received, exiting claudeRemote");
|
|
1408
1490
|
if (isCompactCommand) {
|
|
@@ -1410,26 +1492,28 @@ async function claudeRemote(opts) {
|
|
|
1410
1492
|
if (opts.onCompletionEvent) {
|
|
1411
1493
|
opts.onCompletionEvent("Compaction completed");
|
|
1412
1494
|
}
|
|
1495
|
+
isCompactCommand = false;
|
|
1413
1496
|
}
|
|
1414
|
-
|
|
1497
|
+
const next = await opts.nextMessage();
|
|
1498
|
+
if (!next) {
|
|
1499
|
+
messages.end();
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
mode = next.mode;
|
|
1503
|
+
messages.push({ type: "user", message: { role: "user", content: next.message } });
|
|
1415
1504
|
}
|
|
1416
|
-
if (
|
|
1417
|
-
const msg =
|
|
1505
|
+
if (message.type === "user") {
|
|
1506
|
+
const msg = message;
|
|
1418
1507
|
if (msg.message.role === "user" && Array.isArray(msg.message.content)) {
|
|
1419
1508
|
for (let c of msg.message.content) {
|
|
1420
|
-
if (c.type === "tool_result" &&
|
|
1421
|
-
logger.debug("[claudeRemote]
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
if (c.type === "tool_result" && c.tool_use_id && opts.responses.has(c.tool_use_id) && !opts.responses.get(c.tool_use_id).approved) {
|
|
1425
|
-
logger.debug("[claudeRemote] Tool rejected, exiting claudeRemote");
|
|
1509
|
+
if (c.type === "tool_result" && c.tool_use_id && opts.isAborted(c.tool_use_id)) {
|
|
1510
|
+
logger.debug("[claudeRemote] Tool aborted, exiting claudeRemote");
|
|
1426
1511
|
return;
|
|
1427
1512
|
}
|
|
1428
1513
|
}
|
|
1429
1514
|
}
|
|
1430
1515
|
}
|
|
1431
1516
|
}
|
|
1432
|
-
logger.debug(`[claudeRemote] Finished iterating over response`);
|
|
1433
1517
|
} catch (e) {
|
|
1434
1518
|
if (e instanceof AbortError) {
|
|
1435
1519
|
logger.debug(`[claudeRemote] Aborted`);
|
|
@@ -1439,71 +1523,11 @@ async function claudeRemote(opts) {
|
|
|
1439
1523
|
} finally {
|
|
1440
1524
|
updateThinking(false);
|
|
1441
1525
|
}
|
|
1442
|
-
logger.debug(`[claudeRemote] Function completed`);
|
|
1443
1526
|
}
|
|
1444
1527
|
|
|
1445
1528
|
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.`;
|
|
1446
1529
|
const PLAN_FAKE_RESTART = `PlEaZe Continue with plan.`;
|
|
1447
1530
|
|
|
1448
|
-
async function startPermissionServerV2(handler) {
|
|
1449
|
-
const mcp = new McpServer({
|
|
1450
|
-
name: "Permission Server",
|
|
1451
|
-
version: "1.0.0",
|
|
1452
|
-
description: "A server that allows you to request permissions from the user"
|
|
1453
|
-
});
|
|
1454
|
-
mcp.registerTool("ask_permission", {
|
|
1455
|
-
description: "Request permission to execute a tool",
|
|
1456
|
-
title: "Request Permission",
|
|
1457
|
-
inputSchema: {
|
|
1458
|
-
tool_name: z$1.string().describe("The tool that needs permission"),
|
|
1459
|
-
input: z$1.any().describe("The arguments for the tool")
|
|
1460
|
-
}
|
|
1461
|
-
}, async (args) => {
|
|
1462
|
-
const response = await handler({ name: args.tool_name, arguments: args.input });
|
|
1463
|
-
logger.debugLargeJson("[permissionServerV2] Response", response);
|
|
1464
|
-
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.` };
|
|
1465
|
-
return {
|
|
1466
|
-
content: [
|
|
1467
|
-
{
|
|
1468
|
-
type: "text",
|
|
1469
|
-
text: JSON.stringify(result)
|
|
1470
|
-
}
|
|
1471
|
-
],
|
|
1472
|
-
isError: false
|
|
1473
|
-
};
|
|
1474
|
-
});
|
|
1475
|
-
const transport = new StreamableHTTPServerTransport({
|
|
1476
|
-
// NOTE: Returning session id here will result in claude
|
|
1477
|
-
// sdk spawn to fail with `Invalid Request: Server already initialized`
|
|
1478
|
-
sessionIdGenerator: void 0
|
|
1479
|
-
});
|
|
1480
|
-
await mcp.connect(transport);
|
|
1481
|
-
const server = createServer(async (req, res) => {
|
|
1482
|
-
try {
|
|
1483
|
-
await transport.handleRequest(req, res);
|
|
1484
|
-
} catch (error) {
|
|
1485
|
-
logger.debug("Error handling request:", error);
|
|
1486
|
-
if (!res.headersSent) {
|
|
1487
|
-
res.writeHead(500).end();
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
});
|
|
1491
|
-
const baseUrl = await new Promise((resolve) => {
|
|
1492
|
-
server.listen(0, "127.0.0.1", () => {
|
|
1493
|
-
const addr = server.address();
|
|
1494
|
-
resolve(new URL(`http://127.0.0.1:${addr.port}`));
|
|
1495
|
-
});
|
|
1496
|
-
});
|
|
1497
|
-
return {
|
|
1498
|
-
url: baseUrl.toString(),
|
|
1499
|
-
toolName: "ask_permission",
|
|
1500
|
-
stop: () => {
|
|
1501
|
-
mcp.close();
|
|
1502
|
-
server.close();
|
|
1503
|
-
}
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1531
|
function deepEqual(a, b) {
|
|
1508
1532
|
if (a === b) return true;
|
|
1509
1533
|
if (a == null || b == null) return false;
|
|
@@ -1518,133 +1542,218 @@ function deepEqual(a, b) {
|
|
|
1518
1542
|
return true;
|
|
1519
1543
|
}
|
|
1520
1544
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1545
|
+
const STANDARD_TOOLS = {
|
|
1546
|
+
// File operations
|
|
1547
|
+
"Read": "Read File",
|
|
1548
|
+
"Write": "Write File",
|
|
1549
|
+
"Edit": "Edit File",
|
|
1550
|
+
"MultiEdit": "Edit File",
|
|
1551
|
+
"NotebookEdit": "Edit Notebook",
|
|
1552
|
+
// Search and navigation
|
|
1553
|
+
"Glob": "Find Files",
|
|
1554
|
+
"Grep": "Search in Files",
|
|
1555
|
+
"LS": "List Directory",
|
|
1556
|
+
// Command execution
|
|
1557
|
+
"Bash": "Run Command",
|
|
1558
|
+
"BashOutput": "Check Command Output",
|
|
1559
|
+
"KillBash": "Stop Command",
|
|
1560
|
+
// Task management
|
|
1561
|
+
"TodoWrite": "Update Tasks",
|
|
1562
|
+
"TodoRead": "Read Tasks",
|
|
1563
|
+
"Task": "Launch Agent",
|
|
1564
|
+
// Web tools
|
|
1565
|
+
"WebFetch": "Fetch Web Page",
|
|
1566
|
+
"WebSearch": "Search Web",
|
|
1567
|
+
// Special cases
|
|
1568
|
+
"exit_plan_mode": "Execute Plan",
|
|
1569
|
+
"ExitPlanMode": "Execute Plan"
|
|
1570
|
+
};
|
|
1571
|
+
function toTitleCase(str) {
|
|
1572
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1573
|
+
}
|
|
1574
|
+
function getToolName(toolName) {
|
|
1575
|
+
if (STANDARD_TOOLS[toolName]) {
|
|
1576
|
+
return STANDARD_TOOLS[toolName];
|
|
1577
|
+
}
|
|
1578
|
+
if (toolName.startsWith("mcp__")) {
|
|
1579
|
+
const parts = toolName.split("__");
|
|
1580
|
+
if (parts.length >= 3) {
|
|
1581
|
+
const server = toTitleCase(parts[1]);
|
|
1582
|
+
const action = toTitleCase(parts.slice(2).join("_"));
|
|
1583
|
+
return `${server}: ${action}`;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return toTitleCase(toolName);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function getToolDescriptor(toolName) {
|
|
1590
|
+
if (toolName === "exit_plan_mode" || toolName === "ExitPlanMode") {
|
|
1591
|
+
return { edit: false, exitPlan: true };
|
|
1592
|
+
}
|
|
1593
|
+
if (toolName === "Edit" || toolName === "MultiEdit" || toolName === "Write" || toolName === "NotebookEdit") {
|
|
1594
|
+
return { edit: true, exitPlan: false };
|
|
1595
|
+
}
|
|
1596
|
+
return { edit: false, exitPlan: false };
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
class PermissionHandler {
|
|
1600
|
+
toolCalls = [];
|
|
1601
|
+
responses = /* @__PURE__ */ new Map();
|
|
1602
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
1603
|
+
session;
|
|
1604
|
+
allowedTools = /* @__PURE__ */ new Set();
|
|
1605
|
+
allowedBashLiterals = /* @__PURE__ */ new Set();
|
|
1606
|
+
allowedBashPrefixes = /* @__PURE__ */ new Set();
|
|
1607
|
+
permissionMode = "default";
|
|
1608
|
+
constructor(session) {
|
|
1609
|
+
this.session = session;
|
|
1610
|
+
this.setupClientHandler();
|
|
1611
|
+
}
|
|
1612
|
+
handleModeChange(mode) {
|
|
1613
|
+
this.permissionMode = mode;
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Handler response
|
|
1617
|
+
*/
|
|
1618
|
+
handlePermissionResponse(response, pending) {
|
|
1619
|
+
if (response.allowTools && response.allowTools.length > 0) {
|
|
1620
|
+
response.allowTools.forEach((tool) => {
|
|
1621
|
+
if (tool.startsWith("Bash(") || tool === "Bash") {
|
|
1622
|
+
this.parseBashPermission(tool);
|
|
1623
|
+
} else {
|
|
1624
|
+
this.allowedTools.add(tool);
|
|
1625
|
+
}
|
|
1539
1626
|
});
|
|
1540
1627
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
resolve({ approved: false, reason: PLAN_FAKE_REJECT });
|
|
1555
|
-
} else {
|
|
1556
|
-
resolve(response);
|
|
1557
|
-
}
|
|
1558
|
-
};
|
|
1559
|
-
requests.set(id, wrappedResolve);
|
|
1628
|
+
if (response.mode) {
|
|
1629
|
+
this.permissionMode = response.mode;
|
|
1630
|
+
}
|
|
1631
|
+
if (pending.toolName === "exit_plan_mode" || pending.toolName === "ExitPlanMode") {
|
|
1632
|
+
logger.debug("Plan mode result received", response);
|
|
1633
|
+
if (response.approved) {
|
|
1634
|
+
logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
|
|
1635
|
+
if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
|
|
1636
|
+
this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
|
|
1637
|
+
} else {
|
|
1638
|
+
this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
|
|
1639
|
+
}
|
|
1640
|
+
pending.resolve({ behavior: "deny", message: PLAN_FAKE_REJECT });
|
|
1560
1641
|
} else {
|
|
1561
|
-
|
|
1642
|
+
pending.resolve({ behavior: "deny", message: response.reason || "Plan rejected" });
|
|
1562
1643
|
}
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
reason: "Timeout"
|
|
1582
|
-
}
|
|
1644
|
+
} else {
|
|
1645
|
+
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.` };
|
|
1646
|
+
pending.resolve(result);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Creates the canCallTool callback for the SDK
|
|
1651
|
+
*/
|
|
1652
|
+
handleToolCall = async (toolName, input, mode, options) => {
|
|
1653
|
+
if (toolName === "Bash") {
|
|
1654
|
+
const inputObj = input;
|
|
1655
|
+
if (inputObj?.command) {
|
|
1656
|
+
if (this.allowedBashLiterals.has(inputObj.command)) {
|
|
1657
|
+
return { behavior: "allow", updatedInput: input };
|
|
1658
|
+
}
|
|
1659
|
+
for (const prefix of this.allowedBashPrefixes) {
|
|
1660
|
+
if (inputObj.command.startsWith(prefix)) {
|
|
1661
|
+
return { behavior: "allow", updatedInput: input };
|
|
1583
1662
|
}
|
|
1584
|
-
};
|
|
1585
|
-
});
|
|
1586
|
-
}, 1e3 * 60 * 4.5);
|
|
1587
|
-
logger.debug("Permission request" + id + " " + JSON.stringify(request));
|
|
1588
|
-
session.api.push().sendToAllDevices(
|
|
1589
|
-
"Permission Request",
|
|
1590
|
-
`Claude wants to use ${request.name}`,
|
|
1591
|
-
{
|
|
1592
|
-
sessionId: session.client.sessionId,
|
|
1593
|
-
requestId: id,
|
|
1594
|
-
tool: request.name,
|
|
1595
|
-
type: "permission_request"
|
|
1596
|
-
}
|
|
1597
|
-
);
|
|
1598
|
-
session.client.updateAgentState((currentState) => ({
|
|
1599
|
-
...currentState,
|
|
1600
|
-
requests: {
|
|
1601
|
-
...currentState.requests,
|
|
1602
|
-
[id]: {
|
|
1603
|
-
tool: request.name,
|
|
1604
|
-
arguments: request.arguments,
|
|
1605
|
-
createdAt: Date.now()
|
|
1606
1663
|
}
|
|
1607
1664
|
}
|
|
1608
|
-
}))
|
|
1609
|
-
|
|
1610
|
-
return promise;
|
|
1611
|
-
}
|
|
1612
|
-
session.client.setHandler("permission", async (message) => {
|
|
1613
|
-
logger.debug("Permission response" + JSON.stringify(message));
|
|
1614
|
-
const id = message.id;
|
|
1615
|
-
const resolve = requests.get(id);
|
|
1616
|
-
if (resolve) {
|
|
1617
|
-
responses.set(id, message);
|
|
1618
|
-
resolve({ approved: message.approved, reason: message.reason, mode: message.mode });
|
|
1619
|
-
requests.delete(id);
|
|
1620
|
-
} else {
|
|
1621
|
-
logger.debug("Permission request stale, likely timed out");
|
|
1622
|
-
return;
|
|
1665
|
+
} else if (this.allowedTools.has(toolName)) {
|
|
1666
|
+
return { behavior: "allow", updatedInput: input };
|
|
1623
1667
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1668
|
+
const descriptor = getToolDescriptor(toolName);
|
|
1669
|
+
if (this.permissionMode === "bypassPermissions") {
|
|
1670
|
+
return { behavior: "allow", updatedInput: input };
|
|
1671
|
+
}
|
|
1672
|
+
if (this.permissionMode === "acceptEdits" && descriptor.edit) {
|
|
1673
|
+
return { behavior: "allow", updatedInput: input };
|
|
1674
|
+
}
|
|
1675
|
+
let toolCallId = this.resolveToolCallId(toolName, input);
|
|
1676
|
+
if (!toolCallId) {
|
|
1677
|
+
await delay(1e3);
|
|
1678
|
+
toolCallId = this.resolveToolCallId(toolName, input);
|
|
1679
|
+
if (!toolCallId) {
|
|
1680
|
+
throw new Error(`Could not resolve tool call ID for ${toolName}`);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
return this.handlePermissionRequest(toolCallId, toolName, input, options.signal);
|
|
1684
|
+
};
|
|
1685
|
+
/**
|
|
1686
|
+
* Handles individual permission requests
|
|
1687
|
+
*/
|
|
1688
|
+
async handlePermissionRequest(id, toolName, input, signal) {
|
|
1689
|
+
return new Promise((resolve, reject) => {
|
|
1690
|
+
const abortHandler = () => {
|
|
1691
|
+
this.pendingRequests.delete(id);
|
|
1692
|
+
reject(new Error("Permission request aborted"));
|
|
1693
|
+
};
|
|
1694
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1695
|
+
this.pendingRequests.set(id, {
|
|
1696
|
+
resolve: (result) => {
|
|
1697
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1698
|
+
resolve(result);
|
|
1699
|
+
},
|
|
1700
|
+
reject: (error) => {
|
|
1701
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1702
|
+
reject(error);
|
|
1703
|
+
},
|
|
1704
|
+
toolName,
|
|
1705
|
+
input
|
|
1706
|
+
});
|
|
1707
|
+
this.session.api.push().sendToAllDevices(
|
|
1708
|
+
"Permission Request",
|
|
1709
|
+
`Claude wants to ${getToolName(toolName)}`,
|
|
1710
|
+
{
|
|
1711
|
+
sessionId: this.session.client.sessionId,
|
|
1712
|
+
requestId: id,
|
|
1713
|
+
tool: toolName,
|
|
1714
|
+
type: "permission_request"
|
|
1715
|
+
}
|
|
1716
|
+
);
|
|
1717
|
+
this.session.client.updateAgentState((currentState) => ({
|
|
1631
1718
|
...currentState,
|
|
1632
|
-
requests:
|
|
1633
|
-
|
|
1634
|
-
...currentState.completedRequests,
|
|
1719
|
+
requests: {
|
|
1720
|
+
...currentState.requests,
|
|
1635
1721
|
[id]: {
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
reason: isExitPlanModeSuccess ? "Plan approved" : message.reason
|
|
1722
|
+
tool: toolName,
|
|
1723
|
+
arguments: input,
|
|
1724
|
+
createdAt: Date.now()
|
|
1640
1725
|
}
|
|
1641
1726
|
}
|
|
1642
|
-
};
|
|
1727
|
+
}));
|
|
1728
|
+
logger.debug(`Permission request sent for tool call ${id}: ${toolName}`);
|
|
1643
1729
|
});
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Parses Bash permission strings into literal and prefix sets
|
|
1733
|
+
*/
|
|
1734
|
+
parseBashPermission(permission) {
|
|
1735
|
+
if (permission === "Bash") {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
const bashPattern = /^Bash\((.+?)\)$/;
|
|
1739
|
+
const match = permission.match(bashPattern);
|
|
1740
|
+
if (!match) {
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
const command = match[1];
|
|
1744
|
+
if (command.endsWith(":*")) {
|
|
1745
|
+
const prefix = command.slice(0, -2);
|
|
1746
|
+
this.allowedBashPrefixes.add(prefix);
|
|
1747
|
+
} else {
|
|
1748
|
+
this.allowedBashLiterals.add(command);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Resolves tool call ID based on tool name and input
|
|
1753
|
+
*/
|
|
1754
|
+
resolveToolCallId(name, args) {
|
|
1755
|
+
for (let i = this.toolCalls.length - 1; i >= 0; i--) {
|
|
1756
|
+
const call = this.toolCalls[i];
|
|
1648
1757
|
if (call.name === name && deepEqual(call.input, args)) {
|
|
1649
1758
|
if (call.used) {
|
|
1650
1759
|
return null;
|
|
@@ -1654,59 +1763,22 @@ async function startPermissionResolver(session) {
|
|
|
1654
1763
|
}
|
|
1655
1764
|
}
|
|
1656
1765
|
return null;
|
|
1657
|
-
};
|
|
1658
|
-
function reset() {
|
|
1659
|
-
toolCalls = [];
|
|
1660
|
-
requests.clear();
|
|
1661
|
-
responses.clear();
|
|
1662
|
-
for (const pending of pendingPermissionRequests) {
|
|
1663
|
-
clearTimeout(pending.timeout);
|
|
1664
|
-
}
|
|
1665
|
-
pendingPermissionRequests = [];
|
|
1666
|
-
session.client.updateAgentState((currentState) => {
|
|
1667
|
-
const pendingRequests = currentState.requests || {};
|
|
1668
|
-
const completedRequests = { ...currentState.completedRequests };
|
|
1669
|
-
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
1670
|
-
completedRequests[id] = {
|
|
1671
|
-
...request,
|
|
1672
|
-
completedAt: Date.now(),
|
|
1673
|
-
status: "canceled",
|
|
1674
|
-
reason: "Session switched to local mode"
|
|
1675
|
-
};
|
|
1676
|
-
}
|
|
1677
|
-
return {
|
|
1678
|
-
...currentState,
|
|
1679
|
-
requests: {},
|
|
1680
|
-
// Clear all pending requests
|
|
1681
|
-
completedRequests
|
|
1682
|
-
};
|
|
1683
|
-
});
|
|
1684
1766
|
}
|
|
1685
|
-
|
|
1767
|
+
/**
|
|
1768
|
+
* Handles messages to track tool calls
|
|
1769
|
+
*/
|
|
1770
|
+
onMessage(message) {
|
|
1686
1771
|
if (message.type === "assistant") {
|
|
1687
1772
|
const assistantMsg = message;
|
|
1688
1773
|
if (assistantMsg.message && assistantMsg.message.content) {
|
|
1689
1774
|
for (const block of assistantMsg.message.content) {
|
|
1690
1775
|
if (block.type === "tool_use") {
|
|
1691
|
-
toolCalls.push({
|
|
1776
|
+
this.toolCalls.push({
|
|
1692
1777
|
id: block.id,
|
|
1693
1778
|
name: block.name,
|
|
1694
1779
|
input: block.input,
|
|
1695
1780
|
used: false
|
|
1696
1781
|
});
|
|
1697
|
-
for (let i = pendingPermissionRequests.length - 1; i >= 0; i--) {
|
|
1698
|
-
const pending = pendingPermissionRequests[i];
|
|
1699
|
-
if (pending.request.name === block.name && deepEqual(pending.request.arguments, block.input)) {
|
|
1700
|
-
logger.debug(`Resolving pending permission request for ${block.name} with ID ${block.id}`);
|
|
1701
|
-
clearTimeout(pending.timeout);
|
|
1702
|
-
pendingPermissionRequests.splice(i, 1);
|
|
1703
|
-
handlePermissionRequest(block.id, pending.request).then(
|
|
1704
|
-
pending.resolve,
|
|
1705
|
-
pending.reject
|
|
1706
|
-
);
|
|
1707
|
-
break;
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
1782
|
}
|
|
1711
1783
|
}
|
|
1712
1784
|
}
|
|
@@ -1716,7 +1788,7 @@ async function startPermissionResolver(session) {
|
|
|
1716
1788
|
if (userMsg.message && userMsg.message.content && Array.isArray(userMsg.message.content)) {
|
|
1717
1789
|
for (const block of userMsg.message.content) {
|
|
1718
1790
|
if (block.type === "tool_result" && block.tool_use_id) {
|
|
1719
|
-
const toolCall = toolCalls.find((tc) => tc.id === block.tool_use_id);
|
|
1791
|
+
const toolCall = this.toolCalls.find((tc) => tc.id === block.tool_use_id);
|
|
1720
1792
|
if (toolCall && !toolCall.used) {
|
|
1721
1793
|
toolCall.used = true;
|
|
1722
1794
|
}
|
|
@@ -1725,12 +1797,95 @@ async function startPermissionResolver(session) {
|
|
|
1725
1797
|
}
|
|
1726
1798
|
}
|
|
1727
1799
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
responses
|
|
1733
|
-
|
|
1800
|
+
/**
|
|
1801
|
+
* Checks if a tool call is rejected
|
|
1802
|
+
*/
|
|
1803
|
+
isAborted(toolCallId) {
|
|
1804
|
+
if (this.responses.get(toolCallId)?.approved === false) {
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
const toolCall = this.toolCalls.find((tc) => tc.id === toolCallId);
|
|
1808
|
+
if (toolCall && (toolCall.name === "exit_plan_mode" || toolCall.name === "ExitPlanMode")) {
|
|
1809
|
+
return true;
|
|
1810
|
+
}
|
|
1811
|
+
return false;
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Resets all state for new sessions
|
|
1815
|
+
*/
|
|
1816
|
+
reset() {
|
|
1817
|
+
this.toolCalls = [];
|
|
1818
|
+
this.responses.clear();
|
|
1819
|
+
this.allowedTools.clear();
|
|
1820
|
+
this.allowedBashLiterals.clear();
|
|
1821
|
+
this.allowedBashPrefixes.clear();
|
|
1822
|
+
for (const [, pending] of this.pendingRequests.entries()) {
|
|
1823
|
+
pending.reject(new Error("Session reset"));
|
|
1824
|
+
}
|
|
1825
|
+
this.pendingRequests.clear();
|
|
1826
|
+
this.session.client.updateAgentState((currentState) => {
|
|
1827
|
+
const pendingRequests = currentState.requests || {};
|
|
1828
|
+
const completedRequests = { ...currentState.completedRequests };
|
|
1829
|
+
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
1830
|
+
completedRequests[id] = {
|
|
1831
|
+
...request,
|
|
1832
|
+
completedAt: Date.now(),
|
|
1833
|
+
status: "canceled",
|
|
1834
|
+
reason: "Session switched to local mode"
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
return {
|
|
1838
|
+
...currentState,
|
|
1839
|
+
requests: {},
|
|
1840
|
+
// Clear all pending requests
|
|
1841
|
+
completedRequests
|
|
1842
|
+
};
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Sets up the client handler for permission responses
|
|
1847
|
+
*/
|
|
1848
|
+
setupClientHandler() {
|
|
1849
|
+
this.session.client.setHandler("permission", async (message) => {
|
|
1850
|
+
logger.debug(`Permission response: ${JSON.stringify(message)}`);
|
|
1851
|
+
const id = message.id;
|
|
1852
|
+
const pending = this.pendingRequests.get(id);
|
|
1853
|
+
if (!pending) {
|
|
1854
|
+
logger.debug("Permission request not found or already resolved");
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
this.responses.set(id, { ...message, receivedAt: Date.now() });
|
|
1858
|
+
this.pendingRequests.delete(id);
|
|
1859
|
+
this.handlePermissionResponse(message, pending);
|
|
1860
|
+
this.session.client.updateAgentState((currentState) => {
|
|
1861
|
+
const request = currentState.requests?.[id];
|
|
1862
|
+
if (!request) return currentState;
|
|
1863
|
+
let r = { ...currentState.requests };
|
|
1864
|
+
delete r[id];
|
|
1865
|
+
return {
|
|
1866
|
+
...currentState,
|
|
1867
|
+
requests: r,
|
|
1868
|
+
completedRequests: {
|
|
1869
|
+
...currentState.completedRequests,
|
|
1870
|
+
[id]: {
|
|
1871
|
+
...request,
|
|
1872
|
+
completedAt: Date.now(),
|
|
1873
|
+
status: message.approved ? "approved" : "denied",
|
|
1874
|
+
reason: message.reason,
|
|
1875
|
+
mode: message.mode,
|
|
1876
|
+
allowTools: message.allowTools
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1880
|
+
});
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Gets the responses map (for compatibility with existing code)
|
|
1885
|
+
*/
|
|
1886
|
+
getResponses() {
|
|
1887
|
+
return this.responses;
|
|
1888
|
+
}
|
|
1734
1889
|
}
|
|
1735
1890
|
|
|
1736
1891
|
function formatClaudeMessageForInk(message, messageBuffer, onAssistantResult) {
|
|
@@ -2104,12 +2259,6 @@ async function claudeRemoteLauncher(session) {
|
|
|
2104
2259
|
}
|
|
2105
2260
|
process.stdin.setEncoding("utf8");
|
|
2106
2261
|
}
|
|
2107
|
-
const scanner = await createSessionScanner({
|
|
2108
|
-
sessionId: session.sessionId,
|
|
2109
|
-
workingDirectory: session.path,
|
|
2110
|
-
onMessage: (message) => {
|
|
2111
|
-
}
|
|
2112
|
-
});
|
|
2113
2262
|
let exitReason = null;
|
|
2114
2263
|
let abortController = null;
|
|
2115
2264
|
let abortFuture = null;
|
|
@@ -2132,17 +2281,17 @@ async function claudeRemoteLauncher(session) {
|
|
|
2132
2281
|
}
|
|
2133
2282
|
session.client.setHandler("abort", doAbort);
|
|
2134
2283
|
session.client.setHandler("switch", doSwitch);
|
|
2135
|
-
const
|
|
2284
|
+
const permissionHandler = new PermissionHandler(session);
|
|
2136
2285
|
const sdkToLogConverter = new SDKToLogConverter({
|
|
2137
2286
|
sessionId: session.sessionId || "unknown",
|
|
2138
2287
|
cwd: session.path,
|
|
2139
2288
|
version: process.env.npm_package_version
|
|
2140
|
-
},
|
|
2289
|
+
}, permissionHandler.getResponses());
|
|
2141
2290
|
let planModeToolCalls = /* @__PURE__ */ new Set();
|
|
2142
2291
|
let ongoingToolCalls = /* @__PURE__ */ new Map();
|
|
2143
2292
|
function onMessage(message) {
|
|
2144
2293
|
formatClaudeMessageForInk(message, messageBuffer);
|
|
2145
|
-
|
|
2294
|
+
permissionHandler.onMessage(message);
|
|
2146
2295
|
if (message.type === "assistant") {
|
|
2147
2296
|
let umessage = message;
|
|
2148
2297
|
if (umessage.message.content && Array.isArray(umessage.message.content)) {
|
|
@@ -2206,6 +2355,32 @@ async function claudeRemoteLauncher(session) {
|
|
|
2206
2355
|
}
|
|
2207
2356
|
const logMessage = sdkToLogConverter.convert(msg);
|
|
2208
2357
|
if (logMessage) {
|
|
2358
|
+
if (logMessage.type === "user" && logMessage.message?.content) {
|
|
2359
|
+
const content = Array.isArray(logMessage.message.content) ? logMessage.message.content : [];
|
|
2360
|
+
for (let i = 0; i < content.length; i++) {
|
|
2361
|
+
const c = content[i];
|
|
2362
|
+
if (c.type === "tool_result" && c.tool_use_id) {
|
|
2363
|
+
const responses = permissionHandler.getResponses();
|
|
2364
|
+
const response = responses.get(c.tool_use_id);
|
|
2365
|
+
if (response) {
|
|
2366
|
+
const permissions = {
|
|
2367
|
+
date: response.receivedAt || Date.now(),
|
|
2368
|
+
result: response.approved ? "approved" : "denied"
|
|
2369
|
+
};
|
|
2370
|
+
if (response.mode) {
|
|
2371
|
+
permissions.mode = response.mode;
|
|
2372
|
+
}
|
|
2373
|
+
if (response.allowTools && response.allowTools.length > 0) {
|
|
2374
|
+
permissions.allowedTools = response.allowTools;
|
|
2375
|
+
}
|
|
2376
|
+
content[i] = {
|
|
2377
|
+
...c,
|
|
2378
|
+
permissions
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2209
2384
|
if (logMessage.type !== "system") {
|
|
2210
2385
|
session.client.sendClaudeSessionMessage(logMessage);
|
|
2211
2386
|
}
|
|
@@ -2225,58 +2400,57 @@ async function claudeRemoteLauncher(session) {
|
|
|
2225
2400
|
}
|
|
2226
2401
|
}
|
|
2227
2402
|
try {
|
|
2403
|
+
let pending = null;
|
|
2228
2404
|
while (!exitReason) {
|
|
2229
|
-
logger.debug("[remote]: fetch next message");
|
|
2230
|
-
abortController = new AbortController();
|
|
2231
|
-
abortFuture = new Future();
|
|
2232
|
-
const messageData = await session.queue.waitForMessagesAndGetAsString(abortController.signal);
|
|
2233
|
-
if (!messageData || abortController.signal.aborted) {
|
|
2234
|
-
logger.debug("[remote]: fetch next message done: no message or aborted");
|
|
2235
|
-
abortFuture?.resolve(void 0);
|
|
2236
|
-
if (exitReason) {
|
|
2237
|
-
return exitReason;
|
|
2238
|
-
} else {
|
|
2239
|
-
continue;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
logger.debug("[remote]: fetch next message done: message received");
|
|
2243
|
-
abortFuture?.resolve(void 0);
|
|
2244
|
-
abortFuture = null;
|
|
2245
|
-
abortController = null;
|
|
2246
2405
|
logger.debug("[remote]: launch");
|
|
2247
2406
|
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
2248
2407
|
messageBuffer.addMessage("Starting new Claude session...", "status");
|
|
2249
|
-
|
|
2408
|
+
const controller = new AbortController();
|
|
2409
|
+
abortController = controller;
|
|
2250
2410
|
abortFuture = new Future();
|
|
2251
|
-
|
|
2411
|
+
permissionHandler.reset();
|
|
2252
2412
|
sdkToLogConverter.resetParentChain();
|
|
2413
|
+
let modeHash = null;
|
|
2414
|
+
let mode = null;
|
|
2253
2415
|
try {
|
|
2254
2416
|
await claudeRemote({
|
|
2255
2417
|
sessionId: session.sessionId,
|
|
2256
2418
|
path: session.path,
|
|
2257
|
-
|
|
2258
|
-
mcpServers:
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2419
|
+
allowedTools: session.allowedTools ?? [],
|
|
2420
|
+
mcpServers: session.mcpServers,
|
|
2421
|
+
canCallTool: permissionHandler.handleToolCall,
|
|
2422
|
+
isAborted: (toolCallId) => {
|
|
2423
|
+
return permissionHandler.isAborted(toolCallId);
|
|
2424
|
+
},
|
|
2425
|
+
nextMessage: async () => {
|
|
2426
|
+
if (pending) {
|
|
2427
|
+
let p = pending;
|
|
2428
|
+
pending = null;
|
|
2429
|
+
permissionHandler.handleModeChange(p.mode.permissionMode);
|
|
2430
|
+
return p;
|
|
2263
2431
|
}
|
|
2432
|
+
let msg = await session.queue.waitForMessagesAndGetAsString(controller.signal);
|
|
2433
|
+
if (msg) {
|
|
2434
|
+
if (modeHash && msg.hash !== modeHash || msg.isolate) {
|
|
2435
|
+
logger.debug("[remote]: mode has changed, pending message");
|
|
2436
|
+
pending = msg;
|
|
2437
|
+
return null;
|
|
2438
|
+
}
|
|
2439
|
+
modeHash = msg.hash;
|
|
2440
|
+
mode = msg.mode;
|
|
2441
|
+
permissionHandler.handleModeChange(mode.permissionMode);
|
|
2442
|
+
return {
|
|
2443
|
+
message: msg.message,
|
|
2444
|
+
mode: msg.mode
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
return null;
|
|
2264
2448
|
},
|
|
2265
|
-
permissionPromptToolName: "mcp__permission__" + permissions.server.toolName,
|
|
2266
|
-
permissionMode: messageData.mode.permissionMode,
|
|
2267
|
-
model: messageData.mode.model,
|
|
2268
|
-
fallbackModel: messageData.mode.fallbackModel,
|
|
2269
|
-
customSystemPrompt: messageData.mode.customSystemPrompt,
|
|
2270
|
-
appendSystemPrompt: messageData.mode.appendSystemPrompt ? messageData.mode.appendSystemPrompt + "\n" + systemPrompt : systemPrompt,
|
|
2271
|
-
allowedTools: messageData.mode.allowedTools ? [...messageData.mode.allowedTools, ...session.allowedTools ? session.allowedTools : []] : session.allowedTools ? [...session.allowedTools] : void 0,
|
|
2272
|
-
disallowedTools: messageData.mode.disallowedTools,
|
|
2273
2449
|
onSessionFound: (sessionId) => {
|
|
2274
2450
|
sdkToLogConverter.updateSessionId(sessionId);
|
|
2275
2451
|
session.onSessionFound(sessionId);
|
|
2276
|
-
scanner.onNewSession(sessionId);
|
|
2277
2452
|
},
|
|
2278
2453
|
onThinkingChange: session.onThinkingChange,
|
|
2279
|
-
message: messageData.message,
|
|
2280
2454
|
claudeEnvVars: session.claudeEnvVars,
|
|
2281
2455
|
claudeArgs: session.claudeArgs,
|
|
2282
2456
|
onMessage,
|
|
@@ -2294,11 +2468,13 @@ async function claudeRemoteLauncher(session) {
|
|
|
2294
2468
|
session.client.sendSessionEvent({ type: "message", message: "Aborted by user" });
|
|
2295
2469
|
}
|
|
2296
2470
|
} catch (e) {
|
|
2471
|
+
logger.debug("[remote]: launch error", e);
|
|
2297
2472
|
if (!exitReason) {
|
|
2298
2473
|
session.client.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
|
|
2299
2474
|
continue;
|
|
2300
2475
|
}
|
|
2301
2476
|
} finally {
|
|
2477
|
+
logger.debug("[remote]: launch finally");
|
|
2302
2478
|
for (let [toolCallId, { parentToolCallId }] of ongoingToolCalls) {
|
|
2303
2479
|
const converted = sdkToLogConverter.generateInterruptedToolResult(toolCallId, parentToolCallId);
|
|
2304
2480
|
if (converted) {
|
|
@@ -2311,11 +2487,13 @@ async function claudeRemoteLauncher(session) {
|
|
|
2311
2487
|
abortFuture?.resolve(void 0);
|
|
2312
2488
|
abortFuture = null;
|
|
2313
2489
|
logger.debug("[remote]: launch done");
|
|
2314
|
-
|
|
2490
|
+
permissionHandler.reset();
|
|
2491
|
+
modeHash = null;
|
|
2492
|
+
mode = null;
|
|
2315
2493
|
}
|
|
2316
2494
|
}
|
|
2317
2495
|
} finally {
|
|
2318
|
-
|
|
2496
|
+
permissionHandler.reset();
|
|
2319
2497
|
process.stdin.off("data", abort);
|
|
2320
2498
|
if (process.stdin.isTTY) {
|
|
2321
2499
|
process.stdin.setRawMode(false);
|
|
@@ -2327,7 +2505,6 @@ async function claudeRemoteLauncher(session) {
|
|
|
2327
2505
|
if (abortFuture) {
|
|
2328
2506
|
abortFuture.resolve(void 0);
|
|
2329
2507
|
}
|
|
2330
|
-
await scanner.cleanup();
|
|
2331
2508
|
}
|
|
2332
2509
|
return exitReason || "exit";
|
|
2333
2510
|
}
|
|
@@ -2379,7 +2556,7 @@ async function loop(opts) {
|
|
|
2379
2556
|
}
|
|
2380
2557
|
|
|
2381
2558
|
var name = "happy-coder";
|
|
2382
|
-
var version = "0.
|
|
2559
|
+
var version = "0.9.0-1";
|
|
2383
2560
|
var description = "Claude Code session sharing CLI";
|
|
2384
2561
|
var author = "Kirill Dubovitskiy";
|
|
2385
2562
|
var license = "MIT";
|
|
@@ -2429,18 +2606,14 @@ var scripts = {
|
|
|
2429
2606
|
test: "yarn build && vitest run",
|
|
2430
2607
|
"test:watch": "vitest",
|
|
2431
2608
|
"test:integration-test-env": "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
2432
|
-
dev: "
|
|
2609
|
+
dev: "yarn build && DEBUG=1 npx tsx src/index.ts",
|
|
2433
2610
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
2434
2611
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
2435
2612
|
prepublishOnly: "yarn build && yarn test",
|
|
2436
|
-
|
|
2437
|
-
"patch:publish": "yarn build && npm version patch && npm publish",
|
|
2438
|
-
"version:prerelease": "yarn build && npm version prerelease --preid=beta",
|
|
2439
|
-
"publish:prerelease": "npm publish --tag beta",
|
|
2440
|
-
"beta:publish": "yarn version:prerelease && yarn publish:prerelease"
|
|
2613
|
+
release: "release-it"
|
|
2441
2614
|
};
|
|
2442
2615
|
var dependencies = {
|
|
2443
|
-
"@anthropic-ai/claude-code": "^1.0.
|
|
2616
|
+
"@anthropic-ai/claude-code": "^1.0.89",
|
|
2444
2617
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
2445
2618
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
2446
2619
|
"@stablelib/base64": "^2.0.1",
|
|
@@ -2469,6 +2642,7 @@ var devDependencies = {
|
|
|
2469
2642
|
eslint: "^9",
|
|
2470
2643
|
"eslint-config-prettier": "^10",
|
|
2471
2644
|
pkgroll: "^2.14.2",
|
|
2645
|
+
"release-it": "^19.0.4",
|
|
2472
2646
|
shx: "^0.3.3",
|
|
2473
2647
|
"ts-node": "^10",
|
|
2474
2648
|
tsx: "^4.20.3",
|
|
@@ -2476,7 +2650,12 @@ var devDependencies = {
|
|
|
2476
2650
|
vitest: "^3.2.4"
|
|
2477
2651
|
};
|
|
2478
2652
|
var resolutions = {
|
|
2479
|
-
"whatwg-url": "14.2.0"
|
|
2653
|
+
"whatwg-url": "14.2.0",
|
|
2654
|
+
"parse-path": "7.0.3",
|
|
2655
|
+
"@types/parse-path": "7.0.3"
|
|
2656
|
+
};
|
|
2657
|
+
var publishConfig = {
|
|
2658
|
+
registry: "https://registry.npmjs.org"
|
|
2480
2659
|
};
|
|
2481
2660
|
var packageManager = "yarn@1.22.22";
|
|
2482
2661
|
var packageJson = {
|
|
@@ -2499,6 +2678,7 @@ var packageJson = {
|
|
|
2499
2678
|
dependencies: dependencies,
|
|
2500
2679
|
devDependencies: devDependencies,
|
|
2501
2680
|
resolutions: resolutions,
|
|
2681
|
+
publishConfig: publishConfig,
|
|
2502
2682
|
packageManager: packageManager
|
|
2503
2683
|
};
|
|
2504
2684
|
|
|
@@ -2885,15 +3065,17 @@ async function clearDaemonState() {
|
|
|
2885
3065
|
}
|
|
2886
3066
|
|
|
2887
3067
|
class MessageQueue2 {
|
|
2888
|
-
constructor(modeHasher) {
|
|
2889
|
-
this.modeHasher = modeHasher;
|
|
2890
|
-
logger.debug(`[MessageQueue2] Initialized`);
|
|
2891
|
-
}
|
|
2892
3068
|
queue = [];
|
|
2893
3069
|
// Made public for testing
|
|
2894
3070
|
waiter = null;
|
|
2895
3071
|
closed = false;
|
|
2896
3072
|
onMessageHandler = null;
|
|
3073
|
+
modeHasher;
|
|
3074
|
+
constructor(modeHasher, onMessageHandler = null) {
|
|
3075
|
+
this.modeHasher = modeHasher;
|
|
3076
|
+
this.onMessageHandler = onMessageHandler;
|
|
3077
|
+
logger.debug(`[MessageQueue2] Initialized`);
|
|
3078
|
+
}
|
|
2897
3079
|
/**
|
|
2898
3080
|
* Set a handler that will be called when a message arrives
|
|
2899
3081
|
*/
|
|
@@ -3068,6 +3250,7 @@ class MessageQueue2 {
|
|
|
3068
3250
|
const firstItem = this.queue[0];
|
|
3069
3251
|
const sameModeMessages = [];
|
|
3070
3252
|
let mode = firstItem.mode;
|
|
3253
|
+
let isolate = firstItem.isolate ?? false;
|
|
3071
3254
|
const targetModeHash = firstItem.modeHash;
|
|
3072
3255
|
if (firstItem.isolate) {
|
|
3073
3256
|
const item = this.queue.shift();
|
|
@@ -3083,7 +3266,9 @@ class MessageQueue2 {
|
|
|
3083
3266
|
const combinedMessage = sameModeMessages.join("\n");
|
|
3084
3267
|
return {
|
|
3085
3268
|
message: combinedMessage,
|
|
3086
|
-
mode
|
|
3269
|
+
mode,
|
|
3270
|
+
hash: targetModeHash,
|
|
3271
|
+
isolate
|
|
3087
3272
|
};
|
|
3088
3273
|
}
|
|
3089
3274
|
/**
|
|
@@ -3978,10 +4163,10 @@ async function doWebAuth(keypair) {
|
|
|
3978
4163
|
console.log("\u2713 Browser opened\n");
|
|
3979
4164
|
console.log("Complete authentication in your browser window.");
|
|
3980
4165
|
} else {
|
|
3981
|
-
console.log("Could not open browser automatically
|
|
3982
|
-
console.log("Please open this URL manually:");
|
|
3983
|
-
console.log(webUrl);
|
|
4166
|
+
console.log("Could not open browser automatically.");
|
|
3984
4167
|
}
|
|
4168
|
+
console.log("\nIf the browser did not open, please copy and paste this URL:");
|
|
4169
|
+
console.log(webUrl);
|
|
3985
4170
|
console.log("");
|
|
3986
4171
|
return await waitForAuthentication(keypair);
|
|
3987
4172
|
}
|
|
@@ -4492,7 +4677,15 @@ async function start(credentials, options = {}) {
|
|
|
4492
4677
|
if (caffeinateStarted) {
|
|
4493
4678
|
logger.infoDeveloper("Sleep prevention enabled (macOS)");
|
|
4494
4679
|
}
|
|
4495
|
-
const messageQueue = new MessageQueue2((mode) => hashObject(
|
|
4680
|
+
const messageQueue = new MessageQueue2((mode) => hashObject({
|
|
4681
|
+
isPlan: mode.permissionMode === "plan",
|
|
4682
|
+
model: mode.model,
|
|
4683
|
+
fallbackModel: mode.fallbackModel,
|
|
4684
|
+
customSystemPrompt: mode.customSystemPrompt,
|
|
4685
|
+
appendSystemPrompt: mode.appendSystemPrompt,
|
|
4686
|
+
allowedTools: mode.allowedTools,
|
|
4687
|
+
disallowedTools: mode.disallowedTools
|
|
4688
|
+
}));
|
|
4496
4689
|
registerHandlers(session);
|
|
4497
4690
|
let currentPermissionMode = options.permissionMode;
|
|
4498
4691
|
let currentModel = options.model;
|