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
@@ -9,12 +9,25 @@ function attachMessageProcessor(ctx) {
9
9
  var pushModule = ctx.pushModule;
10
10
  var getNotificationsModule = ctx.getNotificationsModule || function () { return null; };
11
11
  var getSDK = ctx.getSDK;
12
+ var adapter = ctx.adapter;
13
+ var cwd = ctx.cwd;
12
14
  var onProcessingChanged = ctx.onProcessingChanged;
13
15
  var onTurnDone = ctx.onTurnDone;
16
+ var onAutoTitle = ctx.onAutoTitle;
14
17
  var opts = ctx.opts;
15
18
  var discoverSkillDirs = ctx.discoverSkillDirs;
16
19
  var mergeSkills = ctx.mergeSkills;
17
20
 
21
+ var AUTO_TITLE_TURN_THRESHOLD = 3;
22
+
23
+ function getMateIdForNotification() {
24
+ if (!isMate) return null;
25
+ if (typeof slug === "string" && slug.indexOf("mate-") === 0) {
26
+ return slug.substring(5) || null;
27
+ }
28
+ return null;
29
+ }
30
+
18
31
  function sendAndRecord(session, obj) {
19
32
  sm.sendAndRecord(session, obj);
20
33
  }
@@ -37,11 +50,11 @@ function attachMessageProcessor(ctx) {
37
50
  }
38
51
 
39
52
  function processSubagentMessage(session, parsed) {
40
- var parentId = parsed.parent_tool_use_id;
41
- var content = parsed.message.content;
53
+ var parentId = parsed.parentToolUseId;
54
+ var content = parsed.content;
42
55
  if (!Array.isArray(content)) return;
43
56
 
44
- if (parsed.type === "assistant") {
57
+ if (parsed.messageRole === "assistant") {
45
58
  // Extract tool_use blocks from sub-agent assistant messages
46
59
  for (var i = 0; i < content.length; i++) {
47
60
  var block = content[i];
@@ -69,59 +82,57 @@ function attachMessageProcessor(ctx) {
69
82
  }
70
83
  }
71
84
  }
72
- // user messages with parent_tool_use_id contain tool_results -- skip silently
85
+ // user messages with parentToolUseId contain tool_results -- skip silently
73
86
  }
74
87
 
75
88
  function processSDKMessage(session, parsed) {
76
89
  // Timing: log key SDK milestones relative to query start
77
90
  if (session._queryStartTs) {
78
91
  var _elapsed = Date.now() - session._queryStartTs;
79
- if (parsed.type === "system" && parsed.subtype === "init") {
92
+ if (parsed.yokeType === "init") {
80
93
  console.log("[PERF] processSDKMessage: system/init +" + _elapsed + "ms");
81
94
  }
82
- if (parsed.type === "stream_event" && parsed.event) {
83
- if (parsed.event.type === "message_start") {
84
- console.log("[PERF] processSDKMessage: message_start (API response begun) +" + _elapsed + "ms");
85
- }
86
- if (parsed.event.type === "content_block_delta" && !session._firstTextLogged) {
87
- session._firstTextLogged = true;
88
- console.log("[PERF] processSDKMessage: FIRST content_block_delta (visible text) +" + _elapsed + "ms");
89
- }
95
+ if (parsed.yokeType === "turn_start") {
96
+ console.log("[PERF] processSDKMessage: message_start (API response begun) +" + _elapsed + "ms");
90
97
  }
91
- if (parsed.type === "result") {
98
+ if ((parsed.yokeType === "text_delta" || parsed.yokeType === "tool_input_delta" || parsed.yokeType === "thinking_delta") && !session._firstTextLogged) {
99
+ session._firstTextLogged = true;
100
+ console.log("[PERF] processSDKMessage: FIRST content_block_delta (visible text) +" + _elapsed + "ms");
101
+ }
102
+ if (parsed.yokeType === "result") {
92
103
  console.log("[PERF] processSDKMessage: result +" + _elapsed + "ms");
93
104
  }
94
105
  }
95
106
 
96
107
  // Extract session_id from any message that carries it
97
- if (parsed.session_id && !session.cliSessionId) {
98
- session.cliSessionId = parsed.session_id;
108
+ if (parsed.sessionId && !session.cliSessionId) {
109
+ session.cliSessionId = parsed.sessionId;
99
110
  sm.saveSessionFile(session);
100
111
  sendAndRecord(session, { type: "session_id", cliSessionId: session.cliSessionId });
101
- } else if (parsed.session_id) {
102
- session.cliSessionId = parsed.session_id;
112
+ } else if (parsed.sessionId) {
113
+ session.cliSessionId = parsed.sessionId;
103
114
  }
104
115
 
105
116
  // Capture message UUIDs for rewind support
106
117
  if (parsed.uuid) {
107
- if (parsed.type === "user" && !parsed.parent_tool_use_id) {
118
+ if (parsed.messageType === "user" && !parsed.parentToolUseId) {
108
119
  session.messageUUIDs.push({ uuid: parsed.uuid, type: "user", historyIndex: session.history.length });
109
120
  sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "user" });
110
- } else if (parsed.type === "assistant") {
121
+ } else if (parsed.messageType === "assistant") {
111
122
  session.messageUUIDs.push({ uuid: parsed.uuid, type: "assistant", historyIndex: session.history.length });
112
123
  sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "assistant" });
113
124
  }
114
125
  }
115
126
 
116
127
  // Cache slash_commands and model from CLI init message
117
- if (parsed.type === "system" && parsed.subtype === "init") {
128
+ if (parsed.yokeType === "init") {
118
129
  var fsSkills = discoverSkillDirs();
119
130
  sm.skillNames = mergeSkills(parsed.skills, fsSkills);
120
- if (parsed.slash_commands) {
131
+ if (parsed.slashCommands) {
121
132
  // Union: SDK slash_commands + merged skills (deduplicated)
122
133
  var seen = new Set();
123
134
  var combined = [];
124
- var all = parsed.slash_commands.concat(Array.from(sm.skillNames));
135
+ var all = parsed.slashCommands.concat(Array.from(sm.skillNames));
125
136
  for (var k = 0; k < all.length; k++) {
126
137
  if (!seen.has(all[k])) {
127
138
  seen.add(all[k]);
@@ -132,102 +143,93 @@ function attachMessageProcessor(ctx) {
132
143
  send({ type: "slash_commands", commands: sm.slashCommands });
133
144
  }
134
145
  if (parsed.model) {
135
- sm.currentModel = sm._savedDefaultModel || parsed.model;
146
+ sm.currentModel = sm.currentModel || sm._savedDefaultModel || parsed.model;
136
147
  send({ type: "model_info", model: sm.currentModel, models: sm.availableModels || [] });
137
148
  }
138
- if (parsed.fast_mode_state) {
139
- sendAndRecord(session, { type: "fast_mode_state", state: parsed.fast_mode_state });
149
+ if (parsed.fastModeState) {
150
+ sendAndRecord(session, { type: "fast_mode_state", state: parsed.fastModeState });
140
151
  }
141
152
  }
142
153
 
143
- if (parsed.type === "stream_event" && parsed.event) {
144
- var evt = parsed.event;
145
-
146
- if (evt.type === "message_start" && evt.message && evt.message.usage) {
147
- var u = evt.message.usage;
148
- session.lastStreamInputTokens = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0);
154
+ if (parsed.yokeType === "turn_start") {
155
+ if (parsed.inputTokens) {
156
+ session.lastStreamInputTokens = parsed.inputTokens;
149
157
  }
150
158
 
151
- if (evt.type === "content_block_start") {
152
- var block = evt.content_block;
153
- var idx = evt.index;
159
+ } else if (parsed.yokeType === "tool_start" || parsed.yokeType === "thinking_start" || parsed.yokeType === "text_start") {
160
+ var idx = parsed.blockId;
154
161
 
155
- if (block.type === "tool_use") {
156
- session.blocks[idx] = { type: "tool_use", id: block.id, name: block.name, inputJson: "" };
157
- sendAndRecord(session, { type: "tool_start", id: block.id, name: block.name });
158
- } else if (block.type === "thinking") {
159
- session.blocks[idx] = { type: "thinking", thinkingText: "", startTime: Date.now() };
160
- sendAndRecord(session, { type: "thinking_start" });
161
- } else if (block.type === "text") {
162
- session.blocks[idx] = { type: "text" };
163
- }
162
+ if (parsed.yokeType === "tool_start") {
163
+ session.blocks[idx] = { type: "tool_use", id: parsed.toolId, name: parsed.toolName, inputJson: "" };
164
+ sendAndRecord(session, { type: "tool_start", id: parsed.toolId, name: parsed.toolName });
165
+ } else if (parsed.yokeType === "thinking_start") {
166
+ session.blocks[idx] = { type: "thinking", thinkingText: "", startTime: Date.now() };
167
+ sendAndRecord(session, { type: "thinking_start" });
168
+ } else if (parsed.yokeType === "text_start") {
169
+ session.blocks[idx] = { type: "text" };
164
170
  }
165
171
 
166
- if (evt.type === "content_block_delta" && evt.delta) {
167
- var idx = evt.index;
172
+ } else if (parsed.yokeType === "text_delta" || parsed.yokeType === "tool_input_delta" || parsed.yokeType === "thinking_delta") {
173
+ var idx = parsed.blockId;
168
174
 
169
- if (evt.delta.type === "text_delta" && typeof evt.delta.text === "string") {
170
- session.streamedText = true;
171
- if (session.responsePreview.length < 200) {
172
- session.responsePreview += evt.delta.text;
173
- }
174
- // Accumulate text for mate DM response
175
- if (typeof session._mateDmResponseText === "string") {
176
- session._mateDmResponseText += evt.delta.text;
177
- }
178
- sendAndRecord(session, { type: "delta", text: evt.delta.text });
179
- } else if (evt.delta.type === "input_json_delta" && session.blocks[idx]) {
180
- session.blocks[idx].inputJson += evt.delta.partial_json;
181
- } else if (evt.delta.type === "thinking_delta" && session.blocks[idx]) {
182
- session.blocks[idx].thinkingText += evt.delta.thinking;
183
- sendAndRecord(session, { type: "thinking_delta", text: evt.delta.thinking });
175
+ if (parsed.yokeType === "text_delta" && typeof parsed.text === "string") {
176
+ session.streamedText = true;
177
+ if (session.responsePreview.length < 200) {
178
+ session.responsePreview += parsed.text;
184
179
  }
185
- }
186
-
187
- if (evt.type === "content_block_stop") {
188
- var idx = evt.index;
189
- var block = session.blocks[idx];
190
-
191
- if (block && block.type === "tool_use") {
192
- var input = {};
193
- try { input = JSON.parse(block.inputJson); } catch (e) {}
194
- sendAndRecord(session, { type: "tool_executing", id: block.id, name: block.name, input: input });
195
-
196
- // Track active Task tools for sub-agent done detection
197
- if (block.name === "Task") {
198
- if (!session.activeTaskToolIds) session.activeTaskToolIds = {};
199
- session.activeTaskToolIds[block.id] = true;
200
- }
201
-
202
- if (pushModule && block.name === "AskUserQuestion" && input.questions) {
203
- var q = input.questions[0];
204
- pushModule.sendPush({
205
- type: "ask_user",
206
- slug: slug,
207
- title: (mateDisplayName || "Claude") + " has a question",
208
- body: q ? q.question : "Waiting for your response",
209
- tag: "claude-ask",
210
- });
211
- }
212
- } else if (block && block.type === "thinking") {
213
- var duration = block.startTime ? (Date.now() - block.startTime) / 1000 : 0;
214
- sendAndRecord(session, { type: "thinking_stop", duration: duration });
180
+ // Accumulate text for mate DM response
181
+ if (typeof session._mateDmResponseText === "string") {
182
+ session._mateDmResponseText += parsed.text;
183
+ }
184
+ sendAndRecord(session, { type: "delta", text: parsed.text });
185
+ } else if (parsed.yokeType === "tool_input_delta" && session.blocks[idx]) {
186
+ session.blocks[idx].inputJson += parsed.partialJson;
187
+ } else if (parsed.yokeType === "thinking_delta" && session.blocks[idx]) {
188
+ session.blocks[idx].thinkingText += parsed.text;
189
+ sendAndRecord(session, { type: "thinking_delta", text: parsed.text });
190
+ }
191
+
192
+ } else if (parsed.yokeType === "block_stop") {
193
+ var idx = parsed.blockId;
194
+ var block = session.blocks[idx];
195
+
196
+ if (block && block.type === "tool_use") {
197
+ var input = {};
198
+ try { input = JSON.parse(block.inputJson); } catch (e) {}
199
+ sendAndRecord(session, { type: "tool_executing", id: block.id, name: block.name, input: input });
200
+
201
+ // Track active Task tools for sub-agent done detection
202
+ if (block.name === "Task") {
203
+ if (!session.activeTaskToolIds) session.activeTaskToolIds = {};
204
+ session.activeTaskToolIds[block.id] = true;
215
205
  }
216
206
 
217
- delete session.blocks[idx];
207
+ if (pushModule && block.name === "AskUserQuestion" && input.questions) {
208
+ var q = input.questions[0];
209
+ pushModule.sendPush({
210
+ type: "ask_user",
211
+ slug: slug,
212
+ title: (mateDisplayName || "Claude") + " has a question",
213
+ body: q ? q.question : "Waiting for your response",
214
+ tag: "claude-ask",
215
+ });
216
+ }
217
+ } else if (block && block.type === "thinking") {
218
+ var duration = block.startTime ? (Date.now() - block.startTime) / 1000 : 0;
219
+ sendAndRecord(session, { type: "thinking_stop", duration: duration });
218
220
  }
219
221
 
220
- } else if ((parsed.type === "assistant" || parsed.type === "user") && parsed.message && parsed.message.content) {
222
+ delete session.blocks[idx];
223
+
224
+ } else if (parsed.yokeType === "subagent_message") {
221
225
  // Sub-agent messages: extract tool_use blocks for activity display
222
- if (parsed.parent_tool_use_id) {
223
- processSubagentMessage(session, parsed);
224
- return;
225
- }
226
+ processSubagentMessage(session, parsed);
226
227
 
227
- var content = parsed.message.content;
228
+ } else if (parsed.yokeType === "message") {
229
+ var content = parsed.content;
228
230
 
229
231
  // Fallback: if assistant text wasn't streamed via deltas, send it now
230
- if (parsed.type === "assistant" && !session.streamedText && Array.isArray(content)) {
232
+ if (parsed.messageRole === "assistant" && !session.streamedText && Array.isArray(content)) {
231
233
  var assistantText = content
232
234
  .filter(function(c) { return c.type === "text"; })
233
235
  .map(function(c) { return c.text; })
@@ -241,7 +243,7 @@ function attachMessageProcessor(ctx) {
241
243
  }
242
244
 
243
245
  // Check for local slash command output in user messages
244
- if (parsed.type === "user") {
246
+ if (parsed.messageRole === "user") {
245
247
  var fullText = "";
246
248
  if (typeof content === "string") {
247
249
  fullText = content;
@@ -303,7 +305,7 @@ function attachMessageProcessor(ctx) {
303
305
  }
304
306
  }
305
307
 
306
- } else if (parsed.type === "result") {
308
+ } else if (parsed.yokeType === "result") {
307
309
  session.blocks = {};
308
310
  session.sentToolResults = {};
309
311
  session.pendingPermissions = {};
@@ -319,10 +321,10 @@ function attachMessageProcessor(ctx) {
319
321
  // Only clear rateLimitResetsAt on genuine success (non-zero cost).
320
322
  // When rate-limited, the SDK sends result with zero cost right after
321
323
  // rate_limit_event; clearing here would prevent auto-continue scheduling.
322
- if (parsed.total_cost_usd && parsed.total_cost_usd > 0) {
324
+ if (parsed.cost && parsed.cost > 0) {
323
325
  session.rateLimitResetsAt = null;
324
326
  }
325
- console.log("[sdk-bridge] result handler: session " + session.localId + " cost=" + parsed.total_cost_usd + " rateLimitResetsAt=" + session.rateLimitResetsAt);
327
+ console.log("[sdk-bridge] result handler: session " + session.localId + " cost=" + parsed.cost + " rateLimitResetsAt=" + session.rateLimitResetsAt);
326
328
 
327
329
  // Handle SDK execution errors: show the error to the user instead of
328
330
  // silently swallowing it. These have subtype "error_during_execution".
@@ -331,7 +333,7 @@ function attachMessageProcessor(ctx) {
331
333
  var execError = execErrors.length > 0
332
334
  ? execErrors.join("; ")
333
335
  : "Unknown SDK error";
334
- if (parsed.terminal_reason) execError += " (reason: " + parsed.terminal_reason + ")";
336
+ if (parsed.terminalReason) execError += " (reason: " + parsed.terminalReason + ")";
335
337
  console.error("[sdk-bridge] Execution error for session " + session.localId + ": " + execError);
336
338
  session.isProcessing = false;
337
339
  onProcessingChanged();
@@ -345,7 +347,7 @@ function attachMessageProcessor(ctx) {
345
347
  onProcessingChanged();
346
348
  // Detect "Not logged in" scenario early for the check below
347
349
  var previewTrimmed = (session.responsePreview || "").trim();
348
- var isZeroCost = !parsed.total_cost_usd || parsed.total_cost_usd === 0;
350
+ var isZeroCost = !parsed.cost || parsed.cost === 0;
349
351
  var isLoginPrompt = isZeroCost && previewTrimmed.length < 100
350
352
  && /not logged in/i.test(previewTrimmed) && /\/login/i.test(previewTrimmed);
351
353
  // Fetch rich context usage breakdown (fire-and-forget, non-blocking)
@@ -357,19 +359,19 @@ function attachMessageProcessor(ctx) {
357
359
  console.error("[sdk-bridge] getContextUsage failed (non-fatal):", e.message || e);
358
360
  });
359
361
  }
360
- var lastStreamInput = session.lastStreamInputTokens || null;
362
+ var lastStreamInput = session.lastStreamInputTokens || parsed.lastStreamInputTokens || null;
361
363
  session.lastStreamInputTokens = null;
362
364
  sendAndRecord(session, {
363
365
  type: "result",
364
- cost: parsed.total_cost_usd,
365
- duration: parsed.duration_ms,
366
+ cost: parsed.cost,
367
+ duration: parsed.duration,
366
368
  usage: parsed.usage || null,
367
369
  modelUsage: parsed.modelUsage || null,
368
- sessionId: parsed.session_id,
370
+ sessionId: parsed.sessionId,
369
371
  lastStreamInputTokens: lastStreamInput,
370
372
  });
371
- if (parsed.fast_mode_state) {
372
- sendAndRecord(session, { type: "fast_mode_state", state: parsed.fast_mode_state });
373
+ if (parsed.fastModeState) {
374
+ sendAndRecord(session, { type: "fast_mode_state", state: parsed.fastModeState });
373
375
  }
374
376
  // Detect "Not logged in / Please run /login" from SDK.
375
377
  // This is a short canned response with zero cost, not actual AI output.
@@ -410,20 +412,34 @@ function attachMessageProcessor(ctx) {
410
412
  preview: _donePreviewText,
411
413
  slug: slug,
412
414
  sessionId: session.localId,
415
+ mateId: getMateIdForNotification(),
413
416
  ownerId: session.ownerId || null,
414
417
  });
415
418
  }
416
419
  // Reset for next turn in the same query
417
420
  session.lastActivityAt = Date.now();
421
+ session.turnCount = (session.turnCount || 0) + 1;
418
422
  var donePreview = session.responsePreview || "";
419
423
  session.responsePreview = "";
420
424
  session.streamedText = false;
421
425
  sm.broadcastSessionList();
426
+
427
+ // Auto-generate title after N turns (skip if loop or already auto-generated)
428
+ if (session.turnCount === AUTO_TITLE_TURN_THRESHOLD
429
+ && !session.titleAutoGenerated
430
+ && !session.titleManuallySet
431
+ && !session.loop
432
+ && onAutoTitle) {
433
+ try { onAutoTitle(session); } catch (e) {
434
+ console.error("[auto-title] onAutoTitle threw:", e.message || e);
435
+ }
436
+ }
437
+
422
438
  if (onTurnDone) {
423
439
  try { onTurnDone(session, donePreview); } catch (e) {}
424
440
  }
425
441
 
426
- } else if (parsed.type === "system" && parsed.subtype === "status") {
442
+ } else if (parsed.yokeType === "status") {
427
443
  if (parsed.status === "compacting") {
428
444
  sendAndRecord(session, { type: "compacting", active: true });
429
445
  } else if (session.compacting) {
@@ -431,34 +447,34 @@ function attachMessageProcessor(ctx) {
431
447
  }
432
448
  session.compacting = parsed.status === "compacting";
433
449
 
434
- } else if (parsed.type === "system" && parsed.subtype === "task_started") {
435
- var parentId = parsed.tool_use_id;
450
+ } else if (parsed.yokeType === "task_started") {
451
+ var parentId = parsed.parentToolId;
436
452
  if (parentId) {
437
453
  if (!session.taskIdMap) session.taskIdMap = {};
438
- session.taskIdMap[parentId] = parsed.task_id;
454
+ session.taskIdMap[parentId] = parsed.taskId;
439
455
  sendAndRecord(session, {
440
456
  type: "task_started",
441
457
  parentToolId: parentId,
442
- taskId: parsed.task_id,
458
+ taskId: parsed.taskId,
443
459
  description: parsed.description || "",
444
460
  });
445
461
  }
446
462
 
447
- } else if (parsed.type === "system" && parsed.subtype === "task_progress") {
448
- var parentId = parsed.tool_use_id;
463
+ } else if (parsed.yokeType === "task_progress") {
464
+ var parentId = parsed.parentToolId;
449
465
  if (parentId) {
450
466
  sendAndRecord(session, {
451
467
  type: "task_progress",
452
468
  parentToolId: parentId,
453
- taskId: parsed.task_id,
469
+ taskId: parsed.taskId,
454
470
  usage: parsed.usage || null,
455
- lastToolName: parsed.last_tool_name || null,
471
+ lastToolName: parsed.lastToolName || null,
456
472
  description: parsed.description || "",
457
473
  summary: parsed.summary || null,
458
474
  });
459
475
  }
460
476
 
461
- } else if (parsed.type === "system" && parsed.subtype === "task_updated") {
477
+ } else if (parsed.yokeType === "task_updated") {
462
478
  // Live task state patches (status, description, error, backgrounded)
463
479
  var taskId = parsed.task_id;
464
480
  var patch = parsed.patch || {};
@@ -477,19 +493,19 @@ function attachMessageProcessor(ctx) {
477
493
  });
478
494
  }
479
495
 
480
- } else if (parsed.type === "tool_progress") {
496
+ } else if (parsed.yokeType === "tool_progress") {
481
497
  // Sub-agent tool_progress: forward as activity update
482
- var parentId = parsed.parent_tool_use_id;
498
+ var parentId = parsed.parentToolId;
483
499
  if (parentId) {
484
500
  sendAndRecord(session, {
485
501
  type: "subagent_activity",
486
502
  parentToolId: parentId,
487
- text: parsed.content || "",
503
+ text: parsed.text || "",
488
504
  });
489
505
  }
490
506
 
491
- } else if (parsed.type === "task_notification") {
492
- var parentId = parsed.parent_tool_use_id;
507
+ } else if (parsed.yokeType === "task_notification") {
508
+ var parentId = parsed.parentToolId;
493
509
  if (parentId) {
494
510
  sendAndRecord(session, {
495
511
  type: "subagent_done",
@@ -501,15 +517,15 @@ function attachMessageProcessor(ctx) {
501
517
  }
502
518
  if (session.taskIdMap) {
503
519
  for (var k in session.taskIdMap) {
504
- if (session.taskIdMap[k] === parsed.task_id) {
520
+ if (session.taskIdMap[k] === parsed.taskId) {
505
521
  delete session.taskIdMap[k];
506
522
  break;
507
523
  }
508
524
  }
509
525
  }
510
526
 
511
- } else if (parsed.type === "rate_limit_event" && parsed.rate_limit_info) {
512
- var info = parsed.rate_limit_info;
527
+ } else if (parsed.yokeType === "rate_limit") {
528
+ var info = parsed.rateLimitInfo;
513
529
  console.log("[sdk-bridge] rate_limit_event for session " + session.localId + ": status=" + info.status + " resetsAt=" + info.resetsAt + " isUsingOverage=" + info.isUsingOverage + " isProcessing=" + session.isProcessing);
514
530
 
515
531
  // Broadcast reset time for top-bar usage link
@@ -565,13 +581,13 @@ function attachMessageProcessor(ctx) {
565
581
  }
566
582
  }
567
583
 
568
- } else if (parsed.type === "prompt_suggestion") {
584
+ } else if (parsed.yokeType === "prompt_suggestion") {
569
585
  sendAndRecord(session, {
570
586
  type: "prompt_suggestion",
571
587
  suggestion: parsed.suggestion || "",
572
588
  });
573
589
 
574
- } else if (parsed.type === "system" && parsed.subtype === "notification") {
590
+ } else if (parsed.yokeType === "notification") {
575
591
  var notifText = parsed.text || "";
576
592
  var notifPriority = parsed.priority || "low";
577
593
  if (notifText) {
@@ -585,12 +601,12 @@ function attachMessageProcessor(ctx) {
585
601
  });
586
602
  }
587
603
 
588
- } else if (parsed.type === "system" && parsed.subtype === "api_retry") {
589
- // Transient retry notification show in UI but don't persist in history
604
+ } else if (parsed.yokeType === "api_retry") {
605
+ // Transient retry notification, show in UI but don't persist in history
590
606
  var retryText = parsed.message || parsed.error || "Retrying API request...";
591
607
  sendToSession(session, { type: "system_info", text: retryText });
592
608
 
593
- } else if (parsed.type === "system") {
609
+ } else if (parsed.yokeType === "system") {
594
610
  // Catch-all for unhandled system subtypes (e.g. hook-block errors).
595
611
  // Extract any error text and surface it in the UI.
596
612
  var sysText = parsed.error || parsed.message || parsed.text || "";
@@ -604,7 +620,6 @@ function attachMessageProcessor(ctx) {
604
620
  console.log("[sdk-bridge] Unhandled system message (subtype=" + (parsed.subtype || "none") + "): " + sysText.substring(0, 200));
605
621
  sendAndRecord(session, { type: "error", text: sysText });
606
622
  }
607
- } else if (parsed.type && parsed.type !== "user") {
608
623
  }
609
624
  }
610
625
 
package/lib/sdk-worker.js CHANGED
@@ -1,3 +1,7 @@
1
+ // DEPRECATED: This file has been moved to lib/yoke/adapters/claude-worker.js
2
+ // It is kept here temporarily for backward compatibility with running worker processes.
3
+ // New worker spawns use the path from adapter.workerScriptPath.
4
+ //
1
5
  // sdk-worker.js — Standalone worker process for OS-level user isolation.
2
6
  // Runs as a target Linux user, loads the Claude Agent SDK, and communicates
3
7
  // with the main Clay daemon over a Unix domain socket using JSON lines.
package/lib/server-dm.js CHANGED
@@ -77,6 +77,7 @@ function attachDm(ctx) {
77
77
  primary: !!mate.primary,
78
78
  mateStatus: mate.status,
79
79
  seedData: mate.seedData || {},
80
+ vendor: mate.vendor || null,
80
81
  },
81
82
  }));
82
83
  return true;
package/lib/server.js CHANGED
@@ -459,6 +459,75 @@ 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
+
462
531
  // --- Skills routes (delegated to server-skills) ---
463
532
  if (skills.handleRequest(req, res, fullUrl)) return;
464
533
 
@@ -563,7 +632,22 @@ function createServer(opts) {
563
632
  }
564
633
 
565
634
  // Auth check for project routes
566
- if (!isRequestAuthed(req)) {
635
+ // Bypass auth for MCP bridge endpoint (localhost only).
636
+ // The mcp-bridge-server.js runs as a local child process and cannot carry cookies.
637
+ var projectUrlForAuth = stripPrefix(req.url.split("?")[0], slug);
638
+ // req.socket.remoteAddress may differ between HTTP/HTTPS (TLSSocket wraps net.Socket).
639
+ // Also check req.connection.remoteAddress for compatibility.
640
+ var remoteAddr = req.socket.remoteAddress
641
+ || (req.connection && req.connection.remoteAddress)
642
+ || "";
643
+ var isLocalhost = remoteAddr === "127.0.0.1" || remoteAddr === "::1" || remoteAddr === "::ffff:127.0.0.1";
644
+ var isMcpBridgeLocal = projectUrlForAuth === "/api/mcp-bridge"
645
+ && req.method === "POST"
646
+ && isLocalhost;
647
+ if (projectUrlForAuth === "/api/mcp-bridge") {
648
+ console.log("[server] MCP bridge auth: method=" + req.method + " addr=" + remoteAddr + " bypass=" + isMcpBridgeLocal);
649
+ }
650
+ if (!isMcpBridgeLocal && !isRequestAuthed(req)) {
567
651
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
568
652
  res.end(auth.getAuthPage());
569
653
  return;
@@ -872,6 +956,7 @@ function createServer(opts) {
872
956
  lanHost: lanHost,
873
957
  port: portNum,
874
958
  tls: !!tlsOptions,
959
+ authToken: pinHash || null,
875
960
  getProjectCount: function () { return projects.size; },
876
961
  getProjectList: function (userId) {
877
962
  var list = [];