clay-server 4.2.0-beta.1 → 4.2.0-beta.3
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/lib/project-connection.js +5 -2
- package/lib/project-full-access.js +69 -56
- package/lib/project-sessions.js +126 -26
- package/lib/project-user-message.js +5 -2
- package/lib/project.js +1 -0
- package/lib/public/app.js +6 -0
- package/lib/public/css/filebrowser.css +20 -0
- package/lib/public/css/messages.css +27 -0
- package/lib/public/css/overlays.css +60 -0
- package/lib/public/css/pane.css +21 -4
- package/lib/public/css/pending-message-queue.css +5 -0
- package/lib/public/index.html +8 -3
- package/lib/public/modules/app-connection.js +4 -0
- package/lib/public/modules/app-header.js +18 -25
- package/lib/public/modules/app-messages.js +17 -7
- package/lib/public/modules/app-panels.js +22 -11
- package/lib/public/modules/clay-file-links.js +7 -0
- package/lib/public/modules/clay-log-links.js +4 -0
- package/lib/public/modules/github-links.js +115 -0
- package/lib/public/modules/markdown.js +9 -2
- package/lib/public/modules/mcp-ui.js +154 -17
- package/lib/public/modules/pane-bridge.js +2 -0
- package/lib/public/modules/pane-file-bridge.js +69 -0
- package/lib/public/modules/pending-message-queue-model.js +6 -0
- package/lib/public/modules/pending-message-queue-recovery.js +74 -0
- package/lib/public/modules/pending-message-queue.js +70 -67
- package/lib/public/modules/permission-control.js +187 -0
- package/lib/public/modules/split-view.js +29 -29
- package/lib/sdk-bridge.js +95 -40
- package/lib/session-permission-mode.js +42 -0
- package/lib/sessions.js +14 -1
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker-policy.js +67 -0
- package/lib/yoke/adapters/claude-worker.js +47 -7
- package/lib/yoke/adapters/claude.js +114 -6
- package/package.json +1 -1
package/lib/ws-schema.js
CHANGED
|
@@ -252,6 +252,7 @@ var schema = {
|
|
|
252
252
|
"set_server_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default model" },
|
|
253
253
|
"set_project_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the project default model" },
|
|
254
254
|
"set_permission_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set permission mode for the current session" },
|
|
255
|
+
"permission_mode_result": { direction: "s2c", handler: "lib/public/modules/permission-control.js", description: "Correlated result for an exact project/session permission mode change" },
|
|
255
256
|
"set_session_full_access": { direction: "c2s", handler: "lib/project-sessions.js", description: "Toggle Clay-managed permission skipping for a GUI session" },
|
|
256
257
|
"session_full_access_changed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A session's full-access state changed" },
|
|
257
258
|
"session_full_access_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of toggling session full access" },
|
|
@@ -269,6 +270,7 @@ var schema = {
|
|
|
269
270
|
"autonomous_run_state": { direction: "s2c", handler: "lib/public/modules/autonomous-run.js", description: "Synchronized Until complete run state" },
|
|
270
271
|
"reload_skills": { direction: "c2s", handler: "lib/project-sessions.js", description: "Hot-reload skills/MCP/agents on the active query without a restart" },
|
|
271
272
|
"set_mcp_permission_mode_override": { direction: "c2s", handler: "lib/project-sessions.js", description: "Override permission mode (default/auto/null) for one MCP server" },
|
|
273
|
+
"mcp_permission_mode_result": { direction: "s2c", handler: "lib/public/modules/mcp-ui.js", description: "Correlated result for one session MCP AI review policy change" },
|
|
272
274
|
"set_server_default_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default permission mode" },
|
|
273
275
|
"set_project_default_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the project default permission mode" },
|
|
274
276
|
"set_effort": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set effort level for the current session" },
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Bounded fail-closed lifecycle for worker PreToolUse policy IPC requests.
|
|
2
|
+
|
|
3
|
+
function denied(message) {
|
|
4
|
+
return { behavior: "deny", message: message };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function createPolicyBroker(options) {
|
|
8
|
+
options = options || {};
|
|
9
|
+
var send = options.send;
|
|
10
|
+
var timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : 10000;
|
|
11
|
+
var pending = {};
|
|
12
|
+
|
|
13
|
+
function settle(requestId, result) {
|
|
14
|
+
var record = pending[requestId];
|
|
15
|
+
if (!record) return false;
|
|
16
|
+
delete pending[requestId];
|
|
17
|
+
clearTimeout(record.timer);
|
|
18
|
+
if (record.signal && record.onAbort && typeof record.signal.removeEventListener === "function") {
|
|
19
|
+
record.signal.removeEventListener("abort", record.onAbort);
|
|
20
|
+
}
|
|
21
|
+
record.resolve(result);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function request(message, signal) {
|
|
26
|
+
return new Promise(function(resolve) {
|
|
27
|
+
if (signal && signal.aborted) {
|
|
28
|
+
resolve(denied("Permission policy cancelled."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
var requestId = message.requestId;
|
|
32
|
+
var record = { resolve: resolve, signal: signal || null, onAbort: null, timer: null };
|
|
33
|
+
pending[requestId] = record;
|
|
34
|
+
record.onAbort = function() {
|
|
35
|
+
settle(requestId, denied("Permission policy cancelled."));
|
|
36
|
+
};
|
|
37
|
+
if (signal && typeof signal.addEventListener === "function") {
|
|
38
|
+
signal.addEventListener("abort", record.onAbort, { once: true });
|
|
39
|
+
}
|
|
40
|
+
record.timer = setTimeout(function() {
|
|
41
|
+
settle(requestId, denied("Permission policy timed out."));
|
|
42
|
+
}, timeoutMs);
|
|
43
|
+
var sent = false;
|
|
44
|
+
try { sent = send(message) === true; }
|
|
45
|
+
catch (error) { sent = false; }
|
|
46
|
+
if (!sent) settle(requestId, denied("Permission policy IPC is unavailable."));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function respond(message) {
|
|
51
|
+
var result = message.error ? denied(message.error) : message.result;
|
|
52
|
+
return settle(message.requestId, result || null);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clear(message) {
|
|
56
|
+
var ids = Object.keys(pending);
|
|
57
|
+
for (var i = 0; i < ids.length; i++) settle(ids[i], denied(message || "Permission policy IPC closed."));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pendingCount() {
|
|
61
|
+
return Object.keys(pending).length;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { request: request, respond: respond, clear: clear, pendingCount: pendingCount };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { createPolicyBroker: createPolicyBroker };
|
|
@@ -18,6 +18,7 @@ var crypto = require("crypto");
|
|
|
18
18
|
var path = require("path");
|
|
19
19
|
var claudeContextUsage = require(path.join(__dirname, "../claude-context-usage"));
|
|
20
20
|
var claudeModelSwitch = require(path.join(__dirname, "../claude-model-switch"));
|
|
21
|
+
var createPolicyBroker = require("./claude-worker-policy").createPolicyBroker;
|
|
21
22
|
var fs = require("fs");
|
|
22
23
|
|
|
23
24
|
var socketPath = process.argv[2];
|
|
@@ -244,14 +245,18 @@ function buildMcpServersFromDescriptors(descriptors, sdk) {
|
|
|
244
245
|
|
|
245
246
|
// --- IPC helpers ---
|
|
246
247
|
function sendToDaemon(msg) {
|
|
247
|
-
if (!conn || conn.destroyed) return;
|
|
248
|
+
if (!conn || conn.destroyed) return false;
|
|
248
249
|
try {
|
|
249
250
|
conn.write(JSON.stringify(msg) + "\n");
|
|
251
|
+
return true;
|
|
250
252
|
} catch (e) {
|
|
251
253
|
console.error("[sdk-worker] Failed to send message:", e.message);
|
|
254
|
+
return false;
|
|
252
255
|
}
|
|
253
256
|
}
|
|
254
257
|
|
|
258
|
+
var preToolPolicyBroker = createPolicyBroker({ send: sendToDaemon, timeoutMs: 10000 });
|
|
259
|
+
|
|
255
260
|
function handleMessage(msg) {
|
|
256
261
|
try { require("fs").writeSync(2, "[sdk-worker] MSG: " + msg.type + "\n"); } catch (e) {}
|
|
257
262
|
switch (msg.type) {
|
|
@@ -294,6 +299,9 @@ function handleMessage(msg) {
|
|
|
294
299
|
case "permission_response":
|
|
295
300
|
handlePermissionResponse(msg);
|
|
296
301
|
break;
|
|
302
|
+
case "pre_tool_policy_response":
|
|
303
|
+
handlePreToolPolicyResponse(msg);
|
|
304
|
+
break;
|
|
297
305
|
case "ask_user_response":
|
|
298
306
|
handleAskUserResponse(msg);
|
|
299
307
|
break;
|
|
@@ -394,6 +402,16 @@ function handlePermissionResponse(msg) {
|
|
|
394
402
|
}
|
|
395
403
|
}
|
|
396
404
|
|
|
405
|
+
function handlePreToolPolicyResponse(msg) {
|
|
406
|
+
preToolPolicyBroker.respond(msg);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function preToolUsePolicy(toolName, input, toolUseId, signal) {
|
|
410
|
+
var requestId = crypto.randomUUID();
|
|
411
|
+
return preToolPolicyBroker.request({ type: "pre_tool_policy_request", requestId: requestId, toolName: toolName,
|
|
412
|
+
input: input || {}, toolUseId: toolUseId || "" }, signal);
|
|
413
|
+
}
|
|
414
|
+
|
|
397
415
|
function handleAskUserResponse(msg) {
|
|
398
416
|
var resolve = pendingAskUser[msg.toolUseId];
|
|
399
417
|
if (resolve) {
|
|
@@ -515,6 +533,22 @@ async function handleQueryStart(msg) {
|
|
|
515
533
|
};
|
|
516
534
|
var nativeUserInput = options.userInputMode !== "fallback";
|
|
517
535
|
delete options.userInputMode;
|
|
536
|
+
var usePreToolPolicy = options.clayPreToolUsePolicy === true;
|
|
537
|
+
delete options.clayPreToolUsePolicy;
|
|
538
|
+
if (usePreToolPolicy) {
|
|
539
|
+
options.hooks = options.hooks || {};
|
|
540
|
+
options.hooks.PreToolUse = (options.hooks.PreToolUse || []).concat([{
|
|
541
|
+
hooks: [async function(input, toolUseId, hookOpts) {
|
|
542
|
+
input = input || {};
|
|
543
|
+
var decision = await preToolUsePolicy(input.tool_name, input.tool_input, toolUseId || input.tool_use_id, hookOpts && hookOpts.signal);
|
|
544
|
+
if (!decision) return { continue: true };
|
|
545
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse",
|
|
546
|
+
permissionDecision: decision.behavior === "deny" ? "deny" : "allow",
|
|
547
|
+
permissionDecisionReason: decision.message || "Clay session policy",
|
|
548
|
+
updatedInput: decision.updatedInput } };
|
|
549
|
+
}],
|
|
550
|
+
}]);
|
|
551
|
+
}
|
|
518
552
|
options.canUseTool = function(toolName, input, toolOpts) {
|
|
519
553
|
// AskUserQuestion is handled specially: we send it as a separate IPC type
|
|
520
554
|
// so the daemon can use its own AskUserQuestion handling logic
|
|
@@ -626,6 +660,7 @@ async function handleQueryStart(msg) {
|
|
|
626
660
|
messageQueue = null;
|
|
627
661
|
abortController = null;
|
|
628
662
|
pendingPermissions = {};
|
|
663
|
+
preToolPolicyBroker.clear("Permission policy query ended.");
|
|
629
664
|
pendingAskUser = {};
|
|
630
665
|
pendingElicitations = {};
|
|
631
666
|
}
|
|
@@ -657,12 +692,12 @@ async function handleSetEffort(msg) {
|
|
|
657
692
|
}
|
|
658
693
|
|
|
659
694
|
async function handleSetPermissionMode(msg) {
|
|
660
|
-
if (!queryInstance) return;
|
|
695
|
+
if (!queryInstance) { sendToDaemon({ type: "permission_mode_result", requestId: msg.requestId, error: "No active query." }); return; }
|
|
661
696
|
try {
|
|
662
697
|
await queryInstance.setPermissionMode(msg.mode);
|
|
663
|
-
sendToDaemon({ type: "
|
|
698
|
+
sendToDaemon({ type: "permission_mode_result", requestId: msg.requestId });
|
|
664
699
|
} catch (e) {
|
|
665
|
-
sendToDaemon({ type: "
|
|
700
|
+
sendToDaemon({ type: "permission_mode_result", requestId: msg.requestId, error: "Failed to set permission mode: " + (e.message || e) });
|
|
666
701
|
}
|
|
667
702
|
}
|
|
668
703
|
|
|
@@ -705,11 +740,15 @@ async function handleReloadSkills(msg) {
|
|
|
705
740
|
}
|
|
706
741
|
|
|
707
742
|
async function handleSetMcpPermissionModeOverride(msg) {
|
|
708
|
-
if (!queryInstance || typeof queryInstance.setMcpPermissionModeOverride !== "function")
|
|
743
|
+
if (!queryInstance || typeof queryInstance.setMcpPermissionModeOverride !== "function") {
|
|
744
|
+
sendToDaemon({ type: "mcp_permission_mode_result", requestId: msg.requestId, error: "MCP permission controls are unavailable." });
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
709
747
|
try {
|
|
710
|
-
await queryInstance.setMcpPermissionModeOverride(msg.serverName, msg.mode);
|
|
748
|
+
var result = await queryInstance.setMcpPermissionModeOverride(msg.serverName, msg.mode);
|
|
749
|
+
sendToDaemon({ type: "mcp_permission_mode_result", requestId: msg.requestId, result: result || null });
|
|
711
750
|
} catch (e) {
|
|
712
|
-
|
|
751
|
+
sendToDaemon({ type: "mcp_permission_mode_result", requestId: msg.requestId, error: e.message || String(e) });
|
|
713
752
|
}
|
|
714
753
|
}
|
|
715
754
|
|
|
@@ -789,6 +828,7 @@ async function handleWarmup(msg) {
|
|
|
789
828
|
// --- Cleanup ---
|
|
790
829
|
var _exitScheduled = false;
|
|
791
830
|
function cleanup() {
|
|
831
|
+
preToolPolicyBroker.clear("Permission policy worker closed.");
|
|
792
832
|
if (_keepAlive) {
|
|
793
833
|
try { clearInterval(_keepAlive); } catch (e) {}
|
|
794
834
|
}
|
|
@@ -67,6 +67,29 @@ function applySystemPromptSnapshot(systemPrompt, enabled) {
|
|
|
67
67
|
return systemPrompt;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function attachPreToolUsePolicy(options, policy) {
|
|
71
|
+
if (typeof policy !== "function") return;
|
|
72
|
+
options.hooks = options.hooks || {};
|
|
73
|
+
options.hooks.PreToolUse = (options.hooks.PreToolUse || []).concat([{
|
|
74
|
+
hooks: [async function(input, toolUseId, hookOpts) {
|
|
75
|
+
input = input || {};
|
|
76
|
+
var decision = await policy(input.tool_name, input.tool_input || {}, {
|
|
77
|
+
toolUseID: toolUseId || input.tool_use_id,
|
|
78
|
+
signal: hookOpts && hookOpts.signal,
|
|
79
|
+
});
|
|
80
|
+
if (!decision) return { continue: true };
|
|
81
|
+
return {
|
|
82
|
+
hookSpecificOutput: {
|
|
83
|
+
hookEventName: "PreToolUse",
|
|
84
|
+
permissionDecision: decision.behavior === "deny" ? "deny" : "allow",
|
|
85
|
+
permissionDecisionReason: decision.message || "Clay session policy",
|
|
86
|
+
updatedInput: decision.updatedInput,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}],
|
|
90
|
+
}]);
|
|
91
|
+
}
|
|
92
|
+
|
|
70
93
|
function getSharedSkillPlugins(homeDir, existingPlugins, cwd, env) {
|
|
71
94
|
var plugins = Array.isArray(existingPlugins) ? existingPlugins.slice() : [];
|
|
72
95
|
// undefined means normal in-process Claude and may use the configured
|
|
@@ -122,6 +145,7 @@ function buildWorkerQueryOptions(queryOpts, claudeOpts, workerCwd) {
|
|
|
122
145
|
if (queryOpts.onUserDialog) queryOptions.supportedDialogKinds = SUPPORTED_DIALOG_KINDS;
|
|
123
146
|
queryOptions.perTaskStopAffordance = PER_TASK_STOP_AFFORDANCE;
|
|
124
147
|
if (queryOpts.onModelSwitch) queryOptions.reportModelSwitches = true;
|
|
148
|
+
if (queryOpts.preToolUsePolicy) queryOptions.clayPreToolUsePolicy = true;
|
|
125
149
|
if (queryOpts.model) queryOptions.model = queryOpts.model;
|
|
126
150
|
if (queryOpts.effort) queryOptions.effort = queryOpts.effort;
|
|
127
151
|
if (queryOpts.resumeSessionId) queryOptions.resume = queryOpts.resumeSessionId;
|
|
@@ -316,11 +340,13 @@ function flattenEvent(raw) {
|
|
|
316
340
|
base.skills = raw.skills;
|
|
317
341
|
base.slashCommands = raw.slash_commands;
|
|
318
342
|
base.fastModeState = raw.fast_mode_state || null;
|
|
343
|
+
base.permissionMode = raw.permissionMode || raw.permission_mode || null;
|
|
319
344
|
return base;
|
|
320
345
|
}
|
|
321
346
|
if (raw.subtype === "status") {
|
|
322
347
|
base.yokeType = "status";
|
|
323
348
|
base.status = raw.status;
|
|
349
|
+
base.permissionMode = raw.permissionMode || raw.permission_mode || null;
|
|
324
350
|
return base;
|
|
325
351
|
}
|
|
326
352
|
// API retry notices are transient transport state, not conversation
|
|
@@ -1015,7 +1041,7 @@ function serializeWorkerValue(value, seen) {
|
|
|
1015
1041
|
// in-process QueryHandle. This allows processQueryStream to iterate a worker
|
|
1016
1042
|
// query identically to an in-process query.
|
|
1017
1043
|
|
|
1018
|
-
function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool, onUserDialog, onUserInputRequest, onModelSwitch) {
|
|
1044
|
+
function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool, onUserDialog, onUserInputRequest, onModelSwitch, preToolUsePolicy) {
|
|
1019
1045
|
var contextRequests = claudeContextUsage.createRequests(worker);
|
|
1020
1046
|
var correlation = claudeCorrelation.createCorrelation();
|
|
1021
1047
|
// Async iterable state
|
|
@@ -1028,6 +1054,23 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1028
1054
|
// result from the worker (e.g. rewindFiles). Each entry is keyed by a
|
|
1029
1055
|
// requestId and holds { resolve, reject } of the in-flight Promise.
|
|
1030
1056
|
var pendingRewinds = {};
|
|
1057
|
+
var pendingPermissionModes = {};
|
|
1058
|
+
var pendingMcpPermissionModes = {};
|
|
1059
|
+
|
|
1060
|
+
function rejectPendingPermissionModes(error) {
|
|
1061
|
+
Object.keys(pendingPermissionModes).forEach(function(id) {
|
|
1062
|
+
var pending = pendingPermissionModes[id];
|
|
1063
|
+
delete pendingPermissionModes[id];
|
|
1064
|
+
clearTimeout(pending.timer);
|
|
1065
|
+
pending.reject(error);
|
|
1066
|
+
});
|
|
1067
|
+
Object.keys(pendingMcpPermissionModes).forEach(function(id) {
|
|
1068
|
+
var pending = pendingMcpPermissionModes[id];
|
|
1069
|
+
delete pendingMcpPermissionModes[id];
|
|
1070
|
+
clearTimeout(pending.timer);
|
|
1071
|
+
pending.reject(error);
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1031
1074
|
|
|
1032
1075
|
function pushToIter(value) {
|
|
1033
1076
|
if (iterEnded) return;
|
|
@@ -1087,6 +1130,20 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1087
1130
|
}
|
|
1088
1131
|
break;
|
|
1089
1132
|
|
|
1133
|
+
case "pre_tool_policy_request":
|
|
1134
|
+
if (preToolUsePolicy) {
|
|
1135
|
+
Promise.resolve().then(function() {
|
|
1136
|
+
return preToolUsePolicy(msg.toolName, msg.input || {}, { toolUseID: msg.toolUseId });
|
|
1137
|
+
}).then(function(result) {
|
|
1138
|
+
worker.send({ type: "pre_tool_policy_response", requestId: msg.requestId, result: result || null });
|
|
1139
|
+
}).catch(function(error) {
|
|
1140
|
+
worker.send({ type: "pre_tool_policy_response", requestId: msg.requestId, error: error.message || String(error) });
|
|
1141
|
+
});
|
|
1142
|
+
} else {
|
|
1143
|
+
worker.send({ type: "pre_tool_policy_response", requestId: msg.requestId, result: null });
|
|
1144
|
+
}
|
|
1145
|
+
break;
|
|
1146
|
+
|
|
1090
1147
|
case "ask_user_request":
|
|
1091
1148
|
if (onUserInputRequest) {
|
|
1092
1149
|
claudeUserInput.ask(onUserInputRequest, msg.input, {
|
|
@@ -1164,6 +1221,24 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1164
1221
|
case "context_usage_response":
|
|
1165
1222
|
contextRequests.receive(msg);
|
|
1166
1223
|
break;
|
|
1224
|
+
case "permission_mode_result": {
|
|
1225
|
+
var modePending = pendingPermissionModes[msg.requestId];
|
|
1226
|
+
if (!modePending) break;
|
|
1227
|
+
delete pendingPermissionModes[msg.requestId];
|
|
1228
|
+
clearTimeout(modePending.timer);
|
|
1229
|
+
if (msg.error) modePending.reject(new Error(msg.error));
|
|
1230
|
+
else modePending.resolve();
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
case "mcp_permission_mode_result": {
|
|
1234
|
+
var mcpPending = pendingMcpPermissionModes[msg.requestId];
|
|
1235
|
+
if (!mcpPending) break;
|
|
1236
|
+
delete pendingMcpPermissionModes[msg.requestId];
|
|
1237
|
+
clearTimeout(mcpPending.timer);
|
|
1238
|
+
if (msg.error) mcpPending.reject(new Error(msg.error));
|
|
1239
|
+
else mcpPending.resolve(msg.result || null);
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
1167
1242
|
case "context_usage":
|
|
1168
1243
|
case "model_changed":
|
|
1169
1244
|
case "effort_changed":
|
|
@@ -1184,12 +1259,14 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1184
1259
|
}
|
|
1185
1260
|
|
|
1186
1261
|
case "query_done":
|
|
1262
|
+
rejectPendingPermissionModes(new Error("Worker query ended while changing permission mode"));
|
|
1187
1263
|
console.log("[yoke/claude] IPC query_done received, pid=" + (worker.process ? worker.process.pid : "?"));
|
|
1188
1264
|
worker._queryEnded = true;
|
|
1189
1265
|
endIter();
|
|
1190
1266
|
break;
|
|
1191
1267
|
|
|
1192
1268
|
case "query_error": {
|
|
1269
|
+
rejectPendingPermissionModes(new Error(msg.error || "Worker exited while changing permission mode"));
|
|
1193
1270
|
console.log("[yoke/claude] IPC query_error received, pid=" + (worker.process ? worker.process.pid : "?") + " _fallback=" + !!msg._fallback + " _queryEnded=" + worker._queryEnded + " error=" + (msg.error || "").substring(0, 100));
|
|
1194
1271
|
// Skip fallback errors from exit handler if we already handled the real one
|
|
1195
1272
|
if (msg._fallback && worker._queryEnded) break;
|
|
@@ -1283,8 +1360,22 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1283
1360
|
},
|
|
1284
1361
|
|
|
1285
1362
|
setPermissionMode: function(mode) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1363
|
+
return new Promise(function(resolve, reject) {
|
|
1364
|
+
var requestId = crypto.randomUUID();
|
|
1365
|
+
var timer = setTimeout(function() {
|
|
1366
|
+
if (!pendingPermissionModes[requestId]) return;
|
|
1367
|
+
delete pendingPermissionModes[requestId];
|
|
1368
|
+
reject(new Error("Timed out changing permission mode in worker."));
|
|
1369
|
+
}, worker.permissionModeTimeoutMs || 10000);
|
|
1370
|
+
pendingPermissionModes[requestId] = { resolve: resolve, reject: reject, timer: timer };
|
|
1371
|
+
try {
|
|
1372
|
+
if (worker.send({ type: "set_permission_mode", mode: mode, requestId: requestId }) !== true) {
|
|
1373
|
+
delete pendingPermissionModes[requestId]; clearTimeout(timer); reject(new Error("Worker is unavailable."));
|
|
1374
|
+
}
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
delete pendingPermissionModes[requestId]; clearTimeout(timer); reject(error);
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1288
1379
|
},
|
|
1289
1380
|
|
|
1290
1381
|
stopTask: function(taskId) {
|
|
@@ -1298,8 +1389,22 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1298
1389
|
},
|
|
1299
1390
|
|
|
1300
1391
|
setMcpPermissionModeOverride: function(serverName, mode) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1392
|
+
return new Promise(function(resolve, reject) {
|
|
1393
|
+
var requestId = crypto.randomUUID();
|
|
1394
|
+
var timer = setTimeout(function() {
|
|
1395
|
+
if (!pendingMcpPermissionModes[requestId]) return;
|
|
1396
|
+
delete pendingMcpPermissionModes[requestId];
|
|
1397
|
+
reject(new Error("Timed out changing MCP permission mode in worker."));
|
|
1398
|
+
}, worker.permissionModeTimeoutMs || 10000);
|
|
1399
|
+
pendingMcpPermissionModes[requestId] = { resolve: resolve, reject: reject, timer: timer };
|
|
1400
|
+
try {
|
|
1401
|
+
if (worker.send({ type: "set_mcp_permission_mode_override", serverName: serverName, mode: mode, requestId: requestId }) !== true) {
|
|
1402
|
+
delete pendingMcpPermissionModes[requestId]; clearTimeout(timer); reject(new Error("Worker is unavailable."));
|
|
1403
|
+
}
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
delete pendingMcpPermissionModes[requestId]; clearTimeout(timer); reject(error);
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1303
1408
|
},
|
|
1304
1409
|
|
|
1305
1410
|
getContextUsage: function(options) {
|
|
@@ -1326,6 +1431,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1326
1431
|
},
|
|
1327
1432
|
|
|
1328
1433
|
close: function() {
|
|
1434
|
+
rejectPendingPermissionModes(new Error("Worker query closed while changing permission mode"));
|
|
1329
1435
|
// End the iterator
|
|
1330
1436
|
endIter();
|
|
1331
1437
|
// Send end_messages to worker
|
|
@@ -1625,6 +1731,7 @@ function createClaudeAdapter(opts) {
|
|
|
1625
1731
|
if (queryOpts.canUseTool || nativeUserInputHandler) {
|
|
1626
1732
|
sdkOptions.canUseTool = claudeUserInput.canUseTool(Object.assign({}, queryOpts, { onUserInputRequest: nativeUserInputHandler }));
|
|
1627
1733
|
}
|
|
1734
|
+
attachPreToolUsePolicy(sdkOptions, queryOpts.preToolUsePolicy);
|
|
1628
1735
|
if (nativeUserInputHandler) {
|
|
1629
1736
|
sdkOptions.onElicitation = function (request, elicitOpts) { return claudeUserInput.elicitation(nativeUserInputHandler, request, elicitOpts); };
|
|
1630
1737
|
} else if (queryOpts.userInputMode !== "fallback" && queryOpts.onElicitation) sdkOptions.onElicitation = queryOpts.onElicitation;
|
|
@@ -1795,7 +1902,7 @@ function createClaudeAdapter(opts) {
|
|
|
1795
1902
|
// Create the worker query handle (sets up message handler on worker)
|
|
1796
1903
|
var workerUserInputHandler = queryOpts.userInputMode === "fallback" ? null : queryOpts.onUserInputRequest;
|
|
1797
1904
|
var workerElicitationHandler = queryOpts.userInputMode === "fallback" ? null : queryOpts.onElicitation;
|
|
1798
|
-
var handle = createWorkerQueryHandle(worker, queryOpts.canUseTool, workerElicitationHandler, queryOpts.callMcpTool, queryOpts.onUserDialog, workerUserInputHandler, queryOpts.onModelSwitch);
|
|
1905
|
+
var handle = createWorkerQueryHandle(worker, queryOpts.canUseTool, workerElicitationHandler, queryOpts.callMcpTool, queryOpts.onUserDialog, workerUserInputHandler, queryOpts.onModelSwitch, queryOpts.preToolUsePolicy);
|
|
1799
1906
|
|
|
1800
1907
|
// Wait for worker to be ready before sending query_start
|
|
1801
1908
|
if (!reusingWorker) {
|
|
@@ -2063,5 +2170,6 @@ module.exports = {
|
|
|
2063
2170
|
canReuseWorker: canReuseWorker,
|
|
2064
2171
|
buildWorkerQueryOptions: buildWorkerQueryOptions,
|
|
2065
2172
|
normalizeEvent: flattenEvent,
|
|
2173
|
+
attachPreToolUsePolicy: attachPreToolUsePolicy,
|
|
2066
2174
|
},
|
|
2067
2175
|
};
|
package/package.json
CHANGED