clay-server 2.19.0 → 2.20.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +51 -91
  2. package/bin/cli.js +49 -14
  3. package/lib/builtin-mates.js +360 -0
  4. package/lib/cli-sessions.js +3 -3
  5. package/lib/config.js +30 -4
  6. package/lib/daemon.js +28 -5
  7. package/lib/mates.js +169 -7
  8. package/lib/notes.js +20 -0
  9. package/lib/os-users.js +71 -2
  10. package/lib/project.js +903 -228
  11. package/lib/public/app.js +249 -69
  12. package/lib/public/css/icon-strip.css +55 -2
  13. package/lib/public/css/input.css +50 -30
  14. package/lib/public/css/mates.css +316 -2
  15. package/lib/public/css/mention.css +23 -0
  16. package/lib/public/css/mobile-nav.css +198 -0
  17. package/lib/public/css/overlays.css +23 -0
  18. package/lib/public/css/rewind.css +32 -0
  19. package/lib/public/css/title-bar.css +3 -0
  20. package/lib/public/css/user-settings.css +2 -2
  21. package/lib/public/index.html +65 -14
  22. package/lib/public/mates/ally.png +0 -0
  23. package/lib/public/mates/sage.jpg +0 -0
  24. package/lib/public/mates/scout.png +0 -0
  25. package/lib/public/modules/command-palette.js +44 -4
  26. package/lib/public/modules/filebrowser.js +2 -0
  27. package/lib/public/modules/input.js +158 -16
  28. package/lib/public/modules/mate-knowledge.js +2 -0
  29. package/lib/public/modules/mate-memory.js +353 -0
  30. package/lib/public/modules/mention.js +77 -2
  31. package/lib/public/modules/notifications.js +0 -8
  32. package/lib/public/modules/server-settings.js +11 -4
  33. package/lib/public/modules/sidebar.js +507 -21
  34. package/lib/public/modules/theme.js +10 -12
  35. package/lib/public/modules/tools.js +84 -12
  36. package/lib/public/modules/user-settings.js +45 -12
  37. package/lib/sdk-bridge.js +122 -61
  38. package/lib/server.js +209 -13
  39. package/lib/sessions.js +4 -4
  40. package/lib/users.js +89 -0
  41. package/package.json +1 -1
@@ -389,6 +389,9 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
389
389
  enableMainInput();
390
390
  if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
391
391
 
