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.
- package/lib/browser-mcp-server.js +32 -44
- package/lib/codex-defaults.js +18 -0
- package/lib/debate-mcp-server.js +14 -31
- package/lib/mcp-local.js +31 -1
- package/lib/project-connection.js +9 -6
- package/lib/project-debate.js +8 -0
- package/lib/project-filesystem.js +47 -1
- package/lib/project-http.js +75 -8
- package/lib/project-mate-interaction.js +102 -16
- package/lib/project-mcp.js +4 -0
- package/lib/project-notifications.js +9 -0
- package/lib/project-sessions.js +94 -51
- package/lib/project-user-message.js +12 -7
- package/lib/project.js +234 -99
- package/lib/public/app.js +135 -454
- 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 +338 -104
- 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 +26 -0
- package/lib/public/css/tooltip.css +47 -0
- package/lib/public/index.html +78 -26
- 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 +175 -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 +199 -153
- package/lib/public/modules/app-misc.js +23 -12
- package/lib/public/modules/app-notifications.js +119 -9
- package/lib/public/modules/app-panels.js +203 -49
- package/lib/public/modules/app-projects.js +161 -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 +102 -66
- package/lib/public/modules/dom-refs.js +21 -0
- package/lib/public/modules/filebrowser.js +173 -2
- package/lib/public/modules/input.js +122 -0
- package/lib/public/modules/markdown.js +5 -1
- 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 +79 -35
- 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/terminal.js +12 -0
- package/lib/public/modules/tools.js +18 -13
- package/lib/public/modules/tooltip.js +32 -5
- package/lib/sdk-bridge.js +562 -1114
- package/lib/sdk-message-processor.js +150 -135
- package/lib/sdk-worker.js +4 -0
- package/lib/server-dm.js +1 -0
- package/lib/server.js +86 -1
- package/lib/sessions.js +81 -37
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker.js +559 -0
- package/lib/yoke/adapters/claude.js +1483 -0
- package/lib/yoke/adapters/codex.js +1121 -0
- package/lib/yoke/adapters/gemini.js +709 -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 +98 -0
- package/lib/yoke/mcp-bridge-server.js +294 -0
- package/lib/yoke/package.json +7 -0
- 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.
|
|
41
|
-
var content = parsed.
|
|
53
|
+
var parentId = parsed.parentToolUseId;
|
|
54
|
+
var content = parsed.content;
|
|
42
55
|
if (!Array.isArray(content)) return;
|
|
43
56
|
|
|
44
|
-
if (parsed.
|
|
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
|
|
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.
|
|
92
|
+
if (parsed.yokeType === "init") {
|
|
80
93
|
console.log("[PERF] processSDKMessage: system/init +" + _elapsed + "ms");
|
|
81
94
|
}
|
|
82
|
-
if (parsed.
|
|
83
|
-
|
|
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.
|
|
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.
|
|
98
|
-
session.cliSessionId = parsed.
|
|
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.
|
|
102
|
-
session.cliSessionId = parsed.
|
|
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.
|
|
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.
|
|
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.
|
|
128
|
+
if (parsed.yokeType === "init") {
|
|
118
129
|
var fsSkills = discoverSkillDirs();
|
|
119
130
|
sm.skillNames = mergeSkills(parsed.skills, fsSkills);
|
|
120
|
-
if (parsed.
|
|
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.
|
|
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.
|
|
139
|
-
sendAndRecord(session, { type: "fast_mode_state", state: parsed.
|
|
149
|
+
if (parsed.fastModeState) {
|
|
150
|
+
sendAndRecord(session, { type: "fast_mode_state", state: parsed.fastModeState });
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
if (parsed.
|
|
144
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
167
|
-
|
|
172
|
+
} else if (parsed.yokeType === "text_delta" || parsed.yokeType === "tool_input_delta" || parsed.yokeType === "thinking_delta") {
|
|
173
|
+
var idx = parsed.blockId;
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
processSubagentMessage(session, parsed);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
+
processSubagentMessage(session, parsed);
|
|
226
227
|
|
|
227
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
365
|
-
duration: parsed.
|
|
366
|
+
cost: parsed.cost,
|
|
367
|
+
duration: parsed.duration,
|
|
366
368
|
usage: parsed.usage || null,
|
|
367
369
|
modelUsage: parsed.modelUsage || null,
|
|
368
|
-
sessionId: parsed.
|
|
370
|
+
sessionId: parsed.sessionId,
|
|
369
371
|
lastStreamInputTokens: lastStreamInput,
|
|
370
372
|
});
|
|
371
|
-
if (parsed.
|
|
372
|
-
sendAndRecord(session, { type: "fast_mode_state", state: parsed.
|
|
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.
|
|
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.
|
|
435
|
-
var parentId = parsed.
|
|
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.
|
|
454
|
+
session.taskIdMap[parentId] = parsed.taskId;
|
|
439
455
|
sendAndRecord(session, {
|
|
440
456
|
type: "task_started",
|
|
441
457
|
parentToolId: parentId,
|
|
442
|
-
taskId: parsed.
|
|
458
|
+
taskId: parsed.taskId,
|
|
443
459
|
description: parsed.description || "",
|
|
444
460
|
});
|
|
445
461
|
}
|
|
446
462
|
|
|
447
|
-
} else if (parsed.
|
|
448
|
-
var parentId = parsed.
|
|
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.
|
|
469
|
+
taskId: parsed.taskId,
|
|
454
470
|
usage: parsed.usage || null,
|
|
455
|
-
lastToolName: parsed.
|
|
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.
|
|
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.
|
|
496
|
+
} else if (parsed.yokeType === "tool_progress") {
|
|
481
497
|
// Sub-agent tool_progress: forward as activity update
|
|
482
|
-
var parentId = parsed.
|
|
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.
|
|
503
|
+
text: parsed.text || "",
|
|
488
504
|
});
|
|
489
505
|
}
|
|
490
506
|
|
|
491
|
-
} else if (parsed.
|
|
492
|
-
var parentId = parsed.
|
|
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.
|
|
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.
|
|
512
|
-
var info = parsed.
|
|
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.
|
|
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.
|
|
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.
|
|
589
|
-
// Transient retry notification
|
|
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.
|
|
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
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
|
-
|
|
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 = [];
|