clay-server 2.47.0-beta.3 → 2.47.0-beta.4
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/claude-hook-installer.js +0 -1
- package/lib/project-session-spawn.js +366 -0
- package/lib/project.js +36 -26
- package/lib/public/app.js +0 -2
- package/lib/public/css/mates.css +0 -196
- package/lib/public/modules/app-messages.js +0 -21
- package/lib/public/modules/mate-sidebar.js +0 -3
- package/lib/sdk-bridge.js +5 -10
- package/lib/session-spawn-mcp-server.js +56 -0
- package/lib/sessions.js +3 -0
- package/lib/ws-schema.js +0 -14
- package/package.json +1 -1
- package/lib/mate-datastore.js +0 -359
- package/lib/project-mate-datastore.js +0 -232
- package/lib/public/modules/mate-datastore-ui.js +0 -280
|
@@ -43,7 +43,6 @@ var CLAY_MANAGED_ALLOW = [
|
|
|
43
43
|
"mcp__clay-browser__browser_watch_tab",
|
|
44
44
|
"mcp__clay-browser__browser_unwatch_tab",
|
|
45
45
|
"mcp__clay-debate__propose_debate",
|
|
46
|
-
"mcp__clay-datastore__*",
|
|
47
46
|
"mcp__clay-history__*",
|
|
48
47
|
// Email: read-side only. Send / reply / mark_read still prompt.
|
|
49
48
|
"mcp__clay-email__clay_read_email",
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
var yoke = require("./yoke");
|
|
2
|
+
var sessionSpawnMcp = require("./session-spawn-mcp-server");
|
|
3
|
+
var cliSessions = require("./cli-sessions");
|
|
4
|
+
var os = require("os");
|
|
5
|
+
var osUsers = require("./os-users");
|
|
6
|
+
|
|
7
|
+
var MAX_SESSIONS_PER_CALL = 10;
|
|
8
|
+
var MAX_CHILDREN_PER_PARENT = 20;
|
|
9
|
+
var SPAWN_CONCURRENCY = 3;
|
|
10
|
+
|
|
11
|
+
function parseBatch(raw) {
|
|
12
|
+
var parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(raw);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
throw new Error("sessions must be a valid JSON array");
|
|
17
|
+
}
|
|
18
|
+
if (!Array.isArray(parsed)) {
|
|
19
|
+
throw new Error("sessions must be a valid JSON array");
|
|
20
|
+
}
|
|
21
|
+
if (parsed.length < 1 || parsed.length > MAX_SESSIONS_PER_CALL) {
|
|
22
|
+
throw new Error("sessions must contain between 1 and 10 entries");
|
|
23
|
+
}
|
|
24
|
+
var result = [];
|
|
25
|
+
for (var i = 0; i < parsed.length; i++) {
|
|
26
|
+
var entry = parsed[i];
|
|
27
|
+
if (!entry || typeof entry.prompt !== "string" || !entry.prompt.trim()) {
|
|
28
|
+
throw new Error("session " + (i + 1) + " must include a non-empty prompt");
|
|
29
|
+
}
|
|
30
|
+
var title = typeof entry.title === "string" ? entry.title.trim() : "";
|
|
31
|
+
result.push({
|
|
32
|
+
title: title || "Spawned task " + (i + 1),
|
|
33
|
+
prompt: entry.prompt.trim(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getSessionsArray(sessions) {
|
|
40
|
+
if (!sessions) return [];
|
|
41
|
+
if (typeof sessions.values === "function") return Array.from(sessions.values());
|
|
42
|
+
return sessions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function assertSpawnAllowed(parent, sessions, requestedCount) {
|
|
46
|
+
if (!parent) throw new Error("no active parent session");
|
|
47
|
+
if (parent.spawn) throw new Error("spawned sessions cannot spawn further sessions");
|
|
48
|
+
var all = getSessionsArray(sessions);
|
|
49
|
+
var childCount = 0;
|
|
50
|
+
for (var i = 0; i < all.length; i++) {
|
|
51
|
+
if (all[i].spawn && all[i].spawn.parentId === parent.localId) childCount++;
|
|
52
|
+
}
|
|
53
|
+
if (childCount + requestedCount > MAX_CHILDREN_PER_PARENT) {
|
|
54
|
+
throw new Error("a parent session cannot have more than 20 children");
|
|
55
|
+
}
|
|
56
|
+
return childCount;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateVendor(vendor, adapters, linuxUser, getVendorInfo) {
|
|
60
|
+
if (!vendor || !adapters || !adapters[vendor]) {
|
|
61
|
+
throw new Error("vendor is not available: " + (vendor || "unknown"));
|
|
62
|
+
}
|
|
63
|
+
var lookup = getVendorInfo || yoke.getVendorInfo;
|
|
64
|
+
var info = lookup(vendor);
|
|
65
|
+
if (linuxUser && info && info.osUserIsolation === false) {
|
|
66
|
+
throw new Error((info.displayName || vendor) + " is not available for OS-isolated users");
|
|
67
|
+
}
|
|
68
|
+
return vendor;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createSpawnQueue(concurrency) {
|
|
72
|
+
var limit = concurrency || SPAWN_CONCURRENCY;
|
|
73
|
+
var pending = [];
|
|
74
|
+
var running = [];
|
|
75
|
+
|
|
76
|
+
function startTask(task) {
|
|
77
|
+
task.state = "running";
|
|
78
|
+
running.push(task);
|
|
79
|
+
var finished = false;
|
|
80
|
+
function complete(err) {
|
|
81
|
+
if (finished) return;
|
|
82
|
+
finished = true;
|
|
83
|
+
var index = running.indexOf(task);
|
|
84
|
+
if (index !== -1) running.splice(index, 1);
|
|
85
|
+
task.state = err ? "error" : "done";
|
|
86
|
+
task.error = err || null;
|
|
87
|
+
pump();
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
task.start(complete);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
complete(e);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function pump() {
|
|
97
|
+
while (running.length < limit && pending.length > 0) {
|
|
98
|
+
var task = pending.shift();
|
|
99
|
+
startTask(task);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function add(tasks) {
|
|
104
|
+
for (var i = 0; i < tasks.length; i++) {
|
|
105
|
+
tasks[i].state = "queued";
|
|
106
|
+
pending.push(tasks[i]);
|
|
107
|
+
}
|
|
108
|
+
pump();
|
|
109
|
+
var queued = 0;
|
|
110
|
+
var active = 0;
|
|
111
|
+
for (var j = 0; j < tasks.length; j++) {
|
|
112
|
+
if (tasks[j].state === "queued") queued++;
|
|
113
|
+
if (tasks[j].state === "running") active++;
|
|
114
|
+
}
|
|
115
|
+
return { queued: queued, running: active };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
add: add,
|
|
120
|
+
get pendingCount() { return pending.length; },
|
|
121
|
+
get runningCount() { return running.length; },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function hasSessionError(session) {
|
|
126
|
+
var history = session.history || [];
|
|
127
|
+
var recent = history.slice(-3);
|
|
128
|
+
for (var i = 0; i < recent.length; i++) {
|
|
129
|
+
if (recent[i].type === "error" || (recent[i].type === "done" && recent[i].code === 1)) return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function toolResult(value) {
|
|
135
|
+
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function toolError(err) {
|
|
139
|
+
return Promise.resolve({
|
|
140
|
+
content: [{ type: "text", text: "Error: " + (err.message || err) }],
|
|
141
|
+
isError: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rebuildMessageUUIDs(history) {
|
|
146
|
+
var messageUUIDs = [];
|
|
147
|
+
for (var i = 0; i < history.length; i++) {
|
|
148
|
+
if (history[i].type === "message_uuid") {
|
|
149
|
+
messageUUIDs.push({
|
|
150
|
+
uuid: history[i].uuid,
|
|
151
|
+
type: history[i].messageType,
|
|
152
|
+
historyIndex: i,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return messageUUIDs;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function attachSessionSpawn(ctx) {
|
|
160
|
+
var sm = ctx.sm;
|
|
161
|
+
var queue = createSpawnQueue(SPAWN_CONCURRENCY);
|
|
162
|
+
var adapters = ctx.adapters || {};
|
|
163
|
+
var readCliSessionHistory = ctx.readCliSessionHistory || cliSessions.readCliSessionHistory;
|
|
164
|
+
|
|
165
|
+
function resolveSessionHome(session) {
|
|
166
|
+
var linuxUser = ctx.getLinuxUserForSession(session);
|
|
167
|
+
if (linuxUser) {
|
|
168
|
+
try {
|
|
169
|
+
var info = osUsers.resolveOsUserInfo(linuxUser);
|
|
170
|
+
if (info && info.home) return info.home;
|
|
171
|
+
} catch (e) {}
|
|
172
|
+
}
|
|
173
|
+
return os.homedir();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateFork(parent, vendor, explicitVendor) {
|
|
177
|
+
if (!parent.cliSessionId) {
|
|
178
|
+
throw new Error("forkFromCurrent requires the calling session to have at least one completed turn");
|
|
179
|
+
}
|
|
180
|
+
var capabilities = sm.capabilitiesByVendor && sm.capabilitiesByVendor[vendor];
|
|
181
|
+
if (!capabilities || capabilities.fork !== true) {
|
|
182
|
+
throw new Error("forkFromCurrent is not supported by vendor: " + vendor);
|
|
183
|
+
}
|
|
184
|
+
var parentVendor = parent.vendor || sm.defaultVendor;
|
|
185
|
+
if (explicitVendor && explicitVendor !== parentVendor) {
|
|
186
|
+
throw new Error("forkFromCurrent children must use the parent's vendor");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function applyForkToTask(task, forkResult, history) {
|
|
191
|
+
task.session.cliSessionId = forkResult.sessionId;
|
|
192
|
+
task.session.history = history.slice();
|
|
193
|
+
task.session.messageUUIDs = rebuildMessageUUIDs(task.session.history);
|
|
194
|
+
sm.saveSessionFile(task.session);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function spawnOne(parent, spec, index, batchId, vendor, linuxUser) {
|
|
198
|
+
var create = typeof sm.createSessionRaw === "function" ? sm.createSessionRaw : sm.createSession;
|
|
199
|
+
var session = create.call(sm, {
|
|
200
|
+
ownerId: parent.ownerId || null,
|
|
201
|
+
sessionVisibility: parent.sessionVisibility || "shared",
|
|
202
|
+
vendor: vendor,
|
|
203
|
+
});
|
|
204
|
+
session.spawn = { parentId: parent.localId, index: index, batchId: batchId };
|
|
205
|
+
session.title = spec.title;
|
|
206
|
+
sm.saveSessionFile(session);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
session: session,
|
|
210
|
+
state: "created",
|
|
211
|
+
start: function (done) {
|
|
212
|
+
function finish(err) {
|
|
213
|
+
delete session.onQueryComplete;
|
|
214
|
+
delete session.singleTurn;
|
|
215
|
+
done(err);
|
|
216
|
+
}
|
|
217
|
+
var userMessage = { type: "user_message", text: spec.prompt };
|
|
218
|
+
session.history.push(userMessage);
|
|
219
|
+
sm.appendToSessionFile(session, userMessage);
|
|
220
|
+
session.isProcessing = true;
|
|
221
|
+
session.lastActivity = Date.now();
|
|
222
|
+
session.sentToolResults = {};
|
|
223
|
+
session.singleTurn = true;
|
|
224
|
+
session.onQueryComplete = function () {
|
|
225
|
+
finish(hasSessionError(session) ? new Error("child session failed") : null);
|
|
226
|
+
};
|
|
227
|
+
var sdk = ctx.getSdk();
|
|
228
|
+
if (!sdk || typeof sdk.startQuery !== "function") {
|
|
229
|
+
session.isProcessing = false;
|
|
230
|
+
finish(new Error("SDK bridge is not ready"));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
Promise.resolve(sdk.startQuery(session, spec.prompt, undefined, linuxUser)).then(function () {
|
|
234
|
+
if (!session.isProcessing && !session.queryInstance) {
|
|
235
|
+
finish(new Error("child session failed to start"));
|
|
236
|
+
}
|
|
237
|
+
}).catch(function (err) {
|
|
238
|
+
session.isProcessing = false;
|
|
239
|
+
session.history.push({ type: "error", text: err.message || String(err) });
|
|
240
|
+
finish(err);
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function spawn(args, caller) {
|
|
247
|
+
try {
|
|
248
|
+
// The caller is bound per query in getLocalMcpServers (project.js), so
|
|
249
|
+
// a child session resolving itself here is what makes the depth guard
|
|
250
|
+
// sound. Never fall back to sm.getActiveSession(): that is the
|
|
251
|
+
// project-global "last viewed" session and can belong to someone else.
|
|
252
|
+
var parent = caller;
|
|
253
|
+
if (!parent) throw new Error("spawn_sessions requires a session-bound tool server");
|
|
254
|
+
var specs = parseBatch(args.sessions);
|
|
255
|
+
assertSpawnAllowed(parent, sm.sessions, specs.length);
|
|
256
|
+
var explicitVendor = args.vendor && args.vendor.trim();
|
|
257
|
+
var vendor = explicitVendor || parent.vendor || sm.defaultVendor;
|
|
258
|
+
var linuxUser = ctx.getLinuxUserForSession(parent);
|
|
259
|
+
validateVendor(vendor, adapters, linuxUser);
|
|
260
|
+
var forkFromCurrent = args.forkFromCurrent === true;
|
|
261
|
+
if (forkFromCurrent) validateFork(parent, vendor, explicitVendor);
|
|
262
|
+
var batchId = "sp_" + Date.now().toString(36);
|
|
263
|
+
var tasks = [];
|
|
264
|
+
var spawned = [];
|
|
265
|
+
var failed = null;
|
|
266
|
+
var sdk = ctx.getSdk();
|
|
267
|
+
var lastMessage = parent.messageUUIDs && parent.messageUUIDs.length > 0
|
|
268
|
+
? parent.messageUUIDs[parent.messageUUIDs.length - 1]
|
|
269
|
+
: null;
|
|
270
|
+
// Claude treats an omitted upToMessageId as a full-session fork;
|
|
271
|
+
// Codex forks the whole thread and ignores the UUID option.
|
|
272
|
+
var lastUuid = lastMessage ? lastMessage.uuid : undefined;
|
|
273
|
+
for (var i = 0; i < specs.length; i++) {
|
|
274
|
+
var forkResult = null;
|
|
275
|
+
var forkHistory = null;
|
|
276
|
+
if (forkFromCurrent) {
|
|
277
|
+
try {
|
|
278
|
+
if (!sdk || typeof sdk.forkSession !== "function") throw new Error("SDK bridge is not ready");
|
|
279
|
+
forkResult = await sdk.forkSession(parent, lastUuid);
|
|
280
|
+
if (!forkResult || !forkResult.sessionId) throw new Error("Fork returned no session id");
|
|
281
|
+
if (forkResult.useLocalHistory) {
|
|
282
|
+
forkHistory = parent.history.slice();
|
|
283
|
+
} else {
|
|
284
|
+
forkHistory = await readCliSessionHistory(
|
|
285
|
+
resolveSessionHome(parent), ctx.cwd, forkResult.sessionId
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {
|
|
289
|
+
failed = { index: i, error: e.message || String(e) };
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
var task = spawnOne(parent, specs[i], i, batchId, vendor, linuxUser);
|
|
294
|
+
if (forkResult) applyForkToTask(task, forkResult, forkHistory || []);
|
|
295
|
+
tasks.push(task);
|
|
296
|
+
spawned.push({ localId: task.session.localId, title: task.session.title });
|
|
297
|
+
}
|
|
298
|
+
var counts = queue.add(tasks);
|
|
299
|
+
if (tasks.length > 0) sm.broadcastSessionList();
|
|
300
|
+
var result = { spawned: spawned, queued: counts.queued, running: counts.running };
|
|
301
|
+
if (failed) result.failed = failed;
|
|
302
|
+
return toolResult(result);
|
|
303
|
+
} catch (e) {
|
|
304
|
+
return toolError(e);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function check(args, caller) {
|
|
309
|
+
try {
|
|
310
|
+
var parent = caller;
|
|
311
|
+
if (!parent) throw new Error("check_spawned_sessions requires a session-bound tool server");
|
|
312
|
+
var parentOnly = args.parentOnly !== false;
|
|
313
|
+
var all = getSessionsArray(sm.sessions);
|
|
314
|
+
var statuses = [];
|
|
315
|
+
for (var i = 0; i < all.length; i++) {
|
|
316
|
+
var session = all[i];
|
|
317
|
+
if (!session.spawn) continue;
|
|
318
|
+
if (parentOnly && session.spawn.parentId !== parent.localId) continue;
|
|
319
|
+
statuses.push({
|
|
320
|
+
localId: session.localId,
|
|
321
|
+
title: session.title || "New Session",
|
|
322
|
+
status: session.isProcessing ? "running" : (hasSessionError(session) ? "error" : "done"),
|
|
323
|
+
turnCount: session.turnCount || 0,
|
|
324
|
+
lastActivity: session.lastActivity || session.createdAt || 0,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return toolResult(statuses);
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return toolError(e);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// boundSession: the session whose query this tool server instance is
|
|
334
|
+
// mounted into. Omitted for the static instance kept in the project's
|
|
335
|
+
// mcpServers map, which exists only so tool descriptors can be listed;
|
|
336
|
+
// its handlers fail closed if anything ever routes a call to them.
|
|
337
|
+
function createMcpServer(adapter, boundSession) {
|
|
338
|
+
if (ctx.isMate || !adapter || typeof adapter.createToolServer !== "function") return null;
|
|
339
|
+
return adapter.createToolServer({
|
|
340
|
+
name: "clay-sessions",
|
|
341
|
+
version: "1.0.0",
|
|
342
|
+
tools: sessionSpawnMcp.getToolDefs({
|
|
343
|
+
spawn: function (args) { return spawn(args, boundSession || null); },
|
|
344
|
+
check: function (args) { return check(args, boundSession || null); },
|
|
345
|
+
}),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
createMcpServer: createMcpServer,
|
|
351
|
+
spawnOne: spawnOne,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
module.exports = {
|
|
356
|
+
MAX_SESSIONS_PER_CALL: MAX_SESSIONS_PER_CALL,
|
|
357
|
+
MAX_CHILDREN_PER_PARENT: MAX_CHILDREN_PER_PARENT,
|
|
358
|
+
SPAWN_CONCURRENCY: SPAWN_CONCURRENCY,
|
|
359
|
+
parseBatch: parseBatch,
|
|
360
|
+
assertSpawnAllowed: assertSpawnAllowed,
|
|
361
|
+
validateVendor: validateVendor,
|
|
362
|
+
createSpawnQueue: createSpawnQueue,
|
|
363
|
+
hasSessionError: hasSessionError,
|
|
364
|
+
rebuildMessageUUIDs: rebuildMessageUUIDs,
|
|
365
|
+
attachSessionSpawn: attachSessionSpawn,
|
|
366
|
+
};
|
package/lib/project.js
CHANGED
|
@@ -28,9 +28,9 @@ var { attachSessions } = require("./project-sessions");
|
|
|
28
28
|
var { attachUserMessage } = require("./project-user-message");
|
|
29
29
|
var { attachConnection } = require("./project-connection");
|
|
30
30
|
var { attachMcp } = require("./project-mcp");
|
|
31
|
-
var { attachMateDatastore } = require("./project-mate-datastore");
|
|
32
31
|
var { createLocalMcp } = require("./mcp-local");
|
|
33
32
|
var { attachEmail: attachEmailModule } = require("./project-email");
|
|
33
|
+
var { attachSessionSpawn } = require("./project-session-spawn");
|
|
34
34
|
// project-notifications is attached globally in server.js, passed via opts.notificationsModule
|
|
35
35
|
|
|
36
36
|
// --- Context Sources persistence ---
|
|
@@ -476,23 +476,34 @@ function createProjectContext(opts) {
|
|
|
476
476
|
},
|
|
477
477
|
});
|
|
478
478
|
|
|
479
|
-
//
|
|
480
|
-
|
|
479
|
+
// The SDK bridge is created after local MCP servers. Session spawning uses
|
|
480
|
+
// a getter so tool handlers see the initialized bridge when they run.
|
|
481
|
+
var sdk = null;
|
|
482
|
+
var _sessionSpawn = attachSessionSpawn({
|
|
481
483
|
cwd: cwd,
|
|
482
|
-
|
|
483
|
-
|
|
484
|
+
sm: sm,
|
|
485
|
+
getSdk: function () { return sdk; },
|
|
484
486
|
send: send,
|
|
485
|
-
|
|
486
|
-
clients: clients,
|
|
487
|
-
getSessionForWs: getSessionForWs,
|
|
487
|
+
isMate: isMate,
|
|
488
488
|
usersModule: usersModule,
|
|
489
|
-
|
|
489
|
+
adapters: adapters,
|
|
490
|
+
getLinuxUserForSession: getLinuxUserForSession,
|
|
490
491
|
});
|
|
491
492
|
|
|
492
493
|
// --- MCP tool servers (created via YOKE adapter) ---
|
|
493
494
|
var mcpServers = (function () {
|
|
494
495
|
var servers = {};
|
|
495
496
|
|
|
497
|
+
// Agent-driven sibling session fan-out (main projects only).
|
|
498
|
+
if (!isMate) {
|
|
499
|
+
try {
|
|
500
|
+
var sessionSpawnMcpConfig = _sessionSpawn.createMcpServer(adapter);
|
|
501
|
+
if (sessionSpawnMcpConfig) servers[sessionSpawnMcpConfig.name || "clay-sessions"] = sessionSpawnMcpConfig;
|
|
502
|
+
} catch (e) {
|
|
503
|
+
console.error("[project] Failed to create session spawn MCP server:", e.message);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
496
507
|
// Debate MCP server (available to both mates and main project)
|
|
497
508
|
try {
|
|
498
509
|
var debateMcp = require("./debate-mcp-server");
|
|
@@ -643,15 +654,6 @@ function createProjectContext(opts) {
|
|
|
643
654
|
console.error("[project] Failed to create email MCP server:", e.message);
|
|
644
655
|
}
|
|
645
656
|
|
|
646
|
-
if (isMate) {
|
|
647
|
-
try {
|
|
648
|
-
var datastoreMcp = _mateDatastore.createMcpServer();
|
|
649
|
-
if (datastoreMcp) servers[datastoreMcp.name || "clay-datastore"] = datastoreMcp;
|
|
650
|
-
} catch (e) {
|
|
651
|
-
console.error("[project] Failed to create datastore MCP server:", e.message);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
657
|
return Object.keys(servers).length > 0 ? servers : undefined;
|
|
656
658
|
})();
|
|
657
659
|
|
|
@@ -663,7 +665,12 @@ function createProjectContext(opts) {
|
|
|
663
665
|
//
|
|
664
666
|
// clay-browser -> only when the Chrome extension is connected
|
|
665
667
|
// clay-email -> only when the user has an account or server SMTP
|
|
666
|
-
|
|
668
|
+
//
|
|
669
|
+
// forSession (optional): the session whose query these servers are mounted
|
|
670
|
+
// into. clay-sessions must know its caller (depth guard + child ownership),
|
|
671
|
+
// so it is re-instantiated bound to that session; the static instance in
|
|
672
|
+
// mcpServers only serves descriptor listing and fails closed on calls.
|
|
673
|
+
function getLocalMcpServers(forSession) {
|
|
667
674
|
if (!mcpServers) return undefined;
|
|
668
675
|
var extWs = browserState._extensionWs;
|
|
669
676
|
var extConnected = !!(extWs && extWs.readyState === 1);
|
|
@@ -675,6 +682,15 @@ function createProjectContext(opts) {
|
|
|
675
682
|
var name = keys[i];
|
|
676
683
|
if (name === "clay-browser" && !extConnected) continue;
|
|
677
684
|
if (name === "clay-email" && !emailAvailable) continue;
|
|
685
|
+
if (name === "clay-sessions" && forSession) {
|
|
686
|
+
try {
|
|
687
|
+
var boundSpawn = _sessionSpawn.createMcpServer(adapter, forSession);
|
|
688
|
+
if (boundSpawn) { filtered[name] = boundSpawn; hasAny = true; }
|
|
689
|
+
} catch (e) {
|
|
690
|
+
console.error("[project] Failed to bind session spawn MCP server:", e.message);
|
|
691
|
+
}
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
678
694
|
filtered[name] = mcpServers[name];
|
|
679
695
|
hasAny = true;
|
|
680
696
|
}
|
|
@@ -682,7 +698,7 @@ function createProjectContext(opts) {
|
|
|
682
698
|
}
|
|
683
699
|
|
|
684
700
|
// --- SDK bridge ---
|
|
685
|
-
|
|
701
|
+
sdk = createSDKBridge({
|
|
686
702
|
cwd: cwd,
|
|
687
703
|
slug: slug,
|
|
688
704
|
sessionManager: sm,
|
|
@@ -1028,9 +1044,6 @@ function createProjectContext(opts) {
|
|
|
1028
1044
|
// --- MCP bridge (remote MCP servers via extension) ---
|
|
1029
1045
|
if (_mcp.handleMcpMessage(ws, msg)) return;
|
|
1030
1046
|
|
|
1031
|
-
// --- Mate datastore ---
|
|
1032
|
-
if (_mateDatastore.handleMateDatastoreMessage(ws, msg)) return;
|
|
1033
|
-
|
|
1034
1047
|
// --- Knowledge file management (delegated to project-knowledge.js) ---
|
|
1035
1048
|
if (_knowledge.handleKnowledgeMessage(ws, msg)) return;
|
|
1036
1049
|
|
|
@@ -1491,9 +1504,6 @@ function createProjectContext(opts) {
|
|
|
1491
1504
|
function destroy() {
|
|
1492
1505
|
_loop.stopTimer();
|
|
1493
1506
|
_email.destroy();
|
|
1494
|
-
if (_mateDatastore && typeof _mateDatastore.closeAllDatastores === "function") {
|
|
1495
|
-
try { _mateDatastore.closeAllDatastores(); } catch (e) {}
|
|
1496
|
-
}
|
|
1497
1507
|
stopFileWatch();
|
|
1498
1508
|
stopAllDirWatches();
|
|
1499
1509
|
// Abort all active sessions and clean up mention sessions
|
package/lib/public/app.js
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
openMobileSheet, setMobileSheetMateData, refreshMobileChatSheet
|
|
19
19
|
} from './modules/sidebar-mobile.js';
|
|
20
20
|
import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
|
|
21
|
-
import { initMateDatastoreUI } from './modules/mate-datastore-ui.js';
|
|
22
21
|
import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
|
|
23
22
|
import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
|
|
24
23
|
import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton, onRewindComplete, onRewindError } from './modules/rewind.js';
|
|
@@ -416,7 +415,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
416
415
|
initSidebar(sidebarCtx);
|
|
417
416
|
var wsGetter = function () { return _getWsRef(); };
|
|
418
417
|
initMateSidebar(wsGetter);
|
|
419
|
-
initMateDatastoreUI(wsGetter);
|
|
420
418
|
initMateKnowledge(wsGetter);
|
|
421
419
|
initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
|
|
422
420
|
initMateWizard(
|