clay-server 2.31.0 → 2.32.0-beta.2
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/browser-mcp-server.js +32 -44
- package/lib/debate-mcp-server.js +14 -31
- package/lib/mcp-local.js +31 -1
- package/lib/project-connection.js +4 -2
- package/lib/project-filesystem.js +47 -1
- package/lib/project-http.js +75 -8
- package/lib/project-mcp.js +4 -0
- package/lib/project-sessions.js +88 -51
- package/lib/project-user-message.js +12 -7
- package/lib/project.js +204 -90
- package/lib/public/app.js +123 -448
- package/lib/public/codex-avatar.png +0 -0
- package/lib/public/css/debate.css +3 -2
- package/lib/public/css/filebrowser.css +91 -1
- package/lib/public/css/icon-strip.css +21 -5
- package/lib/public/css/input.css +181 -100
- package/lib/public/css/mates.css +43 -0
- package/lib/public/css/mention.css +48 -4
- package/lib/public/css/menus.css +1 -1
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/notifications-center.css +19 -0
- package/lib/public/index.html +46 -24
- package/lib/public/modules/app-connection.js +138 -37
- package/lib/public/modules/app-cursors.js +18 -17
- package/lib/public/modules/app-debate-ui.js +9 -9
- package/lib/public/modules/app-dm.js +170 -131
- package/lib/public/modules/app-favicon.js +28 -26
- package/lib/public/modules/app-header.js +79 -68
- package/lib/public/modules/app-home-hub.js +55 -47
- package/lib/public/modules/app-loop-ui.js +34 -18
- package/lib/public/modules/app-loop-wizard.js +6 -6
- package/lib/public/modules/app-messages.js +195 -152
- package/lib/public/modules/app-misc.js +23 -12
- package/lib/public/modules/app-notifications.js +97 -3
- package/lib/public/modules/app-panels.js +203 -49
- package/lib/public/modules/app-projects.js +159 -150
- package/lib/public/modules/app-rate-limit.js +5 -4
- package/lib/public/modules/app-rendering.js +149 -101
- package/lib/public/modules/app-skills-install.js +4 -4
- package/lib/public/modules/context-sources.js +12 -41
- package/lib/public/modules/dom-refs.js +21 -0
- package/lib/public/modules/filebrowser.js +173 -2
- package/lib/public/modules/input.js +86 -0
- package/lib/public/modules/mate-sidebar.js +38 -0
- package/lib/public/modules/mention.js +24 -6
- package/lib/public/modules/scheduler.js +1 -1
- package/lib/public/modules/sidebar-mates.js +66 -34
- package/lib/public/modules/sidebar-mobile.js +34 -30
- package/lib/public/modules/sidebar-projects.js +60 -57
- package/lib/public/modules/sidebar-sessions.js +75 -69
- package/lib/public/modules/sidebar.js +12 -20
- package/lib/public/modules/skills.js +8 -9
- package/lib/public/modules/sticky-notes.js +1 -2
- package/lib/public/modules/store.js +9 -2
- package/lib/public/modules/stt.js +4 -1
- package/lib/public/modules/tools.js +14 -9
- package/lib/sdk-bridge.js +511 -1113
- package/lib/sdk-message-processor.js +123 -134
- package/lib/sdk-worker.js +4 -0
- package/lib/server-dm.js +1 -0
- package/lib/server.js +86 -1
- package/lib/sessions.js +47 -36
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker.js +559 -0
- package/lib/yoke/adapters/claude.js +1418 -0
- package/lib/yoke/adapters/codex.js +968 -0
- package/lib/yoke/adapters/gemini.js +668 -0
- package/lib/yoke/codex-app-server.js +307 -0
- package/lib/yoke/index.js +199 -0
- package/lib/yoke/instructions.js +62 -0
- package/lib/yoke/interface.js +92 -0
- package/lib/yoke/mcp-bridge-server.js +294 -0
- package/lib/yoke/package.json +7 -0
- package/package.json +3 -1
package/lib/project-sessions.js
CHANGED
|
@@ -13,7 +13,7 @@ var { execFileSync } = require("child_process");
|
|
|
13
13
|
* opts, usersModule, userPresence, matesModule, pushModule,
|
|
14
14
|
* getSessionForWs, getLinuxUserForSession, getOsUserInfoForWs,
|
|
15
15
|
* hydrateImageRefs, onProcessingChanged, broadcastPresence,
|
|
16
|
-
*
|
|
16
|
+
* adapter, getProjectList, getProjectCount, getScheduleCount,
|
|
17
17
|
* moveScheduleToProject, moveAllSchedulesToProject, getHubSchedules,
|
|
18
18
|
* fetchVersion, isNewer, onCreateWorktree, IGNORED_DIRS,
|
|
19
19
|
* scheduleMessage, cancelScheduledMessage,
|
|
@@ -46,7 +46,7 @@ function attachSessions(ctx) {
|
|
|
46
46
|
var hydrateImageRefs = ctx.hydrateImageRefs;
|
|
47
47
|
var onProcessingChanged = ctx.onProcessingChanged;
|
|
48
48
|
var broadcastPresence = ctx.broadcastPresence;
|
|
49
|
-
var
|
|
49
|
+
var adapter = ctx.adapter;
|
|
50
50
|
var getProjectList = ctx.getProjectList;
|
|
51
51
|
var getProjectCount = ctx.getProjectCount;
|
|
52
52
|
var getScheduleCount = ctx.getScheduleCount;
|
|
@@ -96,6 +96,7 @@ function attachSessions(ctx) {
|
|
|
96
96
|
var sessionOpts = {};
|
|
97
97
|
if (ws._clayUser && usersModule.isMultiUser()) sessionOpts.ownerId = ws._clayUser.id;
|
|
98
98
|
if (msg.sessionVisibility) sessionOpts.sessionVisibility = msg.sessionVisibility;
|
|
99
|
+
if (msg.vendor) sessionOpts.vendor = msg.vendor;
|
|
99
100
|
var newSess = sm.createSession(sessionOpts, ws);
|
|
100
101
|
ws._clayActiveSession = newSess.localId;
|
|
101
102
|
// Apply project-level email defaults to new session
|
|
@@ -153,9 +154,7 @@ function attachSessions(ctx) {
|
|
|
153
154
|
if (!msg.cliSessionId) return true;
|
|
154
155
|
var cliSess = require("./cli-sessions");
|
|
155
156
|
// Try SDK for title first, then fall back to manual parsing
|
|
156
|
-
var titlePromise =
|
|
157
|
-
return sdkMod.getSessionInfo(msg.cliSessionId, { dir: cwd });
|
|
158
|
-
}).then(function(info) {
|
|
157
|
+
var titlePromise = adapter.getSessionInfo(msg.cliSessionId, { dir: cwd }).then(function(info) {
|
|
159
158
|
return (info && info.summary) ? info.summary.substring(0, 100) : null;
|
|
160
159
|
}).catch(function() { return null; });
|
|
161
160
|
|
|
@@ -200,9 +199,7 @@ function attachSessions(ctx) {
|
|
|
200
199
|
}
|
|
201
200
|
} catch (e) {}
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
return sdkMod.listSessions({ dir: cwd });
|
|
205
|
-
}).then(function(sdkSessions) {
|
|
202
|
+
adapter.listSessions({ dir: cwd }).then(function(sdkSessions) {
|
|
206
203
|
var filtered = sdkSessions.filter(function(s) {
|
|
207
204
|
return !relayIds[s.sessionId];
|
|
208
205
|
}).map(function(s) {
|
|
@@ -287,11 +284,9 @@ function attachSessions(ctx) {
|
|
|
287
284
|
sm.broadcastSessionList();
|
|
288
285
|
// Sync title to SDK session
|
|
289
286
|
if (s.cliSessionId) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
});
|
|
294
|
-
}).catch(function() {});
|
|
287
|
+
adapter.renameSession(s.cliSessionId, s.title, { dir: cwd }).catch(function(e) {
|
|
288
|
+
console.error("[project] SDK renameSession failed:", e.message);
|
|
289
|
+
});
|
|
295
290
|
}
|
|
296
291
|
}
|
|
297
292
|
return true;
|
|
@@ -381,8 +376,9 @@ function attachSessions(ctx) {
|
|
|
381
376
|
|
|
382
377
|
if (msg.type === "stop") {
|
|
383
378
|
var session = getSessionForWs(ws);
|
|
384
|
-
if (session && session.
|
|
385
|
-
session.
|
|
379
|
+
if (session && session.isProcessing) {
|
|
380
|
+
session.taskStopRequested = true;
|
|
381
|
+
if (session.abortController) session.abortController.abort();
|
|
386
382
|
}
|
|
387
383
|
return true;
|
|
388
384
|
}
|
|
@@ -521,32 +517,34 @@ function attachSessions(ctx) {
|
|
|
521
517
|
return true;
|
|
522
518
|
}
|
|
523
519
|
|
|
520
|
+
// Codex-specific settings (stored on sessionManager, passed to adapter via adapterOptions)
|
|
521
|
+
if (msg.type === "set_codex_approval") {
|
|
522
|
+
sm.codexApproval = msg.approval || "on-failure";
|
|
523
|
+
send({ type: "codex_config", approval: sm.codexApproval, sandbox: sm.codexSandbox || "workspace-write", webSearch: sm.codexWebSearch || "disabled" });
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
if (msg.type === "set_codex_sandbox") {
|
|
527
|
+
sm.codexSandbox = msg.sandbox || "workspace-write";
|
|
528
|
+
send({ type: "codex_config", approval: sm.codexApproval || "on-failure", sandbox: sm.codexSandbox, webSearch: sm.codexWebSearch || "disabled" });
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
if (msg.type === "set_codex_websearch") {
|
|
532
|
+
sm.codexWebSearch = msg.webSearch || "disabled";
|
|
533
|
+
send({ type: "codex_config", approval: sm.codexApproval || "on-failure", sandbox: sm.codexSandbox || "workspace-write", webSearch: sm.codexWebSearch });
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
|
|
524
537
|
if (msg.type === "rewind_preview") {
|
|
525
538
|
var session = getSessionForWs(ws);
|
|
526
539
|
if (!session || !session.cliSessionId || !msg.uuid) return true;
|
|
527
|
-
// Reject preview requests while a rewind is executing
|
|
528
540
|
if (session._rewindInProgress) return true;
|
|
529
541
|
|
|
530
542
|
(async function () {
|
|
531
|
-
var result;
|
|
532
543
|
try {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
var diffs = {};
|
|
536
|
-
var changedFiles = preview.filesChanged || [];
|
|
537
|
-
for (var f = 0; f < changedFiles.length; f++) {
|
|
538
|
-
try {
|
|
539
|
-
diffs[changedFiles[f]] = execFileSync(
|
|
540
|
-
"git", ["diff", "HEAD", "--", changedFiles[f]],
|
|
541
|
-
{ cwd: cwd, encoding: "utf8", timeout: 5000 }
|
|
542
|
-
) || "";
|
|
543
|
-
} catch (e) { diffs[changedFiles[f]] = ""; }
|
|
544
|
-
}
|
|
545
|
-
sendTo(ws, { type: "rewind_preview_result", preview: preview, diffs: diffs, uuid: msg.uuid });
|
|
544
|
+
var r = await sdk.rewindPreview(session, msg.uuid);
|
|
545
|
+
sendTo(ws, { type: "rewind_preview_result", preview: r.preview, diffs: r.diffs, uuid: msg.uuid, chatOnly: r.chatOnly || false });
|
|
546
546
|
} catch (err) {
|
|
547
547
|
sendTo(ws, { type: "rewind_error", text: "Failed to preview rewind: " + err.message });
|
|
548
|
-
} finally {
|
|
549
|
-
if (result && result.isTemp) result.cleanup();
|
|
550
548
|
}
|
|
551
549
|
})();
|
|
552
550
|
return true;
|
|
@@ -564,12 +562,10 @@ function attachSessions(ctx) {
|
|
|
564
562
|
var mode = msg.mode || "both";
|
|
565
563
|
|
|
566
564
|
(async function () {
|
|
567
|
-
var result;
|
|
568
565
|
try {
|
|
569
|
-
// File restoration (
|
|
566
|
+
// File restoration (delegated to adapter via sdk-bridge)
|
|
570
567
|
if (mode !== "chat") {
|
|
571
|
-
|
|
572
|
-
await result.query.rewindFiles(msg.uuid, { dryRun: false });
|
|
568
|
+
await sdk.rewindExecuteFiles(session, msg.uuid);
|
|
573
569
|
}
|
|
574
570
|
|
|
575
571
|
// Conversation rollback (skip for files-only mode)
|
|
@@ -582,6 +578,14 @@ function attachSessions(ctx) {
|
|
|
582
578
|
}
|
|
583
579
|
}
|
|
584
580
|
|
|
581
|
+
// Count turns to roll back BEFORE trimming local history
|
|
582
|
+
var turnsToRollBack = 0;
|
|
583
|
+
if (targetIdx >= 0) {
|
|
584
|
+
for (var ri = targetIdx; ri < session.messageUUIDs.length; ri++) {
|
|
585
|
+
if (session.messageUUIDs[ri].type === "user") turnsToRollBack++;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
585
589
|
if (targetIdx >= 0) {
|
|
586
590
|
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
587
591
|
for (var k = trimTo - 1; k >= 0; k--) {
|
|
@@ -594,6 +598,15 @@ function attachSessions(ctx) {
|
|
|
594
598
|
session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
|
|
595
599
|
}
|
|
596
600
|
|
|
601
|
+
// Notify adapter of conversation rollback (e.g. Codex thread/rollback)
|
|
602
|
+
if (turnsToRollBack > 0) {
|
|
603
|
+
try {
|
|
604
|
+
await sdk.rollbackConversation(session, turnsToRollBack);
|
|
605
|
+
} catch (rbErr) {
|
|
606
|
+
console.error("[project-sessions] conversation rollback failed:", rbErr.message || rbErr);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
597
610
|
var kept = session.messageUUIDs;
|
|
598
611
|
session.lastRewindUuid = kept.length > 0 ? kept[kept.length - 1].uuid : null;
|
|
599
612
|
}
|
|
@@ -622,7 +635,6 @@ function attachSessions(ctx) {
|
|
|
622
635
|
sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + err.message });
|
|
623
636
|
} finally {
|
|
624
637
|
session._rewindInProgress = false;
|
|
625
|
-
if (result && result.isTemp) result.cleanup();
|
|
626
638
|
}
|
|
627
639
|
})();
|
|
628
640
|
return true;
|
|
@@ -634,22 +646,47 @@ function attachSessions(ctx) {
|
|
|
634
646
|
sendTo(ws, { type: "error", text: "Cannot fork: no CLI session" });
|
|
635
647
|
return true;
|
|
636
648
|
}
|
|
637
|
-
var forkCliId = session.cliSessionId;
|
|
638
649
|
var forkTitle = (session.title || "New Session") + " (fork)";
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
return cliSess.readCliSessionHistory(cwd, result.sessionId).then(function(history) {
|
|
647
|
-
var forked = sm.resumeSession(result.sessionId, { history: history, title: forkTitle }, ws);
|
|
648
|
-
if (forked) {
|
|
649
|
-
ws._clayActiveSession = forked.localId;
|
|
650
|
-
sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
|
|
650
|
+
|
|
651
|
+
sdk.forkSession(session, msg.uuid).then(function(result) {
|
|
652
|
+
if (result.useLocalHistory) {
|
|
653
|
+
// Copy local history up to the target UUID
|
|
654
|
+
var targetIdx = -1;
|
|
655
|
+
for (var fi = 0; fi < session.messageUUIDs.length; fi++) {
|
|
656
|
+
if (session.messageUUIDs[fi].uuid === msg.uuid) { targetIdx = fi; break; }
|
|
651
657
|
}
|
|
652
|
-
|
|
658
|
+
var forkHistory = [];
|
|
659
|
+
if (targetIdx >= 0) {
|
|
660
|
+
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
661
|
+
forkHistory = session.history.slice(0, trimTo);
|
|
662
|
+
} else {
|
|
663
|
+
forkHistory = session.history.slice();
|
|
664
|
+
}
|
|
665
|
+
var forked = sm.createSession({ vendor: session.vendor, ownerId: session.ownerId || null }, ws);
|
|
666
|
+
forked.cliSessionId = result.sessionId;
|
|
667
|
+
forked.title = forkTitle;
|
|
668
|
+
forked.history = forkHistory;
|
|
669
|
+
forked.messageUUIDs = [];
|
|
670
|
+
for (var hi = 0; hi < forkHistory.length; hi++) {
|
|
671
|
+
if (forkHistory[hi].type === "message_uuid") {
|
|
672
|
+
forked.messageUUIDs.push({ uuid: forkHistory[hi].uuid, type: forkHistory[hi].messageType, historyIndex: hi });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
sm.saveSessionFile(forked);
|
|
676
|
+
sm.switchSession(forked.localId, ws, hydrateImageRefs);
|
|
677
|
+
sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
|
|
678
|
+
sm.broadcastSessionList();
|
|
679
|
+
} else {
|
|
680
|
+
// Read history from CLI session files
|
|
681
|
+
var cliSess = require("./cli-sessions");
|
|
682
|
+
return cliSess.readCliSessionHistory(cwd, result.sessionId).then(function(history) {
|
|
683
|
+
var forked = sm.resumeSession(result.sessionId, { history: history, title: forkTitle }, ws);
|
|
684
|
+
if (forked) {
|
|
685
|
+
ws._clayActiveSession = forked.localId;
|
|
686
|
+
sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
653
690
|
}).catch(function(e) {
|
|
654
691
|
sendTo(ws, { type: "error", text: "Fork failed: " + (e.message || e) });
|
|
655
692
|
});
|
|
@@ -23,7 +23,7 @@ var fs = require("fs");
|
|
|
23
23
|
* scheduleMessage, cancelScheduledMessage,
|
|
24
24
|
* loadContextSources, saveContextSources,
|
|
25
25
|
* digestDmTurn, gateMemory,
|
|
26
|
-
*
|
|
26
|
+
* adapter - YOKE adapter instance
|
|
27
27
|
*/
|
|
28
28
|
function attachUserMessage(ctx) {
|
|
29
29
|
var cwd = ctx.cwd;
|
|
@@ -69,7 +69,7 @@ function attachUserMessage(ctx) {
|
|
|
69
69
|
var loadContextSources = ctx.loadContextSources;
|
|
70
70
|
var saveContextSources = ctx.saveContextSources;
|
|
71
71
|
|
|
72
|
-
var
|
|
72
|
+
var adapter = ctx.adapter;
|
|
73
73
|
var _email = ctx._email;
|
|
74
74
|
|
|
75
75
|
// --------------- Sticky notes ---------------
|
|
@@ -292,6 +292,13 @@ function attachUserMessage(ctx) {
|
|
|
292
292
|
var session = getSessionForWs(ws);
|
|
293
293
|
if (!session) return true;
|
|
294
294
|
|
|
295
|
+
// Bind vendor to session on first message (if not already set)
|
|
296
|
+
if (!session.vendor && msg.vendor) {
|
|
297
|
+
session.vendor = msg.vendor;
|
|
298
|
+
sm.saveSessionFile(session);
|
|
299
|
+
sm.broadcastSessionList();
|
|
300
|
+
}
|
|
301
|
+
|
|
295
302
|
// Backfill ownerId for legacy sessions restored without one (multi-user only)
|
|
296
303
|
if (!session.ownerId && ws._clayUser && usersModule.isMultiUser()) {
|
|
297
304
|
session.ownerId = ws._clayUser.id;
|
|
@@ -337,11 +344,9 @@ function attachUserMessage(ctx) {
|
|
|
337
344
|
sm.broadcastSessionList();
|
|
338
345
|
// Sync auto-title to SDK
|
|
339
346
|
if (session.cliSessionId) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
}).catch(function() {});
|
|
347
|
+
adapter.renameSession(session.cliSessionId, session.title, { dir: cwd }).catch(function(e) {
|
|
348
|
+
console.error("[project] SDK renameSession failed:", e.message);
|
|
349
|
+
});
|
|
345
350
|
}
|
|
346
351
|
}
|
|
347
352
|
|
package/lib/project.js
CHANGED
|
@@ -84,12 +84,8 @@ function validateEnvString(str) {
|
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
//
|
|
88
|
-
var
|
|
89
|
-
function getSDK() {
|
|
90
|
-
if (!sdkModule) sdkModule = import("@anthropic-ai/claude-agent-sdk");
|
|
91
|
-
return sdkModule;
|
|
92
|
-
}
|
|
87
|
+
// YOKE adapter (replaces direct SDK access)
|
|
88
|
+
var yoke = require("./yoke");
|
|
93
89
|
|
|
94
90
|
// --- Shared constants ---
|
|
95
91
|
var IGNORED_DIRS = new Set(["node_modules", ".git", ".next", "__pycache__", ".cache", "dist", "build", ".clay", ".claude-relay"]);
|
|
@@ -161,8 +157,15 @@ function createProjectContext(opts) {
|
|
|
161
157
|
var onCreateWorktree = opts.onCreateWorktree || null;
|
|
162
158
|
var serverPort = opts.port || 2633;
|
|
163
159
|
var serverTls = opts.tls || false;
|
|
160
|
+
var serverAuthToken = opts.authToken || null;
|
|
164
161
|
var latestVersion = null;
|
|
165
162
|
|
|
163
|
+
// --- YOKE adapters (multi-vendor, lazy init) ---
|
|
164
|
+
var _yokeState = yoke.createAdapters({ cwd: cwd });
|
|
165
|
+
var adapters = _yokeState.adapters;
|
|
166
|
+
var defaultVendor = adapters.claude ? "claude" : Object.keys(adapters)[0] || "claude";
|
|
167
|
+
var adapter = adapters[defaultVendor] || null;
|
|
168
|
+
|
|
166
169
|
// Browser MCP server runs in-process via createSdkMcpServer (no child process spawn).
|
|
167
170
|
// Do NOT write to .claude-local/settings.json -- the SDK reads that too, causing duplicate spawns.
|
|
168
171
|
|
|
@@ -398,6 +401,9 @@ function createProjectContext(opts) {
|
|
|
398
401
|
},
|
|
399
402
|
onSessionDone: onSessionDone,
|
|
400
403
|
});
|
|
404
|
+
sm.availableVendors = Object.keys(adapters);
|
|
405
|
+
sm.defaultVendor = defaultVendor;
|
|
406
|
+
|
|
401
407
|
var _projMode = typeof opts.onGetProjectDefaultMode === "function" ? opts.onGetProjectDefaultMode(slug) : null;
|
|
402
408
|
var _srvMode = typeof opts.onGetServerDefaultMode === "function" ? opts.onGetServerDefaultMode() : null;
|
|
403
409
|
sm._savedDefaultMode = (_projMode && _projMode.mode) || (_srvMode && _srvMode.mode) || "default";
|
|
@@ -451,6 +457,89 @@ function createProjectContext(opts) {
|
|
|
451
457
|
},
|
|
452
458
|
});
|
|
453
459
|
|
|
460
|
+
// --- MCP tool servers (created via YOKE adapter) ---
|
|
461
|
+
var mcpServers = (function () {
|
|
462
|
+
var servers = {};
|
|
463
|
+
|
|
464
|
+
// Debate MCP server (available to both mates and main project)
|
|
465
|
+
try {
|
|
466
|
+
var debateMcp = require("./debate-mcp-server");
|
|
467
|
+
var debateToolDefs = debateMcp.getToolDefs(function onPropose(briefData) {
|
|
468
|
+
return new Promise(function (resolve) {
|
|
469
|
+
var proposalId = "dp_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
|
|
470
|
+
briefData.proposalId = proposalId;
|
|
471
|
+
_pendingDebateProposals[proposalId] = {
|
|
472
|
+
resolve: resolve,
|
|
473
|
+
briefData: briefData,
|
|
474
|
+
};
|
|
475
|
+
// The SDK sends tool_executing with briefData as input.
|
|
476
|
+
// Client renders the debate brief card when it sees propose_debate.
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
var debateMcpConfig = adapter.createToolServer({ name: "clay-debate", version: "1.0.0", tools: debateToolDefs });
|
|
480
|
+
if (debateMcpConfig) servers[debateMcpConfig.name || "clay-debate"] = debateMcpConfig;
|
|
481
|
+
} catch (e) {
|
|
482
|
+
console.error("[project] Failed to create debate MCP server:", e.message);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Browser MCP server (main project only, not mates)
|
|
486
|
+
if (!isMate) {
|
|
487
|
+
try {
|
|
488
|
+
var browserMcp = require("./browser-mcp-server");
|
|
489
|
+
var browserToolDefs = browserMcp.getToolDefs(sendExtensionCommandAny, function () {
|
|
490
|
+
return Object.values(browserState._browserTabList || {});
|
|
491
|
+
}, {
|
|
492
|
+
watchTab: function (tabId) {
|
|
493
|
+
var key = "tab:" + tabId;
|
|
494
|
+
// Apply to all connected clients' active sessions
|
|
495
|
+
for (var c of clients) {
|
|
496
|
+
if (c.readyState !== 1) continue;
|
|
497
|
+
var sid = c._clayActiveSession || null;
|
|
498
|
+
var active = loadContextSources(slug, sid);
|
|
499
|
+
if (active.indexOf(key) === -1) {
|
|
500
|
+
active.push(key);
|
|
501
|
+
saveContextSources(slug, sid, active);
|
|
502
|
+
c.send(JSON.stringify({ type: "context_sources_state", active: active }));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return [];
|
|
506
|
+
},
|
|
507
|
+
unwatchTab: function (tabId) {
|
|
508
|
+
var key = "tab:" + tabId;
|
|
509
|
+
for (var c of clients) {
|
|
510
|
+
if (c.readyState !== 1) continue;
|
|
511
|
+
var sid = c._clayActiveSession || null;
|
|
512
|
+
var active = loadContextSources(slug, sid);
|
|
513
|
+
var idx = active.indexOf(key);
|
|
514
|
+
if (idx !== -1) {
|
|
515
|
+
active.splice(idx, 1);
|
|
516
|
+
saveContextSources(slug, sid, active);
|
|
517
|
+
c.send(JSON.stringify({ type: "context_sources_state", active: active }));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return active;
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
var mcpConfig = adapter.createToolServer({ name: "clay-browser", version: "1.0.0", tools: browserToolDefs });
|
|
524
|
+
if (mcpConfig) servers[mcpConfig.name || "clay-browser"] = mcpConfig;
|
|
525
|
+
} catch (e) {
|
|
526
|
+
console.error("[project] Failed to create browser MCP server:", e.message);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Email MCP server (available to both mates and main project)
|
|
531
|
+
// Note: email-mcp-server still uses the legacy create() pattern (not yet converted to getToolDefs).
|
|
532
|
+
try {
|
|
533
|
+
var emailMcp = require("./email-mcp-server");
|
|
534
|
+
var emailMcpConfig = emailMcp.create(_email.createMcpDeps());
|
|
535
|
+
if (emailMcpConfig) servers[emailMcpConfig.name || "clay-email"] = emailMcpConfig;
|
|
536
|
+
} catch (e) {
|
|
537
|
+
console.error("[project] Failed to create email MCP server:", e.message);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return Object.keys(servers).length > 0 ? servers : undefined;
|
|
541
|
+
})();
|
|
542
|
+
|
|
454
543
|
// --- SDK bridge ---
|
|
455
544
|
var sdk = createSDKBridge({
|
|
456
545
|
cwd: cwd,
|
|
@@ -458,92 +547,17 @@ function createProjectContext(opts) {
|
|
|
458
547
|
sessionManager: sm,
|
|
459
548
|
send: send,
|
|
460
549
|
pushModule: pushModule,
|
|
550
|
+
adapter: adapter,
|
|
551
|
+
adapters: adapters,
|
|
461
552
|
getNotificationsModule: function () { return _notifications; },
|
|
462
|
-
getSDK: getSDK,
|
|
463
553
|
mateDisplayName: opts.mateDisplayName || "",
|
|
464
554
|
isMate: isMate,
|
|
465
555
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
466
|
-
mcpServers:
|
|
467
|
-
var servers = {};
|
|
468
|
-
|
|
469
|
-
// Debate MCP server (available to both mates and main project)
|
|
470
|
-
try {
|
|
471
|
-
var debateMcp = require("./debate-mcp-server");
|
|
472
|
-
var debateMcpConfig = debateMcp.create(function onPropose(briefData) {
|
|
473
|
-
return new Promise(function (resolve) {
|
|
474
|
-
var proposalId = "dp_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
|
|
475
|
-
briefData.proposalId = proposalId;
|
|
476
|
-
_pendingDebateProposals[proposalId] = {
|
|
477
|
-
resolve: resolve,
|
|
478
|
-
briefData: briefData,
|
|
479
|
-
};
|
|
480
|
-
// The SDK sends tool_executing with briefData as input.
|
|
481
|
-
// Client renders the debate brief card when it sees propose_debate.
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
if (debateMcpConfig) servers[debateMcpConfig.name || "clay-debate"] = debateMcpConfig;
|
|
485
|
-
} catch (e) {
|
|
486
|
-
console.error("[project] Failed to create debate MCP server:", e.message);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Browser MCP server (main project only, not mates)
|
|
490
|
-
if (!isMate) {
|
|
491
|
-
try {
|
|
492
|
-
var browserMcp = require("./browser-mcp-server");
|
|
493
|
-
var mcpConfig = browserMcp.create(sendExtensionCommandAny, function () {
|
|
494
|
-
return Object.values(browserState._browserTabList || {});
|
|
495
|
-
}, {
|
|
496
|
-
watchTab: function (tabId) {
|
|
497
|
-
var key = "tab:" + tabId;
|
|
498
|
-
// Apply to all connected clients' active sessions
|
|
499
|
-
for (var c of clients) {
|
|
500
|
-
if (c.readyState !== 1) continue;
|
|
501
|
-
var sid = c._clayActiveSession || null;
|
|
502
|
-
var active = loadContextSources(slug, sid);
|
|
503
|
-
if (active.indexOf(key) === -1) {
|
|
504
|
-
active.push(key);
|
|
505
|
-
saveContextSources(slug, sid, active);
|
|
506
|
-
c.send(JSON.stringify({ type: "context_sources_state", active: active }));
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
return [];
|
|
510
|
-
},
|
|
511
|
-
unwatchTab: function (tabId) {
|
|
512
|
-
var key = "tab:" + tabId;
|
|
513
|
-
for (var c of clients) {
|
|
514
|
-
if (c.readyState !== 1) continue;
|
|
515
|
-
var sid = c._clayActiveSession || null;
|
|
516
|
-
var active = loadContextSources(slug, sid);
|
|
517
|
-
var idx = active.indexOf(key);
|
|
518
|
-
if (idx !== -1) {
|
|
519
|
-
active.splice(idx, 1);
|
|
520
|
-
saveContextSources(slug, sid, active);
|
|
521
|
-
c.send(JSON.stringify({ type: "context_sources_state", active: active }));
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
return active;
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
if (mcpConfig) servers[mcpConfig.name || "clay-browser"] = mcpConfig;
|
|
528
|
-
} catch (e) {
|
|
529
|
-
console.error("[project] Failed to create browser MCP server:", e.message);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Email MCP server (available to both mates and main project)
|
|
534
|
-
try {
|
|
535
|
-
var emailMcp = require("./email-mcp-server");
|
|
536
|
-
// Use "default" userId initially; the actual userId is resolved per-tool-call
|
|
537
|
-
// via the deps closures which read from the active WS connection.
|
|
538
|
-
var emailMcpConfig = emailMcp.create(_email.createMcpDeps());
|
|
539
|
-
if (emailMcpConfig) servers[emailMcpConfig.name || "clay-email"] = emailMcpConfig;
|
|
540
|
-
} catch (e) {
|
|
541
|
-
console.error("[project] Failed to create email MCP server:", e.message);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return Object.keys(servers).length > 0 ? servers : undefined;
|
|
545
|
-
})(),
|
|
556
|
+
mcpServers: mcpServers,
|
|
546
557
|
getRemoteMcpServers: function () { return _mcp.getMcpServers(); },
|
|
558
|
+
clayPort: serverPort,
|
|
559
|
+
clayTls: serverTls,
|
|
560
|
+
clayAuthToken: serverAuthToken,
|
|
547
561
|
onProcessingChanged: onProcessingChanged,
|
|
548
562
|
onTurnDone: isMate ? function (session, preview) {
|
|
549
563
|
digestDmTurn(session, preview);
|
|
@@ -718,6 +732,16 @@ function createProjectContext(opts) {
|
|
|
718
732
|
return;
|
|
719
733
|
}
|
|
720
734
|
|
|
735
|
+
// --- Vendor model switching ---
|
|
736
|
+
if (msg.type === "get_vendor_models") {
|
|
737
|
+
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
|
|
738
|
+
var firstModel = vendorModels[0] || "";
|
|
739
|
+
// model value can be string or {value, displayName} object
|
|
740
|
+
var defaultModel = typeof firstModel === "string" ? firstModel : (firstModel.value || "");
|
|
741
|
+
sendTo(ws, { type: "model_info", model: defaultModel, models: vendorModels, vendor: msg.vendor, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
721
745
|
// --- Debate ---
|
|
722
746
|
if (msg.type === "debate_start") {
|
|
723
747
|
handleDebateStart(ws, msg);
|
|
@@ -950,7 +974,7 @@ function createProjectContext(opts) {
|
|
|
950
974
|
hydrateImageRefs: hydrateImageRefs,
|
|
951
975
|
onProcessingChanged: onProcessingChanged,
|
|
952
976
|
broadcastPresence: broadcastPresence,
|
|
953
|
-
|
|
977
|
+
adapter: adapter,
|
|
954
978
|
getProjectList: getProjectList,
|
|
955
979
|
getProjectCount: getProjectCount,
|
|
956
980
|
getScheduleCount: getScheduleCount,
|
|
@@ -1010,7 +1034,7 @@ function createProjectContext(opts) {
|
|
|
1010
1034
|
digestDmTurn: digestDmTurn,
|
|
1011
1035
|
gateMemory: gateMemory,
|
|
1012
1036
|
escapeRegex: escapeRegex,
|
|
1013
|
-
|
|
1037
|
+
adapter: adapter,
|
|
1014
1038
|
getHubSchedules: getHubSchedules,
|
|
1015
1039
|
getProjectOwnerId: function () { return projectOwnerId; },
|
|
1016
1040
|
_email: _email,
|
|
@@ -1040,6 +1064,94 @@ function createProjectContext(opts) {
|
|
|
1040
1064
|
FS_MAX_SIZE: FS_MAX_SIZE,
|
|
1041
1065
|
});
|
|
1042
1066
|
|
|
1067
|
+
// --- MCP bridge handler for Codex (Track 2) ---
|
|
1068
|
+
// Provides list_tools and call_tool operations over HTTP for mcp-bridge-server.js.
|
|
1069
|
+
// Excludes local MCP servers since Codex manages those natively via Track 1.
|
|
1070
|
+
function getMcpBridgeHandler() {
|
|
1071
|
+
// Build set of local MCP server names to exclude (Codex handles these natively)
|
|
1072
|
+
var localMcpNames = {};
|
|
1073
|
+
try {
|
|
1074
|
+
var mcpLocalModule = require("./mcp-local");
|
|
1075
|
+
var localConfig = mcpLocalModule.readMergedServers();
|
|
1076
|
+
var lcNames = Object.keys(localConfig);
|
|
1077
|
+
for (var li = 0; li < lcNames.length; li++) {
|
|
1078
|
+
localMcpNames[lcNames[li]] = true;
|
|
1079
|
+
}
|
|
1080
|
+
} catch (e) { /* no local MCP config */ }
|
|
1081
|
+
|
|
1082
|
+
return {
|
|
1083
|
+
listTools: function () {
|
|
1084
|
+
var tools = [];
|
|
1085
|
+
var toJSONSchema;
|
|
1086
|
+
try { toJSONSchema = require("zod").toJSONSchema; } catch (e) { /* fallback */ }
|
|
1087
|
+
|
|
1088
|
+
// Helper to extract tools from an SDK MCP server object
|
|
1089
|
+
function extractServerTools(serverName, server) {
|
|
1090
|
+
if (!server || !server.instance || !server.instance._registeredTools) return;
|
|
1091
|
+
var toolNames = Object.keys(server.instance._registeredTools);
|
|
1092
|
+
for (var j = 0; j < toolNames.length; j++) {
|
|
1093
|
+
var toolDef = server.instance._registeredTools[toolNames[j]];
|
|
1094
|
+
var inputSchema = { type: "object", properties: {} };
|
|
1095
|
+
try {
|
|
1096
|
+
if (toJSONSchema && toolDef.inputSchema) inputSchema = toJSONSchema(toolDef.inputSchema);
|
|
1097
|
+
} catch (e) { /* fallback */ }
|
|
1098
|
+
tools.push({
|
|
1099
|
+
server: serverName,
|
|
1100
|
+
name: toolNames[j],
|
|
1101
|
+
description: toolDef.description || toolNames[j],
|
|
1102
|
+
inputSchema: inputSchema,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// In-app MCP servers (debate, browser, email)
|
|
1108
|
+
if (mcpServers) {
|
|
1109
|
+
var inAppNames = Object.keys(mcpServers);
|
|
1110
|
+
for (var i = 0; i < inAppNames.length; i++) {
|
|
1111
|
+
extractServerTools(inAppNames[i], mcpServers[inAppNames[i]]);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Remote MCP servers (extension-proxied only, skip local proxy servers)
|
|
1116
|
+
var remoteServers = _mcp.getMcpServers();
|
|
1117
|
+
if (remoteServers) {
|
|
1118
|
+
var remoteNames = Object.keys(remoteServers);
|
|
1119
|
+
for (var ri = 0; ri < remoteNames.length; ri++) {
|
|
1120
|
+
// Skip servers that Codex manages natively via Track 1
|
|
1121
|
+
if (localMcpNames[remoteNames[ri]]) continue;
|
|
1122
|
+
extractServerTools(remoteNames[ri], remoteServers[remoteNames[ri]]);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return Promise.resolve(tools);
|
|
1127
|
+
},
|
|
1128
|
+
callTool: function (serverName, toolName, args) {
|
|
1129
|
+
// Try in-app servers first
|
|
1130
|
+
if (mcpServers && mcpServers[serverName]) {
|
|
1131
|
+
var server = mcpServers[serverName];
|
|
1132
|
+
if (server.instance && server.instance._registeredTools && server.instance._registeredTools[toolName]) {
|
|
1133
|
+
var handler = server.instance._registeredTools[toolName].handler;
|
|
1134
|
+
if (typeof handler === "function") {
|
|
1135
|
+
return Promise.resolve(handler(args));
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
// Try remote/local proxy servers
|
|
1140
|
+
var remoteServers = _mcp.getMcpServers();
|
|
1141
|
+
if (remoteServers && remoteServers[serverName]) {
|
|
1142
|
+
var rServer = remoteServers[serverName];
|
|
1143
|
+
if (rServer.instance && rServer.instance._registeredTools && rServer.instance._registeredTools[toolName]) {
|
|
1144
|
+
var rHandler = rServer.instance._registeredTools[toolName].handler;
|
|
1145
|
+
if (typeof rHandler === "function") {
|
|
1146
|
+
return Promise.resolve(rHandler(args));
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return Promise.reject(new Error("Tool not found: " + serverName + "/" + toolName));
|
|
1151
|
+
},
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1043
1155
|
// --- HTTP handler (delegated to project-http.js) ---
|
|
1044
1156
|
var _http = attachHTTP({
|
|
1045
1157
|
cwd: cwd,
|
|
@@ -1056,6 +1168,7 @@ function createProjectContext(opts) {
|
|
|
1056
1168
|
sendExtensionCommandAny: sendExtensionCommandAny,
|
|
1057
1169
|
_extToken: _extToken,
|
|
1058
1170
|
_browserTabList: browserState._browserTabList,
|
|
1171
|
+
getMcpBridgeHandler: getMcpBridgeHandler,
|
|
1059
1172
|
});
|
|
1060
1173
|
var handleHTTP = _http.handleHTTP;
|
|
1061
1174
|
|
|
@@ -1275,6 +1388,7 @@ function createProjectContext(opts) {
|
|
|
1275
1388
|
handleMessage: handleMessage,
|
|
1276
1389
|
handleDisconnection: handleDisconnection,
|
|
1277
1390
|
handleHTTP: handleHTTP,
|
|
1391
|
+
getMcpBridgeHandler: getMcpBridgeHandler,
|
|
1278
1392
|
getStatus: getStatus,
|
|
1279
1393
|
getSessionManager: function () { return sm; },
|
|
1280
1394
|
getNotificationsModule: function () { return _notifications; },
|
|
@@ -1300,7 +1414,7 @@ function createProjectContext(opts) {
|
|
|
1300
1414
|
sdk.warmup();
|
|
1301
1415
|
sdk.startIdleReaper();
|
|
1302
1416
|
// Migrate existing relay session titles to SDK format (one-time, async)
|
|
1303
|
-
sm.migrateSessionTitles(
|
|
1417
|
+
sm.migrateSessionTitles(adapter, cwd);
|
|
1304
1418
|
},
|
|
1305
1419
|
destroy: function () {
|
|
1306
1420
|
sdk.stopIdleReaper();
|