clay-server 2.27.0-beta.13 → 2.27.0-beta.14

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/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
- // Split shell command on operators (&&, ||, ;, |) while respecting quotes
11
- // and parentheses. Returns array of command segments.
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;
@@ -187,596 +73,29 @@ function createSDKBridge(opts) {
187
73
  }
188
74
  }
189
75
 
190
- // --- Skill discovery helpers ---
191
-
192
- function discoverSkillDirs() {
193
- var skills = {};
194
- var dirs = [
195
- path.join(require("./config").REAL_HOME, ".claude", "skills"),
196
- path.join(cwd, ".claude", "skills"),
197
- ];
198
- for (var d = 0; d < dirs.length; d++) {
199
- var base = dirs[d];
200
- var entries;
201
- try {
202
- entries = fs.readdirSync(base, { withFileTypes: true });
203
- } catch (e) {
204
- continue; // directory doesn't exist
205
- }
206
- for (var i = 0; i < entries.length; i++) {
207
- var entry = entries[i];
208
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
209
- var skillDir = path.join(base, entry.name);
210
- var skillMd = path.join(skillDir, "SKILL.md");
211
- try {
212
- fs.accessSync(skillMd, fs.constants.R_OK);
213
- // project skills override global skills with same name
214
- skills[entry.name] = skillDir;
215
- } catch (e) {
216
- // no SKILL.md, skip
217
- }
218
- }
219
- }
220
- return skills;
221
- }
222
-
223
- function mergeSkills(sdkSkills, fsSkills) {
224
- var merged = new Set();
225
- if (Array.isArray(sdkSkills)) {
226
- for (var i = 0; i < sdkSkills.length; i++) {
227
- merged.add(sdkSkills[i]);
228
- }
229
- }
230
- var fsNames = Object.keys(fsSkills);
231
- for (var i = 0; i < fsNames.length; i++) {
232
- merged.add(fsNames[i]);
233
- }
234
- return merged;
235
- }
236
-
237
- function sendAndRecord(session, obj) {
238
- sm.sendAndRecord(session, obj);
239
- }
240
-
241
- function sendToSession(session, obj) {
242
- sm.sendToSession(session, obj);
243
- }
244
-
245
- function processSDKMessage(session, parsed) {
246
- // Timing: log key SDK milestones relative to query start
247
- if (session._queryStartTs) {
248
- var _elapsed = Date.now() - session._queryStartTs;
249
- if (parsed.type === "system" && parsed.subtype === "init") {
250
- console.log("[PERF] processSDKMessage: system/init +" + _elapsed + "ms");
251
- }
252
- if (parsed.type === "stream_event" && parsed.event) {
253
- if (parsed.event.type === "message_start") {
254
- console.log("[PERF] processSDKMessage: message_start (API response begun) +" + _elapsed + "ms");
255
- }
256
- if (parsed.event.type === "content_block_delta" && !session._firstTextLogged) {
257
- session._firstTextLogged = true;
258
- console.log("[PERF] processSDKMessage: FIRST content_block_delta (visible text) +" + _elapsed + "ms");
259
- }
260
- }
261
- if (parsed.type === "result") {
262
- console.log("[PERF] processSDKMessage: result +" + _elapsed + "ms");
263
- }
264
- }
265
-
266
- // Extract session_id from any message that carries it
267
- if (parsed.session_id && !session.cliSessionId) {
268
- session.cliSessionId = parsed.session_id;
269
- sm.saveSessionFile(session);
270
- sendAndRecord(session, { type: "session_id", cliSessionId: session.cliSessionId });
271
- } else if (parsed.session_id) {
272
- session.cliSessionId = parsed.session_id;
273
- }
274
-
275
- // Capture message UUIDs for rewind support
276
- if (parsed.uuid) {
277
- if (parsed.type === "user" && !parsed.parent_tool_use_id) {
278
- session.messageUUIDs.push({ uuid: parsed.uuid, type: "user", historyIndex: session.history.length });
279
- sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "user" });
280
- } else if (parsed.type === "assistant") {
281
- session.messageUUIDs.push({ uuid: parsed.uuid, type: "assistant", historyIndex: session.history.length });
282
- sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "assistant" });
283
- }
284
- }
285
-
286
- // Cache slash_commands and model from CLI init message
287
- if (parsed.type === "system" && parsed.subtype === "init") {
288
- var fsSkills = discoverSkillDirs();
289
- sm.skillNames = mergeSkills(parsed.skills, fsSkills);
290
- if (parsed.slash_commands) {
291
- // Union: SDK slash_commands + merged skills (deduplicated)
292
- var seen = new Set();
293
- var combined = [];
294
- var all = parsed.slash_commands.concat(Array.from(sm.skillNames));
295
- for (var k = 0; k < all.length; k++) {
296
- if (!seen.has(all[k])) {
297
- seen.add(all[k]);
298
- combined.push(all[k]);
299
- }
300
- }
301
- sm.slashCommands = combined;
302
- send({ type: "slash_commands", commands: sm.slashCommands });
303
- }
304
- if (parsed.model) {
305
- sm.currentModel = sm._savedDefaultModel || parsed.model;
306
- send({ type: "model_info", model: sm.currentModel, models: sm.availableModels || [] });
307
- }
308
- if (parsed.fast_mode_state) {
309
- sendAndRecord(session, { type: "fast_mode_state", state: parsed.fast_mode_state });
310
- }
311
- }
312
-
313
- if (parsed.type === "stream_event" && parsed.event) {
314
- var evt = parsed.event;
315
-
316
- if (evt.type === "message_start" && evt.message && evt.message.usage) {
317
- var u = evt.message.usage;
318
- session.lastStreamInputTokens = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0);
319
- }
320
-
321
- if (evt.type === "content_block_start") {
322
- var block = evt.content_block;
323
- var idx = evt.index;
324
-
325
- if (block.type === "tool_use") {
326
- session.blocks[idx] = { type: "tool_use", id: block.id, name: block.name, inputJson: "" };
327
- sendAndRecord(session, { type: "tool_start", id: block.id, name: block.name });
328
- } else if (block.type === "thinking") {
329
- session.blocks[idx] = { type: "thinking", thinkingText: "", startTime: Date.now() };
330
- sendAndRecord(session, { type: "thinking_start" });
331
- } else if (block.type === "text") {
332
- session.blocks[idx] = { type: "text" };
333
- }
334
- }
335
-
336
- if (evt.type === "content_block_delta" && evt.delta) {
337
- var idx = evt.index;
338
-
339
- if (evt.delta.type === "text_delta" && typeof evt.delta.text === "string") {
340
- session.streamedText = true;
341
- if (session.responsePreview.length < 200) {
342
- session.responsePreview += evt.delta.text;
343
- }
344
- // Accumulate text for mate DM response
345
- if (typeof session._mateDmResponseText === "string") {
346
- session._mateDmResponseText += evt.delta.text;
347
- }
348
- sendAndRecord(session, { type: "delta", text: evt.delta.text });
349
- } else if (evt.delta.type === "input_json_delta" && session.blocks[idx]) {
350
- session.blocks[idx].inputJson += evt.delta.partial_json;
351
- } else if (evt.delta.type === "thinking_delta" && session.blocks[idx]) {
352
- session.blocks[idx].thinkingText += evt.delta.thinking;
353
- sendAndRecord(session, { type: "thinking_delta", text: evt.delta.thinking });
354
- }
355
- }
356
-
357
- if (evt.type === "content_block_stop") {
358
- var idx = evt.index;
359
- var block = session.blocks[idx];
360
-
361
- if (block && block.type === "tool_use") {
362
- var input = {};
363
- try { input = JSON.parse(block.inputJson); } catch {}
364
- sendAndRecord(session, { type: "tool_executing", id: block.id, name: block.name, input: input });
365
-
366
- // Track active Task tools for sub-agent done detection
367
- if (block.name === "Task") {
368
- if (!session.activeTaskToolIds) session.activeTaskToolIds = {};
369
- session.activeTaskToolIds[block.id] = true;
370
- }
371
-
372
- if (pushModule && block.name === "AskUserQuestion" && input.questions) {
373
- var q = input.questions[0];
374
- pushModule.sendPush({
375
- type: "ask_user",
376
- slug: slug,
377
- title: (mateDisplayName || "Claude") + " has a question",
378
- body: q ? q.question : "Waiting for your response",
379
- tag: "claude-ask",
380
- });
381
- }
382
- } else if (block && block.type === "thinking") {
383
- var duration = block.startTime ? (Date.now() - block.startTime) / 1000 : 0;
384
- sendAndRecord(session, { type: "thinking_stop", duration: duration });
385
- }
386
-
387
- delete session.blocks[idx];
388
- }
389
-
390
- } else if ((parsed.type === "assistant" || parsed.type === "user") && parsed.message && parsed.message.content) {
391
- // Sub-agent messages: extract tool_use blocks for activity display
392
- if (parsed.parent_tool_use_id) {
393
- processSubagentMessage(session, parsed);
394
- return;
395
- }
396
-
397
- var content = parsed.message.content;
398
-
399
- // Fallback: if assistant text wasn't streamed via deltas, send it now
400
- if (parsed.type === "assistant" && !session.streamedText && Array.isArray(content)) {
401
- var assistantText = content
402
- .filter(function(c) { return c.type === "text"; })
403
- .map(function(c) { return c.text; })
404
- .join("");
405
- if (assistantText) {
406
- if (session.responsePreview.length < 200) {
407
- session.responsePreview += assistantText;
408
- }
409
- sendAndRecord(session, { type: "delta", text: assistantText });
410
- }
411
- }
412
-
413
- // Check for local slash command output in user messages
414
- if (parsed.type === "user") {
415
- var fullText = "";
416
- if (typeof content === "string") {
417
- fullText = content;
418
- } else if (Array.isArray(content)) {
419
- fullText = content
420
- .filter(function(c) { return c.type === "text"; })
421
- .map(function(c) { return c.text; })
422
- .join("\n");
423
- }
424
- if (fullText.indexOf("local-command-stdout") !== -1) {
425
- var m = fullText.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
426
- if (m) {
427
- sendAndRecord(session, { type: "slash_command_result", text: m[1].trim() });
428
- }
429
- }
430
- }
431
-
432
- if (Array.isArray(content)) {
433
- for (var i = 0; i < content.length; i++) {
434
- var block = content[i];
435
- if (block.type === "tool_result" && !session.sentToolResults[block.tool_use_id]) {
436
- // Clear active Task tool when its result arrives
437
- if (session.activeTaskToolIds && session.activeTaskToolIds[block.tool_use_id]) {
438
- sendAndRecord(session, {
439
- type: "subagent_done",
440
- parentToolId: block.tool_use_id,
441
- });
442
- delete session.activeTaskToolIds[block.tool_use_id];
443
- }
444
- var resultText = "";
445
- var resultImages = [];
446
- if (typeof block.content === "string") {
447
- resultText = block.content;
448
- } else if (Array.isArray(block.content)) {
449
- resultText = block.content
450
- .filter(function(c) { return c.type === "text"; })
451
- .map(function(c) { return c.text; })
452
- .join("\n");
453
- for (var ri = 0; ri < block.content.length; ri++) {
454
- var rc = block.content[ri];
455
- if (rc.type === "image" && rc.source) {
456
- resultImages.push({
457
- mediaType: rc.source.media_type,
458
- data: rc.source.data,
459
- });
460
- }
461
- }
462
- }
463
- session.sentToolResults[block.tool_use_id] = true;
464
- var toolResultMsg = {
465
- type: "tool_result",
466
- id: block.tool_use_id,
467
- content: resultText,
468
- is_error: block.is_error || false,
469
- };
470
- if (resultImages.length > 0) toolResultMsg.images = resultImages;
471
- sendAndRecord(session, toolResultMsg);
472
- }
473
- }
474
- }
475
-
476
- } else if (parsed.type === "result") {
477
- session.blocks = {};
478
- session.sentToolResults = {};
479
- session.pendingPermissions = {};
480
- session.pendingElicitations = {};
481
- // Record ask_user_answered for any leftover pending questions so replay pairs correctly
482
- var leftoverAskIds = Object.keys(session.pendingAskUser);
483
- for (var lai = 0; lai < leftoverAskIds.length; lai++) {
484
- sendAndRecord(session, { type: "ask_user_answered", toolId: leftoverAskIds[lai] });
485
- }
486
- session.pendingAskUser = {};
487
- session.activeTaskToolIds = {};
488
- session.taskIdMap = {};
489
- // Only clear rateLimitResetsAt on genuine success (non-zero cost).
490
- // When rate-limited, the SDK sends result with zero cost right after
491
- // rate_limit_event; clearing here would prevent auto-continue scheduling.
492
- if (parsed.total_cost_usd && parsed.total_cost_usd > 0) {
493
- session.rateLimitResetsAt = null;
494
- }
495
- console.log("[sdk-bridge] result handler: session " + session.localId + " cost=" + parsed.total_cost_usd + " rateLimitResetsAt=" + session.rateLimitResetsAt);
496
-
497
- // Handle SDK execution errors: show the error to the user instead of
498
- // silently swallowing it. These have subtype "error_during_execution".
499
- if (parsed.subtype === "error_during_execution") {
500
- var execErrors = parsed.errors || [];
501
- var execError = execErrors.length > 0
502
- ? execErrors.join("; ")
503
- : "Unknown SDK error";
504
- if (parsed.terminal_reason) execError += " (reason: " + parsed.terminal_reason + ")";
505
- console.error("[sdk-bridge] Execution error for session " + session.localId + ": " + execError);
506
- session.isProcessing = false;
507
- onProcessingChanged();
508
- sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
509
- sendAndRecord(session, { type: "done", code: 1 });
510
- sm.broadcastSessionList();
511
- return;
512
- }
513
-
514
- session.isProcessing = false;
515
- onProcessingChanged();
516
- // Detect "Not logged in" scenario early for the check below
517
- var previewTrimmed = (session.responsePreview || "").trim();
518
- var isZeroCost = !parsed.total_cost_usd || parsed.total_cost_usd === 0;
519
- var isLoginPrompt = isZeroCost && previewTrimmed.length < 100
520
- && /not logged in/i.test(previewTrimmed) && /\/login/i.test(previewTrimmed);
521
- // Fetch rich context usage breakdown (fire-and-forget, non-blocking)
522
- if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
523
- session.queryInstance.getContextUsage().then(function(ctxUsage) {
524
- session.lastContextUsage = ctxUsage;
525
- sendToSession(session, { type: "context_usage", data: ctxUsage });
526
- }).catch(function(e) {
527
- console.error("[sdk-bridge] getContextUsage failed (non-fatal):", e.message || e);
528
- });
529
- }
530
- var lastStreamInput = session.lastStreamInputTokens || null;
531
- session.lastStreamInputTokens = null;
532
- sendAndRecord(session, {
533
- type: "result",
534
- cost: parsed.total_cost_usd,
535
- duration: parsed.duration_ms,
536
- usage: parsed.usage || null,
537
- modelUsage: parsed.modelUsage || null,
538
- sessionId: parsed.session_id,
539
- lastStreamInputTokens: lastStreamInput,
540
- });
541
- if (parsed.fast_mode_state) {
542
- sendAndRecord(session, { type: "fast_mode_state", state: parsed.fast_mode_state });
543
- }
544
- // Detect "Not logged in · Please run /login" from SDK.
545
- // This is a short canned response with zero cost, not actual AI output.
546
- if (isLoginPrompt) {
547
- var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
548
- var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
549
- var canAutoLogin = !usersModule.isMultiUser()
550
- || !!authLinuxUser
551
- || (authUser && authUser.role === "admin");
552
- sendAndRecord(session, {
553
- type: "auth_required",
554
- text: "Claude Code is not logged in.",
555
- linuxUser: authLinuxUser,
556
- canAutoLogin: canAutoLogin,
557
- });
558
- // Reset CLI session so next query starts fresh with new auth
559
- session.cliSessionId = null;
560
- }
561
- sendAndRecord(session, { type: "done", code: 0 });
562
- if (pushModule) {
563
- var preview = (session.responsePreview || "").replace(/\s+/g, " ").trim();
564
- if (preview.length > 140) preview = preview.substring(0, 140) + "...";
565
- pushModule.sendPush({
566
- type: "done",
567
- slug: slug,
568
- title: mateDisplayName ? (mateDisplayName + " responded") : (session.title || "Claude"),
569
- body: preview || "Response ready",
570
- tag: "claude-done",
571
- });
572
- }
573
- // Reset for next turn in the same query
574
- session.lastActivityAt = Date.now();
575
- var donePreview = session.responsePreview || "";
576
- session.responsePreview = "";
577
- session.streamedText = false;
578
- sm.broadcastSessionList();
579
- if (onTurnDone) {
580
- try { onTurnDone(session, donePreview); } catch (e) {}
581
- }
582
-
583
- } else if (parsed.type === "system" && parsed.subtype === "status") {
584
- if (parsed.status === "compacting") {
585
- sendAndRecord(session, { type: "compacting", active: true });
586
- } else if (session.compacting) {
587
- sendAndRecord(session, { type: "compacting", active: false });
588
- }
589
- session.compacting = parsed.status === "compacting";
590
-
591
- } else if (parsed.type === "system" && parsed.subtype === "task_started") {
592
- var parentId = parsed.tool_use_id;
593
- if (parentId) {
594
- if (!session.taskIdMap) session.taskIdMap = {};
595
- session.taskIdMap[parentId] = parsed.task_id;
596
- sendAndRecord(session, {
597
- type: "task_started",
598
- parentToolId: parentId,
599
- taskId: parsed.task_id,
600
- description: parsed.description || "",
601
- });
602
- }
603
-
604
- } else if (parsed.type === "system" && parsed.subtype === "task_progress") {
605
- var parentId = parsed.tool_use_id;
606
- if (parentId) {
607
- sendAndRecord(session, {
608
- type: "task_progress",
609
- parentToolId: parentId,
610
- taskId: parsed.task_id,
611
- usage: parsed.usage || null,
612
- lastToolName: parsed.last_tool_name || null,
613
- description: parsed.description || "",
614
- summary: parsed.summary || null,
615
- });
616
- }
617
-
618
- } else if (parsed.type === "tool_progress") {
619
- // Sub-agent tool_progress: forward as activity update
620
- var parentId = parsed.parent_tool_use_id;
621
- if (parentId) {
622
- sendAndRecord(session, {
623
- type: "subagent_activity",
624
- parentToolId: parentId,
625
- text: parsed.content || "",
626
- });
627
- }
628
-
629
- } else if (parsed.type === "task_notification") {
630
- var parentId = parsed.parent_tool_use_id;
631
- if (parentId) {
632
- sendAndRecord(session, {
633
- type: "subagent_done",
634
- parentToolId: parentId,
635
- status: parsed.status || "completed",
636
- summary: parsed.summary || "",
637
- usage: parsed.usage || null,
638
- });
639
- }
640
- if (session.taskIdMap) {
641
- for (var k in session.taskIdMap) {
642
- if (session.taskIdMap[k] === parsed.task_id) {
643
- delete session.taskIdMap[k];
644
- break;
645
- }
646
- }
647
- }
648
-
649
- } else if (parsed.type === "rate_limit_event" && parsed.rate_limit_info) {
650
- var info = parsed.rate_limit_info;
651
- console.log("[sdk-bridge] rate_limit_event for session " + session.localId + ": status=" + info.status + " resetsAt=" + info.resetsAt + " isUsingOverage=" + info.isUsingOverage + " isProcessing=" + session.isProcessing);
652
-
653
- // Broadcast reset time for top-bar usage link
654
- if (info.rateLimitType && info.resetsAt) {
655
- send({
656
- type: "rate_limit_usage",
657
- rateLimitType: info.rateLimitType,
658
- resetsAt: info.resetsAt * 1000,
659
- status: info.status,
660
- });
661
- }
662
-
663
- // Warning/rejection handling (existing behavior)
664
- if (info.status === "allowed_warning" || info.status === "rejected") {
665
- sendAndRecord(session, {
666
- type: "rate_limit",
667
- status: info.status,
668
- resetsAt: info.resetsAt ? info.resetsAt * 1000 : null,
669
- rateLimitType: info.rateLimitType || null,
670
- utilization: info.utilization || null,
671
- isUsingOverage: info.isUsingOverage || false,
672
- });
673
- // Track rejection for auto-continue / scheduled message support
674
- if (info.status === "rejected" && info.resetsAt) {
675
- session.rateLimitResetsAt = info.resetsAt * 1000;
676
-
677
- // Schedule auto-continue immediately on rejection (don't wait for
678
- // query completion which has timing issues with worker/non-worker paths).
679
- if (!session.scheduledMessage && !session.destroying) {
680
- var acEnabled = session.onQueryComplete ||
681
- (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
682
- console.log("[sdk-bridge] rate_limit rejected: acEnabled=" + acEnabled + " overage=" + !!info.isUsingOverage + " session=" + session.localId);
683
- if (acEnabled) {
684
- session.rateLimitAutoContinuePending = true;
685
- if (info.isUsingOverage) {
686
- // Extra usage available: send continue immediately (5s delay for query to finish)
687
- console.log("[sdk-bridge] Overage available, sending immediate continue for session " + session.localId);
688
- session.rateLimitResetsAt = null;
689
- if (typeof opts.scheduleMessage === "function") {
690
- opts.scheduleMessage(session, "continue", Date.now());
691
- }
692
- } else {
693
- // No overage: schedule after rate limit resets
694
- var acResetsAt = session.rateLimitResetsAt;
695
- session.rateLimitResetsAt = null;
696
- console.log("[sdk-bridge] Scheduling auto-continue on rate limit rejection for session " + session.localId);
697
- if (typeof opts.scheduleMessage === "function") {
698
- opts.scheduleMessage(session, "continue", acResetsAt);
699
- }
700
- }
701
- }
702
- }
703
- }
704
- }
705
-
706
- } else if (parsed.type === "prompt_suggestion") {
707
- sendAndRecord(session, {
708
- type: "prompt_suggestion",
709
- suggestion: parsed.suggestion || "",
710
- });
711
-
712
- } else if (parsed.type === "system") {
713
- // Catch-all for unhandled system subtypes (e.g. hook-block errors).
714
- // Extract any error text and surface it in the UI.
715
- var sysText = parsed.error || parsed.message || parsed.text || "";
716
- if (!sysText && Array.isArray(parsed.content)) {
717
- sysText = parsed.content
718
- .filter(function(c) { return c.type === "text"; })
719
- .map(function(c) { return c.text; })
720
- .join("\n");
721
- }
722
- if (sysText) {
723
- console.log("[sdk-bridge] Unhandled system message (subtype=" + (parsed.subtype || "none") + "): " + sysText.substring(0, 200));
724
- sendAndRecord(session, { type: "error", text: sysText });
725
- }
726
- } else if (parsed.type && parsed.type !== "user") {
727
- }
728
- }
729
-
730
- // --- Sub-agent message processing ---
731
-
732
- function toolActivityTextForSubagent(name, input) {
733
- if (name === "Bash" && input && input.description) return input.description;
734
- if (name === "Read" && input && input.file_path) return "Reading " + input.file_path.split("/").pop();
735
- if (name === "Edit" && input && input.file_path) return "Editing " + input.file_path.split("/").pop();
736
- if (name === "Write" && input && input.file_path) return "Writing " + input.file_path.split("/").pop();
737
- if (name === "Grep" && input && input.pattern) return "Searching for " + input.pattern;
738
- if (name === "Glob" && input && input.pattern) return "Finding " + input.pattern;
739
- if (name === "WebSearch" && input && input.query) return "Searching: " + input.query;
740
- if (name === "WebFetch") return "Fetching URL...";
741
- if (name === "Task" && input && input.description) return input.description;
742
- return "Running " + name + "...";
743
- }
744
-
745
- function processSubagentMessage(session, parsed) {
746
- var parentId = parsed.parent_tool_use_id;
747
- var content = parsed.message.content;
748
- if (!Array.isArray(content)) return;
749
-
750
- if (parsed.type === "assistant") {
751
- // Extract tool_use blocks from sub-agent assistant messages
752
- for (var i = 0; i < content.length; i++) {
753
- var block = content[i];
754
- if (block.type === "tool_use") {
755
- var activityText = toolActivityTextForSubagent(block.name, block.input);
756
- sendAndRecord(session, {
757
- type: "subagent_tool",
758
- parentToolId: parentId,
759
- toolName: block.name,
760
- toolId: block.id,
761
- text: activityText,
762
- });
763
- } else if (block.type === "thinking") {
764
- sendAndRecord(session, {
765
- type: "subagent_activity",
766
- parentToolId: parentId,
767
- text: "Thinking...",
768
- });
769
- } else if (block.type === "text" && block.text) {
770
- sendAndRecord(session, {
771
- type: "subagent_activity",
772
- parentToolId: parentId,
773
- text: "Writing response...",
774
- });
775
- }
776
- }
777
- }
778
- // user messages with parent_tool_use_id contain tool_results — skip silently
779
- }
76
+ // --- Skill discovery (extracted to sdk-skill-discovery.js) ---
77
+ var skills = attachSkillDiscovery({ cwd: cwd });
78
+ var discoverSkillDirs = skills.discoverSkillDirs;
79
+ var mergeSkills = skills.mergeSkills;
80
+
81
+ // --- Message processing (extracted to sdk-message-processor.js) ---
82
+ var msgProcessor = attachMessageProcessor({
83
+ sm: sm,
84
+ send: send,
85
+ slug: slug,
86
+ isMate: isMate,
87
+ mateDisplayName: mateDisplayName,
88
+ pushModule: pushModule,
89
+ getSDK: getSDK,
90
+ onProcessingChanged: onProcessingChanged,
91
+ onTurnDone: onTurnDone,
92
+ opts: opts,
93
+ discoverSkillDirs: discoverSkillDirs,
94
+ mergeSkills: mergeSkills,
95
+ });
96
+ var processSDKMessage = msgProcessor.processSDKMessage;
97
+ var sendAndRecord = msgProcessor.sendAndRecord;
98
+ var sendToSession = msgProcessor.sendToSession;
780
99
 
781
100
  // --- MCP elicitation ---
782
101