clay-server 2.33.1 → 2.34.0-beta.10
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/ask-user-mcp-server.js +120 -0
- package/lib/config.js +9 -13
- package/lib/daemon.js +116 -55
- package/lib/mate-datastore.js +359 -0
- package/lib/mates.js +2 -2
- package/lib/os-users.js +70 -37
- package/lib/project-connection.js +16 -9
- package/lib/project-http.js +3 -4
- package/lib/project-image.js +3 -2
- package/lib/project-mate-datastore.js +232 -0
- package/lib/project-sessions.js +110 -7
- package/lib/project-user-message.js +4 -3
- package/lib/project.js +126 -10
- package/lib/public/app.js +2 -0
- package/lib/public/css/mates.css +228 -11
- package/lib/public/css/messages.css +23 -0
- package/lib/public/css/mobile-nav.css +0 -14
- package/lib/public/css/notifications-center.css +80 -0
- package/lib/public/css/sidebar.css +326 -101
- package/lib/public/index.html +24 -29
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +23 -0
- package/lib/public/modules/app-rendering.js +0 -2
- package/lib/public/modules/diff.js +21 -7
- package/lib/public/modules/mate-datastore-ui.js +280 -0
- package/lib/public/modules/mate-sidebar.js +3 -9
- package/lib/public/modules/mate-wizard.js +15 -15
- package/lib/public/modules/sidebar-mobile.js +10 -20
- package/lib/public/modules/sidebar-sessions.js +490 -113
- package/lib/public/modules/sidebar.js +8 -6
- package/lib/public/modules/tools.js +115 -18
- package/lib/public/sw.js +1 -1
- package/lib/sdk-bridge.js +56 -41
- package/lib/sdk-message-processor.js +21 -4
- package/lib/server.js +28 -72
- package/lib/sessions.js +157 -20
- package/lib/updater.js +2 -2
- package/lib/users.js +2 -2
- package/lib/ws-schema.js +16 -0
- package/lib/yoke/adapters/claude-worker.js +114 -2
- package/lib/yoke/adapters/claude.js +56 -5
- package/lib/yoke/adapters/codex.js +350 -58
- package/lib/yoke/index.js +93 -48
- package/lib/yoke/instructions.js +0 -1
- package/lib/yoke/mcp-bridge-server.js +14 -6
- package/package.json +1 -2
- package/lib/yoke/adapters/gemini.js +0 -709
|
@@ -94,12 +94,14 @@ export function initSidebar(_ctx) {
|
|
|
94
94
|
}
|
|
95
95
|
} catch (e) {}
|
|
96
96
|
|
|
97
|
-
ctx.newSessionBtn
|
|
98
|
-
|
|
99
|
-
ctx.ws
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
if (ctx.newSessionBtn) {
|
|
98
|
+
ctx.newSessionBtn.addEventListener("click", function () {
|
|
99
|
+
if (ctx.ws && ctx.connected) {
|
|
100
|
+
ctx.ws.send(JSON.stringify({ type: "new_session" }));
|
|
101
|
+
closeSidebar();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
103
105
|
|
|
104
106
|
// --- Loop (Ralph wizard) tool-palette tile ---
|
|
105
107
|
// The tile is rendered by tool-palette.js at the stable id
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
4
|
-
import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
|
|
4
|
+
import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff, reconstructPatchSources } from './diff.js';
|
|
5
5
|
import { openFile } from './filebrowser.js';
|
|
6
6
|
import { mateAvatarUrl } from './avatar.js';
|
|
7
7
|
import { getChatLayout } from './theme.js';
|
|
@@ -20,6 +20,14 @@ var todoItems = [];
|
|
|
20
20
|
var todoWidgetEl = null;
|
|
21
21
|
var todoWidgetVisible = true; // whether in-chat widget is in viewport
|
|
22
22
|
var todoObserver = null;
|
|
23
|
+
var todoMeta = {
|
|
24
|
+
variant: "tasks",
|
|
25
|
+
title: "Tasks",
|
|
26
|
+
icon: "list-checks",
|
|
27
|
+
showProgress: true,
|
|
28
|
+
showCompletedCount: true,
|
|
29
|
+
stickyEnabled: true,
|
|
30
|
+
};
|
|
23
31
|
|
|
24
32
|
// --- Tool tracking ---
|
|
25
33
|
var tools = {};
|
|
@@ -791,7 +799,7 @@ function resolvePermissionIdentity(mateId, vendor) {
|
|
|
791
799
|
}
|
|
792
800
|
}
|
|
793
801
|
// Project chat: use vendor name and avatar
|
|
794
|
-
var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png"
|
|
802
|
+
var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png" };
|
|
795
803
|
var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
|
|
796
804
|
return {
|
|
797
805
|
name: vendorName,
|
|
@@ -1262,6 +1270,7 @@ function todoStatusIcon(status) {
|
|
|
1262
1270
|
|
|
1263
1271
|
export function handleTodoWrite(input) {
|
|
1264
1272
|
if (!input || !Array.isArray(input.todos)) return;
|
|
1273
|
+
todoMeta = normalizeTodoMeta(input.meta);
|
|
1265
1274
|
todoItems = input.todos.map(function (t, i) {
|
|
1266
1275
|
return {
|
|
1267
1276
|
id: t.id || String(i + 1),
|
|
@@ -1275,6 +1284,7 @@ export function handleTodoWrite(input) {
|
|
|
1275
1284
|
|
|
1276
1285
|
export function handleTaskCreate(input) {
|
|
1277
1286
|
if (!input) return;
|
|
1287
|
+
todoMeta = normalizeTodoMeta();
|
|
1278
1288
|
var id = String(todoItems.length + 1);
|
|
1279
1289
|
todoItems.push({
|
|
1280
1290
|
id: id,
|
|
@@ -1287,6 +1297,7 @@ export function handleTaskCreate(input) {
|
|
|
1287
1297
|
|
|
1288
1298
|
export function handleTaskUpdate(input) {
|
|
1289
1299
|
if (!input || !input.taskId) return;
|
|
1300
|
+
todoMeta = normalizeTodoMeta();
|
|
1290
1301
|
for (var i = 0; i < todoItems.length; i++) {
|
|
1291
1302
|
if (todoItems[i].id === input.taskId) {
|
|
1292
1303
|
if (input.status === "deleted") {
|
|
@@ -1302,11 +1313,33 @@ export function handleTaskUpdate(input) {
|
|
|
1302
1313
|
renderTodoWidget();
|
|
1303
1314
|
}
|
|
1304
1315
|
|
|
1316
|
+
function normalizeTodoMeta(meta) {
|
|
1317
|
+
if (meta && meta.variant === "plan") {
|
|
1318
|
+
return {
|
|
1319
|
+
variant: "plan",
|
|
1320
|
+
title: "Plan",
|
|
1321
|
+
icon: "map",
|
|
1322
|
+
showProgress: false,
|
|
1323
|
+
showCompletedCount: false,
|
|
1324
|
+
stickyEnabled: false,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
variant: "tasks",
|
|
1329
|
+
title: "Tasks",
|
|
1330
|
+
icon: "list-checks",
|
|
1331
|
+
showProgress: true,
|
|
1332
|
+
showCompletedCount: true,
|
|
1333
|
+
stickyEnabled: true,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1305
1337
|
function renderTodoWidget() {
|
|
1306
1338
|
if (todoItems.length === 0) {
|
|
1307
1339
|
if (todoWidgetEl) { todoWidgetEl.remove(); todoWidgetEl = null; }
|
|
1308
1340
|
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
|
1309
1341
|
todoWidgetVisible = true;
|
|
1342
|
+
todoMeta = normalizeTodoMeta();
|
|
1310
1343
|
updateTodoSticky();
|
|
1311
1344
|
return;
|
|
1312
1345
|
}
|
|
@@ -1316,19 +1349,26 @@ function renderTodoWidget() {
|
|
|
1316
1349
|
todoWidgetEl = document.createElement("div");
|
|
1317
1350
|
todoWidgetEl.className = "todo-widget";
|
|
1318
1351
|
}
|
|
1352
|
+
todoWidgetEl.className = "todo-widget" + (todoMeta.variant === "plan" ? " todo-widget-plan" : "");
|
|
1319
1353
|
|
|
1320
1354
|
var completed = 0;
|
|
1321
1355
|
for (var i = 0; i < todoItems.length; i++) {
|
|
1322
1356
|
if (todoItems[i].status === "completed") completed++;
|
|
1323
1357
|
}
|
|
1324
1358
|
|
|
1359
|
+
var countText = todoMeta.showCompletedCount
|
|
1360
|
+
? (completed + "/" + todoItems.length)
|
|
1361
|
+
: (todoItems.length + " " + (todoItems.length === 1 ? "step" : "steps"));
|
|
1362
|
+
|
|
1325
1363
|
var html = '<div class="todo-header">' +
|
|
1326
|
-
'<span class="todo-header-icon">' + iconHtml(
|
|
1327
|
-
'<span class="todo-header-title">
|
|
1328
|
-
'<span class="todo-header-count">' +
|
|
1364
|
+
'<span class="todo-header-icon">' + iconHtml(todoMeta.icon) + '</span>' +
|
|
1365
|
+
'<span class="todo-header-title">' + todoMeta.title + '</span>' +
|
|
1366
|
+
'<span class="todo-header-count">' + countText + '</span>' +
|
|
1329
1367
|
'</div>';
|
|
1330
|
-
|
|
1331
|
-
|
|
1368
|
+
if (todoMeta.showProgress) {
|
|
1369
|
+
html += '<div class="todo-progress"><div class="todo-progress-bar" style="width:' +
|
|
1370
|
+
(todoItems.length > 0 ? Math.round(completed / todoItems.length * 100) : 0) + '%"></div></div>';
|
|
1371
|
+
}
|
|
1332
1372
|
html += '<div class="todo-items">';
|
|
1333
1373
|
for (var i = 0; i < todoItems.length; i++) {
|
|
1334
1374
|
var t = todoItems[i];
|
|
@@ -1369,6 +1409,10 @@ function setupTodoObserver() {
|
|
|
1369
1409
|
function updateTodoStickyVisibility() {
|
|
1370
1410
|
var stickyEl = document.getElementById("todo-sticky");
|
|
1371
1411
|
if (!stickyEl) return;
|
|
1412
|
+
if (!todoMeta.stickyEnabled) {
|
|
1413
|
+
stickyEl.classList.add("hidden");
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1372
1416
|
|
|
1373
1417
|
if (todoWidgetVisible) {
|
|
1374
1418
|
stickyEl.classList.add("hidden");
|
|
@@ -1387,6 +1431,11 @@ function updateTodoStickyVisibility() {
|
|
|
1387
1431
|
function updateTodoSticky() {
|
|
1388
1432
|
var stickyEl = document.getElementById("todo-sticky");
|
|
1389
1433
|
if (!stickyEl) return;
|
|
1434
|
+
if (!todoMeta.stickyEnabled) {
|
|
1435
|
+
stickyEl.classList.add("hidden");
|
|
1436
|
+
stickyEl.innerHTML = "";
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1390
1439
|
|
|
1391
1440
|
// Hide if no active tasks (all completed or empty)
|
|
1392
1441
|
var hasActive = false;
|
|
@@ -1538,14 +1587,32 @@ export function stopThinking(duration) {
|
|
|
1538
1587
|
} else {
|
|
1539
1588
|
currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
|
|
1540
1589
|
}
|
|
1541
|
-
//
|
|
1590
|
+
// If no thinking text was streamed (e.g. Codex reasoning items arrive
|
|
1591
|
+
// with encrypted/hidden content, or Claude without extended-thinking),
|
|
1592
|
+
// the expand affordance is misleading because there's nothing inside.
|
|
1593
|
+
// Strip the chevron and the click handler so the header reads as a
|
|
1594
|
+
// plain label.
|
|
1595
|
+
var hasContent = !!(currentThinking.fullText && currentThinking.fullText.length > 0);
|
|
1596
|
+
if (!hasContent) {
|
|
1597
|
+
currentThinking.el.classList.add("empty");
|
|
1598
|
+
var chev = currentThinking.el.querySelector(".thinking-chevron");
|
|
1599
|
+
if (chev) chev.style.display = "none";
|
|
1600
|
+
var hdr = currentThinking.el.querySelector(".thinking-header");
|
|
1601
|
+
if (hdr) {
|
|
1602
|
+
hdr.style.cursor = "default";
|
|
1603
|
+
// Replace click listener by cloning the node (cheapest way to strip listeners).
|
|
1604
|
+
var clone = hdr.cloneNode(true);
|
|
1605
|
+
hdr.parentNode.replaceChild(clone, hdr);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
// In mate mode: hide sparkle activity, show compact thinking header.
|
|
1542
1609
|
if (currentThinking.el.classList.contains("mate-thinking")) {
|
|
1543
1610
|
var actRow = currentThinking.el.querySelector(".mate-thinking-activity");
|
|
1544
1611
|
if (actRow) actRow.style.display = "none";
|
|
1545
1612
|
var header = currentThinking.el.querySelector(".thinking-header");
|
|
1546
1613
|
if (header) {
|
|
1547
1614
|
header.style.display = "";
|
|
1548
|
-
header.style.cursor = "pointer";
|
|
1615
|
+
header.style.cursor = hasContent ? "pointer" : "default";
|
|
1549
1616
|
}
|
|
1550
1617
|
}
|
|
1551
1618
|
currentThinking = null;
|
|
@@ -1673,12 +1740,12 @@ export function updateToolExecuting(id, name, input) {
|
|
|
1673
1740
|
ctx.scrollToBottom();
|
|
1674
1741
|
}
|
|
1675
1742
|
|
|
1676
|
-
|
|
1743
|
+
// Shared chrome (filename header + unified/split toggle) for diff renderings.
|
|
1744
|
+
// makeUnified and makeSplit are factories that return a fresh body element.
|
|
1745
|
+
function buildDiffChrome(filePath, linkOldStr, linkNewStr, makeUnified, makeSplit) {
|
|
1677
1746
|
var wrapper = document.createElement("div");
|
|
1678
1747
|
wrapper.className = "edit-diff";
|
|
1679
|
-
var lang = getLanguageFromPath(filePath);
|
|
1680
1748
|
|
|
1681
|
-
// Header with file path and split toggle (desktop only)
|
|
1682
1749
|
var header = document.createElement("div");
|
|
1683
1750
|
header.className = "edit-diff-header";
|
|
1684
1751
|
|
|
@@ -1691,7 +1758,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
|
|
|
1691
1758
|
e.stopPropagation();
|
|
1692
1759
|
openFile(fp, { diff: { oldStr: os || "", newStr: ns || "" } });
|
|
1693
1760
|
});
|
|
1694
|
-
})(filePath,
|
|
1761
|
+
})(filePath, linkOldStr, linkNewStr);
|
|
1695
1762
|
}
|
|
1696
1763
|
header.appendChild(pathSpan);
|
|
1697
1764
|
|
|
@@ -1717,7 +1784,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
|
|
|
1717
1784
|
|
|
1718
1785
|
wrapper.appendChild(header);
|
|
1719
1786
|
|
|
1720
|
-
var currentBody =
|
|
1787
|
+
var currentBody = makeUnified();
|
|
1721
1788
|
wrapper.appendChild(currentBody);
|
|
1722
1789
|
|
|
1723
1790
|
unifiedBtn.addEventListener("click", function (e) {
|
|
@@ -1727,7 +1794,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
|
|
|
1727
1794
|
unifiedBtn.classList.add("active");
|
|
1728
1795
|
splitBtn.classList.remove("active");
|
|
1729
1796
|
wrapper.removeChild(currentBody);
|
|
1730
|
-
currentBody =
|
|
1797
|
+
currentBody = makeUnified();
|
|
1731
1798
|
wrapper.appendChild(currentBody);
|
|
1732
1799
|
refreshIcons();
|
|
1733
1800
|
});
|
|
@@ -1739,7 +1806,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
|
|
|
1739
1806
|
splitBtn.classList.add("active");
|
|
1740
1807
|
unifiedBtn.classList.remove("active");
|
|
1741
1808
|
wrapper.removeChild(currentBody);
|
|
1742
|
-
currentBody =
|
|
1809
|
+
currentBody = makeSplit();
|
|
1743
1810
|
wrapper.appendChild(currentBody);
|
|
1744
1811
|
refreshIcons();
|
|
1745
1812
|
});
|
|
@@ -1747,6 +1814,29 @@ function renderEditDiff(oldStr, newStr, filePath) {
|
|
|
1747
1814
|
return wrapper;
|
|
1748
1815
|
}
|
|
1749
1816
|
|
|
1817
|
+
function renderEditDiff(oldStr, newStr, filePath) {
|
|
1818
|
+
var lang = getLanguageFromPath(filePath);
|
|
1819
|
+
return buildDiffChrome(
|
|
1820
|
+
filePath,
|
|
1821
|
+
oldStr,
|
|
1822
|
+
newStr,
|
|
1823
|
+
function () { return renderUnifiedDiff(oldStr, newStr, lang); },
|
|
1824
|
+
function () { return renderSplitDiff(oldStr, newStr, lang); }
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function renderPatchDiffBlock(patchText, filePath) {
|
|
1829
|
+
var lang = getLanguageFromPath(filePath);
|
|
1830
|
+
var sources = reconstructPatchSources(patchText);
|
|
1831
|
+
return buildDiffChrome(
|
|
1832
|
+
filePath,
|
|
1833
|
+
sources.oldStr,
|
|
1834
|
+
sources.newStr,
|
|
1835
|
+
function () { return renderPatchDiff(patchText, lang); },
|
|
1836
|
+
function () { return renderSplitDiff(sources.oldStr, sources.newStr, lang); }
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1750
1840
|
function isDiffContent(text) {
|
|
1751
1841
|
var lines = text.split("\n");
|
|
1752
1842
|
var hasHunkHeader = false;
|
|
@@ -1854,8 +1944,12 @@ export function updateToolResult(id, content, isError, images) {
|
|
|
1854
1944
|
if (hasEditDiff) {
|
|
1855
1945
|
resultBlock.appendChild(renderEditDiff(tool.input.old_string, tool.input.new_string, tool.input.file_path));
|
|
1856
1946
|
} else if (!isError && isDiffContent(displayContent)) {
|
|
1857
|
-
var
|
|
1858
|
-
|
|
1947
|
+
var patchFilePath = tool.input && tool.input.file_path ? tool.input.file_path : null;
|
|
1948
|
+
if (patchFilePath) {
|
|
1949
|
+
resultBlock.appendChild(renderPatchDiffBlock(displayContent, patchFilePath));
|
|
1950
|
+
} else {
|
|
1951
|
+
resultBlock.appendChild(renderPatchDiff(displayContent, null));
|
|
1952
|
+
}
|
|
1859
1953
|
} else if (!isError && tool.name === "Read" && tool.input && tool.input.file_path && isImagePath(tool.input.file_path)) {
|
|
1860
1954
|
// Image file: show inline preview
|
|
1861
1955
|
var imgWrap = document.createElement("div");
|
|
@@ -2195,6 +2289,7 @@ export function saveToolState() {
|
|
|
2195
2289
|
tools: tools,
|
|
2196
2290
|
currentThinking: currentThinking,
|
|
2197
2291
|
todoWidgetEl: todoWidgetEl,
|
|
2292
|
+
todoMeta: todoMeta,
|
|
2198
2293
|
inPlanMode: inPlanMode,
|
|
2199
2294
|
planContent: planContent,
|
|
2200
2295
|
currentPlanCardEl: currentPlanCardEl,
|
|
@@ -2209,6 +2304,7 @@ export function restoreToolState(saved) {
|
|
|
2209
2304
|
tools = saved.tools;
|
|
2210
2305
|
currentThinking = saved.currentThinking;
|
|
2211
2306
|
todoWidgetEl = saved.todoWidgetEl;
|
|
2307
|
+
todoMeta = saved.todoMeta || normalizeTodoMeta();
|
|
2212
2308
|
inPlanMode = saved.inPlanMode;
|
|
2213
2309
|
planContent = saved.planContent;
|
|
2214
2310
|
currentPlanCardEl = saved.currentPlanCardEl || null;
|
|
@@ -2229,6 +2325,7 @@ export function resetToolState() {
|
|
|
2229
2325
|
planContent = null;
|
|
2230
2326
|
currentPlanCardEl = null;
|
|
2231
2327
|
todoItems = [];
|
|
2328
|
+
todoMeta = normalizeTodoMeta();
|
|
2232
2329
|
todoWidgetEl = null;
|
|
2233
2330
|
todoWidgetVisible = true;
|
|
2234
2331
|
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
package/lib/public/sw.js
CHANGED
package/lib/sdk-bridge.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const crypto = require("crypto");
|
|
2
2
|
var fs = require("fs");
|
|
3
3
|
var path = require("path");
|
|
4
|
-
var
|
|
4
|
+
var execSync = require("child_process").execSync;
|
|
5
|
+
var execFileSync = require("child_process").execFileSync;
|
|
5
6
|
var usersModule = require("./users");
|
|
6
7
|
var { getCodexConfig } = require("./codex-defaults");
|
|
7
8
|
var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
|
|
@@ -373,12 +374,12 @@ function createSDKBridge(opts) {
|
|
|
373
374
|
// Create and chown the project directory once
|
|
374
375
|
if (!fs.existsSync(dstDir)) {
|
|
375
376
|
fs.mkdirSync(dstDir, { recursive: true });
|
|
376
|
-
try {
|
|
377
|
+
try { execFileSync("chown", ["-R", String(uid), path.join(linuxUserHome, ".claude")]); } catch (e2) {}
|
|
377
378
|
} else {
|
|
378
379
|
try {
|
|
379
380
|
var dirStat = fs.statSync(dstDir);
|
|
380
381
|
if (dirStat.uid !== uid) {
|
|
381
|
-
|
|
382
|
+
execFileSync("chown", [String(uid), dstDir]);
|
|
382
383
|
}
|
|
383
384
|
} catch (e2) {}
|
|
384
385
|
}
|
|
@@ -389,7 +390,7 @@ function createSDKBridge(opts) {
|
|
|
389
390
|
var dstFile = path.join(dstDir, sessionFileName);
|
|
390
391
|
if (fs.existsSync(srcFile) && !fs.existsSync(dstFile)) {
|
|
391
392
|
fs.copyFileSync(srcFile, dstFile);
|
|
392
|
-
try {
|
|
393
|
+
try { execFileSync("chown", [String(uid), dstFile]); } catch (e2) {}
|
|
393
394
|
console.log("[sdk-bridge] Pre-copied CLI session " + session.cliSessionId + " to " + linuxUser);
|
|
394
395
|
}
|
|
395
396
|
}
|
|
@@ -447,6 +448,12 @@ function createSDKBridge(opts) {
|
|
|
447
448
|
}
|
|
448
449
|
}
|
|
449
450
|
|
|
451
|
+
// Auto-approve Mate datastore tools. These are scoped to the active Mate
|
|
452
|
+
// project and already enforce SQL policy server-side.
|
|
453
|
+
if (toolName.indexOf("mcp__clay-datastore__") === 0) {
|
|
454
|
+
return { behavior: "allow", updatedInput: input };
|
|
455
|
+
}
|
|
456
|
+
|
|
450
457
|
// Auto-approve remote MCP tools that the user explicitly enabled in project settings.
|
|
451
458
|
// These are user-owned local MCP servers, so no additional permission prompt needed.
|
|
452
459
|
if (toolName.indexOf("mcp__") === 0 && getRemoteMcpServers) {
|
|
@@ -646,7 +653,7 @@ function createSDKBridge(opts) {
|
|
|
646
653
|
*/
|
|
647
654
|
function findConflictingClaude() {
|
|
648
655
|
try {
|
|
649
|
-
var output =
|
|
656
|
+
var output = execFileSync("ps", ["ax", "-o", "pid,command"], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
650
657
|
var lines = output.trim().split("\n");
|
|
651
658
|
var candidates = [];
|
|
652
659
|
for (var i = 1; i < lines.length; i++) { // skip header
|
|
@@ -691,7 +698,7 @@ function createSDKBridge(opts) {
|
|
|
691
698
|
*/
|
|
692
699
|
function isClaudeProcess(pid) {
|
|
693
700
|
try {
|
|
694
|
-
var output =
|
|
701
|
+
var output = execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
695
702
|
return /\/claude(\s|$)/.test(output) || /^claude(\s|$)/.test(output);
|
|
696
703
|
} catch (e) {
|
|
697
704
|
return false;
|
|
@@ -1168,17 +1175,21 @@ function createSDKBridge(opts) {
|
|
|
1168
1175
|
|
|
1169
1176
|
if (dangerouslySkipPermissions) {
|
|
1170
1177
|
claudeOpts.allowDangerouslySkipPermissions = true;
|
|
1178
|
+
claudeOpts.permissionMode = "bypassPermissions";
|
|
1179
|
+
} else {
|
|
1180
|
+
var globalMode = sm.currentPermissionMode || "default";
|
|
1181
|
+
var effectiveDefault;
|
|
1182
|
+
if (globalMode === "bypassPermissions") effectiveDefault = "bypassPermissions";
|
|
1183
|
+
else if (session.acceptEditsAfterStart) effectiveDefault = "acceptEdits";
|
|
1184
|
+
else effectiveDefault = globalMode;
|
|
1185
|
+
var modeToApply = session._loopPermissionMode || effectiveDefault;
|
|
1186
|
+
if (modeToApply && modeToApply !== "default") {
|
|
1187
|
+
claudeOpts.permissionMode = modeToApply;
|
|
1188
|
+
}
|
|
1171
1189
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
if (globalMode === "bypassPermissions") effectiveDefault = "bypassPermissions";
|
|
1175
|
-
else if (session.acceptEditsAfterStart) effectiveDefault = "acceptEdits";
|
|
1176
|
-
else effectiveDefault = globalMode;
|
|
1177
|
-
var modeToApply = session._loopPermissionMode || effectiveDefault;
|
|
1190
|
+
// Clear one-shot acceptEditsAfterStart regardless of which branch ran above,
|
|
1191
|
+
// so the flag does not linger into subsequent turns.
|
|
1178
1192
|
if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
|
|
1179
|
-
if (modeToApply && modeToApply !== "default") {
|
|
1180
|
-
claudeOpts.permissionMode = modeToApply;
|
|
1181
|
-
}
|
|
1182
1193
|
if (session.cliSessionId && session.lastRewindUuid) {
|
|
1183
1194
|
claudeOpts.resumeSessionAt = session.lastRewindUuid;
|
|
1184
1195
|
delete session.lastRewindUuid;
|
|
@@ -1199,21 +1210,30 @@ function createSDKBridge(opts) {
|
|
|
1199
1210
|
}
|
|
1200
1211
|
}
|
|
1201
1212
|
|
|
1202
|
-
//
|
|
1213
|
+
// Pick a model that belongs to the session's vendor. sm.currentModel is
|
|
1214
|
+
// shared project-wide, so a Codex session that last set it to
|
|
1215
|
+
// "gpt-5.4-mini" would otherwise leak into a Claude session in the same
|
|
1216
|
+
// project (or in another session that switches vendor to claude) and
|
|
1217
|
+
// Claude would reject the unknown model. We validate against the
|
|
1218
|
+
// session vendor's model list regardless of which vendor happens to be
|
|
1219
|
+
// the project's default adapter.
|
|
1203
1220
|
var queryModel = ls.model || sm.currentModel || undefined;
|
|
1204
|
-
|
|
1205
|
-
|
|
1221
|
+
var sessionVendor = session.vendor || (adapter && adapter.vendor) || null;
|
|
1222
|
+
if (sessionVendor) {
|
|
1223
|
+
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[sessionVendor]) || [];
|
|
1206
1224
|
if (vendorModels.length > 0 && queryModel && vendorModels.indexOf(queryModel) === -1) {
|
|
1207
1225
|
queryModel = vendorModels[0];
|
|
1208
1226
|
}
|
|
1209
1227
|
}
|
|
1210
1228
|
|
|
1211
1229
|
var codexConfig = getCodexConfig(sm);
|
|
1230
|
+
var mergedMcpServers = mergeMcpServers(getMcpServers(), getRemoteMcpServers) || undefined;
|
|
1212
1231
|
var queryOpts = {
|
|
1213
1232
|
cwd: cwd,
|
|
1214
1233
|
model: queryModel,
|
|
1215
1234
|
effort: ls.effort || sm.currentEffort || undefined,
|
|
1216
|
-
toolServers:
|
|
1235
|
+
toolServers: mergedMcpServers,
|
|
1236
|
+
toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
|
|
1217
1237
|
resumeSessionId: session.cliSessionId || undefined,
|
|
1218
1238
|
abortController: linuxUser ? undefined : session.abortController,
|
|
1219
1239
|
canUseTool: function(toolName, input, toolOpts) {
|
|
@@ -1222,6 +1242,9 @@ function createSDKBridge(opts) {
|
|
|
1222
1242
|
onElicitation: function(request, elicitOpts) {
|
|
1223
1243
|
return handleElicitation(session, request, elicitOpts);
|
|
1224
1244
|
},
|
|
1245
|
+
callMcpTool: function(serverName, toolName, args) {
|
|
1246
|
+
return callMcpToolHandler(mergedMcpServers, serverName, toolName, args);
|
|
1247
|
+
},
|
|
1225
1248
|
adapterOptions: {
|
|
1226
1249
|
CLAUDE: claudeOpts,
|
|
1227
1250
|
CODEX: {
|
|
@@ -1349,17 +1372,17 @@ function createSDKBridge(opts) {
|
|
|
1349
1372
|
// Detect which vendor binaries are installed for this user.
|
|
1350
1373
|
// In multi-user mode, runs checks as the specific Linux user.
|
|
1351
1374
|
function detectInstalledVendors(linuxUser) {
|
|
1352
|
-
var
|
|
1375
|
+
var execFileSync = require("child_process").execFileSync;
|
|
1353
1376
|
var fs = require("fs");
|
|
1354
|
-
var path = require("path");
|
|
1355
1377
|
var result = [];
|
|
1356
1378
|
|
|
1357
|
-
function
|
|
1379
|
+
function tryLookup(name) {
|
|
1358
1380
|
try {
|
|
1359
1381
|
if (linuxUser) {
|
|
1360
|
-
|
|
1382
|
+
execFileSync("su", ["-", linuxUser, "-c", "which " + name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1361
1383
|
} else {
|
|
1362
|
-
|
|
1384
|
+
if (process.platform === "win32") execFileSync("where", [name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1385
|
+
else execFileSync("which", [name], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1363
1386
|
}
|
|
1364
1387
|
return true;
|
|
1365
1388
|
} catch (e) {
|
|
@@ -1368,14 +1391,14 @@ function createSDKBridge(opts) {
|
|
|
1368
1391
|
}
|
|
1369
1392
|
|
|
1370
1393
|
// Claude: check if binary is in PATH
|
|
1371
|
-
if (
|
|
1394
|
+
if (tryLookup("claude")) result.push("claude");
|
|
1372
1395
|
|
|
1373
1396
|
// Codex: check bundled binary or PATH
|
|
1374
1397
|
var codexBin = null;
|
|
1375
1398
|
try {
|
|
1376
1399
|
codexBin = require("./yoke/codex-app-server").findCodexPath();
|
|
1377
1400
|
} catch (e) {}
|
|
1378
|
-
if ((codexBin && fs.existsSync(codexBin)) ||
|
|
1401
|
+
if ((codexBin && fs.existsSync(codexBin)) || tryLookup("codex")) result.push("codex");
|
|
1379
1402
|
|
|
1380
1403
|
return result;
|
|
1381
1404
|
}
|
|
@@ -1432,22 +1455,14 @@ function createSDKBridge(opts) {
|
|
|
1432
1455
|
}
|
|
1433
1456
|
}
|
|
1434
1457
|
|
|
1435
|
-
//
|
|
1458
|
+
// Non-default adapters are NOT eagerly initialized here. Doing so used
|
|
1459
|
+
// to spawn a CodexAppServer and an mcp-bridge child per project even
|
|
1460
|
+
// when the user never touched that vendor. Lazy paths cover the gap:
|
|
1461
|
+
// - get_vendor_models (project.js) inits a vendor when the user
|
|
1462
|
+
// opens its model picker.
|
|
1463
|
+
// - ensureVendorReady (this file) inits a vendor when a session
|
|
1464
|
+
// actually issues a query with it.
|
|
1436
1465
|
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1437
|
-
var otherVendors = Object.keys(adapters).filter(function(v) {
|
|
1438
|
-
return v !== defaultVendor && !sm.modelsByVendor[v];
|
|
1439
|
-
});
|
|
1440
|
-
for (var i = 0; i < otherVendors.length; i++) {
|
|
1441
|
-
(function(v) {
|
|
1442
|
-
adapters[v].init({ cwd: cwd, clayPort: clayPort, clayTls: clayTls, clayAuthToken: clayAuthToken, slug: slug }).then(function(r) {
|
|
1443
|
-
sm.modelsByVendor[v] = r.models || [];
|
|
1444
|
-
sm.capabilitiesByVendor[v] = r.capabilities || {};
|
|
1445
|
-
if (r.slashCommands) sm.setSlashCommandsForVendor(v, r.slashCommands);
|
|
1446
|
-
}).catch(function(e) {
|
|
1447
|
-
console.error("[sdk-bridge] warmup: " + v + " init failed:", e.message || e);
|
|
1448
|
-
});
|
|
1449
|
-
})(otherVendors[i]);
|
|
1450
|
-
}
|
|
1451
1466
|
|
|
1452
1467
|
// Detect installed vendors per-user (binary existence check)
|
|
1453
1468
|
sm.installedVendors = detectInstalledVendors(linuxUser);
|
|
@@ -234,7 +234,13 @@ function attachMessageProcessor(ctx) {
|
|
|
234
234
|
type: "tool_executing",
|
|
235
235
|
id: parsed.turnId || "codex-plan",
|
|
236
236
|
name: "TodoWrite",
|
|
237
|
-
input: {
|
|
237
|
+
input: {
|
|
238
|
+
todos: todos,
|
|
239
|
+
meta: {
|
|
240
|
+
variant: "plan",
|
|
241
|
+
title: parsed.title || "Plan",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
238
244
|
});
|
|
239
245
|
|
|
240
246
|
} else if (parsed.yokeType === "plan_content") {
|
|
@@ -364,12 +370,23 @@ function attachMessageProcessor(ctx) {
|
|
|
364
370
|
session.sentToolResults = {};
|
|
365
371
|
session.pendingPermissions = {};
|
|
366
372
|
session.pendingElicitations = {};
|
|
367
|
-
// Record ask_user_answered for any leftover pending questions so replay pairs correctly
|
|
373
|
+
// Record ask_user_answered for any leftover pending questions so replay pairs correctly.
|
|
374
|
+
// EXCEPTION: "mcp" mode entries are stateless — the tool returned immediately and the
|
|
375
|
+
// turn is expected to end while the card is still awaiting the user's answer. Those
|
|
376
|
+
// entries must survive across turns so the eventual ask_user_response can inject the
|
|
377
|
+
// answer as the next user message. Only blocking modes (Claude canUseTool) get closed.
|
|
368
378
|
var leftoverAskIds = Object.keys(session.pendingAskUser);
|
|
379
|
+
var keptAskUser = {};
|
|
369
380
|
for (var lai = 0; lai < leftoverAskIds.length; lai++) {
|
|
370
|
-
|
|
381
|
+
var lid = leftoverAskIds[lai];
|
|
382
|
+
var lentry = session.pendingAskUser[lid];
|
|
383
|
+
if (lentry && lentry.mode === "mcp") {
|
|
384
|
+
keptAskUser[lid] = lentry;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
sendAndRecord(session, { type: "ask_user_answered", toolId: lid });
|
|
371
388
|
}
|
|
372
|
-
session.pendingAskUser =
|
|
389
|
+
session.pendingAskUser = keptAskUser;
|
|
373
390
|
session.activeTaskToolIds = {};
|
|
374
391
|
session.taskIdMap = {};
|
|
375
392
|
// Only clear rateLimitResetsAt on genuine success (non-zero cost).
|