clay-server 2.31.0 → 2.32.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.
Files changed (82) hide show
  1. package/lib/browser-mcp-server.js +32 -44
  2. package/lib/codex-defaults.js +18 -0
  3. package/lib/debate-mcp-server.js +14 -31
  4. package/lib/mcp-local.js +31 -1
  5. package/lib/project-connection.js +9 -6
  6. package/lib/project-debate.js +8 -0
  7. package/lib/project-filesystem.js +47 -1
  8. package/lib/project-http.js +75 -8
  9. package/lib/project-mate-interaction.js +102 -16
  10. package/lib/project-mcp.js +4 -0
  11. package/lib/project-notifications.js +9 -0
  12. package/lib/project-sessions.js +94 -51
  13. package/lib/project-user-message.js +12 -7
  14. package/lib/project.js +234 -99
  15. package/lib/public/app.js +135 -454
  16. package/lib/public/codex-avatar.png +0 -0
  17. package/lib/public/css/debate.css +3 -2
  18. package/lib/public/css/filebrowser.css +91 -1
  19. package/lib/public/css/icon-strip.css +21 -5
  20. package/lib/public/css/input.css +338 -104
  21. package/lib/public/css/mates.css +43 -0
  22. package/lib/public/css/mention.css +48 -4
  23. package/lib/public/css/menus.css +1 -1
  24. package/lib/public/css/messages.css +2 -0
  25. package/lib/public/css/notifications-center.css +26 -0
  26. package/lib/public/css/tooltip.css +47 -0
  27. package/lib/public/index.html +78 -26
  28. package/lib/public/modules/app-connection.js +138 -37
  29. package/lib/public/modules/app-cursors.js +18 -17
  30. package/lib/public/modules/app-debate-ui.js +9 -9
  31. package/lib/public/modules/app-dm.js +175 -131
  32. package/lib/public/modules/app-favicon.js +28 -26
  33. package/lib/public/modules/app-header.js +79 -68
  34. package/lib/public/modules/app-home-hub.js +55 -47
  35. package/lib/public/modules/app-loop-ui.js +34 -18
  36. package/lib/public/modules/app-loop-wizard.js +6 -6
  37. package/lib/public/modules/app-messages.js +199 -153
  38. package/lib/public/modules/app-misc.js +23 -12
  39. package/lib/public/modules/app-notifications.js +119 -9
  40. package/lib/public/modules/app-panels.js +203 -49
  41. package/lib/public/modules/app-projects.js +161 -150
  42. package/lib/public/modules/app-rate-limit.js +5 -4
  43. package/lib/public/modules/app-rendering.js +149 -101
  44. package/lib/public/modules/app-skills-install.js +4 -4
  45. package/lib/public/modules/context-sources.js +102 -66
  46. package/lib/public/modules/dom-refs.js +21 -0
  47. package/lib/public/modules/filebrowser.js +173 -2
  48. package/lib/public/modules/input.js +122 -0
  49. package/lib/public/modules/markdown.js +5 -1
  50. package/lib/public/modules/mate-sidebar.js +38 -0
  51. package/lib/public/modules/mention.js +24 -6
  52. package/lib/public/modules/scheduler.js +1 -1
  53. package/lib/public/modules/sidebar-mates.js +79 -35
  54. package/lib/public/modules/sidebar-mobile.js +34 -30
  55. package/lib/public/modules/sidebar-projects.js +60 -57
  56. package/lib/public/modules/sidebar-sessions.js +75 -69
  57. package/lib/public/modules/sidebar.js +12 -20
  58. package/lib/public/modules/skills.js +8 -9
  59. package/lib/public/modules/sticky-notes.js +1 -2
  60. package/lib/public/modules/store.js +9 -2
  61. package/lib/public/modules/stt.js +4 -1
  62. package/lib/public/modules/terminal.js +12 -0
  63. package/lib/public/modules/tools.js +18 -13
  64. package/lib/public/modules/tooltip.js +32 -5
  65. package/lib/sdk-bridge.js +562 -1114
  66. package/lib/sdk-message-processor.js +150 -135
  67. package/lib/sdk-worker.js +4 -0
  68. package/lib/server-dm.js +1 -0
  69. package/lib/server.js +86 -1
  70. package/lib/sessions.js +81 -37
  71. package/lib/ws-schema.js +2 -0
  72. package/lib/yoke/adapters/claude-worker.js +559 -0
  73. package/lib/yoke/adapters/claude.js +1483 -0
  74. package/lib/yoke/adapters/codex.js +1121 -0
  75. package/lib/yoke/adapters/gemini.js +709 -0
  76. package/lib/yoke/codex-app-server.js +307 -0
  77. package/lib/yoke/index.js +199 -0
  78. package/lib/yoke/instructions.js +62 -0
  79. package/lib/yoke/interface.js +98 -0
  80. package/lib/yoke/mcp-bridge-server.js +294 -0
  81. package/lib/yoke/package.json +7 -0
  82. package/package.json +3 -1