392
+ // Show user's answers inline
393
+ showAnswerSummary(container, questions, result);
394
+
392
395
  if (ctx.ws && ctx.connected) {
393
396
  ctx.ws.send(JSON.stringify({
394
397
  type: "ask_user_response",
@@ -398,11 +401,72 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
398
401
  }
399
402
  }
400
403
 
401
- export function markAskUserAnswered(toolId) {
404
+ function showAnswerSummary(container, questions, answers) {
405
+ if (!answers || Object.keys(answers).length === 0) return;
406
+ var existing = container.querySelector(".ask-user-answer-summary");
407
+ if (existing) return;
408
+ var summary = document.createElement("div");
409
+ summary.className = "ask-user-answer-summary";
410
+ for (var key in answers) {
411
+ if (!answers.hasOwnProperty(key)) continue;
412
+ var row = document.createElement("div");
413
+ row.className = "ask-user-answer-row";
414
+ var qi = parseInt(key, 10);
415
+ var label = (questions && questions[qi] && questions[qi].question) ? questions[qi].question : "Answer";
416
+ row.innerHTML = '<span class="ask-user-answer-label">' + escapeHtml(label) + '</span>' +
417
+ '<span class="ask-user-answer-value">' + escapeHtml(String(answers[key])) + '</span>';
418
+ summary.appendChild(row);
419
+ }
420
+ // Insert before submit button
421
+ var submitBtn = container.querySelector(".ask-user-submit");
422
+ if (submitBtn) {
423
+ submitBtn.parentNode.insertBefore(summary, submitBtn);
424
+ } else {
425
+ container.appendChild(summary);
426
+ }
427
+ }
428
+
429
+ export function markAskUserAnswered(toolId, answers) {
402
430
  var container = document.querySelector('.ask-user-container[data-tool-id="' + toolId + '"]');
403
431
  if (container && !container.classList.contains("answered")) {
404
432
  container.classList.add("answered");
405
433
  enableMainInput();
434
+ // Restore answers from history replay
435
+ if (answers && Object.keys(answers).length > 0) {
436
+ // Find matching questions from the tool_executing input
437
+ var questions = null;
438
+ var askQuestions = container.querySelectorAll(".ask-user-question");
439
+ if (askQuestions.length > 0) {
440
+ questions = [];
441
+ for (var qi = 0; qi < askQuestions.length; qi++) {
442
+ var qTextEl = askQuestions[qi].querySelector(".ask-user-question-text");
443
+ questions.push({ question: qTextEl ? qTextEl.textContent : "" });
444
+ }
445
+ }
446
+ showAnswerSummary(container, questions, answers);
447
+ // Also mark selected options to match the answers
448
+ for (var key in answers) {
449
+ if (!answers.hasOwnProperty(key)) continue;
450
+ var idx = parseInt(key, 10);
451
+ if (askQuestions[idx]) {
452
+ var answerVal = String(answers[key]);
453
+ var options = askQuestions[idx].querySelectorAll(".ask-user-option");
454
+ var matched = false;
455
+ for (var oi = 0; oi < options.length; oi++) {
456
+ var labelEl = options[oi].querySelector(".option-label");
457
+ if (labelEl && labelEl.textContent === answerVal) {
458
+ options[oi].classList.add("selected");
459
+ matched = true;
460
+ }
461
+ }
462
+ // If not matched to an option, fill the "Other" input
463
+ if (!matched) {
464
+ var otherInput = askQuestions[idx].querySelector(".ask-user-other input");
465
+ if (otherInput) otherInput.value = answerVal;
466
+ }
467
+ }
468
+ }
469
+ }
406
470
  }
407
471
  }
408
472
 
@@ -1345,17 +1409,22 @@ export function startThinking() {
1345
1409
  var el = thinkingGroup.el;
1346
1410
  el.classList.remove("done");
1347
1411
  el.querySelector(".thinking-content").textContent = "";
1348
- // Mate mode: restore dots row, hide thinking header
1412
+ // Mate mode: restore sparkle activity row, hide thinking header
1349
1413
  if (el.classList.contains("mate-thinking")) {
1350
- var dotsRow = el.querySelector(".mate-thinking-row");
1351
- if (dotsRow) dotsRow.style.display = "flex";
1414
+ var actRow = el.querySelector(".mate-thinking-activity");
1415
+ if (actRow) {
1416
+ actRow.style.display = "";
1417
+ actRow.querySelector(".activity-text").textContent = randomThinkingVerb() + "...";
1418
+ }
1352
1419
  var header = el.querySelector(".thinking-header");
1353
1420
  if (header) header.style.display = "none";
1354
1421
  }
1355
1422
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1356
1423
  refreshIcons();
1357
1424
  ctx.scrollToBottom();
1358
- ctx.setActivity(randomThinkingVerb() + "...");
1425
+ if (!el.classList.contains("mate-thinking")) {
1426
+ ctx.setActivity(randomThinkingVerb() + "...");
1427
+ }
1359
1428
  return;
1360
1429
  }
1361
1430
 
@@ -1369,9 +1438,10 @@ export function startThinking() {
1369
1438
  el.innerHTML =
1370
1439
  '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="">' +
1371
1440
  '<div class="dm-bubble-content">' +
1372
- '<div class="mate-thinking-row">' +
1373
- '<span class="dm-bubble-name">' + escapeHtml(mateName) + '</span>' +
1374
- '<span class="mate-thinking-dots"><span></span><span></span><span></span></span>' +
1441
+ '<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
1442
+ '<div class="activity-inline mate-thinking-activity">' +
1443
+ '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
1444
+ '<span class="activity-text">' + randomThinkingVerb() + '...</span>' +
1375
1445
  '</div>' +
1376
1446
  '<div class="thinking-header" style="display:none">' +
1377
1447
  '<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
@@ -1401,7 +1471,9 @@ export function startThinking() {
1401
1471
  ctx.scrollToBottom();
1402
1472
  thinkingGroup = { el: el, count: 0, totalDuration: 0 };
1403
1473
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1404
- ctx.setActivity(randomThinkingVerb() + "...");
1474
+ if (!ctx.isMateDm()) {
1475
+ ctx.setActivity(randomThinkingVerb() + "...");
1476
+ }
1405
1477
  }
1406
1478
 
1407
1479
  export function appendThinking(text) {
@@ -1422,10 +1494,10 @@ export function stopThinking(duration) {
1422
1494
  } else {
1423
1495
  currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
1424
1496
  }
1425
- // In mate mode: hide dots, show compact expandable thinking header
1497
+ // In mate mode: hide sparkle activity, show compact expandable thinking header
1426
1498
  if (currentThinking.el.classList.contains("mate-thinking")) {
1427
- var dotsRow = currentThinking.el.querySelector(".mate-thinking-row");
1428
- if (dotsRow) dotsRow.style.display = "none";
1499
+ var actRow = currentThinking.el.querySelector(".mate-thinking-activity");
1500
+ if (actRow) actRow.style.display = "none";
1429
1501
  var header = currentThinking.el.querySelector(".thinking-header");
1430
1502
  if (header) {
1431
1503
  header.style.display = "";
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { refreshIcons } from './icons.js';
5
5
  import { showToast } from './utils.js';
6
+ import { toggleDarkMode, getCurrentTheme } from './theme.js';
6
7
 
7
8
  var ctx = null;
8
9
  var settingsEl = null;
@@ -66,17 +67,40 @@ export function initUserSettings(appCtx) {
66
67
  var pinInput = document.getElementById('us-pin-input');
67
68
  var pinSave = document.getElementById('us-pin-save');
68
69
  if (pinInput && pinSave) {
69
- pinInput.addEventListener('input', function () {
70
+ function validatePin() {
70
71
  pinSave.disabled = !/^\d{6}$/.test(pinInput.value);
71
- });
72
+ }
73
+ pinInput.addEventListener('input', validatePin);
74
+ pinInput.addEventListener('keyup', function (e) { e.stopPropagation(); validatePin(); });
72
75
  pinInput.addEventListener('keydown', stopProp);
73
- pinInput.addEventListener('keyup', stopProp);
74
76
  pinInput.addEventListener('keypress', stopProp);
75
77
  pinSave.addEventListener('click', function () {
76
78
  savePin(pinInput.value);
77
79
  });
78
80
  }
79
81
 
82
+ // Auto-continue toggle
83
+ var autoContinueToggle = document.getElementById('us-auto-continue');
84
+ if (autoContinueToggle) {
85
+ autoContinueToggle.addEventListener('change', function () {
86
+ fetch('/api/user/auto-continue', {
87
+ method: 'PUT',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ enabled: this.checked }),
90
+ }).then(function (r) { return r.json(); }).then(function (data) {
91
+ if (data.ok) showToast(data.autoContinueOnRateLimit ? 'Auto-continue on' : 'Auto-continue off');
92
+ }).catch(function () {});
93
+ });
94
+ }
95
+
96
+ // Theme toggle (light/dark)
97
+ var themeToggle = document.getElementById('us-theme-toggle');
98
+ if (themeToggle) {
99
+ themeToggle.addEventListener('change', function () {
100
+ toggleDarkMode();
101
+ });
102
+ }
103
+
80
104
  // Logout button
81
105
  var logoutBtn = document.getElementById('us-logout-btn');
82
106
  if (logoutBtn) {
@@ -139,6 +163,15 @@ function populateAccount() {
139
163
  if (accountNav && !data.username) {
140
164
  accountNav.style.display = 'none';
141
165
  }
166
+ // Auto-continue toggle
167
+ var acToggle = document.getElementById('us-auto-continue');
168
+ if (acToggle) acToggle.checked = !!data.autoContinueOnRateLimit;
169
+ // Theme toggle
170
+ var thToggle = document.getElementById('us-theme-toggle');
171
+ if (thToggle) {
172
+ var theme = getCurrentTheme();
173
+ thToggle.checked = theme && theme.variant === 'light';
174
+ }
142
175
  }).catch(function () {});
143
176
  }
144
177
 
@@ -148,36 +181,36 @@ function savePin(pin) {
148
181
  var pinMsg = document.getElementById('us-pin-msg');
149
182
 
150
183
  pinSave.disabled = true;
151
- pinSave.textContent = 'Saving...';
184
+ pinSave.textContent = 'Saving\u2026';
152
185
 
153
186
  fetch('/api/user/pin', {
154
187
  method: 'PUT',
155
188
  headers: { 'Content-Type': 'application/json' },
156
- body: JSON.stringify({ pin: pin }),
189
+ body: JSON.stringify({ newPin: pin }),
157
190
  }).then(function (r) { return r.json(); }).then(function (data) {
158
191
  if (data.ok) {
159
192
  pinInput.value = '';
160
- pinSave.textContent = 'Update PIN';
193
+ pinSave.textContent = 'Change PIN';
161
194
  if (pinMsg) {
162
- pinMsg.textContent = 'PIN updated successfully.';
195
+ pinMsg.textContent = 'Your PIN has been changed.';
163
196
  pinMsg.className = 'us-pin-msg us-pin-msg-ok';
164
197
  pinMsg.classList.remove('hidden');
165
198
  }
166
- showToast('PIN updated');
199
+ showToast('PIN changed');
167
200
  } else {
168
201
  pinSave.disabled = false;
169
- pinSave.textContent = 'Update PIN';
202
+ pinSave.textContent = 'Change PIN';
170
203
  if (pinMsg) {
171
- pinMsg.textContent = data.error || 'Failed to update PIN.';
204
+ pinMsg.textContent = data.error || 'Could not change your PIN. Please try again.';
172
205
  pinMsg.className = 'us-pin-msg us-pin-msg-err';
173
206
  pinMsg.classList.remove('hidden');
174
207
  }
175
208
  }
176
209
  }).catch(function () {
177
210
  pinSave.disabled = false;
178
- pinSave.textContent = 'Update PIN';
211
+ pinSave.textContent = 'Change PIN';
179
212
  if (pinMsg) {
180
- pinMsg.textContent = 'Network error.';
213
+ pinMsg.textContent = 'Connection lost. Check your network and try again.';
181
214
  pinMsg.className = 'us-pin-msg us-pin-msg-err';
182
215
  pinMsg.classList.remove('hidden');
183
216
  }
package/lib/sdk-bridge.js CHANGED
@@ -59,14 +59,14 @@ function createSDKBridge(opts) {
59
59
  var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
60
60
  var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
61
61
  var onProcessingChanged = opts.onProcessingChanged || function () {};
62
-
62
+ var onTurnDone = opts.onTurnDone || null;
63
63
 
64
64
  // --- Skill discovery helpers ---
65
65
 
66
66
  function discoverSkillDirs() {
67
67
  var skills = {};
68
68
  var dirs = [
69
- path.join(os.homedir(), ".claude", "skills"),
69
+ path.join(require("./config").REAL_HOME, ".claude", "skills"),
70
70
  path.join(cwd, ".claude", "skills"),
71
71
  ];
72
72
  for (var d = 0; d < dirs.length; d++) {
@@ -387,9 +387,13 @@ function createSDKBridge(opts) {
387
387
  });
388
388
  }
389
389
  // Reset for next turn in the same query
390
+ var donePreview = session.responsePreview || "";
390
391
  session.responsePreview = "";
391
392
  session.streamedText = false;
392
393
  sm.broadcastSessionList();
394
+ if (onTurnDone) {
395
+ try { onTurnDone(session, donePreview); } catch (e) {}
396
+ }
393
397
 
394
398
  } else if (parsed.type === "system" && parsed.subtype === "status") {
395
399
  if (parsed.status === "compacting") {
@@ -459,6 +463,18 @@ function createSDKBridge(opts) {
459
463
 
460
464
  } else if (parsed.type === "rate_limit_event" && parsed.rate_limit_info) {
461
465
  var info = parsed.rate_limit_info;
466
+
467
+ // Broadcast reset time for top-bar usage link
468
+ if (info.rateLimitType && info.resetsAt) {
469
+ send({
470
+ type: "rate_limit_usage",
471
+ rateLimitType: info.rateLimitType,
472
+ resetsAt: info.resetsAt * 1000,
473
+ status: info.status,
474
+ });
475
+ }
476
+
477
+ // Warning/rejection handling (existing behavior)
462
478
  if (info.status === "allowed_warning" || info.status === "rejected") {
463
479
  sendAndRecord(session, {
464
480
  type: "rate_limit",
@@ -832,14 +848,12 @@ function createSDKBridge(opts) {
832
848
  }
833
849
 
834
850
  if (dangerouslySkipPermissions) {
835
- queryOptions.permissionMode = "bypassPermissions";
836
851
  queryOptions.allowDangerouslySkipPermissions = true;
837
- } else {
838
- var modeToApply = session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode;
839
- if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
840
- if (modeToApply && modeToApply !== "default") {
841
- queryOptions.permissionMode = modeToApply;
842
- }
852
+ }
853
+ var modeToApply = session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode;
854
+ if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
855
+ if (modeToApply && modeToApply !== "default") {
856
+ queryOptions.permissionMode = modeToApply;
843
857
  }
844
858
 
845
859
  if (session.cliSessionId) {
@@ -980,27 +994,19 @@ function createSDKBridge(opts) {
980
994
  sm.broadcastSessionList();
981
995
  }
982
996
  cleanupSessionWorker(session);
983
- // Auto-continue for scheduler sessions on rate limit
997
+ // Auto-continue on rate limit (scheduler sessions, or user setting)
984
998
  var workerDidScheduleAC = false;
999
+ var workerACEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
985
1000
  if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()
986
- && session.onQueryComplete && !session.destroying) {
987
- var wacDelay = session.rateLimitResetsAt - Date.now() + 3000;
1001
+ && workerACEnabled && !session.destroying) {
988
1002
  var wacResetsAt = session.rateLimitResetsAt;
989
1003
  session.rateLimitResetsAt = null;
990
1004
  session.rateLimitAutoContinuePending = true;
991
1005
  workerDidScheduleAC = true;
992
- console.log("[sdk-bridge] Rate limited (worker), scheduling auto-continue in " + Math.round(wacDelay / 1000) + "s for session " + session.localId);
993
- sendAndRecord(session, { type: "auto_continue_scheduled", resetsAt: wacResetsAt });
994
- session.autoContinueTimer = setTimeout(function () {
995
- session.autoContinueTimer = null;
996
- session.rateLimitAutoContinuePending = false;
997
- if (session.destroying) return;
998
- console.log("[sdk-bridge] Auto-continue (worker) firing for session " + session.localId);
999
- session.isProcessing = true;
1000
- onProcessingChanged();
1001
- sendAndRecord(session, { type: "auto_continue_fired" });
1002
- startQuery(session, "continue", null, session.lastLinuxUser || null);
1003
- }, wacDelay);
1006
+ console.log("[sdk-bridge] Rate limited (worker), scheduling auto-continue via scheduleMessage for session " + session.localId);
1007
+ if (typeof opts.scheduleMessage === "function") {
1008
+ opts.scheduleMessage(session, "continue", wacResetsAt);
1009
+ }
1004
1010
  }
1005
1011
  if (session.onQueryComplete && !workerDidScheduleAC) {
1006
1012
  try { session.onQueryComplete(session); } catch (err) {
@@ -1149,6 +1155,52 @@ function createSDKBridge(opts) {
1149
1155
  if (mateAutoTools[toolName]) {
1150
1156
  return Promise.resolve({ behavior: "allow", updatedInput: input });
1151
1157
  }
1158
+ // Auto-approve safe Bash commands (read-only, non-destructive)
1159
+ if (toolName === "Bash" && input && input.command) {
1160
+ var cmd = input.command.trim();
1161
+ var firstWord = cmd.split(/[\s;|&]/)[0];
1162
+ var safeBashCommands = {
1163
+ ls: true, cat: true, head: true, tail: true, wc: true, file: true,
1164
+ // File/dir inspection
1165
+ ls: true, cat: true, head: true, tail: true, wc: true, file: true,
1166
+ stat: true, find: true, tree: true, du: true, df: true,
1167
+ readlink: true, realpath: true, basename: true, dirname: true,
1168
+ // Search
1169
+ grep: true, rg: true, ag: true, ack: true, fgrep: true, egrep: true,
1170
+ // Lookup
1171
+ which: true, type: true, whereis: true, command: true, hash: true,
1172
+ // Environment/system info
1173
+ echo: true, printf: true, env: true, printenv: true, pwd: true,
1174
+ whoami: true, id: true, groups: true,
1175
+ date: true, uname: true, hostname: true, uptime: true, arch: true,
1176
+ nproc: true, free: true, lsb_release: true, sw_vers: true,
1177
+ locale: true, timedatectl: true,
1178
+ // Version checks
1179
+ git: true, dotnet: true, ruby: true, java: true, javac: true,
1180
+ rustc: true, gcc: true, clang: true, cmake: true,
1181
+ // Text processing (pure stdin/stdout, no side effects)
1182
+ jq: true, yq: true, sort: true, uniq: true, cut: true, tr: true,
1183
+ awk: true, sed: true, paste: true, column: true, fold: true,
1184
+ rev: true, tac: true, nl: true, expand: true, unexpand: true,
1185
+ fmt: true, pr: true, csplit: true, comm: true, join: true,
1186
+ // Comparison/hashing
1187
+ diff: true, cmp: true, md5sum: true, sha256sum: true, sha1sum: true,
1188
+ shasum: true, cksum: true, sum: true, b2sum: true, base64: true,
1189
+ xxd: true, od: true, hexdump: true,
1190
+ // Misc read-only
1191
+ test: true, true: true, false: true, seq: true, yes: true,
1192
+ sleep: true, tee: true, xargs: true, time: true,
1193
+ man: true, help: true, info: true, apropos: true,
1194
+ cal: true, bc: true, expr: true, factor: true,
1195
+ lsof: true, ps: true, top: true, htop: true, pgrep: true,
1196
+ netstat: true, ss: true, ifconfig: true, ip: true, dig: true,
1197
+ nslookup: true, host: true, ping: true, traceroute: true,
1198
+ curl: true, wget: true, http: true,
1199
+ };
1200
+ if (safeBashCommands[firstWord]) {
1201
+ return Promise.resolve({ behavior: "allow", updatedInput: input });
1202
+ }
1203
+ }
1152
1204
  }
1153
1205
 
1154
1206
  // AskUserQuestion: wait for user answers via WebSocket
@@ -1373,27 +1425,19 @@ function createSDKBridge(opts) {
1373
1425
  session.pendingAskUser = {};
1374
1426
  session.pendingElicitations = {};
1375
1427
 
1376
- // Auto-continue for scheduler (Ralph Loop) sessions on rate limit
1428
+ // Auto-continue on rate limit (scheduler sessions, or user setting)
1377
1429
  var didScheduleAutoContinue = false;
1430
+ var acEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
1378
1431
  if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()
1379
- && session.onQueryComplete && !session.destroying) {
1380
- var acDelay = session.rateLimitResetsAt - Date.now() + 3000;
1432
+ && acEnabled && !session.destroying) {
1381
1433
  var acResetsAt = session.rateLimitResetsAt;
1382
1434
  session.rateLimitResetsAt = null;
1383
1435
  session.rateLimitAutoContinuePending = true;
1384
1436
  didScheduleAutoContinue = true;
1385
- console.log("[sdk-bridge] Rate limited, scheduling auto-continue in " + Math.round(acDelay / 1000) + "s for session " + session.localId);
1386
- sendAndRecord(session, { type: "auto_continue_scheduled", resetsAt: acResetsAt });
1387
- session.autoContinueTimer = setTimeout(function () {
1388
- session.autoContinueTimer = null;
1389
- session.rateLimitAutoContinuePending = false;
1390
- if (session.destroying) return;
1391
- console.log("[sdk-bridge] Auto-continue firing for session " + session.localId);
1392
- session.isProcessing = true;
1393
- onProcessingChanged();
1394
- sendAndRecord(session, { type: "auto_continue_fired" });
1395
- startQuery(session, "continue", null, session.lastLinuxUser || null);
1396
- }, acDelay);
1437
+ console.log("[sdk-bridge] Rate limited, scheduling auto-continue via scheduleMessage for session " + session.localId);
1438
+ if (typeof opts.scheduleMessage === "function") {
1439
+ opts.scheduleMessage(session, "continue", acResetsAt);
1440
+ }
1397
1441
  }
1398
1442
 
1399
1443
  // Ralph Loop: notify completion so loop orchestrator can proceed
@@ -1527,15 +1571,13 @@ function createSDKBridge(opts) {
1527
1571
  }
1528
1572
 
1529
1573
  if (dangerouslySkipPermissions) {
1530
- queryOptions.permissionMode = "bypassPermissions";
1531
1574
  queryOptions.allowDangerouslySkipPermissions = true;
1532
- } else {
1533
- // Pass permissionMode in queryOptions at creation time to avoid race condition
1534
- var modeToApply = session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode;
1535
- if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
1536
- if (modeToApply && modeToApply !== "default") {
1537
- queryOptions.permissionMode = modeToApply;
1538
- }
1575
+ }
1576
+ // Pass permissionMode in queryOptions at creation time to avoid race condition
1577
+ var modeToApply = session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode;
1578
+ if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
1579
+ if (modeToApply && modeToApply !== "default") {
1580
+ queryOptions.permissionMode = modeToApply;
1539
1581
  }
1540
1582
 
1541
1583
  if (session.cliSessionId) {
@@ -1745,12 +1787,6 @@ function createSDKBridge(opts) {
1745
1787
  }
1746
1788
 
1747
1789
  async function setPermissionMode(session, mode) {
1748
- // When dangerouslySkipPermissions is active, ignore mode changes from UI
1749
- // to prevent accidentally downgrading from bypassPermissions
1750
- if (dangerouslySkipPermissions) {
1751
- send({ type: "config_state", model: sm.currentModel || "", mode: "bypassPermissions", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [] });
1752
- return;
1753
- }
1754
1790
  if (session.worker) {
1755
1791
  session.worker.send({ type: "set_permission_mode", mode: mode });
1756
1792
  return;
@@ -1819,9 +1855,7 @@ function createSDKBridge(opts) {
1819
1855
 
1820
1856
  var query;
1821
1857
  try {
1822
- query = sdk.query({
1823
- prompt: mq,
1824
- options: {
1858
+ var mentionQueryOptions = {
1825
1859
  cwd: cwd,
1826
1860
  systemPrompt: opts.claudeMd,
1827
1861
  settingSources: ["user"],
@@ -1837,18 +1871,32 @@ function createSDKBridge(opts) {
1837
1871
  message: "Read-only access. You cannot make changes via @mention.",
1838
1872
  });
1839
1873
  },
1840
- },
1874
+ };
1875
+ if (opts.model) mentionQueryOptions.model = opts.model;
1876
+ query = sdk.query({
1877
+ prompt: mq,
1878
+ options: mentionQueryOptions,
1841
1879
  });
1842
1880
  } catch (e) {
1843
1881
  opts.onError("Failed to create mention query: " + (e.message || e));
1844
1882
  return null;
1845
1883
  }
1846
1884
 
1847
- // Push the initial message (context + question)
1885
+ // Push the initial message (context + question, with optional images)
1848
1886
  var initialPrompt = opts.initialContext + "\n\n" + opts.initialMessage;
1887
+ var initialContent = [];
1888
+ if (opts.initialImages && opts.initialImages.length > 0) {
1889
+ for (var ii = 0; ii < opts.initialImages.length; ii++) {
1890
+ initialContent.push({
1891
+ type: "image",
1892
+ source: { type: "base64", media_type: opts.initialImages[ii].mediaType, data: opts.initialImages[ii].data },
1893
+ });
1894
+ }
1895
+ }
1896
+ initialContent.push({ type: "text", text: initialPrompt });
1849
1897
  mq.push({
1850
1898
  type: "user",
1851
- message: { role: "user", content: [{ type: "text", text: initialPrompt }] },
1899
+ message: { role: "user", content: initialContent },
1852
1900
  });
1853
1901
 
1854
1902
  // Background stream processing loop
@@ -1951,7 +1999,7 @@ function createSDKBridge(opts) {
1951
1999
 
1952
2000
  return {
1953
2001
  // Push a follow-up message to the existing mention session
1954
- pushMessage: function (text, callbacks) {
2002
+ pushMessage: function (text, callbacks, images) {
1955
2003
  currentOnDelta = callbacks.onDelta;
1956
2004
  currentOnDone = callbacks.onDone;
1957
2005
  currentOnError = callbacks.onError;
@@ -1959,11 +2007,24 @@ function createSDKBridge(opts) {
1959
2007
  mentionBlocks = {};
1960
2008
  responseFullText = "";
1961
2009
  responseStreamedText = false;
2010
+ var content = [];
2011
+ if (images && images.length > 0) {
2012
+ for (var pi = 0; pi < images.length; pi++) {
2013
+ content.push({
2014
+ type: "image",
2015
+ source: { type: "base64", media_type: images[pi].mediaType, data: images[pi].data },
2016
+ });
2017
+ }
2018
+ }
2019
+ content.push({ type: "text", text: text });
1962
2020
  mq.push({
1963
2021
  type: "user",
1964
- message: { role: "user", content: [{ type: "text", text: text }] },
2022
+ message: { role: "user", content: content },
1965
2023
  });
1966
2024
  },
2025
+ abort: function () {
2026
+ try { abortController.abort(); } catch (e) {}
2027
+ },
1967
2028
  close: function () {
1968
2029
  alive = false;
1969
2030
  try { mq.end(); } catch (e) {}