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
package/lib/server.js
CHANGED
|
@@ -459,75 +459,6 @@ function createServer(opts) {
|
|
|
459
459
|
return;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
// --- Global MCP bridge endpoint (localhost only) ---
|
|
463
|
-
// Used by Codex mcp-bridge-server.js which can't know the active project slug.
|
|
464
|
-
// Aggregates MCP tools from all project contexts.
|
|
465
|
-
if (req.method === "POST" && fullUrl === "/api/mcp-bridge") {
|
|
466
|
-
var mcpRemoteAddr = req.socket.remoteAddress || "";
|
|
467
|
-
var mcpIsLocal = mcpRemoteAddr === "127.0.0.1" || mcpRemoteAddr === "::1" || mcpRemoteAddr === "::ffff:127.0.0.1";
|
|
468
|
-
if (!mcpIsLocal) {
|
|
469
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
470
|
-
res.end('{"error":"Forbidden"}');
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
var parseJsonBody = function(r) {
|
|
474
|
-
return new Promise(function(resolve, reject) {
|
|
475
|
-
var chunks = [];
|
|
476
|
-
r.on("data", function(c) { chunks.push(c); });
|
|
477
|
-
r.on("end", function() {
|
|
478
|
-
try { resolve(JSON.parse(Buffer.concat(chunks).toString("utf8"))); }
|
|
479
|
-
catch(e) { reject(e); }
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
};
|
|
483
|
-
parseJsonBody(req).then(function(body) {
|
|
484
|
-
// Find the first project context that has a MCP bridge handler
|
|
485
|
-
var handler = null;
|
|
486
|
-
projects.forEach(function(ctx) {
|
|
487
|
-
if (handler) return;
|
|
488
|
-
if (ctx.getMcpBridgeHandler) {
|
|
489
|
-
var h = ctx.getMcpBridgeHandler();
|
|
490
|
-
if (h) handler = h;
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
if (!handler) {
|
|
494
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
495
|
-
res.end('{"error":"No MCP bridge handler available"}');
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (body.action === "list_tools") {
|
|
499
|
-
handler.listTools().then(function(tools) {
|
|
500
|
-
var serverCounts = {};
|
|
501
|
-
for (var ti = 0; ti < tools.length; ti++) {
|
|
502
|
-
serverCounts[tools[ti].server] = (serverCounts[tools[ti].server] || 0) + 1;
|
|
503
|
-
}
|
|
504
|
-
console.log("[mcp-bridge-http] global list_tools:", tools.length, "tools -", Object.keys(serverCounts).map(function(s) { return s + "(" + serverCounts[s] + ")"; }).join(", ") || "(none)");
|
|
505
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
506
|
-
res.end(JSON.stringify({ tools: tools }));
|
|
507
|
-
}).catch(function(err) {
|
|
508
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
509
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
510
|
-
});
|
|
511
|
-
} else if (body.action === "call_tool") {
|
|
512
|
-
console.log("[mcp-bridge-http] global call_tool:", body.server + "/" + body.tool);
|
|
513
|
-
handler.callTool(body.server, body.tool, body.args || {}).then(function(result) {
|
|
514
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
515
|
-
res.end(JSON.stringify({ result: result }));
|
|
516
|
-
}).catch(function(err) {
|
|
517
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
518
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
519
|
-
});
|
|
520
|
-
} else {
|
|
521
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
522
|
-
res.end('{"error":"Unknown action"}');
|
|
523
|
-
}
|
|
524
|
-
}).catch(function() {
|
|
525
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
526
|
-
res.end('{"error":"Invalid JSON"}');
|
|
527
|
-
});
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
462
|
// --- Skills routes (delegated to server-skills) ---
|
|
532
463
|
if (skills.handleRequest(req, res, fullUrl)) return;
|
|
533
464
|
|
|
@@ -1090,7 +1021,11 @@ function createServer(opts) {
|
|
|
1090
1021
|
getProject: function (s) { return projects.get(s) || null; },
|
|
1091
1022
|
});
|
|
1092
1023
|
projects.set(slug, ctx);
|
|
1093
|
-
ctx.warmup()
|
|
1024
|
+
// ctx.warmup() is now deferred to the first websocket connection into
|
|
1025
|
+
// this project (see project-connection.js handleConnection). Warming
|
|
1026
|
+
// every project at startup spawned a CodexAppServer and an mcp-bridge
|
|
1027
|
+
// child for each one, which cost 30+ processes on daemons with many
|
|
1028
|
+
// projects/mates even though the user typically only opens one.
|
|
1094
1029
|
// Schedule project registry refresh for all mates when a non-mate project is added
|
|
1095
1030
|
if (!extra.isMate) scheduleRegistryRefresh();
|
|
1096
1031
|
return true;
|
|
@@ -1126,8 +1061,13 @@ function createServer(opts) {
|
|
|
1126
1061
|
var ctx = projects.get(slug);
|
|
1127
1062
|
if (!ctx) return false;
|
|
1128
1063
|
var wasMate = ctx.getStatus().isMate;
|
|
1129
|
-
ctx.destroy();
|
|
1064
|
+
var shutdownResult = ctx.destroy();
|
|
1130
1065
|
projects.delete(slug);
|
|
1066
|
+
if (shutdownResult && typeof shutdownResult.catch === "function") {
|
|
1067
|
+
shutdownResult.catch(function(err) {
|
|
1068
|
+
console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1131
1071
|
if (!wasMate) scheduleRegistryRefresh();
|
|
1132
1072
|
return true;
|
|
1133
1073
|
}
|
|
@@ -1289,12 +1229,26 @@ function createServer(opts) {
|
|
|
1289
1229
|
});
|
|
1290
1230
|
}
|
|
1291
1231
|
|
|
1232
|
+
function forEachProject(fn) {
|
|
1233
|
+
projects.forEach(function (ctx, slug) {
|
|
1234
|
+
fn(ctx, slug);
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1292
1238
|
function destroyAll() {
|
|
1239
|
+
var shutdowns = [];
|
|
1293
1240
|
projects.forEach(function (ctx, slug) {
|
|
1294
1241
|
console.log("[server] Destroying project:", slug);
|
|
1295
|
-
ctx.destroy();
|
|
1242
|
+
var result = ctx.destroy();
|
|
1243
|
+
if (result && typeof result.then === "function") {
|
|
1244
|
+
shutdowns.push(result.catch(function(err) {
|
|
1245
|
+
console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1246
|
+
return false;
|
|
1247
|
+
}));
|
|
1248
|
+
}
|
|
1296
1249
|
});
|
|
1297
1250
|
projects.clear();
|
|
1251
|
+
return Promise.all(shutdowns);
|
|
1298
1252
|
}
|
|
1299
1253
|
|
|
1300
1254
|
// --- Periodic cleanup of old chat images ---
|
|
@@ -1358,6 +1312,8 @@ function createServer(opts) {
|
|
|
1358
1312
|
setRecovery: auth.setRecovery,
|
|
1359
1313
|
clearRecovery: auth.clearRecovery,
|
|
1360
1314
|
broadcastAll: broadcastAll,
|
|
1315
|
+
forEachProject: forEachProject,
|
|
1316
|
+
destroyProject: removeProject,
|
|
1361
1317
|
destroyAll: destroyAll,
|
|
1362
1318
|
};
|
|
1363
1319
|
}
|
package/lib/sessions.js
CHANGED
|
@@ -96,6 +96,7 @@ function createSessionManager(opts) {
|
|
|
96
96
|
if (session.vendor) metaObj.vendor = session.vendor;
|
|
97
97
|
if (session.sessionVisibility) metaObj.sessionVisibility = session.sessionVisibility;
|
|
98
98
|
if (session.bookmarked) metaObj.bookmarked = true;
|
|
99
|
+
if (typeof session.favoriteOrder === "number") metaObj.favoriteOrder = session.favoriteOrder;
|
|
99
100
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
100
101
|
if (session.loop) metaObj.loop = session.loop;
|
|
101
102
|
if (session.debateState) metaObj.debateState = session.debateState;
|
|
@@ -202,6 +203,7 @@ function createSessionManager(opts) {
|
|
|
202
203
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
203
204
|
session.sessionVisibility = m.sessionVisibility || "shared";
|
|
204
205
|
session.bookmarked = !!m.bookmarked;
|
|
206
|
+
session.favoriteOrder = typeof m.favoriteOrder === "number" ? m.favoriteOrder : null;
|
|
205
207
|
sessions.set(localId, session);
|
|
206
208
|
}
|
|
207
209
|
}
|
|
@@ -241,6 +243,7 @@ function createSessionManager(opts) {
|
|
|
241
243
|
ownerId: s.ownerId || null,
|
|
242
244
|
sessionVisibility: s.sessionVisibility || "shared",
|
|
243
245
|
bookmarked: !!s.bookmarked,
|
|
246
|
+
favoriteOrder: typeof s.favoriteOrder === "number" ? s.favoriteOrder : null,
|
|
244
247
|
unread: unreadMap[s.localId] || 0,
|
|
245
248
|
vendor: s.vendor || null,
|
|
246
249
|
};
|
|
@@ -303,6 +306,7 @@ function createSessionManager(opts) {
|
|
|
303
306
|
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
304
307
|
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility) || "shared",
|
|
305
308
|
bookmarked: false,
|
|
309
|
+
favoriteOrder: null,
|
|
306
310
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
307
311
|
};
|
|
308
312
|
sessions.set(localId, session);
|
|
@@ -334,6 +338,7 @@ function createSessionManager(opts) {
|
|
|
334
338
|
ownerId: (sessionOpts && sessionOpts.ownerId) || null,
|
|
335
339
|
sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility) || "shared",
|
|
336
340
|
bookmarked: false,
|
|
341
|
+
favoriteOrder: null,
|
|
337
342
|
vendor: (sessionOpts && sessionOpts.vendor) || null,
|
|
338
343
|
};
|
|
339
344
|
sessions.set(localId, session);
|
|
@@ -525,6 +530,38 @@ function createSessionManager(opts) {
|
|
|
525
530
|
sessions.delete(localId);
|
|
526
531
|
}
|
|
527
532
|
|
|
533
|
+
function deleteSessionsBulk(localIds, targetWs) {
|
|
534
|
+
if (!Array.isArray(localIds) || localIds.length === 0) return;
|
|
535
|
+
|
|
536
|
+
var seen = {};
|
|
537
|
+
var ids = [];
|
|
538
|
+
for (var i = 0; i < localIds.length; i++) {
|
|
539
|
+
var id = localIds[i];
|
|
540
|
+
if (typeof id !== "number" || seen[id] || !sessions.has(id)) continue;
|
|
541
|
+
seen[id] = true;
|
|
542
|
+
ids.push(id);
|
|
543
|
+
}
|
|
544
|
+
if (ids.length === 0) return;
|
|
545
|
+
|
|
546
|
+
var deletedActive = false;
|
|
547
|
+
for (var j = 0; j < ids.length; j++) {
|
|
548
|
+
if (ids[j] === activeSessionId) deletedActive = true;
|
|
549
|
+
deleteSessionQuiet(ids[j]);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (sessions.size === 0) {
|
|
553
|
+
createSession(null, targetWs);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (deletedActive) {
|
|
558
|
+
var remaining = [...sessions.keys()];
|
|
559
|
+
switchSession(remaining[remaining.length - 1], targetWs);
|
|
560
|
+
} else {
|
|
561
|
+
broadcastSessionList();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
528
565
|
function doSendToSession(session, obj) {
|
|
529
566
|
// Send to active clients without recording to history/disk (ephemeral data)
|
|
530
567
|
if (sendEach) {
|
|
@@ -622,6 +659,7 @@ function createSessionManager(opts) {
|
|
|
622
659
|
history: cliHistory,
|
|
623
660
|
messageUUIDs: [],
|
|
624
661
|
bookmarked: false,
|
|
662
|
+
favoriteOrder: null,
|
|
625
663
|
};
|
|
626
664
|
sessions.set(localId, session);
|
|
627
665
|
saveSessionFile(session);
|
|
@@ -680,35 +718,82 @@ function createSessionManager(opts) {
|
|
|
680
718
|
var session = sessions.get(localId);
|
|
681
719
|
if (!session) return { hits: [], total: 0 };
|
|
682
720
|
var q = query.toLowerCase();
|
|
721
|
+
var qLen = query.length;
|
|
683
722
|
var history = session.history;
|
|
684
723
|
var hits = [];
|
|
685
|
-
|
|
686
|
-
|
|
724
|
+
|
|
725
|
+
// Assistant turns can consist of many streaming deltas (especially Codex,
|
|
726
|
+
// where agentMessage/delta fragments arrive in small chunks). We accumulate
|
|
727
|
+
// delta text per turn, scan for ALL occurrences of the query across the
|
|
728
|
+
// accumulated buffer, then map each occurrence back to the historyIndex of
|
|
729
|
+
// the delta that contains its starting offset. This catches multiple
|
|
730
|
+
// matches within a single turn and also matches that straddle delta
|
|
731
|
+
// boundaries.
|
|
732
|
+
var turnBuffer = "";
|
|
733
|
+
var turnSegments = []; // [{ start, end, historyIndex, ts }]
|
|
734
|
+
|
|
735
|
+
function pushScalarHits(text, historyIndex, role, ts) {
|
|
736
|
+
if (!text) return;
|
|
737
|
+
var lower = text.toLowerCase();
|
|
738
|
+
var from = 0;
|
|
739
|
+
while (true) {
|
|
740
|
+
var idx = lower.indexOf(q, from);
|
|
741
|
+
if (idx === -1) break;
|
|
742
|
+
var s = Math.max(0, idx - 15);
|
|
743
|
+
var e = Math.min(text.length, idx + qLen + 15);
|
|
744
|
+
var snippet = (s > 0 ? "\u2026" : "") + text.substring(s, e) + (e < text.length ? "\u2026" : "");
|
|
745
|
+
hits.push({ historyIndex: historyIndex, snippet: snippet, role: role, ts: ts });
|
|
746
|
+
from = idx + qLen;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function flushTurn() {
|
|
751
|
+
if (!turnBuffer || turnSegments.length === 0) {
|
|
752
|
+
turnBuffer = "";
|
|
753
|
+
turnSegments = [];
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
var lowerBuf = turnBuffer.toLowerCase();
|
|
757
|
+
var from = 0;
|
|
758
|
+
var segCursor = 0;
|
|
759
|
+
while (true) {
|
|
760
|
+
var idx = lowerBuf.indexOf(q, from);
|
|
761
|
+
if (idx === -1) break;
|
|
762
|
+
// Advance segCursor to the segment containing idx.
|
|
763
|
+
while (segCursor < turnSegments.length - 1 && turnSegments[segCursor].end <= idx) {
|
|
764
|
+
segCursor++;
|
|
765
|
+
}
|
|
766
|
+
var seg = turnSegments[segCursor];
|
|
767
|
+
var s = Math.max(0, idx - 15);
|
|
768
|
+
var e = Math.min(turnBuffer.length, idx + qLen + 15);
|
|
769
|
+
var snippet = (s > 0 ? "\u2026" : "") + turnBuffer.substring(s, e) + (e < turnBuffer.length ? "\u2026" : "");
|
|
770
|
+
hits.push({ historyIndex: seg.historyIndex, snippet: snippet, role: "assistant", ts: seg.ts });
|
|
771
|
+
from = idx + qLen;
|
|
772
|
+
}
|
|
773
|
+
turnBuffer = "";
|
|
774
|
+
turnSegments = [];
|
|
775
|
+
}
|
|
776
|
+
|
|
687
777
|
for (var i = 0; i < history.length; i++) {
|
|
688
778
|
var entry = history[i];
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
var lowerText = text.toLowerCase();
|
|
698
|
-
var idx = lowerText.indexOf(q);
|
|
699
|
-
if (idx === -1) continue;
|
|
700
|
-
var start = Math.max(0, idx - 15);
|
|
701
|
-
var end = Math.min(text.length, idx + query.length + 15);
|
|
702
|
-
var snippet = (start > 0 ? "\u2026" : "") + text.substring(start, end) + (end < text.length ? "\u2026" : "");
|
|
703
|
-
if (entry.type === "delta") lastAssistantHitTurn = currentTurnStart;
|
|
704
|
-
hits.push({
|
|
779
|
+
var t = entry.type;
|
|
780
|
+
if (t === "user_message" || t === "mention_user") {
|
|
781
|
+
flushTurn();
|
|
782
|
+
pushScalarHits(entry.text, i, t === "user_message" ? "user" : "assistant", entry._ts || null);
|
|
783
|
+
} else if (t === "delta" && entry.text) {
|
|
784
|
+
turnSegments.push({
|
|
785
|
+
start: turnBuffer.length,
|
|
786
|
+
end: turnBuffer.length + entry.text.length,
|
|
705
787
|
historyIndex: i,
|
|
706
|
-
snippet: snippet,
|
|
707
|
-
role: entry.type === "user_message" ? "user" : "assistant",
|
|
708
788
|
ts: entry._ts || null,
|
|
709
789
|
});
|
|
790
|
+
turnBuffer += entry.text;
|
|
791
|
+
} else if ((t === "mention_response" || t === "debate_turn_done" || t === "debate_comment_injected") && entry.text) {
|
|
792
|
+
flushTurn();
|
|
793
|
+
pushScalarHits(entry.text, i, "assistant", entry._ts || null);
|
|
710
794
|
}
|
|
711
795
|
}
|
|
796
|
+
flushTurn();
|
|
712
797
|
return { hits: hits, total: history.length };
|
|
713
798
|
}
|
|
714
799
|
|
|
@@ -796,6 +881,7 @@ function createSessionManager(opts) {
|
|
|
796
881
|
switchSession: switchSession,
|
|
797
882
|
deleteSession: deleteSession,
|
|
798
883
|
deleteSessionQuiet: deleteSessionQuiet,
|
|
884
|
+
deleteSessionsBulk: deleteSessionsBulk,
|
|
799
885
|
resumeSession: resumeSession,
|
|
800
886
|
broadcastSessionList: broadcastSessionList,
|
|
801
887
|
getTotalUnread: function (ws) {
|
|
@@ -829,10 +915,61 @@ function createSessionManager(opts) {
|
|
|
829
915
|
var session = sessions.get(localId);
|
|
830
916
|
if (!session) return { error: "Session not found" };
|
|
831
917
|
session.bookmarked = !!bookmarked;
|
|
918
|
+
if (session.bookmarked) {
|
|
919
|
+
var maxOrder = -1;
|
|
920
|
+
sessions.forEach(function (s) {
|
|
921
|
+
if (s.bookmarked && typeof s.favoriteOrder === "number" && s.favoriteOrder > maxOrder) {
|
|
922
|
+
maxOrder = s.favoriteOrder;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
session.favoriteOrder = maxOrder + 1;
|
|
926
|
+
} else {
|
|
927
|
+
session.favoriteOrder = null;
|
|
928
|
+
}
|
|
832
929
|
saveSessionFile(session);
|
|
833
930
|
broadcastSessionList();
|
|
834
931
|
return { ok: true };
|
|
835
932
|
},
|
|
933
|
+
reorderBookmarkedSessions: function (sourceId, targetId, insertBefore) {
|
|
934
|
+
var source = sessions.get(sourceId);
|
|
935
|
+
var target = sessions.get(targetId);
|
|
936
|
+
if (!source || !target) return { error: "Session not found" };
|
|
937
|
+
if (!source.bookmarked || !target.bookmarked) return { error: "Only favorites can be reordered" };
|
|
938
|
+
|
|
939
|
+
var favorites = [];
|
|
940
|
+
sessions.forEach(function (s) {
|
|
941
|
+
if (s.bookmarked) favorites.push(s);
|
|
942
|
+
});
|
|
943
|
+
favorites.sort(function (a, b) {
|
|
944
|
+
var ao = typeof a.favoriteOrder === "number" ? a.favoriteOrder : Number.MAX_SAFE_INTEGER;
|
|
945
|
+
var bo = typeof b.favoriteOrder === "number" ? b.favoriteOrder : Number.MAX_SAFE_INTEGER;
|
|
946
|
+
if (ao !== bo) return ao - bo;
|
|
947
|
+
return (b.lastActivity || 0) - (a.lastActivity || 0);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
var reordered = [];
|
|
951
|
+
for (var i = 0; i < favorites.length; i++) {
|
|
952
|
+
if (favorites[i].localId !== sourceId) reordered.push(favorites[i]);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
var targetIdx = -1;
|
|
956
|
+
for (var j = 0; j < reordered.length; j++) {
|
|
957
|
+
if (reordered[j].localId === targetId) {
|
|
958
|
+
targetIdx = j;
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (targetIdx === -1) return { error: "Target favorite not found" };
|
|
963
|
+
if (!insertBefore) targetIdx++;
|
|
964
|
+
reordered.splice(targetIdx, 0, source);
|
|
965
|
+
|
|
966
|
+
for (var k = 0; k < reordered.length; k++) {
|
|
967
|
+
reordered[k].favoriteOrder = k;
|
|
968
|
+
saveSessionFile(reordered[k]);
|
|
969
|
+
}
|
|
970
|
+
broadcastSessionList();
|
|
971
|
+
return { ok: true };
|
|
972
|
+
},
|
|
836
973
|
setSessionOwner: function (localId, ownerId) {
|
|
837
974
|
var session = sessions.get(localId);
|
|
838
975
|
if (!session) return { error: "Session not found" };
|
package/lib/updater.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const https = require("https");
|
|
2
|
-
const {
|
|
2
|
+
const { execFileSync, spawn } = require("child_process");
|
|
3
3
|
|
|
4
4
|
// ANSI helpers (mirrors cli.js)
|
|
5
5
|
var isBasicTerm = process.env.TERM_PROGRAM === "Apple_Terminal";
|
|
@@ -84,7 +84,7 @@ function isNewer(latest, current) {
|
|
|
84
84
|
function performUpdate(channel) {
|
|
85
85
|
var tag = channel === "beta" ? "beta" : "latest";
|
|
86
86
|
try {
|
|
87
|
-
|
|
87
|
+
execFileSync("npm", ["install", "-g", "clay-server@" + tag], { stdio: "pipe" });
|
|
88
88
|
return true;
|
|
89
89
|
} catch (e) {
|
|
90
90
|
return false;
|
package/lib/users.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var fs = require("fs");
|
|
2
2
|
var path = require("path");
|
|
3
3
|
var crypto = require("crypto");
|
|
4
|
-
var
|
|
4
|
+
var execFileSync = require("child_process").execFileSync;
|
|
5
5
|
var { CONFIG_DIR } = require("./config");
|
|
6
6
|
var { attachAuth } = require("./users-auth");
|
|
7
7
|
var { DEFAULT_PERMISSIONS, ALL_PERMISSIONS, attachPermissions } = require("./users-permissions");
|
|
@@ -243,7 +243,7 @@ function updateLinuxUser(userId, linuxUsername) {
|
|
|
243
243
|
|
|
244
244
|
// Validate Linux user exists
|
|
245
245
|
try {
|
|
246
|
-
|
|
246
|
+
execFileSync("id", [linuxUsername], { encoding: "utf8", timeout: 5000, stdio: "pipe" });
|
|
247
247
|
} catch (e) {
|
|
248
248
|
return { error: "Linux user '" + linuxUsername + "' does not exist" };
|
|
249
249
|
}
|
package/lib/ws-schema.js
CHANGED
|
@@ -21,6 +21,8 @@ var schema = {
|
|
|
21
21
|
"delete_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a session by ID" },
|
|
22
22
|
"rename_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Rename a session" },
|
|
23
23
|
"set_session_bookmark": { direction: "c2s", handler: "lib/project-sessions.js", description: "Bookmark or unbookmark a session in the sidebar" },
|
|
24
|
+
"reorder_session_bookmarks": { direction: "c2s", handler: "lib/project-sessions.js", description: "Reorder favorited sessions within the favorites area" },
|
|
25
|
+
"bulk_delete_sessions": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a group of sessions at once" },
|
|
24
26
|
"resume_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Resume a CLI session by its CLI session ID" },
|
|
25
27
|
"set_session_visibility": { direction: "c2s", handler: "lib/project-sessions.js", description: "Show or hide a session in the sidebar" },
|
|
26
28
|
"search_sessions": { direction: "c2s", handler: "lib/project-sessions.js", description: "Search session titles" },
|
|
@@ -454,6 +456,20 @@ var schema = {
|
|
|
454
456
|
"memory_delete": { direction: "c2s", handler: "lib/project.js", description: "Delete a memory entry by index" },
|
|
455
457
|
"memory_deleted": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Memory entry was deleted" },
|
|
456
458
|
|
|
459
|
+
// -----------------------------------------------------------------------
|
|
460
|
+
// Mate datastore
|
|
461
|
+
// -----------------------------------------------------------------------
|
|
462
|
+
"mate_db_tables": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "List schema objects in the current Mate datastore" },
|
|
463
|
+
"mate_db_describe": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Describe a table or view in the current Mate datastore" },
|
|
464
|
+
"mate_db_query": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Run read-only SQL against the current Mate datastore" },
|
|
465
|
+
"mate_db_exec": { direction: "c2s", handler: "lib/project-mate-datastore.js", description: "Run schema or write SQL against the current Mate datastore" },
|
|
466
|
+
"mate_db_tables_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Schema objects returned from a Mate datastore" },
|
|
467
|
+
"mate_db_describe_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Table description returned from a Mate datastore" },
|
|
468
|
+
"mate_db_query_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Query results from a Mate datastore" },
|
|
469
|
+
"mate_db_exec_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Execution summary from a Mate datastore" },
|
|
470
|
+
"mate_db_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Mate datastore error" },
|
|
471
|
+
"mate_db_change": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Mate datastore changed" },
|
|
472
|
+
|
|
457
473
|
// -----------------------------------------------------------------------
|
|
458
474
|
// Loop (automated task runner)
|
|
459
475
|
// -----------------------------------------------------------------------
|
|
@@ -16,6 +16,7 @@ try { require("fs").writeSync(2, "[sdk-worker] BOOT pid=" + process.pid + " uid=
|
|
|
16
16
|
var net = require("net");
|
|
17
17
|
var crypto = require("crypto");
|
|
18
18
|
var path = require("path");
|
|
19
|
+
var fs = require("fs");
|
|
19
20
|
|
|
20
21
|
var socketPath = process.argv[2];
|
|
21
22
|
if (!socketPath) {
|
|
@@ -31,6 +32,7 @@ var abortController = null;
|
|
|
31
32
|
var pendingPermissions = {}; // requestId -> resolve
|
|
32
33
|
var pendingAskUser = {}; // toolUseId -> resolve
|
|
33
34
|
var pendingElicitations = {}; // requestId -> resolve
|
|
35
|
+
var pendingMcpToolCalls = {}; // requestId -> { resolve, reject }
|
|
34
36
|
var conn = null;
|
|
35
37
|
var buffer = "";
|
|
36
38
|
|
|
@@ -81,6 +83,90 @@ function getSDK() {
|
|
|
81
83
|
return sdkModule;
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
function buildZodShape(z, inputSchema) {
|
|
87
|
+
if (!inputSchema || !inputSchema.properties) return {};
|
|
88
|
+
var shape = {};
|
|
89
|
+
var props = inputSchema.properties;
|
|
90
|
+
var required = inputSchema.required || [];
|
|
91
|
+
var keys = Object.keys(props);
|
|
92
|
+
|
|
93
|
+
for (var i = 0; i < keys.length; i++) {
|
|
94
|
+
var key = keys[i];
|
|
95
|
+
var prop = props[key];
|
|
96
|
+
var field;
|
|
97
|
+
|
|
98
|
+
if (prop.type === "number" || prop.type === "integer") {
|
|
99
|
+
field = z.number();
|
|
100
|
+
} else if (prop.type === "boolean") {
|
|
101
|
+
field = z.boolean();
|
|
102
|
+
} else if (prop.type === "array") {
|
|
103
|
+
field = z.array(z.any());
|
|
104
|
+
} else if (prop.type === "object") {
|
|
105
|
+
field = z.record(z.any());
|
|
106
|
+
} else if (prop.enum) {
|
|
107
|
+
field = z.enum(prop.enum);
|
|
108
|
+
} else {
|
|
109
|
+
field = z.string();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (prop.description) field = field.describe(prop.description);
|
|
113
|
+
if (required.indexOf(key) === -1) field = field.optional();
|
|
114
|
+
shape[key] = field;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return shape;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function createWorkerMcpToolHandler(serverName, toolName) {
|
|
121
|
+
return function(args) {
|
|
122
|
+
var requestId = crypto.randomUUID();
|
|
123
|
+
sendToDaemon({
|
|
124
|
+
type: "mcp_tool_call",
|
|
125
|
+
requestId: requestId,
|
|
126
|
+
serverName: serverName,
|
|
127
|
+
toolName: toolName,
|
|
128
|
+
args: args || {},
|
|
129
|
+
});
|
|
130
|
+
return new Promise(function(resolve, reject) {
|
|
131
|
+
pendingMcpToolCalls[requestId] = { resolve: resolve, reject: reject };
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildMcpServersFromDescriptors(descriptors, sdk) {
|
|
137
|
+
if (!descriptors || !descriptors.length) return null;
|
|
138
|
+
var z;
|
|
139
|
+
try { z = require("zod").z; } catch (e) {
|
|
140
|
+
try { z = require("zod"); } catch (e2) { return null; }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
var servers = {};
|
|
144
|
+
for (var i = 0; i < descriptors.length; i++) {
|
|
145
|
+
var descriptor = descriptors[i];
|
|
146
|
+
if (!descriptor || !descriptor.serverName || !descriptor.tools || !descriptor.tools.length) continue;
|
|
147
|
+
var tools = [];
|
|
148
|
+
for (var j = 0; j < descriptor.tools.length; j++) {
|
|
149
|
+
var toolDescriptor = descriptor.tools[j];
|
|
150
|
+
if (!toolDescriptor || !toolDescriptor.name) continue;
|
|
151
|
+
tools.push(sdk.tool(
|
|
152
|
+
toolDescriptor.name,
|
|
153
|
+
toolDescriptor.description || toolDescriptor.name,
|
|
154
|
+
buildZodShape(z, toolDescriptor.inputSchema),
|
|
155
|
+
createWorkerMcpToolHandler(descriptor.serverName, toolDescriptor.name)
|
|
156
|
+
));
|
|
157
|
+
}
|
|
158
|
+
if (tools.length > 0) {
|
|
159
|
+
servers[descriptor.serverName] = sdk.createSdkMcpServer({
|
|
160
|
+
name: descriptor.serverName,
|
|
161
|
+
version: "1.0.0",
|
|
162
|
+
tools: tools,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return Object.keys(servers).length > 0 ? servers : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
84
170
|
// --- IPC helpers ---
|
|
85
171
|
function sendToDaemon(msg) {
|
|
86
172
|
if (!conn || conn.destroyed) return;
|
|
@@ -127,6 +213,9 @@ function handleMessage(msg) {
|
|
|
127
213
|
case "elicitation_response":
|
|
128
214
|
handleElicitationResponse(msg);
|
|
129
215
|
break;
|
|
216
|
+
case "mcp_tool_result":
|
|
217
|
+
handleMcpToolResult(msg);
|
|
218
|
+
break;
|
|
130
219
|
case "warmup":
|
|
131
220
|
handleWarmup(msg);
|
|
132
221
|
break;
|
|
@@ -208,6 +297,17 @@ function handleElicitationResponse(msg) {
|
|
|
208
297
|
}
|
|
209
298
|
}
|
|
210
299
|
|
|
300
|
+
function handleMcpToolResult(msg) {
|
|
301
|
+
var pending = pendingMcpToolCalls[msg.requestId];
|
|
302
|
+
if (!pending) return;
|
|
303
|
+
delete pendingMcpToolCalls[msg.requestId];
|
|
304
|
+
if (msg.error) {
|
|
305
|
+
pending.reject(new Error(msg.error));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
pending.resolve(msg.result);
|
|
309
|
+
}
|
|
310
|
+
|
|
211
311
|
// --- Query handling ---
|
|
212
312
|
async function handleQueryStart(msg) {
|
|
213
313
|
var t0 = msg._perfT0 || Date.now();
|
|
@@ -238,6 +338,15 @@ async function handleQueryStart(msg) {
|
|
|
238
338
|
options.abortController = abortController;
|
|
239
339
|
options.debug = true;
|
|
240
340
|
options.debugFile = "/tmp/clay-cli-debug-" + process.pid + ".log";
|
|
341
|
+
if (options.mcpServerDescriptors && options.mcpServerDescriptors.length) {
|
|
342
|
+
try {
|
|
343
|
+
var mcpServers = buildMcpServersFromDescriptors(options.mcpServerDescriptors, sdk);
|
|
344
|
+
if (mcpServers) options.mcpServers = mcpServers;
|
|
345
|
+
} catch (e) {
|
|
346
|
+
console.error("[sdk-worker] Failed to build MCP servers:", e.message || e);
|
|
347
|
+
}
|
|
348
|
+
delete options.mcpServerDescriptors;
|
|
349
|
+
}
|
|
241
350
|
// Override CLI subprocess spawn to inject NODE_OPTIONS for IPv4-first DNS.
|
|
242
351
|
// The SDK constructs its own env for the CLI process, so worker env vars
|
|
243
352
|
// like NODE_OPTIONS are not inherited. We intercept the spawn to fix this.
|
|
@@ -247,8 +356,11 @@ async function handleQueryStart(msg) {
|
|
|
247
356
|
// This is needed because the CLI's Axios-based HTTP client ignores
|
|
248
357
|
// NODE_OPTIONS dns flags and still attempts IPv6 connections via its
|
|
249
358
|
// custom TLS agent, causing 5-10s timeouts on IPv6-less servers.
|
|
250
|
-
var preloadScript =
|
|
251
|
-
var extraOpts = "
|
|
359
|
+
var preloadScript = path.join(__dirname, "..", "..", "ipv4-only.js");
|
|
360
|
+
var extraOpts = "";
|
|
361
|
+
if (fs.existsSync(preloadScript)) {
|
|
362
|
+
extraOpts += " --require " + JSON.stringify(preloadScript);
|
|
363
|
+
}
|
|
252
364
|
extraOpts += " --dns-result-order=ipv4first --no-network-family-autoselection";
|
|
253
365
|
spawnOpts.env.NODE_OPTIONS = (spawnOpts.env.NODE_OPTIONS || "") + extraOpts;
|
|
254
366
|
console.log("[sdk-worker] spawnClaudeCodeProcess called, command=" + spawnOpts.command);
|