@@ -1,11 +1,11 @@
1
- // Browser MCP Server for Clay (in-process SDK version)
2
- // Provides browser automation tools to Claude via createSdkMcpServer.
3
- // Calls sendExtensionCommand directly instead of HTTP bridge.
1
+ // Browser MCP Server for Clay
2
+ // Provides browser automation tool definitions.
3
+ // SDK-free: returns runtime-agnostic tool definitions for YOKE adapter.
4
4
  //
5
5
  // Usage:
6
6
  // var browserMcp = require("./browser-mcp-server");
7
- // var mcpConfig = browserMcp.create(sendExtensionCommandAny);
8
- // // Pass mcpConfig to sdk-bridge opts.mcpServers
7
+ // var toolDefs = browserMcp.getToolDefs(sendExtensionCommandAny, getTabList, contextOps);
8
+ // var mcpConfig = adapter.createToolServer({ name: "clay-browser", version: "1.0.0", tools: toolDefs });
9
9
 
10
10
  var z;
11
11
  try { z = require("zod"); } catch (e) { z = null; }
@@ -30,21 +30,14 @@ function buildShape(props, required) {
30
30
  return shape;
31
31
  }
32
32
 
33
- function create(sendCommand, getTabList, contextOps) {
33
+ // Helper: convert positional args (name, desc, schema, handler) to tool definition object
34
+ function def(name, description, inputSchema, handler) {
35
+ return { name: name, description: description, inputSchema: inputSchema, handler: handler };
36
+ }
37
+
38
+ function getToolDefs(sendCommand, getTabList, contextOps) {
34
39
  // sendCommand(command, args, timeout) -> Promise<result>
35
40
  // getTabList() -> array of { id, url, title, favIconUrl }
36
- var sdk;
37
- try { sdk = require("@anthropic-ai/claude-agent-sdk"); } catch (e) {
38
- console.error("[browser-mcp] Failed to load SDK:", e.message);
39
- return null;
40
- }
41
-
42
- var createSdkMcpServer = sdk.createSdkMcpServer;
43
- var tool = sdk.tool;
44
- if (!createSdkMcpServer || !tool) {
45
- console.error("[browser-mcp] SDK missing createSdkMcpServer or tool helper");
46
- return null;
47
- }
48
41
 
49
42
  // Helper: ensure inject.js loaded (best-effort)
50
43
  function ensureInjected(tabId) {
@@ -54,7 +47,7 @@ function create(sendCommand, getTabList, contextOps) {
54
47
  var tools = [];
55
48
 
56
49
  // --- browser_list_tabs ---
57
- tools.push(tool(
50
+ tools.push(def(
58
51
  "browser_list_tabs",
59
52
  "List all open browser tabs with their IDs, URLs, and titles",
60
53
  buildShape({}, []),
@@ -65,7 +58,7 @@ function create(sendCommand, getTabList, contextOps) {
65
58
  ));
66
59
 
67
60
  // --- browser_open ---
68
- tools.push(tool(
61
+ tools.push(def(
69
62
  "browser_open",
70
63
  "Open a new browser tab and return its tab ID",
71
64
  buildShape({
@@ -80,7 +73,7 @@ function create(sendCommand, getTabList, contextOps) {
80
73
  ));
81
74
 
82
75
  // --- browser_close ---
83
- tools.push(tool(
76
+ tools.push(def(
84
77
  "browser_close",
85
78
  "Close a browser tab",
86
79
  buildShape({
@@ -94,7 +87,7 @@ function create(sendCommand, getTabList, contextOps) {
94
87
  ));
95
88
 
96
89
  // --- browser_navigate ---
97
- tools.push(tool(
90
+ tools.push(def(
98
91
  "browser_navigate",
99
92
  "Navigate a tab to a new URL",
100
93
  buildShape({
@@ -109,7 +102,7 @@ function create(sendCommand, getTabList, contextOps) {
109
102
  ));
110
103
 
111
104
  // --- browser_screenshot ---
112
- tools.push(tool(
105
+ tools.push(def(
113
106
  "browser_screenshot",
114
107
  "Capture a screenshot of a browser tab. Skip if the tab is already attached as a context source (data is auto-injected).",
115
108
  buildShape({
@@ -132,7 +125,7 @@ function create(sendCommand, getTabList, contextOps) {
132
125
  ));
133
126
 
134
127
  // --- browser_console ---
135
- tools.push(tool(
128
+ tools.push(def(
136
129
  "browser_console",
137
130
  "Read captured console logs from a tab. Skip if the tab is already a context source (data is auto-injected).",
138
131
  buildShape({
@@ -155,7 +148,7 @@ function create(sendCommand, getTabList, contextOps) {
155
148
  ));
156
149
 
157
150
  // --- browser_network ---
158
- tools.push(tool(
151
+ tools.push(def(
159
152
  "browser_network",
160
153
  "Read captured network requests (fetch/XHR) from a tab. Skip if the tab is already a context source.",
161
154
  buildShape({
@@ -179,7 +172,7 @@ function create(sendCommand, getTabList, contextOps) {
179
172
  ));
180
173
 
181
174
  // --- browser_read_page ---
182
- tools.push(tool(
175
+ tools.push(def(
183
176
  "browser_read_page",
184
177
  "Read page text content (innerText). Skip if the tab is already a context source (text is auto-injected). Use for tabs NOT in context sources, or to read a specific element via selector.",
185
178
  buildShape({
@@ -204,7 +197,7 @@ function create(sendCommand, getTabList, contextOps) {
204
197
  ));
205
198
 
206
199
  // --- browser_dom ---
207
- tools.push(tool(
200
+ tools.push(def(
208
201
  "browser_dom",
209
202
  "Get a simplified DOM tree (tag, id, class, children) for structural analysis",
210
203
  buildShape({
@@ -247,7 +240,7 @@ function create(sendCommand, getTabList, contextOps) {
247
240
  ));
248
241
 
249
242
  // --- browser_styles ---
250
- tools.push(tool(
243
+ tools.push(def(
251
244
  "browser_styles",
252
245
  "Get computed styles of an element (display, position, size, colors, etc.)",
253
246
  buildShape({
@@ -276,7 +269,7 @@ function create(sendCommand, getTabList, contextOps) {
276
269
  ));
277
270
 
278
271
  // --- browser_storage ---
279
- tools.push(tool(
272
+ tools.push(def(
280
273
  "browser_storage",
281
274
  "Read browser storage (localStorage, sessionStorage, or cookies)",
282
275
  buildShape({
@@ -300,7 +293,7 @@ function create(sendCommand, getTabList, contextOps) {
300
293
  ));
301
294
 
302
295
  // --- browser_evaluate ---
303
- tools.push(tool(
296
+ tools.push(def(
304
297
  "browser_evaluate",
305
298
  "Execute arbitrary JavaScript in the page context and return the result",
306
299
  buildShape({
@@ -317,7 +310,7 @@ function create(sendCommand, getTabList, contextOps) {
317
310
  ));
318
311
 
319
312
  // --- browser_click ---
320
- tools.push(tool(
313
+ tools.push(def(
321
314
  "browser_click",
322
315
  "Click an element on the page",
323
316
  buildShape({
@@ -339,7 +332,7 @@ function create(sendCommand, getTabList, contextOps) {
339
332
  ));
340
333
 
341
334
  // --- browser_type ---
342
- tools.push(tool(
335
+ tools.push(def(
343
336
  "browser_type",
344
337
  "Type text into an input element (sets value and dispatches input/change events)",
345
338
  buildShape({
@@ -366,7 +359,7 @@ function create(sendCommand, getTabList, contextOps) {
366
359
  ));
367
360
 
368
361
  // --- browser_scroll ---
369
- tools.push(tool(
362
+ tools.push(def(
370
363
  "browser_scroll",
371
364
  "Scroll the page or scroll a specific element into view",
372
365
  buildShape({
@@ -396,7 +389,7 @@ function create(sendCommand, getTabList, contextOps) {
396
389
  ));
397
390
 
398
391
  // --- browser_wait ---
399
- tools.push(tool(
392
+ tools.push(def(
400
393
  "browser_wait",
401
394
  "Wait for an element matching a CSS selector to appear in the DOM",
402
395
  buildShape({
@@ -432,7 +425,7 @@ function create(sendCommand, getTabList, contextOps) {
432
425
  ));
433
426
 
434
427
  // --- browser_wait_navigation ---
435
- tools.push(tool(
428
+ tools.push(def(
436
429
  "browser_wait_navigation",
437
430
  "Wait for page navigation to complete (URL change + load event)",
438
431
  buildShape({
@@ -450,7 +443,7 @@ function create(sendCommand, getTabList, contextOps) {
450
443
 
451
444
  // --- browser_watch_tab ---
452
445
  if (contextOps && contextOps.watchTab) {
453
- tools.push(tool(
446
+ tools.push(def(
454
447
  "browser_watch_tab",
455
448
  "Add a browser tab as a persistent context source. Its screenshot and text will be automatically included in every subsequent message.",
456
449
  buildShape({
@@ -470,7 +463,7 @@ function create(sendCommand, getTabList, contextOps) {
470
463
  }
471
464
  ));
472
465
 
473
- tools.push(tool(
466
+ tools.push(def(
474
467
  "browser_unwatch_tab",
475
468
  "Remove a browser tab from persistent context sources. Stops auto-including its content.",
476
469
  buildShape({
@@ -485,12 +478,7 @@ function create(sendCommand, getTabList, contextOps) {
485
478
  ));
486
479
  }
487
480
 
488
- // Create the in-process MCP server
489
- return createSdkMcpServer({
490
- name: "clay-browser",
491
- version: "1.0.0",
492
- tools: tools,
493
- });
481
+ return tools;
494
482
  }
495
483
 
496
- module.exports = { create: create };
484
+ module.exports = { getToolDefs: getToolDefs };
@@ -0,0 +1,18 @@
1
+ var CODEX_DEFAULTS = {
2
+ approval: "on-failure",
3
+ sandbox: "danger-full-access",
4
+ webSearch: "live",
5
+ };
6
+
7
+ function getCodexConfig(sm) {
8
+ return {
9
+ approval: (sm && sm.codexApproval) || CODEX_DEFAULTS.approval,
10
+ sandbox: (sm && sm.codexSandbox) || CODEX_DEFAULTS.sandbox,
11
+ webSearch: (sm && sm.codexWebSearch) || CODEX_DEFAULTS.webSearch,
12
+ };
13
+ }
14
+
15
+ module.exports = {
16
+ CODEX_DEFAULTS: CODEX_DEFAULTS,
17
+ getCodexConfig: getCodexConfig,
18
+ };
@@ -1,11 +1,11 @@
1
- // Debate MCP Server for Clay (in-process SDK version)
2
- // Provides the propose_debate tool so mates can propose debates
3
- // via the SDK tool system instead of writing files to disk.
1
+ // Debate MCP Server for Clay
2
+ // Provides the propose_debate tool definition.
3
+ // SDK-free: returns runtime-agnostic tool definitions for YOKE adapter.
4
4
  //
5
5
  // Usage:
6
6
  // var debateMcp = require("./debate-mcp-server");
7
- // var mcpConfig = debateMcp.create(onPropose);
8
- // // Pass mcpConfig to sdk-bridge opts.mcpServers
7
+ // var toolDefs = debateMcp.getToolDefs(onPropose);
8
+ // var mcpConfig = adapter.createToolServer({ name: "clay-debate", version: "1.0.0", tools: toolDefs });
9
9
 
10
10
  var z;
11
11
  try { z = require("zod"); } catch (e) { z = null; }
@@ -31,33 +31,20 @@ function buildShape(props, required) {
31
31
 
32
32
  // onPropose(briefData) -> Promise<{action: "start"|"cancel"}>
33
33
  // The returned Promise blocks the tool until the user approves or cancels.
34
- function create(onPropose) {
35
- var sdk;
36
- try { sdk = require("@anthropic-ai/claude-agent-sdk"); } catch (e) {
37
- console.error("[debate-mcp] Failed to load SDK:", e.message);
38
- return null;
39
- }
40
-
41
- var createSdkMcpServer = sdk.createSdkMcpServer;
42
- var tool = sdk.tool;
43
- if (!createSdkMcpServer || !tool) {
44
- console.error("[debate-mcp] SDK missing createSdkMcpServer or tool helper");
45
- return null;
46
- }
47
-
34
+ function getToolDefs(onPropose) {
48
35
  var tools = [];
49
36
 
50
- tools.push(tool(
51
- "propose_debate",
52
- "Propose a structured debate among Clay Mates. The user will see an inline approval card. The tool blocks until the user approves or cancels.",
53
- buildShape({
37
+ tools.push({
38
+ name: "propose_debate",
39
+ description: "Propose a structured debate among Clay Mates. The user will see an inline approval card. The tool blocks until the user approves or cancels.",
40
+ inputSchema: buildShape({
54
41
  topic: { type: "string", description: "The debate topic" },
55
42
  format: { type: "string", description: "Debate format, e.g. free_discussion (default)" },
56
43
  context: { type: "string", description: "Key context from the conversation that panelists should know" },
57
44
  specialRequests: { type: "string", description: "Special instructions for the debate, or empty" },
58
45
  panelists: { type: "string", description: "JSON array of panelist objects: [{\"mateId\": \"<UUID>\", \"role\": \"perspective\", \"brief\": \"guidance\"}]" },
59
46
  }, ["topic", "panelists"]),
60
- function (args) {
47
+ handler: function (args) {
61
48
  var panelists;
62
49
  try {
63
50
  panelists = JSON.parse(args.panelists);
@@ -82,13 +69,9 @@ function create(onPropose) {
82
69
  return { content: [{ type: "text", text: "Debate proposal was cancelled by the user." }] };
83
70
  });
84
71
  }
85
- ));
86
-
87
- return createSdkMcpServer({
88
- name: "clay-debate",
89
- version: "1.0.0",
90
- tools: tools,
91
72
  });
73
+
74
+ return tools;
92
75
  }
93
76
 
94
- module.exports = { create: create };
77
+ module.exports = { getToolDefs: getToolDefs };
package/lib/mcp-local.js CHANGED
@@ -352,4 +352,34 @@ function createLocalMcp() {
352
352
  };
353
353
  }
354
354
 
355
- module.exports = { createLocalMcp: createLocalMcp };
355
+ // Standalone config reader (no process spawning).
356
+ // Returns merged server definitions from ~/.clay/mcp.json + includes.
357
+ // Used by Codex adapter to pass server configs for native MCP management.
358
+ function readMergedServers() {
359
+ var dir = path.dirname(CLAY_CONFIG_PATH);
360
+ if (!fs.existsSync(dir)) return {};
361
+ var config;
362
+ try {
363
+ config = JSON.parse(fs.readFileSync(CLAY_CONFIG_PATH, "utf8"));
364
+ } catch (e) {
365
+ return {};
366
+ }
367
+ var merged = Object.assign({}, config.mcpServers || {});
368
+ var includes = config.include || [];
369
+ for (var i = 0; i < includes.length; i++) {
370
+ var resolved = includes[i].replace(/^~/, os.homedir());
371
+ try {
372
+ var ext = JSON.parse(fs.readFileSync(resolved, "utf8"));
373
+ var extServers = ext.mcpServers || {};
374
+ var names = Object.keys(extServers);
375
+ for (var j = 0; j < names.length; j++) {
376
+ if (!merged[names[j]]) merged[names[j]] = extServers[names[j]];
377
+ }
378
+ } catch (e) {
379
+ // Skip unreadable files
380
+ }
381
+ }
382
+ return merged;
383
+ }
384
+
385
+ module.exports = { createLocalMcp: createLocalMcp, readMergedServers: readMergedServers };
@@ -3,6 +3,7 @@ var path = require("path");
3
3
  var usersModule = require("./users");
4
4
  var userPresence = require("./user-presence");
5
5
  var emailAccounts = require("./email-accounts");
6
+ var { getCodexConfig } = require("./codex-defaults");
6
7
 
7
8
  /**
8
9
  * Attach connection/disconnection handlers to a project context.
@@ -84,17 +85,18 @@ function attachConnection(ctx) {
84
85
  var project = getProject();
85
86
  var ownerLocked = !!(osUsers && osUsers.length > 0 && /^\/home\/[^/]+\//.test(cwd));
86
87
  sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked });
87
- var latestVersion = getLatestVersion();
88
- if (latestVersion && ws._clayUser && ws._clayUser.role === "admin") {
89
- sendTo(ws, { type: "update_available", version: latestVersion });
90
- }
88
+ // Update notifications are pushed on a scheduled interval (see
89
+ // scheduleUpdateBroadcast). We no longer push on connect to avoid
90
+ // re-triggering the banner on every page refresh.
91
91
  if (sm.slashCommands) {
92
92
  sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
93
93
  }
94
94
  if (sm.currentModel) {
95
- sendTo(ws, { type: "model_info", model: sm.currentModel, models: sm.availableModels || [] });
95
+ // Vendor is resolved per-session in session_switched; send default here
96
+ sendTo(ws, { type: "model_info", model: sm.currentModel, models: sm.availableModels || [], vendor: sm.defaultVendor || "claude", availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
96
97
  }
97
98
  sendTo(ws, { type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
99
+ sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
98
100
  sendTo(ws, { type: "term_list", terminals: tm.list() });
99
101
  // Context sources sent after session is resolved (per-session storage)
100
102
  // Send email accounts list for context sources picker
@@ -180,7 +182,8 @@ function attachConnection(ctx) {
180
182
  sm.saveSessionFile(active);
181
183
  }
182
184
  ws._clayActiveSession = active.localId;
183
- sendTo(ws, { type: "session_switched", id: active.localId, cliSessionId: active.cliSessionId || null, loop: active.loop || null });
185
+ var _vendorCaps = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[active.vendor || sm.defaultVendor || "claude"]) || {};
186
+ sendTo(ws, { type: "session_switched", id: active.localId, cliSessionId: active.cliSessionId || null, loop: active.loop || null, vendor: active.vendor || null, hasHistory: (active.history && active.history.length > 0), capabilities: _vendorCaps });
184
187
  // Send per-session context sources
185
188
  var sessionSources = loadContextSources(slug, active.localId);
186
189
  sendTo(ws, { type: "context_sources_state", active: sessionSources });
@@ -601,7 +601,9 @@ function attachDebate(ctx) {
601
601
  var digests = ctx.loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
602
602
 
603
603
  var briefText = "";
604
+ var _modMate = matesModule.getMate(mateCtx, debate.moderatorId);
604
605
  ctx.sdk.createMentionSession({
606
+ vendor: _modMate ? _modMate.vendor : null,
605
607
  claudeMd: claudeMd,
606
608
  initialContext: digests,
607
609
  initialMessage: quickBriefPrompt,
@@ -794,7 +796,9 @@ function attachDebate(ctx) {
794
796
  var digests = ctx.loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
795
797
  var moderatorContext = buildModeratorContext(debate) + digests;
796
798
 
799
+ var _modMate2 = matesModule.getMate(mateCtx, debate.moderatorId);
797
800
  ctx.sdk.createMentionSession({
801
+ vendor: _modMate2 ? _modMate2.vendor : null,
798
802
  claudeMd: claudeMd,
799
803
  initialContext: moderatorContext,
800
804
  initialMessage: "Begin the debate on: " + debate.topic,
@@ -987,7 +991,9 @@ function attachDebate(ctx) {
987
991
  historyContext += "---";
988
992
  }
989
993
 
994
+ var _panMate = matesModule.getMate(debate.mateCtx, mateId);
990
995
  ctx.sdk.createMentionSession({
996
+ vendor: _panMate ? _panMate.vendor : null,
991
997
  claudeMd: claudeMd,
992
998
  initialContext: panelistContext + historyContext,
993
999
  initialMessage: "The moderator addresses you:\n\n" + moderatorText,
@@ -1521,7 +1527,9 @@ function attachDebate(ctx) {
1521
1527
  }
1522
1528
  moderatorContext += "---\n";
1523
1529
 
1530
+ var _modMate3 = matesModule.getMate(mateCtx, debate.moderatorId);
1524
1531
  ctx.sdk.createMentionSession({
1532
+ vendor: _modMate3 ? _modMate3.vendor : null,
1525
1533
  claudeMd: claudeMd,
1526
1534
  initialContext: moderatorContext,
1527
1535
  initialMessage: resumePrompt,
@@ -41,7 +41,7 @@ function attachFilesystem(ctx) {
41
41
 
42
42
  function handleFilesystemMessage(ws, msg) {
43
43
  // --- File browser permission gate ---
44
- if (msg.type === "fs_list" || msg.type === "fs_read" || msg.type === "fs_write" || msg.type === "fs_delete" || msg.type === "fs_rename" || msg.type === "fs_mkdir" || msg.type === "fs_upload") {
44
+ if (msg.type === "fs_list" || msg.type === "fs_read" || msg.type === "fs_write" || msg.type === "fs_delete" || msg.type === "fs_rename" || msg.type === "fs_mkdir" || msg.type === "fs_upload" || msg.type === "fs_search") {
45
45
  if (ws._clayUser) {
46
46
  var fbPerms = usersModule.getEffectivePermissions(ws._clayUser, osUsers);
47
47
  if (!fbPerms.fileBrowser) {
@@ -98,6 +98,52 @@ function attachFilesystem(ctx) {
98
98
  return true;
99
99
  }
100
100
 
101
+ // --- fs_search ---
102
+ if (msg.type === "fs_search") {
103
+ var query = (msg.query || "").trim().toLowerCase();
104
+ if (!query) {
105
+ sendTo(ws, { type: "fs_search_result", query: msg.query, entries: [] });
106
+ return true;
107
+ }
108
+ try {
109
+ var searchResults = [];
110
+ var MAX_RESULTS = 50;
111
+ var searchUserInfo = getOsUserInfoForWs(ws);
112
+
113
+ function walkDir(dir, relPrefix) {
114
+ if (searchResults.length >= MAX_RESULTS) return;
115
+ var items;
116
+ try {
117
+ if (searchUserInfo) {
118
+ items = fsAsUser("list", { dir: dir }, searchUserInfo);
119
+ } else {
120
+ items = fs.readdirSync(dir, { withFileTypes: true }).map(function (d) {
121
+ return { name: d.name, isDir: d.isDirectory() };
122
+ });
123
+ }
124
+ } catch (e) { return; }
125
+ for (var i = 0; i < items.length; i++) {
126
+ if (searchResults.length >= MAX_RESULTS) return;
127
+ var it = items[i];
128
+ if (it.isDir && IGNORED_DIRS.has(it.name)) continue;
129
+ var rel = relPrefix ? relPrefix + "/" + it.name : it.name;
130
+ if (it.name.toLowerCase().indexOf(query) !== -1) {
131
+ searchResults.push({ name: it.name, type: it.isDir ? "dir" : "file", path: rel });
132
+ }
133
+ if (it.isDir) {
134
+ walkDir(path.join(dir, it.name), rel);
135
+ }
136
+ }
137
+ }
138
+
139
+ walkDir(cwd, "");
140
+ sendTo(ws, { type: "fs_search_result", query: msg.query, entries: searchResults });
141
+ } catch (e) {
142
+ sendTo(ws, { type: "fs_search_result", query: msg.query, entries: [], error: e.message });
143
+ }
144
+ return true;
145
+ }
146
+
101
147
  // --- fs_read ---
102
148
  if (msg.type === "fs_read") {
103
149
  var fsFile = safePath(cwd, msg.path);
@@ -504,6 +504,10 @@ function attachHTTP(ctx) {
504
504
  return true;
505
505
  }
506
506
 
507
+ // Skill update check cache (avoid redundant GitHub fetches)
508
+ if (!ctx._skillCheckCache) ctx._skillCheckCache = {};
509
+ var SKILL_CHECK_TTL = 5 * 60 * 1000; // 5 minutes
510
+
507
511
  // Check skill updates (compare installed vs remote versions)
508
512
  if (req.method === "POST" && urlPath === "/api/check-skill-updates") {
509
513
  parseJsonBody(req).then(function (body) {
@@ -572,7 +576,16 @@ function attachHTTP(ctx) {
572
576
  (function (skill) {
573
577
  var installedVer = getInstalledVersion(skill.name);
574
578
  var installed = !!installedVer;
575
- console.log("[skill-check] " + skill.name + " installed=" + installed + " localVersion=" + (installedVer || "none"));
579
+
580
+ // Return cached result if fresh
581
+ var cacheKey = skill.name + ":" + (installedVer || "");
582
+ var cached = ctx._skillCheckCache[cacheKey];
583
+ if (cached && (Date.now() - cached.ts) < SKILL_CHECK_TTL) {
584
+ results.push(cached.result);
585
+ finishOne();
586
+ return;
587
+ }
588
+
576
589
  // Convert GitHub repo URL to raw SKILL.md URL
577
590
  var rawUrl = "";
578
591
  var ghMatch = skill.url.match(/github\.com\/([^/]+)\/([^/]+)/);
@@ -580,17 +593,16 @@ function attachHTTP(ctx) {
580
593
  rawUrl = "https://raw.githubusercontent.com/" + ghMatch[1] + "/" + ghMatch[2] + "/main/SKILL.md";
581
594
  }
582
595
  if (!rawUrl) {
583
- console.log("[skill-check] " + skill.name + " no valid GitHub URL, skipping remote check");
584
- results.push({ name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: "", status: installed ? "ok" : "missing" });
596
+ var r0 = { name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: "", status: installed ? "ok" : "missing" };
597
+ ctx._skillCheckCache[cacheKey] = { ts: Date.now(), result: r0 };
598
+ results.push(r0);
585
599
  finishOne();
586
600
  return;
587
601
  }
588
- console.log("[skill-check] " + skill.name + " fetching remote: " + rawUrl);
589
602
  // Fetch remote SKILL.md
590
603
  var https = require("https");
591
604
  https.get(rawUrl, function (resp) {
592
- console.log("[skill-check] " + skill.name + " remote response status=" + resp.statusCode);
593
- var data = "";
605
+ var data = "";
594
606
  resp.on("data", function (chunk) { data += chunk; });
595
607
  resp.on("end", function () {
596
608
  try {
@@ -601,8 +613,9 @@ function attachHTTP(ctx) {
601
613
  } else if (remoteVer && compareVersions(installedVer, remoteVer) < 0) {
602
614
  status = "outdated";
603
615
  }
604
- console.log("[skill-check] " + skill.name + " remoteVersion=" + remoteVer + " status=" + status);
605
- results.push({ name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: remoteVer, status: status });
616
+ var r1 = { name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: remoteVer, status: status };
617
+ ctx._skillCheckCache[cacheKey] = { ts: Date.now(), result: r1 };
618
+ results.push(r1);
606
619
  finishOne();
607
620
  } catch (e) {
608
621
  console.error("[skill-check] " + skill.name + " version parse failed:", e.message || e);
@@ -666,6 +679,60 @@ function attachHTTP(ctx) {
666
679
  return true;
667
680
  }
668
681
 
682
+ // MCP bridge endpoint: allows Codex's mcp-bridge-server.js to list/call
683
+ // in-app and remote MCP tools via HTTP (localhost only).
684
+ if (req.method === "POST" && urlPath === "/api/mcp-bridge") {
685
+ parseJsonBody(req).then(function (body) {
686
+ var action = body.action;
687
+ var getMcpBridgeHandler = ctx.getMcpBridgeHandler;
688
+ if (!getMcpBridgeHandler) {
689
+ res.writeHead(500, { "Content-Type": "application/json" });
690
+ res.end('{"error":"MCP bridge not configured"}');
691
+ return;
692
+ }
693
+ var handler = getMcpBridgeHandler();
694
+ if (!handler) {
695
+ res.writeHead(500, { "Content-Type": "application/json" });
696
+ res.end('{"error":"MCP bridge handler unavailable"}');
697
+ return;
698
+ }
699
+
700
+ if (action === "list_tools") {
701
+ handler.listTools().then(function (tools) {
702
+ var serverCounts = {};
703
+ for (var ti = 0; ti < tools.length; ti++) {
704
+ serverCounts[tools[ti].server] = (serverCounts[tools[ti].server] || 0) + 1;
705
+ }
706
+ console.log("[mcp-bridge-http] list_tools:", tools.length, "tools -", Object.keys(serverCounts).map(function(s) { return s + "(" + serverCounts[s] + ")"; }).join(", ") || "(none)");
707
+ res.writeHead(200, { "Content-Type": "application/json" });
708
+ res.end(JSON.stringify({ tools: tools }));
709
+ }).catch(function (err) {
710
+ res.writeHead(500, { "Content-Type": "application/json" });
711
+ res.end(JSON.stringify({ error: err.message || "Failed to list tools" }));
712
+ });
713
+ } else if (action === "call_tool") {
714
+ var server = body.server;
715
+ var tool = body.tool;
716
+ var args = body.args || {};
717
+ console.log("[mcp-bridge-http] call_tool:", server + "/" + tool);
718
+ handler.callTool(server, tool, args).then(function (result) {
719
+ res.writeHead(200, { "Content-Type": "application/json" });
720
+ res.end(JSON.stringify({ result: result }));
721
+ }).catch(function (err) {
722
+ res.writeHead(200, { "Content-Type": "application/json" });
723
+ res.end(JSON.stringify({ error: err.message || "Tool call failed" }));
724
+ });
725
+ } else {
726
+ res.writeHead(400, { "Content-Type": "application/json" });
727
+ res.end('{"error":"Unknown action: ' + (action || '') + '"}');
728
+ }
729
+ }).catch(function () {
730
+ res.writeHead(400, { "Content-Type": "application/json" });
731
+ res.end('{"error":"Invalid JSON body"}');
732
+ });
733
+ return true;
734
+ }
735
+
669
736
  // Info endpoint
670
737
  if (req.method === "GET" && urlPath === "/info") {
671
738
  res.writeHead(200, {