clay-server 2.44.1-beta.1 → 2.45.0-beta.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/lib/daemon.js +27 -4
- package/lib/os-users.js +69 -2
- package/lib/project-http.js +4 -2
- package/lib/project-sessions.js +86 -21
- package/lib/public/app.js +1 -1
- package/lib/public/index.html +15 -0
- package/lib/public/modules/app-messages.js +54 -2
- package/lib/public/modules/mcp-ui.js +44 -0
- package/lib/public/modules/profile.js +2 -2
- package/lib/public/modules/server-settings.js +34 -0
- package/lib/public/modules/skills.js +16 -0
- package/lib/public/modules/tools.js +156 -0
- package/lib/public/modules/user-settings.js +2 -2
- package/lib/sdk-bridge.js +76 -0
- package/lib/sdk-message-processor.js +67 -0
- package/lib/server-settings.js +2 -2
- package/lib/server.js +2 -0
- package/lib/terminal.js +4 -1
- package/lib/users-preferences.js +8 -7
- package/lib/ws-schema.js +9 -0
- package/lib/yoke/adapters/claude-worker.js +62 -0
- package/lib/yoke/adapters/claude.js +121 -3
- package/package.json +2 -2
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
2
|
import { escapeHtml, copyToClipboard, showToast } from './utils.js';
|
|
3
3
|
import { store } from './store.js';
|
|
4
|
+
import { getWs } from './ws-ref.js';
|
|
5
|
+
|
|
6
|
+
// Ask the active session to hot-reload skills so a just-installed/uninstalled
|
|
7
|
+
// skill takes effect without restarting the session. No-op if no live session.
|
|
8
|
+
function reloadActiveSessionSkills() {
|
|
9
|
+
var ws = getWs();
|
|
10
|
+
if (ws && ws.readyState === 1) {
|
|
11
|
+
ws.send(JSON.stringify({ type: "reload_skills" }));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
var modal;
|
|
6
16
|
var contentEl;
|
|
@@ -607,6 +617,9 @@ export function handleSkillInstalled(msg) {
|
|
|
607
617
|
if (activeTab === "installed" && currentView === "list") {
|
|
608
618
|
loadInstalledSkills();
|
|
609
619
|
}
|
|
620
|
+
|
|
621
|
+
// Hot-reload skills in the active session so it's usable immediately
|
|
622
|
+
reloadActiveSessionSkills();
|
|
610
623
|
} else {
|
|
611
624
|
// Show error toast
|
|
612
625
|
showToast("Failed to install " + skill + (msg.error ? ": " + msg.error : ""), "error");
|
|
@@ -676,6 +689,9 @@ export function handleSkillUninstalled(msg) {
|
|
|
676
689
|
if (activeTab === "installed" && currentView === "list") {
|
|
677
690
|
loadInstalledSkills();
|
|
678
691
|
}
|
|
692
|
+
|
|
693
|
+
// Hot-reload skills in the active session so the change applies immediately
|
|
694
|
+
reloadActiveSessionSkills();
|
|
679
695
|
} else {
|
|
680
696
|
showToast("Failed to uninstall " + skill + (msg.error ? ": " + msg.error : ""), "error");
|
|
681
697
|
// Re-enable the uninstall button
|
|
@@ -1181,6 +1181,159 @@ export function markElicitationResolved(requestId, action) {
|
|
|
1181
1181
|
delete pendingElicitations[requestId];
|
|
1182
1182
|
}
|
|
1183
1183
|
|
|
1184
|
+
// --- Host user dialog (SDK request_user_dialog) ---
|
|
1185
|
+
// Generic, defensive renderer. We declare only kinds we handle in
|
|
1186
|
+
// supportedDialogKinds (server side), but treat the payload as opaque: render
|
|
1187
|
+
// a human message + any provided choices, fall back to Proceed/Cancel, and
|
|
1188
|
+
// log the raw payload so unseen shapes can be refined. Cancel is always safe.
|
|
1189
|
+
|
|
1190
|
+
var pendingUserDialogs = {};
|
|
1191
|
+
|
|
1192
|
+
function sendUserDialogResponse(container, requestId, behavior, result) {
|
|
1193
|
+
if (container.classList.contains("resolved")) return;
|
|
1194
|
+
container.classList.add("resolved");
|
|
1195
|
+
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
1196
|
+
|
|
1197
|
+
var isCancel = behavior !== "completed";
|
|
1198
|
+
container.classList.add(isCancel ? "resolved-denied" : "resolved-allowed");
|
|
1199
|
+
|
|
1200
|
+
var actions = container.querySelector(".permission-actions");
|
|
1201
|
+
if (actions) {
|
|
1202
|
+
actions.innerHTML = '<span class="permission-decision-label">' + (isCancel ? "Cancelled" : "Submitted") + '</span>';
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (ctx.ws && ctx.connected) {
|
|
1206
|
+
var msg = {
|
|
1207
|
+
type: "user_dialog_response",
|
|
1208
|
+
requestId: requestId,
|
|
1209
|
+
behavior: isCancel ? "cancelled" : "completed",
|
|
1210
|
+
};
|
|
1211
|
+
if (!isCancel) msg.result = result;
|
|
1212
|
+
ctx.ws.send(JSON.stringify(msg));
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
delete pendingUserDialogs[requestId];
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
export function renderUserDialogRequest(msg) {
|
|
1219
|
+
if (pendingUserDialogs[msg.requestId]) return;
|
|
1220
|
+
ctx.finalizeAssistantBlock();
|
|
1221
|
+
stopThinking();
|
|
1222
|
+
closeToolGroup();
|
|
1223
|
+
|
|
1224
|
+
var payload = msg.payload || {};
|
|
1225
|
+
try { console.log("[user_dialog] kind=" + msg.dialogKind + " payload=", payload); } catch (e) {}
|
|
1226
|
+
|
|
1227
|
+
var container = document.createElement("div");
|
|
1228
|
+
container.className = "permission-container user-dialog-container";
|
|
1229
|
+
container.dataset.requestId = msg.requestId;
|
|
1230
|
+
|
|
1231
|
+
// Header
|
|
1232
|
+
var titleText = payload.title || "Claude needs a decision";
|
|
1233
|
+
var header = document.createElement("div");
|
|
1234
|
+
header.className = "permission-header";
|
|
1235
|
+
header.innerHTML =
|
|
1236
|
+
'<span class="permission-icon">' + iconHtml("key") + '</span>' +
|
|
1237
|
+
'<span class="permission-title">' + escapeHtml(titleText) + '</span>';
|
|
1238
|
+
|
|
1239
|
+
// Body: best-effort human message from common payload fields
|
|
1240
|
+
var body = document.createElement("div");
|
|
1241
|
+
body.className = "permission-body";
|
|
1242
|
+
var messageText = payload.message || payload.prompt || payload.question || payload.text || "";
|
|
1243
|
+
if (messageText) {
|
|
1244
|
+
var messageEl = document.createElement("div");
|
|
1245
|
+
messageEl.className = "permission-reason";
|
|
1246
|
+
messageEl.textContent = String(messageText);
|
|
1247
|
+
body.appendChild(messageEl);
|
|
1248
|
+
}
|
|
1249
|
+
var kindEl = document.createElement("div");
|
|
1250
|
+
kindEl.className = "user-dialog-kind";
|
|
1251
|
+
kindEl.style.cssText = "margin-top: 6px; font-size: 11px; color: var(--text-muted);";
|
|
1252
|
+
kindEl.textContent = msg.dialogKind || "dialog";
|
|
1253
|
+
body.appendChild(kindEl);
|
|
1254
|
+
|
|
1255
|
+
// Actions
|
|
1256
|
+
var actions = document.createElement("div");
|
|
1257
|
+
actions.className = "permission-actions";
|
|
1258
|
+
|
|
1259
|
+
// If the payload carries explicit choices, render one button per choice.
|
|
1260
|
+
var choices = Array.isArray(payload.options) ? payload.options
|
|
1261
|
+
: Array.isArray(payload.choices) ? payload.choices
|
|
1262
|
+
: null;
|
|
1263
|
+
|
|
1264
|
+
if (choices && choices.length) {
|
|
1265
|
+
for (var i = 0; i < choices.length; i++) {
|
|
1266
|
+
(function (choice) {
|
|
1267
|
+
var label = (choice && (choice.label || choice.title || choice.name)) || String(choice);
|
|
1268
|
+
var value = (choice && ("value" in choice)) ? choice.value : choice;
|
|
1269
|
+
var btn = document.createElement("button");
|
|
1270
|
+
btn.className = "permission-btn permission-allow";
|
|
1271
|
+
btn.textContent = label;
|
|
1272
|
+
btn.addEventListener("click", function () {
|
|
1273
|
+
sendUserDialogResponse(container, msg.requestId, "completed", value);
|
|
1274
|
+
});
|
|
1275
|
+
actions.appendChild(btn);
|
|
1276
|
+
})(choices[i]);
|
|
1277
|
+
}
|
|
1278
|
+
} else {
|
|
1279
|
+
var proceedBtn = document.createElement("button");
|
|
1280
|
+
proceedBtn.className = "permission-btn permission-allow";
|
|
1281
|
+
proceedBtn.textContent = "Proceed";
|
|
1282
|
+
proceedBtn.addEventListener("click", function () {
|
|
1283
|
+
sendUserDialogResponse(container, msg.requestId, "completed", {});
|
|
1284
|
+
});
|
|
1285
|
+
actions.appendChild(proceedBtn);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
var cancelBtn = document.createElement("button");
|
|
1289
|
+
cancelBtn.className = "permission-btn permission-deny";
|
|
1290
|
+
cancelBtn.textContent = "Cancel";
|
|
1291
|
+
cancelBtn.addEventListener("click", function () {
|
|
1292
|
+
sendUserDialogResponse(container, msg.requestId, "cancelled", null);
|
|
1293
|
+
});
|
|
1294
|
+
actions.appendChild(cancelBtn);
|
|
1295
|
+
|
|
1296
|
+
container.appendChild(header);
|
|
1297
|
+
container.appendChild(body);
|
|
1298
|
+
container.appendChild(actions);
|
|
1299
|
+
ctx.addToMessages(container);
|
|
1300
|
+
|
|
1301
|
+
pendingUserDialogs[msg.requestId] = container;
|
|
1302
|
+
refreshIcons();
|
|
1303
|
+
ctx.setActivity(null);
|
|
1304
|
+
maybeScrollToBottom();
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
export function markUserDialogResolved(requestId, behavior) {
|
|
1308
|
+
var container = pendingUserDialogs[requestId];
|
|
1309
|
+
if (!container) {
|
|
1310
|
+
container = ctx.messagesEl.querySelector('.user-dialog-container[data-request-id="' + requestId + '"]');
|
|
1311
|
+
}
|
|
1312
|
+
if (!container || container.classList.contains("resolved")) return;
|
|
1313
|
+
|
|
1314
|
+
container.classList.add("resolved");
|
|
1315
|
+
var isCancel = behavior !== "completed";
|
|
1316
|
+
container.classList.add(isCancel ? "resolved-denied" : "resolved-allowed");
|
|
1317
|
+
|
|
1318
|
+
var actionsEl = container.querySelector(".permission-actions");
|
|
1319
|
+
if (actionsEl) {
|
|
1320
|
+
actionsEl.innerHTML = '<span class="permission-decision-label">' + (isCancel ? "Cancelled" : "Submitted") + '</span>';
|
|
1321
|
+
}
|
|
1322
|
+
delete pendingUserDialogs[requestId];
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// --- Live thinking-token estimate ---
|
|
1326
|
+
// Annotates the active thinking block's label with a running token estimate.
|
|
1327
|
+
// No-op when no thinking block is active.
|
|
1328
|
+
export function updateThinkingTokens(estimatedTokens) {
|
|
1329
|
+
if (!currentThinking || !currentThinking.el) return;
|
|
1330
|
+
var label = currentThinking.el.querySelector(".thinking-label");
|
|
1331
|
+
if (!label) return;
|
|
1332
|
+
var n = estimatedTokens || 0;
|
|
1333
|
+
var disp = n >= 1000 ? ("~" + (Math.round(n / 100) / 10) + "k") : ("~" + n);
|
|
1334
|
+
label.textContent = "Thinking " + disp + " tokens";
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1184
1337
|
// --- Plan mode rendering ---
|
|
1185
1338
|
export function renderPlanBanner(type) {
|
|
1186
1339
|
ctx.finalizeAssistantBlock();
|
|
@@ -1554,6 +1707,8 @@ export function startThinking() {
|
|
|
1554
1707
|
var el = thinkingGroup.el;
|
|
1555
1708
|
el.classList.remove("done");
|
|
1556
1709
|
el.querySelector(".thinking-content").textContent = "";
|
|
1710
|
+
var reuseLabel = el.querySelector(".thinking-label");
|
|
1711
|
+
if (reuseLabel) reuseLabel.textContent = "Thinking";
|
|
1557
1712
|
// Mate mode: restore dots activity row, hide thinking header
|
|
1558
1713
|
if (el.classList.contains("mate-thinking")) {
|
|
1559
1714
|
var actRow = el.querySelector(".mate-thinking-activity");
|
|
@@ -2379,6 +2534,7 @@ export function resetToolState() {
|
|
|
2379
2534
|
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
|
2380
2535
|
pendingPermissions = {};
|
|
2381
2536
|
pendingElicitations = {};
|
|
2537
|
+
pendingUserDialogs = {};
|
|
2382
2538
|
currentToolGroup = null;
|
|
2383
2539
|
toolGroupCounter = 0;
|
|
2384
2540
|
toolGroups = {};
|
|
@@ -468,8 +468,8 @@ function populateAccount() {
|
|
|
468
468
|
if (data.mateOnboardingShown) {
|
|
469
469
|
try { localStorage.setItem("clay-mate-onboarding-shown", "1"); } catch (e) {}
|
|
470
470
|
}
|
|
471
|
-
// Mates UI toggle: default
|
|
472
|
-
var matesOn = data.matesEnabled
|
|
471
|
+
// Mates UI toggle: default OFF (opt-in); enable only when explicitly true
|
|
472
|
+
var matesOn = data.matesEnabled === true;
|
|
473
473
|
var matesToggle = document.getElementById('us-mates-enabled');
|
|
474
474
|
if (matesToggle) matesToggle.checked = matesOn;
|
|
475
475
|
store.set({ matesEnabled: matesOn });
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -392,6 +392,54 @@ function createSDKBridge(opts) {
|
|
|
392
392
|
});
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
// --- Host user dialog ---
|
|
396
|
+
// Mirrors handleElicitation: the adapter invokes onUserDialog with an opaque
|
|
397
|
+
// { dialogKind, payload }, we forward a user_dialog_request to the client, and
|
|
398
|
+
// resolve once the client answers. Which dialog kinds exist is the adapter's
|
|
399
|
+
// concern; the relay just transports them. Unknown kinds / autonomous mode
|
|
400
|
+
// resolve to { behavior: "cancelled" } (the no-dialog default).
|
|
401
|
+
|
|
402
|
+
function handleUserDialog(session, request, opts) {
|
|
403
|
+
// Ralph Loop: auto-cancel host dialogs in autonomous mode (CLI falls back
|
|
404
|
+
// to the no-dialog default, e.g. the classic refusal error).
|
|
405
|
+
if (session.loop && session.loop.active && session.loop.role !== "crafting") {
|
|
406
|
+
return Promise.resolve({ behavior: "cancelled" });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return new Promise(function(resolve) {
|
|
410
|
+
var requestId = crypto.randomUUID();
|
|
411
|
+
if (!session.pendingUserDialogs) session.pendingUserDialogs = {};
|
|
412
|
+
session.pendingUserDialogs[requestId] = {
|
|
413
|
+
resolve: resolve,
|
|
414
|
+
request: request,
|
|
415
|
+
};
|
|
416
|
+
sendAndRecord(session, {
|
|
417
|
+
type: "user_dialog_request",
|
|
418
|
+
requestId: requestId,
|
|
419
|
+
dialogKind: request.dialogKind,
|
|
420
|
+
payload: request.payload || {},
|
|
421
|
+
toolUseId: request.toolUseID || null,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
if (pushModule) {
|
|
425
|
+
pushModule.sendPush({
|
|
426
|
+
type: "user_dialog",
|
|
427
|
+
slug: slug,
|
|
428
|
+
title: "Claude needs a decision",
|
|
429
|
+
body: "Waiting for your response",
|
|
430
|
+
tag: "claude-user-dialog",
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (opts.signal) {
|
|
435
|
+
opts.signal.addEventListener("abort", function() {
|
|
436
|
+
delete session.pendingUserDialogs[requestId];
|
|
437
|
+
resolve({ behavior: "cancelled" });
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
395
443
|
|
|
396
444
|
// --- Linux user project directory setup ---
|
|
397
445
|
// Ensures the linux user's .claude project directory exists and is writable,
|
|
@@ -1281,6 +1329,9 @@ function createSDKBridge(opts) {
|
|
|
1281
1329
|
onElicitation: function(request, elicitOpts) {
|
|
1282
1330
|
return handleElicitation(session, request, elicitOpts);
|
|
1283
1331
|
},
|
|
1332
|
+
onUserDialog: function(dialogRequest, dialogOpts) {
|
|
1333
|
+
return handleUserDialog(session, dialogRequest, dialogOpts);
|
|
1334
|
+
},
|
|
1284
1335
|
callMcpTool: function(serverName, toolName, args) {
|
|
1285
1336
|
return callMcpToolHandler(mergedMcpServers, serverName, toolName, args);
|
|
1286
1337
|
},
|
|
@@ -1603,6 +1654,29 @@ function createSDKBridge(opts) {
|
|
|
1603
1654
|
}
|
|
1604
1655
|
}
|
|
1605
1656
|
|
|
1657
|
+
// Hot-reload skills/MCP/agents on the active query without a restart.
|
|
1658
|
+
async function reloadSkills(session) {
|
|
1659
|
+
if (!session || !session.queryInstance) return;
|
|
1660
|
+
try {
|
|
1661
|
+
// Route through QueryHandle (works for both in-process and worker paths)
|
|
1662
|
+
await session.queryInstance.reloadSkills();
|
|
1663
|
+
send({ type: "system_info", text: "Skills reloaded." });
|
|
1664
|
+
} catch (e) {
|
|
1665
|
+
send({ type: "error", text: "Failed to reload skills: " + (e.message || e) });
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Override the permission mode for one MCP server on the active query.
|
|
1670
|
+
async function setMcpPermissionModeOverride(session, serverName, mode) {
|
|
1671
|
+
if (!session || !session.queryInstance) return;
|
|
1672
|
+
try {
|
|
1673
|
+
// Route through QueryHandle (works for both in-process and worker paths)
|
|
1674
|
+
await session.queryInstance.setMcpPermissionModeOverride(serverName, mode);
|
|
1675
|
+
} catch (e) {
|
|
1676
|
+
send({ type: "error", text: "Failed to set MCP permission mode: " + (e.message || e) });
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1606
1680
|
// --- @Mention: persistent read-only session for a mentioned Mate ---
|
|
1607
1681
|
// Creates a mention session that can be reused across multiple mentions
|
|
1608
1682
|
// within a conversation flow (session continuity).
|
|
@@ -1789,6 +1863,8 @@ function createSDKBridge(opts) {
|
|
|
1789
1863
|
permissionPushBody: permissionPushBody,
|
|
1790
1864
|
warmup: warmup,
|
|
1791
1865
|
stopTask: stopTask,
|
|
1866
|
+
reloadSkills: reloadSkills,
|
|
1867
|
+
setMcpPermissionModeOverride: setMcpPermissionModeOverride,
|
|
1792
1868
|
createMentionSession: createMentionSession,
|
|
1793
1869
|
startIdleReaper: startIdleReaper,
|
|
1794
1870
|
stopIdleReaper: stopIdleReaper,
|
|
@@ -698,6 +698,73 @@ function attachMessageProcessor(ctx) {
|
|
|
698
698
|
var retryText = parsed.message || parsed.error || "Retrying API request...";
|
|
699
699
|
sendToSession(session, { type: "system_info", text: retryText });
|
|
700
700
|
|
|
701
|
+
} else if (parsed.yokeType === "commands_changed") {
|
|
702
|
+
// Mid-session command list change: rebuild the union with skills (same as
|
|
703
|
+
// init) and push a full replacement to the client.
|
|
704
|
+
var cmdSeen = new Set();
|
|
705
|
+
var cmdCombined = [];
|
|
706
|
+
var cmdAll = (parsed.commandNames || []).concat(Array.from(sm.skillNames || []));
|
|
707
|
+
for (var ci = 0; ci < cmdAll.length; ci++) {
|
|
708
|
+
if (!cmdSeen.has(cmdAll[ci])) {
|
|
709
|
+
cmdSeen.add(cmdAll[ci]);
|
|
710
|
+
cmdCombined.push(cmdAll[ci]);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
sm.slashCommands = cmdCombined;
|
|
714
|
+
send({ type: "slash_commands", commands: sm.slashCommands });
|
|
715
|
+
|
|
716
|
+
} else if (parsed.yokeType === "worker_shutting_down") {
|
|
717
|
+
// Surface non-routine teardown reasons; "host_exit" is the normal close.
|
|
718
|
+
if (parsed.reason && parsed.reason !== "host_exit") {
|
|
719
|
+
sendToSession(session, { type: "system_info", text: "Session worker shutting down: " + parsed.reason });
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
} else if (parsed.yokeType === "thinking_tokens") {
|
|
723
|
+
// Live thinking-token estimate; ephemeral, do not persist in history.
|
|
724
|
+
sendToSession(session, {
|
|
725
|
+
type: "thinking_tokens",
|
|
726
|
+
estimatedTokens: parsed.estimatedTokens || 0,
|
|
727
|
+
estimatedTokensDelta: parsed.estimatedTokensDelta || 0,
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
} else if (parsed.yokeType === "informational") {
|
|
731
|
+
// Leveled informational message from the agent (info/notice/suggestion/warning).
|
|
732
|
+
if (parsed.content) {
|
|
733
|
+
sendAndRecord(session, {
|
|
734
|
+
type: "informational",
|
|
735
|
+
level: parsed.level || "info",
|
|
736
|
+
content: parsed.content,
|
|
737
|
+
toolUseId: parsed.toolUseId || null,
|
|
738
|
+
preventContinuation: !!parsed.preventContinuation,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
} else if (parsed.yokeType === "permission_denied") {
|
|
743
|
+
// A tool call was blocked; surface why.
|
|
744
|
+
sendAndRecord(session, {
|
|
745
|
+
type: "permission_denied",
|
|
746
|
+
toolName: parsed.toolName || "",
|
|
747
|
+
toolUseId: parsed.toolUseId || null,
|
|
748
|
+
agentId: parsed.agentId || null,
|
|
749
|
+
reasonType: parsed.reasonType || null,
|
|
750
|
+
reason: parsed.reason || null,
|
|
751
|
+
message: parsed.message || "",
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
} else if (parsed.yokeType === "model_refusal") {
|
|
755
|
+
// Model declined the request. "fallback" => the CLI retried on another
|
|
756
|
+
// model; "no_fallback" => the turn ended with a refusal.
|
|
757
|
+
sendAndRecord(session, {
|
|
758
|
+
type: "model_refusal",
|
|
759
|
+
refusalKind: parsed.refusalKind || "no_fallback",
|
|
760
|
+
originalModel: parsed.originalModel || null,
|
|
761
|
+
fallbackModel: parsed.fallbackModel || null,
|
|
762
|
+
direction: parsed.direction || null,
|
|
763
|
+
category: parsed.category || null,
|
|
764
|
+
explanation: parsed.explanation || null,
|
|
765
|
+
content: parsed.content || null,
|
|
766
|
+
});
|
|
767
|
+
|
|
701
768
|
} else if (parsed.yokeType === "system") {
|
|
702
769
|
// Catch-all for unhandled system subtypes (e.g. hook-block errors).
|
|
703
770
|
// Extract any error text and surface it in the UI.
|
package/lib/server-settings.js
CHANGED
|
@@ -29,7 +29,7 @@ function attachSettings(ctx) {
|
|
|
29
29
|
profile.autoContinueOnRateLimit = !!mu.autoContinueOnRateLimit;
|
|
30
30
|
profile.chatLayout = mu.chatLayout || "channel";
|
|
31
31
|
profile.mateOnboardingShown = !!mu.mateOnboardingShown;
|
|
32
|
-
profile.matesEnabled = mu.matesEnabled
|
|
32
|
+
profile.matesEnabled = mu.matesEnabled === true;
|
|
33
33
|
try { profile.terminalFont = users.getTerminalFont(mu.id); } catch (e) {}
|
|
34
34
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
35
35
|
res.end(JSON.stringify(profile));
|
|
@@ -52,7 +52,7 @@ function attachSettings(ctx) {
|
|
|
52
52
|
profile.autoContinueOnRateLimit = !!dc.autoContinueOnRateLimit;
|
|
53
53
|
profile.chatLayout = dc.chatLayout || "channel";
|
|
54
54
|
profile.mateOnboardingShown = !!dc.mateOnboardingShown;
|
|
55
|
-
profile.matesEnabled = dc.matesEnabled
|
|
55
|
+
profile.matesEnabled = dc.matesEnabled === true;
|
|
56
56
|
if (dc.terminalFont && typeof dc.terminalFont === "object") {
|
|
57
57
|
profile.terminalFont = {
|
|
58
58
|
family: dc.terminalFont.family || "'SF Mono', Menlo, Monaco, 'Courier New', monospace",
|
package/lib/server.js
CHANGED
|
@@ -153,6 +153,7 @@ function createServer(opts) {
|
|
|
153
153
|
var onGetDaemonConfig = opts.onGetDaemonConfig || null;
|
|
154
154
|
var onSetPin = opts.onSetPin || null;
|
|
155
155
|
var onSetKeepAwake = opts.onSetKeepAwake || null;
|
|
156
|
+
var onSetInheritGroups = opts.onSetInheritGroups || null;
|
|
156
157
|
var onSetImageRetention = opts.onSetImageRetention || null;
|
|
157
158
|
var onShutdown = opts.onShutdown || null;
|
|
158
159
|
var onRestart = opts.onRestart || null;
|
|
@@ -1257,6 +1258,7 @@ function createServer(opts) {
|
|
|
1257
1258
|
onGetDaemonConfig: onGetDaemonConfig,
|
|
1258
1259
|
onSetPin: onSetPin,
|
|
1259
1260
|
onSetKeepAwake: onSetKeepAwake,
|
|
1261
|
+
onSetInheritGroups: onSetInheritGroups,
|
|
1260
1262
|
onSetImageRetention: onSetImageRetention,
|
|
1261
1263
|
onSetUpdateChannel: onSetUpdateChannel,
|
|
1262
1264
|
updateChannel: onGetDaemonConfig ? (onGetDaemonConfig().updateChannel || "stable") : "stable",
|
package/lib/terminal.js
CHANGED
|
@@ -39,7 +39,10 @@ function createTerminal(cwd, cols, rows, osUserInfo, opts) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
var args = osUserInfo ? ["-l"] : [];
|
|
42
|
-
|
|
42
|
+
// Route through setpriv when group inheritance is enabled so the terminal
|
|
43
|
+
// gets the user's supplementary groups (docker, etc.), not just primary gid.
|
|
44
|
+
var ptySpawn = require("./os-users").wrapSpawnAsUser(shell, args, spawnOpts);
|
|
45
|
+
var term = pty.spawn(ptySpawn.command, ptySpawn.args, ptySpawn.options);
|
|
43
46
|
|
|
44
47
|
if (opts && opts.initialInput) {
|
|
45
48
|
try { term.write(opts.initialInput); } catch (e) {}
|
package/lib/users-preferences.js
CHANGED
|
@@ -334,20 +334,21 @@ function attachPreferences(deps) {
|
|
|
334
334
|
|
|
335
335
|
// --- Per-user Mates UI toggle ---
|
|
336
336
|
//
|
|
337
|
-
// When
|
|
338
|
-
// Mate" entry, home-hub mates strip) is hidden for this user
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
// new users (no field set) get the default-
|
|
337
|
+
// When disabled, the entire Mates surface (sidebar avatars, DM "Create a
|
|
338
|
+
// Mate" entry, home-hub mates strip) is hidden for this user; multi-user
|
|
339
|
+
// and the rest of the sidebar are unaffected. Default is OFF: Mates is
|
|
340
|
+
// opt-in, not opt-out. Stored as `matesEnabled`; only the literal `true`
|
|
341
|
+
// enables it, so brand-new users (no field set) get the default-off
|
|
342
|
+
// experience and must turn Mates on explicitly in settings.
|
|
342
343
|
|
|
343
344
|
function getMatesEnabled(userId) {
|
|
344
345
|
var data = loadUsers();
|
|
345
346
|
for (var i = 0; i < data.users.length; i++) {
|
|
346
347
|
if (data.users[i].id === userId) {
|
|
347
|
-
return data.users[i].matesEnabled
|
|
348
|
+
return data.users[i].matesEnabled === true;
|
|
348
349
|
}
|
|
349
350
|
}
|
|
350
|
-
return
|
|
351
|
+
return false;
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
function setMatesEnabled(userId, enabled) {
|
package/lib/ws-schema.js
CHANGED
|
@@ -67,6 +67,10 @@ var schema = {
|
|
|
67
67
|
"compacting": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Conversation compaction in progress (active: true/false)" },
|
|
68
68
|
"message_uuid": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "UUID assigned to a user or assistant message" },
|
|
69
69
|
"prompt_suggestion": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Suggested follow-up prompt from the assistant" },
|
|
70
|
+
"model_refusal": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Model declined the request (fallback to another model, or no fallback)" },
|
|
71
|
+
"informational": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Leveled informational message from the agent (info/notice/suggestion/warning)" },
|
|
72
|
+
"permission_denied": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A tool call was denied; explains why" },
|
|
73
|
+
"thinking_tokens": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Live thinking-token estimate (ephemeral)" },
|
|
70
74
|
"context_preview": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Preview of context sources included in a query" },
|
|
71
75
|
|
|
72
76
|
// -----------------------------------------------------------------------
|
|
@@ -115,6 +119,9 @@ var schema = {
|
|
|
115
119
|
"elicitation_request": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Structured form elicitation request from assistant" },
|
|
116
120
|
"elicitation_response": { direction: "c2s", handler: "lib/project-sessions.js", description: "User response to an elicitation form" },
|
|
117
121
|
"elicitation_resolved": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Elicitation request resolved" },
|
|
122
|
+
"user_dialog_request": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Host dialog request from assistant (SDK request_user_dialog)" },
|
|
123
|
+
"user_dialog_response": { direction: "c2s", handler: "lib/project-sessions.js", description: "User response to a host dialog request" },
|
|
124
|
+
"user_dialog_resolved": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Host dialog request resolved" },
|
|
118
125
|
|
|
119
126
|
// -----------------------------------------------------------------------
|
|
120
127
|
// Stop / control
|
|
@@ -157,6 +164,8 @@ var schema = {
|
|
|
157
164
|
"set_server_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default model" },
|
|
158
165
|
"set_project_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the project default model" },
|
|
159
166
|
"set_permission_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set permission mode for the current session" },
|
|
167
|
+
"reload_skills": { direction: "c2s", handler: "lib/project-sessions.js", description: "Hot-reload skills/MCP/agents on the active query without a restart" },
|
|
168
|
+
"set_mcp_permission_mode_override": { direction: "c2s", handler: "lib/project-sessions.js", description: "Override permission mode (default/auto/null) for one MCP server" },
|
|
160
169
|
"set_server_default_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default permission mode" },
|
|
161
170
|
"set_project_default_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the project default permission mode" },
|
|
162
171
|
"set_effort": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set effort level for the current session" },
|
|
@@ -99,6 +99,7 @@ var abortController = null;
|
|
|
99
99
|
var pendingPermissions = {}; // requestId -> resolve
|
|
100
100
|
var pendingAskUser = {}; // toolUseId -> resolve
|
|
101
101
|
var pendingElicitations = {}; // requestId -> resolve
|
|
102
|
+
var pendingUserDialogs = {}; // requestId -> resolve
|
|
102
103
|
var pendingMcpToolCalls = {}; // requestId -> { resolve, reject }
|
|
103
104
|
var conn = null;
|
|
104
105
|
var buffer = "";
|
|
@@ -271,6 +272,12 @@ function handleMessage(msg) {
|
|
|
271
272
|
case "stop_task":
|
|
272
273
|
handleStopTask(msg);
|
|
273
274
|
break;
|
|
275
|
+
case "reload_skills":
|
|
276
|
+
handleReloadSkills(msg);
|
|
277
|
+
break;
|
|
278
|
+
case "set_mcp_permission_mode_override":
|
|
279
|
+
handleSetMcpPermissionModeOverride(msg);
|
|
280
|
+
break;
|
|
274
281
|
case "rewind_files":
|
|
275
282
|
handleRewindFiles(msg);
|
|
276
283
|
break;
|
|
@@ -283,6 +290,9 @@ function handleMessage(msg) {
|
|
|
283
290
|
case "elicitation_response":
|
|
284
291
|
handleElicitationResponse(msg);
|
|
285
292
|
break;
|
|
293
|
+
case "user_dialog_response":
|
|
294
|
+
handleUserDialogResponse(msg);
|
|
295
|
+
break;
|
|
286
296
|
case "mcp_tool_result":
|
|
287
297
|
handleMcpToolResult(msg);
|
|
288
298
|
break;
|
|
@@ -343,6 +353,27 @@ function onElicitation(request, opts) {
|
|
|
343
353
|
});
|
|
344
354
|
}
|
|
345
355
|
|
|
356
|
+
// --- onUserDialog: delegates to daemon via IPC ---
|
|
357
|
+
function onUserDialog(request, opts) {
|
|
358
|
+
var requestId = crypto.randomUUID();
|
|
359
|
+
sendToDaemon({
|
|
360
|
+
type: "user_dialog_request",
|
|
361
|
+
requestId: requestId,
|
|
362
|
+
dialogKind: request.dialogKind,
|
|
363
|
+
payload: request.payload || {},
|
|
364
|
+
toolUseId: request.toolUseID || null,
|
|
365
|
+
});
|
|
366
|
+
return new Promise(function(resolve) {
|
|
367
|
+
pendingUserDialogs[requestId] = resolve;
|
|
368
|
+
if (opts.signal) {
|
|
369
|
+
opts.signal.addEventListener("abort", function() {
|
|
370
|
+
delete pendingUserDialogs[requestId];
|
|
371
|
+
resolve({ behavior: "cancelled" });
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
346
377
|
function handlePermissionResponse(msg) {
|
|
347
378
|
var resolve = pendingPermissions[msg.requestId];
|
|
348
379
|
if (resolve) {
|
|
@@ -367,6 +398,14 @@ function handleElicitationResponse(msg) {
|
|
|
367
398
|
}
|
|
368
399
|
}
|
|
369
400
|
|
|
401
|
+
function handleUserDialogResponse(msg) {
|
|
402
|
+
var resolve = pendingUserDialogs[msg.requestId];
|
|
403
|
+
if (resolve) {
|
|
404
|
+
delete pendingUserDialogs[msg.requestId];
|
|
405
|
+
resolve(msg.result);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
370
409
|
function handleMcpToolResult(msg) {
|
|
371
410
|
var pending = pendingMcpToolCalls[msg.requestId];
|
|
372
411
|
if (!pending) return;
|
|
@@ -477,6 +516,11 @@ async function handleQueryStart(msg) {
|
|
|
477
516
|
options.onElicitation = function(request, elicitOpts) {
|
|
478
517
|
return onElicitation(request, elicitOpts);
|
|
479
518
|
};
|
|
519
|
+
// supportedDialogKinds arrives serialized via msg.options (already on `options`);
|
|
520
|
+
// the callback is non-serializable so we wire it here.
|
|
521
|
+
options.onUserDialog = function(request, dialogOpts) {
|
|
522
|
+
return onUserDialog(request, dialogOpts);
|
|
523
|
+
};
|
|
480
524
|
|
|
481
525
|
perf("creating query instance");
|
|
482
526
|
try {
|
|
@@ -630,6 +674,24 @@ async function handleStopTask(msg) {
|
|
|
630
674
|
}
|
|
631
675
|
}
|
|
632
676
|
|
|
677
|
+
async function handleReloadSkills(msg) {
|
|
678
|
+
if (!queryInstance || typeof queryInstance.reloadSkills !== "function") return;
|
|
679
|
+
try {
|
|
680
|
+
await queryInstance.reloadSkills();
|
|
681
|
+
} catch (e) {
|
|
682
|
+
console.error("[sdk-worker] reloadSkills error:", e.message);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function handleSetMcpPermissionModeOverride(msg) {
|
|
687
|
+
if (!queryInstance || typeof queryInstance.setMcpPermissionModeOverride !== "function") return;
|
|
688
|
+
try {
|
|
689
|
+
await queryInstance.setMcpPermissionModeOverride(msg.serverName, msg.mode);
|
|
690
|
+
} catch (e) {
|
|
691
|
+
console.error("[sdk-worker] setMcpPermissionModeOverride error:", e.message);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
633
695
|
// --- Warmup ---
|
|
634
696
|
async function handleWarmup(msg) {
|
|
635
697
|
var sdk;
|