clay-server 2.27.0-beta.8 → 2.27.0
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/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/project-connection.js +2 -0
- package/lib/project-debate.js +19 -12
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8521
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +389 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -0
- package/package.json +1 -1
package/lib/sdk-bridge.js
CHANGED
|
@@ -6,123 +6,9 @@ var net = require("net");
|
|
|
6
6
|
var { execSync, spawn } = require("child_process");
|
|
7
7
|
var { resolveOsUserInfo } = require("./os-users");
|
|
8
8
|
var usersModule = require("./users");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function splitShellSegments(cmd) {
|
|
13
|
-
var segments = [];
|
|
14
|
-
var current = "";
|
|
15
|
-
var inSingle = false;
|
|
16
|
-
var inDouble = false;
|
|
17
|
-
var parenDepth = 0;
|
|
18
|
-
var i = 0;
|
|
19
|
-
while (i < cmd.length) {
|
|
20
|
-
var ch = cmd[i];
|
|
21
|
-
|
|
22
|
-
// Handle escape
|
|
23
|
-
if (ch === "\\" && i + 1 < cmd.length && !inSingle) {
|
|
24
|
-
current += ch + cmd[i + 1];
|
|
25
|
-
i += 2;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Quote tracking
|
|
30
|
-
if (ch === "'" && !inDouble) { inSingle = !inSingle; current += ch; i++; continue; }
|
|
31
|
-
if (ch === '"' && !inSingle) { inDouble = !inDouble; current += ch; i++; continue; }
|
|
32
|
-
|
|
33
|
-
// Inside quotes: no splitting
|
|
34
|
-
if (inSingle || inDouble) { current += ch; i++; continue; }
|
|
35
|
-
|
|
36
|
-
// Parentheses/subshell tracking
|
|
37
|
-
if (ch === "(" || ch === "$" && i + 1 < cmd.length && cmd[i + 1] === "(") {
|
|
38
|
-
parenDepth++;
|
|
39
|
-
current += ch;
|
|
40
|
-
i++;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (ch === ")" && parenDepth > 0) {
|
|
44
|
-
parenDepth--;
|
|
45
|
-
current += ch;
|
|
46
|
-
i++;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Inside subshell: no splitting
|
|
51
|
-
if (parenDepth > 0) { current += ch; i++; continue; }
|
|
52
|
-
|
|
53
|
-
// Check for operators: &&, ||, ;, |
|
|
54
|
-
if (ch === "&" && i + 1 < cmd.length && cmd[i + 1] === "&") {
|
|
55
|
-
segments.push(current);
|
|
56
|
-
current = "";
|
|
57
|
-
i += 2;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (ch === "|" && i + 1 < cmd.length && cmd[i + 1] === "|") {
|
|
61
|
-
segments.push(current);
|
|
62
|
-
current = "";
|
|
63
|
-
i += 2;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (ch === "|") {
|
|
67
|
-
segments.push(current);
|
|
68
|
-
current = "";
|
|
69
|
-
i++;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (ch === ";") {
|
|
73
|
-
segments.push(current);
|
|
74
|
-
current = "";
|
|
75
|
-
i++;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
current += ch;
|
|
80
|
-
i++;
|
|
81
|
-
}
|
|
82
|
-
if (current) segments.push(current);
|
|
83
|
-
return segments;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Async message queue for streaming input to SDK
|
|
87
|
-
function createMessageQueue() {
|
|
88
|
-
var queue = [];
|
|
89
|
-
var waiting = null;
|
|
90
|
-
var ended = false;
|
|
91
|
-
return {
|
|
92
|
-
push: function(msg) {
|
|
93
|
-
if (waiting) {
|
|
94
|
-
var resolve = waiting;
|
|
95
|
-
waiting = null;
|
|
96
|
-
resolve({ value: msg, done: false });
|
|
97
|
-
} else {
|
|
98
|
-
queue.push(msg);
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
end: function() {
|
|
102
|
-
ended = true;
|
|
103
|
-
if (waiting) {
|
|
104
|
-
var resolve = waiting;
|
|
105
|
-
waiting = null;
|
|
106
|
-
resolve({ value: undefined, done: true });
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
[Symbol.asyncIterator]: function() {
|
|
110
|
-
return {
|
|
111
|
-
next: function() {
|
|
112
|
-
if (queue.length > 0) {
|
|
113
|
-
return Promise.resolve({ value: queue.shift(), done: false });
|
|
114
|
-
}
|
|
115
|
-
if (ended) {
|
|
116
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
117
|
-
}
|
|
118
|
-
return new Promise(function(resolve) {
|
|
119
|
-
waiting = resolve;
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
}
|
|
9
|
+
var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discovery");
|
|
10
|
+
var { createMessageQueue } = require("./sdk-message-queue");
|
|
11
|
+
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
126
12
|
|
|
127
13
|
function createSDKBridge(opts) {
|
|
128
14
|
var cwd = opts.cwd;
|
|
@@ -130,6 +16,7 @@ function createSDKBridge(opts) {
|
|
|
130
16
|
var sm = opts.sessionManager; // session manager instance
|
|
131
17
|
var send = opts.send; // broadcast to all clients
|
|
132
18
|
var pushModule = opts.pushModule;
|
|
19
|
+
var getNotificationsModule = opts.getNotificationsModule || function () { return null; };
|
|
133
20
|
var getSDK = opts.getSDK;
|
|
134
21
|
var mateDisplayName = opts.mateDisplayName || "";
|
|
135
22
|
var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
|
|
@@ -138,596 +25,80 @@ function createSDKBridge(opts) {
|
|
|
138
25
|
var onProcessingChanged = opts.onProcessingChanged || function () {};
|
|
139
26
|
var onTurnDone = opts.onTurnDone || null;
|
|
140
27
|
|
|
141
|
-
// ---
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
var merged = new Set();
|
|
176
|
-
if (Array.isArray(sdkSkills)) {
|
|
177
|
-
for (var i = 0; i < sdkSkills.length; i++) {
|
|
178
|
-
merged.add(sdkSkills[i]);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
var fsNames = Object.keys(fsSkills);
|
|
182
|
-
for (var i = 0; i < fsNames.length; i++) {
|
|
183
|
-
merged.add(fsNames[i]);
|
|
184
|
-
}
|
|
185
|
-
return merged;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function sendAndRecord(session, obj) {
|
|
189
|
-
sm.sendAndRecord(session, obj);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function sendToSession(session, obj) {
|
|
193
|
-
sm.sendToSession(session, obj);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function processSDKMessage(session, parsed) {
|
|
197
|
-
// Timing: log key SDK milestones relative to query start
|
|
198
|
-
if (session._queryStartTs) {
|
|
199
|
-
var _elapsed = Date.now() - session._queryStartTs;
|
|
200
|
-
if (parsed.type === "system" && parsed.subtype === "init") {
|
|
201
|
-
console.log("[PERF] processSDKMessage: system/init +" + _elapsed + "ms");
|
|
202
|
-
}
|
|
203
|
-
if (parsed.type === "stream_event" && parsed.event) {
|
|
204
|
-
if (parsed.event.type === "message_start") {
|
|
205
|
-
console.log("[PERF] processSDKMessage: message_start (API response begun) +" + _elapsed + "ms");
|
|
206
|
-
}
|
|
207
|
-
if (parsed.event.type === "content_block_delta" && !session._firstTextLogged) {
|
|
208
|
-
session._firstTextLogged = true;
|
|
209
|
-
console.log("[PERF] processSDKMessage: FIRST content_block_delta (visible text) +" + _elapsed + "ms");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (parsed.type === "result") {
|
|
213
|
-
console.log("[PERF] processSDKMessage: result +" + _elapsed + "ms");
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Extract session_id from any message that carries it
|
|
218
|
-
if (parsed.session_id && !session.cliSessionId) {
|
|
219
|
-
session.cliSessionId = parsed.session_id;
|
|
220
|
-
sm.saveSessionFile(session);
|
|
221
|
-
sendAndRecord(session, { type: "session_id", cliSessionId: session.cliSessionId });
|
|
222
|
-
} else if (parsed.session_id) {
|
|
223
|
-
session.cliSessionId = parsed.session_id;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Capture message UUIDs for rewind support
|
|
227
|
-
if (parsed.uuid) {
|
|
228
|
-
if (parsed.type === "user" && !parsed.parent_tool_use_id) {
|
|
229
|
-
session.messageUUIDs.push({ uuid: parsed.uuid, type: "user", historyIndex: session.history.length });
|
|
230
|
-
sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "user" });
|
|
231
|
-
} else if (parsed.type === "assistant") {
|
|
232
|
-
session.messageUUIDs.push({ uuid: parsed.uuid, type: "assistant", historyIndex: session.history.length });
|
|
233
|
-
sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "assistant" });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Cache slash_commands and model from CLI init message
|
|
238
|
-
if (parsed.type === "system" && parsed.subtype === "init") {
|
|
239
|
-
var fsSkills = discoverSkillDirs();
|
|
240
|
-
sm.skillNames = mergeSkills(parsed.skills, fsSkills);
|
|
241
|
-
if (parsed.slash_commands) {
|
|
242
|
-
// Union: SDK slash_commands + merged skills (deduplicated)
|
|
243
|
-
var seen = new Set();
|
|
244
|
-
var combined = [];
|
|
245
|
-
var all = parsed.slash_commands.concat(Array.from(sm.skillNames));
|
|
246
|
-
for (var k = 0; k < all.length; k++) {
|
|
247
|
-
if (!seen.has(all[k])) {
|
|
248
|
-
seen.add(all[k]);
|
|
249
|
-
combined.push(all[k]);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
sm.slashCommands = combined;
|
|
253
|
-
send({ type: "slash_commands", commands: sm.slashCommands });
|
|
254
|
-
}
|
|
255
|
-
if (parsed.model) {
|
|
256
|
-
sm.currentModel = sm._savedDefaultModel || parsed.model;
|
|
257
|
-
send({ type: "model_info", model: sm.currentModel, models: sm.availableModels || [] });
|
|
258
|
-
}
|
|
259
|
-
if (parsed.fast_mode_state) {
|
|
260
|
-
sendAndRecord(session, { type: "fast_mode_state", state: parsed.fast_mode_state });
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (parsed.type === "stream_event" && parsed.event) {
|
|
265
|
-
var evt = parsed.event;
|
|
266
|
-
|
|
267
|
-
if (evt.type === "message_start" && evt.message && evt.message.usage) {
|
|
268
|
-
var u = evt.message.usage;
|
|
269
|
-
session.lastStreamInputTokens = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (evt.type === "content_block_start") {
|
|
273
|
-
var block = evt.content_block;
|
|
274
|
-
var idx = evt.index;
|
|
275
|
-
|
|
276
|
-
if (block.type === "tool_use") {
|
|
277
|
-
session.blocks[idx] = { type: "tool_use", id: block.id, name: block.name, inputJson: "" };
|
|
278
|
-
sendAndRecord(session, { type: "tool_start", id: block.id, name: block.name });
|
|
279
|
-
} else if (block.type === "thinking") {
|
|
280
|
-
session.blocks[idx] = { type: "thinking", thinkingText: "", startTime: Date.now() };
|
|
281
|
-
sendAndRecord(session, { type: "thinking_start" });
|
|
282
|
-
} else if (block.type === "text") {
|
|
283
|
-
session.blocks[idx] = { type: "text" };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (evt.type === "content_block_delta" && evt.delta) {
|
|
288
|
-
var idx = evt.index;
|
|
289
|
-
|
|
290
|
-
if (evt.delta.type === "text_delta" && typeof evt.delta.text === "string") {
|
|
291
|
-
session.streamedText = true;
|
|
292
|
-
if (session.responsePreview.length < 200) {
|
|
293
|
-
session.responsePreview += evt.delta.text;
|
|
294
|
-
}
|
|
295
|
-
// Accumulate text for mate DM response
|
|
296
|
-
if (typeof session._mateDmResponseText === "string") {
|
|
297
|
-
session._mateDmResponseText += evt.delta.text;
|
|
298
|
-
}
|
|
299
|
-
sendAndRecord(session, { type: "delta", text: evt.delta.text });
|
|
300
|
-
} else if (evt.delta.type === "input_json_delta" && session.blocks[idx]) {
|
|
301
|
-
session.blocks[idx].inputJson += evt.delta.partial_json;
|
|
302
|
-
} else if (evt.delta.type === "thinking_delta" && session.blocks[idx]) {
|
|
303
|
-
session.blocks[idx].thinkingText += evt.delta.thinking;
|
|
304
|
-
sendAndRecord(session, { type: "thinking_delta", text: evt.delta.thinking });
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (evt.type === "content_block_stop") {
|
|
309
|
-
var idx = evt.index;
|
|
310
|
-
var block = session.blocks[idx];
|
|
311
|
-
|
|
312
|
-
if (block && block.type === "tool_use") {
|
|
313
|
-
var input = {};
|
|
314
|
-
try { input = JSON.parse(block.inputJson); } catch {}
|
|
315
|
-
sendAndRecord(session, { type: "tool_executing", id: block.id, name: block.name, input: input });
|
|
316
|
-
|
|
317
|
-
// Track active Task tools for sub-agent done detection
|
|
318
|
-
if (block.name === "Task") {
|
|
319
|
-
if (!session.activeTaskToolIds) session.activeTaskToolIds = {};
|
|
320
|
-
session.activeTaskToolIds[block.id] = true;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (pushModule && block.name === "AskUserQuestion" && input.questions) {
|
|
324
|
-
var q = input.questions[0];
|
|
325
|
-
pushModule.sendPush({
|
|
326
|
-
type: "ask_user",
|
|
327
|
-
slug: slug,
|
|
328
|
-
title: (mateDisplayName || "Claude") + " has a question",
|
|
329
|
-
body: q ? q.question : "Waiting for your response",
|
|
330
|
-
tag: "claude-ask",
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
} else if (block && block.type === "thinking") {
|
|
334
|
-
var duration = block.startTime ? (Date.now() - block.startTime) / 1000 : 0;
|
|
335
|
-
sendAndRecord(session, { type: "thinking_stop", duration: duration });
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
delete session.blocks[idx];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
} else if ((parsed.type === "assistant" || parsed.type === "user") && parsed.message && parsed.message.content) {
|
|
342
|
-
// Sub-agent messages: extract tool_use blocks for activity display
|
|
343
|
-
if (parsed.parent_tool_use_id) {
|
|
344
|
-
processSubagentMessage(session, parsed);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
var content = parsed.message.content;
|
|
349
|
-
|
|
350
|
-
// Fallback: if assistant text wasn't streamed via deltas, send it now
|
|
351
|
-
if (parsed.type === "assistant" && !session.streamedText && Array.isArray(content)) {
|
|
352
|
-
var assistantText = content
|
|
353
|
-
.filter(function(c) { return c.type === "text"; })
|
|
354
|
-
.map(function(c) { return c.text; })
|
|
355
|
-
.join("");
|
|
356
|
-
if (assistantText) {
|
|
357
|
-
if (session.responsePreview.length < 200) {
|
|
358
|
-
session.responsePreview += assistantText;
|
|
359
|
-
}
|
|
360
|
-
sendAndRecord(session, { type: "delta", text: assistantText });
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Check for local slash command output in user messages
|
|
365
|
-
if (parsed.type === "user") {
|
|
366
|
-
var fullText = "";
|
|
367
|
-
if (typeof content === "string") {
|
|
368
|
-
fullText = content;
|
|
369
|
-
} else if (Array.isArray(content)) {
|
|
370
|
-
fullText = content
|
|
371
|
-
.filter(function(c) { return c.type === "text"; })
|
|
372
|
-
.map(function(c) { return c.text; })
|
|
373
|
-
.join("\n");
|
|
374
|
-
}
|
|
375
|
-
if (fullText.indexOf("local-command-stdout") !== -1) {
|
|
376
|
-
var m = fullText.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
377
|
-
if (m) {
|
|
378
|
-
sendAndRecord(session, { type: "slash_command_result", text: m[1].trim() });
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (Array.isArray(content)) {
|
|
384
|
-
for (var i = 0; i < content.length; i++) {
|
|
385
|
-
var block = content[i];
|
|
386
|
-
if (block.type === "tool_result" && !session.sentToolResults[block.tool_use_id]) {
|
|
387
|
-
// Clear active Task tool when its result arrives
|
|
388
|
-
if (session.activeTaskToolIds && session.activeTaskToolIds[block.tool_use_id]) {
|
|
389
|
-
sendAndRecord(session, {
|
|
390
|
-
type: "subagent_done",
|
|
391
|
-
parentToolId: block.tool_use_id,
|
|
392
|
-
});
|
|
393
|
-
delete session.activeTaskToolIds[block.tool_use_id];
|
|
394
|
-
}
|
|
395
|
-
var resultText = "";
|
|
396
|
-
var resultImages = [];
|
|
397
|
-
if (typeof block.content === "string") {
|
|
398
|
-
resultText = block.content;
|
|
399
|
-
} else if (Array.isArray(block.content)) {
|
|
400
|
-
resultText = block.content
|
|
401
|
-
.filter(function(c) { return c.type === "text"; })
|
|
402
|
-
.map(function(c) { return c.text; })
|
|
403
|
-
.join("\n");
|
|
404
|
-
for (var ri = 0; ri < block.content.length; ri++) {
|
|
405
|
-
var rc = block.content[ri];
|
|
406
|
-
if (rc.type === "image" && rc.source) {
|
|
407
|
-
resultImages.push({
|
|
408
|
-
mediaType: rc.source.media_type,
|
|
409
|
-
data: rc.source.data,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
session.sentToolResults[block.tool_use_id] = true;
|
|
415
|
-
var toolResultMsg = {
|
|
416
|
-
type: "tool_result",
|
|
417
|
-
id: block.tool_use_id,
|
|
418
|
-
content: resultText,
|
|
419
|
-
is_error: block.is_error || false,
|
|
420
|
-
};
|
|
421
|
-
if (resultImages.length > 0) toolResultMsg.images = resultImages;
|
|
422
|
-
sendAndRecord(session, toolResultMsg);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
} else if (parsed.type === "result") {
|
|
428
|
-
session.blocks = {};
|
|
429
|
-
session.sentToolResults = {};
|
|
430
|
-
session.pendingPermissions = {};
|
|
431
|
-
session.pendingElicitations = {};
|
|
432
|
-
// Record ask_user_answered for any leftover pending questions so replay pairs correctly
|
|
433
|
-
var leftoverAskIds = Object.keys(session.pendingAskUser);
|
|
434
|
-
for (var lai = 0; lai < leftoverAskIds.length; lai++) {
|
|
435
|
-
sendAndRecord(session, { type: "ask_user_answered", toolId: leftoverAskIds[lai] });
|
|
436
|
-
}
|
|
437
|
-
session.pendingAskUser = {};
|
|
438
|
-
session.activeTaskToolIds = {};
|
|
439
|
-
session.taskIdMap = {};
|
|
440
|
-
// Only clear rateLimitResetsAt on genuine success (non-zero cost).
|
|
441
|
-
// When rate-limited, the SDK sends result with zero cost right after
|
|
442
|
-
// rate_limit_event; clearing here would prevent auto-continue scheduling.
|
|
443
|
-
if (parsed.total_cost_usd && parsed.total_cost_usd > 0) {
|
|
444
|
-
session.rateLimitResetsAt = null;
|
|
445
|
-
}
|
|
446
|
-
console.log("[sdk-bridge] result handler: session " + session.localId + " cost=" + parsed.total_cost_usd + " rateLimitResetsAt=" + session.rateLimitResetsAt);
|
|
447
|
-
|
|
448
|
-
// Handle SDK execution errors: show the error to the user instead of
|
|
449
|
-
// silently swallowing it. These have subtype "error_during_execution".
|
|
450
|
-
if (parsed.subtype === "error_during_execution") {
|
|
451
|
-
var execErrors = parsed.errors || [];
|
|
452
|
-
var execError = execErrors.length > 0
|
|
453
|
-
? execErrors.join("; ")
|
|
454
|
-
: "Unknown SDK error";
|
|
455
|
-
if (parsed.terminal_reason) execError += " (reason: " + parsed.terminal_reason + ")";
|
|
456
|
-
console.error("[sdk-bridge] Execution error for session " + session.localId + ": " + execError);
|
|
457
|
-
session.isProcessing = false;
|
|
458
|
-
onProcessingChanged();
|
|
459
|
-
sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
|
|
460
|
-
sendAndRecord(session, { type: "done", code: 1 });
|
|
461
|
-
sm.broadcastSessionList();
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
session.isProcessing = false;
|
|
466
|
-
onProcessingChanged();
|
|
467
|
-
// Detect "Not logged in" scenario early for the check below
|
|
468
|
-
var previewTrimmed = (session.responsePreview || "").trim();
|
|
469
|
-
var isZeroCost = !parsed.total_cost_usd || parsed.total_cost_usd === 0;
|
|
470
|
-
var isLoginPrompt = isZeroCost && previewTrimmed.length < 100
|
|
471
|
-
&& /not logged in/i.test(previewTrimmed) && /\/login/i.test(previewTrimmed);
|
|
472
|
-
// Fetch rich context usage breakdown (fire-and-forget, non-blocking)
|
|
473
|
-
if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
|
|
474
|
-
session.queryInstance.getContextUsage().then(function(ctxUsage) {
|
|
475
|
-
session.lastContextUsage = ctxUsage;
|
|
476
|
-
sendToSession(session, { type: "context_usage", data: ctxUsage });
|
|
477
|
-
}).catch(function(e) {
|
|
478
|
-
console.error("[sdk-bridge] getContextUsage failed (non-fatal):", e.message || e);
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
var lastStreamInput = session.lastStreamInputTokens || null;
|
|
482
|
-
session.lastStreamInputTokens = null;
|
|
483
|
-
sendAndRecord(session, {
|
|
484
|
-
type: "result",
|
|
485
|
-
cost: parsed.total_cost_usd,
|
|
486
|
-
duration: parsed.duration_ms,
|
|
487
|
-
usage: parsed.usage || null,
|
|
488
|
-
modelUsage: parsed.modelUsage || null,
|
|
489
|
-
sessionId: parsed.session_id,
|
|
490
|
-
lastStreamInputTokens: lastStreamInput,
|
|
491
|
-
});
|
|
492
|
-
if (parsed.fast_mode_state) {
|
|
493
|
-
sendAndRecord(session, { type: "fast_mode_state", state: parsed.fast_mode_state });
|
|
494
|
-
}
|
|
495
|
-
// Detect "Not logged in · Please run /login" from SDK.
|
|
496
|
-
// This is a short canned response with zero cost, not actual AI output.
|
|
497
|
-
if (isLoginPrompt) {
|
|
498
|
-
var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
|
|
499
|
-
var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
|
|
500
|
-
var canAutoLogin = !usersModule.isMultiUser()
|
|
501
|
-
|| !!authLinuxUser
|
|
502
|
-
|| (authUser && authUser.role === "admin");
|
|
503
|
-
sendAndRecord(session, {
|
|
504
|
-
type: "auth_required",
|
|
505
|
-
text: "Claude Code is not logged in.",
|
|
506
|
-
linuxUser: authLinuxUser,
|
|
507
|
-
canAutoLogin: canAutoLogin,
|
|
508
|
-
});
|
|
509
|
-
// Reset CLI session so next query starts fresh with new auth
|
|
510
|
-
session.cliSessionId = null;
|
|
511
|
-
}
|
|
512
|
-
sendAndRecord(session, { type: "done", code: 0 });
|
|
513
|
-
if (pushModule) {
|
|
514
|
-
var preview = (session.responsePreview || "").replace(/\s+/g, " ").trim();
|
|
515
|
-
if (preview.length > 140) preview = preview.substring(0, 140) + "...";
|
|
516
|
-
pushModule.sendPush({
|
|
517
|
-
type: "done",
|
|
518
|
-
slug: slug,
|
|
519
|
-
title: mateDisplayName ? (mateDisplayName + " responded") : (session.title || "Claude"),
|
|
520
|
-
body: preview || "Response ready",
|
|
521
|
-
tag: "claude-done",
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
// Reset for next turn in the same query
|
|
525
|
-
var donePreview = session.responsePreview || "";
|
|
526
|
-
session.responsePreview = "";
|
|
527
|
-
session.streamedText = false;
|
|
528
|
-
sm.broadcastSessionList();
|
|
529
|
-
if (onTurnDone) {
|
|
530
|
-
try { onTurnDone(session, donePreview); } catch (e) {}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
} else if (parsed.type === "system" && parsed.subtype === "status") {
|
|
534
|
-
if (parsed.status === "compacting") {
|
|
535
|
-
sendAndRecord(session, { type: "compacting", active: true });
|
|
536
|
-
} else if (session.compacting) {
|
|
537
|
-
sendAndRecord(session, { type: "compacting", active: false });
|
|
538
|
-
}
|
|
539
|
-
session.compacting = parsed.status === "compacting";
|
|
540
|
-
|
|
541
|
-
} else if (parsed.type === "system" && parsed.subtype === "task_started") {
|
|
542
|
-
var parentId = parsed.tool_use_id;
|
|
543
|
-
if (parentId) {
|
|
544
|
-
if (!session.taskIdMap) session.taskIdMap = {};
|
|
545
|
-
session.taskIdMap[parentId] = parsed.task_id;
|
|
546
|
-
sendAndRecord(session, {
|
|
547
|
-
type: "task_started",
|
|
548
|
-
parentToolId: parentId,
|
|
549
|
-
taskId: parsed.task_id,
|
|
550
|
-
description: parsed.description || "",
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
} else if (parsed.type === "system" && parsed.subtype === "task_progress") {
|
|
555
|
-
var parentId = parsed.tool_use_id;
|
|
556
|
-
if (parentId) {
|
|
557
|
-
sendAndRecord(session, {
|
|
558
|
-
type: "task_progress",
|
|
559
|
-
parentToolId: parentId,
|
|
560
|
-
taskId: parsed.task_id,
|
|
561
|
-
usage: parsed.usage || null,
|
|
562
|
-
lastToolName: parsed.last_tool_name || null,
|
|
563
|
-
description: parsed.description || "",
|
|
564
|
-
summary: parsed.summary || null,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
} else if (parsed.type === "tool_progress") {
|
|
569
|
-
// Sub-agent tool_progress: forward as activity update
|
|
570
|
-
var parentId = parsed.parent_tool_use_id;
|
|
571
|
-
if (parentId) {
|
|
572
|
-
sendAndRecord(session, {
|
|
573
|
-
type: "subagent_activity",
|
|
574
|
-
parentToolId: parentId,
|
|
575
|
-
text: parsed.content || "",
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
} else if (parsed.type === "task_notification") {
|
|
580
|
-
var parentId = parsed.parent_tool_use_id;
|
|
581
|
-
if (parentId) {
|
|
582
|
-
sendAndRecord(session, {
|
|
583
|
-
type: "subagent_done",
|
|
584
|
-
parentToolId: parentId,
|
|
585
|
-
status: parsed.status || "completed",
|
|
586
|
-
summary: parsed.summary || "",
|
|
587
|
-
usage: parsed.usage || null,
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
if (session.taskIdMap) {
|
|
591
|
-
for (var k in session.taskIdMap) {
|
|
592
|
-
if (session.taskIdMap[k] === parsed.task_id) {
|
|
593
|
-
delete session.taskIdMap[k];
|
|
594
|
-
break;
|
|
28
|
+
// --- Idle session reaper ---
|
|
29
|
+
// In single-user (in-process) mode, each session's Claude child process stays
|
|
30
|
+
// alive between turns because the messageQueue push-stream is never ended.
|
|
31
|
+
// Without a reaper, processes accumulate indefinitely as users switch between
|
|
32
|
+
// sessions and projects. This reaper ends the messageQueue for sessions that
|
|
33
|
+
// have been idle for IDLE_TIMEOUT_MS, allowing processQueryStream's finally
|
|
34
|
+
// block to clean up the child process. Session state on disk is preserved —
|
|
35
|
+
// the next startQuery() call resumes with a fresh process.
|
|
36
|
+
var IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
37
|
+
var IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every 60 seconds
|
|
38
|
+
var _idleReaperTimer = null;
|
|
39
|
+
|
|
40
|
+
function startIdleReaper() {
|
|
41
|
+
if (_idleReaperTimer) return;
|
|
42
|
+
_idleReaperTimer = setInterval(function () {
|
|
43
|
+
var now = Date.now();
|
|
44
|
+
sm.sessions.forEach(function (session) {
|
|
45
|
+
// Skip sessions that are actively processing, have no query, use workers,
|
|
46
|
+
// or are single-turn (Ralph Loop — managed by onQueryComplete).
|
|
47
|
+
if (session.isProcessing) return;
|
|
48
|
+
if (!session.queryInstance) return;
|
|
49
|
+
if (session.worker) return;
|
|
50
|
+
if (session.singleTurn) return;
|
|
51
|
+
if (session.destroying) return;
|
|
52
|
+
|
|
53
|
+
var lastActivity = session.lastActivityAt || 0;
|
|
54
|
+
if (now - lastActivity > IDLE_TIMEOUT_MS) {
|
|
55
|
+
console.log("[sdk-bridge] Reaping idle session " + session.localId +
|
|
56
|
+
" (idle " + Math.round((now - lastActivity) / 60000) + "min)" +
|
|
57
|
+
(session.title ? " title=" + JSON.stringify(session.title) : ""));
|
|
58
|
+
// End the message queue so the for-await loop in processQueryStream
|
|
59
|
+
// exits naturally, triggering the finally block cleanup.
|
|
60
|
+
if (session.messageQueue && typeof session.messageQueue.end === "function") {
|
|
61
|
+
try { session.messageQueue.end(); } catch (e) {}
|
|
595
62
|
}
|
|
596
63
|
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
} else if (parsed.type === "rate_limit_event" && parsed.rate_limit_info) {
|
|
600
|
-
var info = parsed.rate_limit_info;
|
|
601
|
-
console.log("[sdk-bridge] rate_limit_event for session " + session.localId + ": status=" + info.status + " resetsAt=" + info.resetsAt + " isUsingOverage=" + info.isUsingOverage + " isProcessing=" + session.isProcessing);
|
|
602
|
-
|
|
603
|
-
// Broadcast reset time for top-bar usage link
|
|
604
|
-
if (info.rateLimitType && info.resetsAt) {
|
|
605
|
-
send({
|
|
606
|
-
type: "rate_limit_usage",
|
|
607
|
-
rateLimitType: info.rateLimitType,
|
|
608
|
-
resetsAt: info.resetsAt * 1000,
|
|
609
|
-
status: info.status,
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Warning/rejection handling (existing behavior)
|
|
614
|
-
if (info.status === "allowed_warning" || info.status === "rejected") {
|
|
615
|
-
sendAndRecord(session, {
|
|
616
|
-
type: "rate_limit",
|
|
617
|
-
status: info.status,
|
|
618
|
-
resetsAt: info.resetsAt ? info.resetsAt * 1000 : null,
|
|
619
|
-
rateLimitType: info.rateLimitType || null,
|
|
620
|
-
utilization: info.utilization || null,
|
|
621
|
-
isUsingOverage: info.isUsingOverage || false,
|
|
622
|
-
});
|
|
623
|
-
// Track rejection for auto-continue / scheduled message support
|
|
624
|
-
if (info.status === "rejected" && info.resetsAt) {
|
|
625
|
-
session.rateLimitResetsAt = info.resetsAt * 1000;
|
|
626
|
-
|
|
627
|
-
// Schedule auto-continue immediately on rejection (don't wait for
|
|
628
|
-
// query completion which has timing issues with worker/non-worker paths).
|
|
629
|
-
if (!session.scheduledMessage && !session.destroying) {
|
|
630
|
-
var acEnabled = session.onQueryComplete ||
|
|
631
|
-
(typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
|
|
632
|
-
console.log("[sdk-bridge] rate_limit rejected: acEnabled=" + acEnabled + " overage=" + !!info.isUsingOverage + " session=" + session.localId);
|
|
633
|
-
if (acEnabled) {
|
|
634
|
-
session.rateLimitAutoContinuePending = true;
|
|
635
|
-
if (info.isUsingOverage) {
|
|
636
|
-
// Extra usage available: send continue immediately (5s delay for query to finish)
|
|
637
|
-
console.log("[sdk-bridge] Overage available, sending immediate continue for session " + session.localId);
|
|
638
|
-
session.rateLimitResetsAt = null;
|
|
639
|
-
if (typeof opts.scheduleMessage === "function") {
|
|
640
|
-
opts.scheduleMessage(session, "continue", Date.now());
|
|
641
|
-
}
|
|
642
|
-
} else {
|
|
643
|
-
// No overage: schedule after rate limit resets
|
|
644
|
-
var acResetsAt = session.rateLimitResetsAt;
|
|
645
|
-
session.rateLimitResetsAt = null;
|
|
646
|
-
console.log("[sdk-bridge] Scheduling auto-continue on rate limit rejection for session " + session.localId);
|
|
647
|
-
if (typeof opts.scheduleMessage === "function") {
|
|
648
|
-
opts.scheduleMessage(session, "continue", acResetsAt);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
} else if (parsed.type === "prompt_suggestion") {
|
|
657
|
-
sendAndRecord(session, {
|
|
658
|
-
type: "prompt_suggestion",
|
|
659
|
-
suggestion: parsed.suggestion || "",
|
|
660
64
|
});
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
// Extract any error text and surface it in the UI.
|
|
665
|
-
var sysText = parsed.error || parsed.message || parsed.text || "";
|
|
666
|
-
if (!sysText && Array.isArray(parsed.content)) {
|
|
667
|
-
sysText = parsed.content
|
|
668
|
-
.filter(function(c) { return c.type === "text"; })
|
|
669
|
-
.map(function(c) { return c.text; })
|
|
670
|
-
.join("\n");
|
|
671
|
-
}
|
|
672
|
-
if (sysText) {
|
|
673
|
-
console.log("[sdk-bridge] Unhandled system message (subtype=" + (parsed.subtype || "none") + "): " + sysText.substring(0, 200));
|
|
674
|
-
sendAndRecord(session, { type: "error", text: sysText });
|
|
675
|
-
}
|
|
676
|
-
} else if (parsed.type && parsed.type !== "user") {
|
|
677
|
-
}
|
|
65
|
+
}, IDLE_CHECK_INTERVAL_MS);
|
|
66
|
+
// Don't prevent process exit
|
|
67
|
+
if (_idleReaperTimer.unref) _idleReaperTimer.unref();
|
|
678
68
|
}
|
|
679
69
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (name === "Read" && input && input.file_path) return "Reading " + input.file_path.split("/").pop();
|
|
685
|
-
if (name === "Edit" && input && input.file_path) return "Editing " + input.file_path.split("/").pop();
|
|
686
|
-
if (name === "Write" && input && input.file_path) return "Writing " + input.file_path.split("/").pop();
|
|
687
|
-
if (name === "Grep" && input && input.pattern) return "Searching for " + input.pattern;
|
|
688
|
-
if (name === "Glob" && input && input.pattern) return "Finding " + input.pattern;
|
|
689
|
-
if (name === "WebSearch" && input && input.query) return "Searching: " + input.query;
|
|
690
|
-
if (name === "WebFetch") return "Fetching URL...";
|
|
691
|
-
if (name === "Task" && input && input.description) return input.description;
|
|
692
|
-
return "Running " + name + "...";
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function processSubagentMessage(session, parsed) {
|
|
696
|
-
var parentId = parsed.parent_tool_use_id;
|
|
697
|
-
var content = parsed.message.content;
|
|
698
|
-
if (!Array.isArray(content)) return;
|
|
699
|
-
|
|
700
|
-
if (parsed.type === "assistant") {
|
|
701
|
-
// Extract tool_use blocks from sub-agent assistant messages
|
|
702
|
-
for (var i = 0; i < content.length; i++) {
|
|
703
|
-
var block = content[i];
|
|
704
|
-
if (block.type === "tool_use") {
|
|
705
|
-
var activityText = toolActivityTextForSubagent(block.name, block.input);
|
|
706
|
-
sendAndRecord(session, {
|
|
707
|
-
type: "subagent_tool",
|
|
708
|
-
parentToolId: parentId,
|
|
709
|
-
toolName: block.name,
|
|
710
|
-
toolId: block.id,
|
|
711
|
-
text: activityText,
|
|
712
|
-
});
|
|
713
|
-
} else if (block.type === "thinking") {
|
|
714
|
-
sendAndRecord(session, {
|
|
715
|
-
type: "subagent_activity",
|
|
716
|
-
parentToolId: parentId,
|
|
717
|
-
text: "Thinking...",
|
|
718
|
-
});
|
|
719
|
-
} else if (block.type === "text" && block.text) {
|
|
720
|
-
sendAndRecord(session, {
|
|
721
|
-
type: "subagent_activity",
|
|
722
|
-
parentToolId: parentId,
|
|
723
|
-
text: "Writing response...",
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
}
|
|
70
|
+
function stopIdleReaper() {
|
|
71
|
+
if (_idleReaperTimer) {
|
|
72
|
+
clearInterval(_idleReaperTimer);
|
|
73
|
+
_idleReaperTimer = null;
|
|
727
74
|
}
|
|
728
|
-
// user messages with parent_tool_use_id contain tool_results — skip silently
|
|
729
75
|
}
|
|
730
76
|
|
|
77
|
+
// --- Skill discovery (extracted to sdk-skill-discovery.js) ---
|
|
78
|
+
var skills = attachSkillDiscovery({ cwd: cwd });
|
|
79
|
+
var discoverSkillDirs = skills.discoverSkillDirs;
|
|
80
|
+
var mergeSkills = skills.mergeSkills;
|
|
81
|
+
|
|
82
|
+
// --- Message processing (extracted to sdk-message-processor.js) ---
|
|
83
|
+
var msgProcessor = attachMessageProcessor({
|
|
84
|
+
sm: sm,
|
|
85
|
+
send: send,
|
|
86
|
+
slug: slug,
|
|
87
|
+
isMate: isMate,
|
|
88
|
+
mateDisplayName: mateDisplayName,
|
|
89
|
+
pushModule: pushModule,
|
|
90
|
+
getNotificationsModule: getNotificationsModule,
|
|
91
|
+
getSDK: getSDK,
|
|
92
|
+
onProcessingChanged: onProcessingChanged,
|
|
93
|
+
onTurnDone: onTurnDone,
|
|
94
|
+
opts: opts,
|
|
95
|
+
discoverSkillDirs: discoverSkillDirs,
|
|
96
|
+
mergeSkills: mergeSkills,
|
|
97
|
+
});
|
|
98
|
+
var processSDKMessage = msgProcessor.processSDKMessage;
|
|
99
|
+
var sendAndRecord = msgProcessor.sendAndRecord;
|
|
100
|
+
var sendToSession = msgProcessor.sendToSession;
|
|
101
|
+
|
|
731
102
|
// --- MCP elicitation ---
|
|
732
103
|
|
|
733
104
|
function handleElicitation(session, request, opts) {
|
|
@@ -1378,15 +749,6 @@ function createSDKBridge(opts) {
|
|
|
1378
749
|
sendAndRecord(session, { type: "error", text: "Claude process error: " + errText });
|
|
1379
750
|
}
|
|
1380
751
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
1381
|
-
if (pushModule) {
|
|
1382
|
-
pushModule.sendPush({
|
|
1383
|
-
type: "error",
|
|
1384
|
-
slug: slug,
|
|
1385
|
-
title: (mateDisplayName || "Claude") + ": Connection Lost",
|
|
1386
|
-
body: (mateDisplayName || "Claude") + " process disconnected: " + (msg.error || "unknown error"),
|
|
1387
|
-
tag: "claude-error",
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
752
|
}
|
|
1391
753
|
sm.broadcastSessionList();
|
|
1392
754
|
}
|
|
@@ -1669,6 +1031,12 @@ function createSDKBridge(opts) {
|
|
|
1669
1031
|
}
|
|
1670
1032
|
|
|
1671
1033
|
function handleCanUseTool(session, toolName, input, opts) {
|
|
1034
|
+
// Full-auto mode: auto-approve everything except AskUserQuestion
|
|
1035
|
+
// (which still needs to go through the user interaction flow).
|
|
1036
|
+
if (sm.currentPermissionMode === "bypassPermissions" && toolName !== "AskUserQuestion") {
|
|
1037
|
+
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1672
1040
|
// Ralph Loop execution: auto-approve all tools, deny interactive ones.
|
|
1673
1041
|
// Crafting sessions are interactive — user and Claude collaborate to build PROMPT.md / JUDGE.md.
|
|
1674
1042
|
if (session.loop && session.loop.active && session.loop.role !== "crafting") {
|
|
@@ -1712,6 +1080,7 @@ function createSDKBridge(opts) {
|
|
|
1712
1080
|
// Regular tool permission request: send to client and wait
|
|
1713
1081
|
return new Promise(function(resolve) {
|
|
1714
1082
|
var requestId = crypto.randomUUID();
|
|
1083
|
+
sm.permissionRequestIndex[requestId] = session.localId;
|
|
1715
1084
|
session.pendingPermissions[requestId] = {
|
|
1716
1085
|
resolve: resolve,
|
|
1717
1086
|
requestId: requestId,
|
|
@@ -1742,9 +1111,24 @@ function createSDKBridge(opts) {
|
|
|
1742
1111
|
});
|
|
1743
1112
|
}
|
|
1744
1113
|
|
|
1114
|
+
var _nm = getNotificationsModule();
|
|
1115
|
+
if (_nm) {
|
|
1116
|
+
_nm.notify("permission_request", {
|
|
1117
|
+
title: permissionPushTitle(toolName, input),
|
|
1118
|
+
body: permissionPushBody(toolName, input),
|
|
1119
|
+
slug: slug,
|
|
1120
|
+
sessionId: session.localId,
|
|
1121
|
+
ownerId: session.ownerId || null,
|
|
1122
|
+
requestId: requestId,
|
|
1123
|
+
toolName: toolName,
|
|
1124
|
+
toolInput: input,
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1745
1128
|
if (opts.signal) {
|
|
1746
1129
|
opts.signal.addEventListener("abort", function() {
|
|
1747
1130
|
delete session.pendingPermissions[requestId];
|
|
1131
|
+
delete sm.permissionRequestIndex[requestId];
|
|
1748
1132
|
sendAndRecord(session, { type: "permission_cancel", requestId: requestId });
|
|
1749
1133
|
onProcessingChanged(); // update cross-project permission badge
|
|
1750
1134
|
resolve({ behavior: "deny", message: "Request cancelled" });
|
|
@@ -1894,15 +1278,6 @@ function createSDKBridge(opts) {
|
|
|
1894
1278
|
}
|
|
1895
1279
|
}
|
|
1896
1280
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
1897
|
-
if (pushModule) {
|
|
1898
|
-
pushModule.sendPush({
|
|
1899
|
-
type: "error",
|
|
1900
|
-
slug: slug,
|
|
1901
|
-
title: (mateDisplayName || "Claude") + ": Connection Lost",
|
|
1902
|
-
body: (mateDisplayName || "Claude") + " process disconnected: " + (err.message || "unknown error"),
|
|
1903
|
-
tag: "claude-error",
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
1281
|
}
|
|
1907
1282
|
sm.broadcastSessionList();
|
|
1908
1283
|
}
|
|
@@ -2129,6 +1504,7 @@ function createSDKBridge(opts) {
|
|
|
2129
1504
|
session.messageQueue.end();
|
|
2130
1505
|
}
|
|
2131
1506
|
|
|
1507
|
+
session.lastActivityAt = Date.now();
|
|
2132
1508
|
session.streamPromise = processQueryStream(session).catch(function(err) {
|
|
2133
1509
|
});
|
|
2134
1510
|
}
|
|
@@ -2150,6 +1526,7 @@ function createSDKBridge(opts) {
|
|
|
2150
1526
|
type: "user",
|
|
2151
1527
|
message: { role: "user", content: content },
|
|
2152
1528
|
};
|
|
1529
|
+
session.lastActivityAt = Date.now();
|
|
2153
1530
|
// Route through worker if active, otherwise direct to message queue
|
|
2154
1531
|
if (session.worker) {
|
|
2155
1532
|
session.worker.send({ type: "push_message", content: userMsg });
|
|
@@ -2566,6 +1943,8 @@ function createSDKBridge(opts) {
|
|
|
2566
1943
|
warmup: warmup,
|
|
2567
1944
|
stopTask: stopTask,
|
|
2568
1945
|
createMentionSession: createMentionSession,
|
|
1946
|
+
startIdleReaper: startIdleReaper,
|
|
1947
|
+
stopIdleReaper: stopIdleReaper,
|
|
2569
1948
|
};
|
|
2570
1949
|
}
|
|
2571
1950
|
|