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.
- package/README.md +51 -91
- package/bin/cli.js +49 -14
- package/lib/builtin-mates.js +360 -0
- package/lib/cli-sessions.js +3 -3
- package/lib/config.js +30 -4
- package/lib/daemon.js +28 -5
- package/lib/mates.js +169 -7
- package/lib/notes.js +20 -0
- package/lib/os-users.js +71 -2
- package/lib/project.js +903 -228
- package/lib/public/app.js +249 -69
- package/lib/public/css/icon-strip.css +55 -2
- package/lib/public/css/input.css +50 -30
- package/lib/public/css/mates.css +316 -2
- package/lib/public/css/mention.css +23 -0
- package/lib/public/css/mobile-nav.css +198 -0
- package/lib/public/css/overlays.css +23 -0
- package/lib/public/css/rewind.css +32 -0
- package/lib/public/css/title-bar.css +3 -0
- package/lib/public/css/user-settings.css +2 -2
- package/lib/public/index.html +65 -14
- package/lib/public/mates/ally.png +0 -0
- package/lib/public/mates/sage.jpg +0 -0
- package/lib/public/mates/scout.png +0 -0
- package/lib/public/modules/command-palette.js +44 -4
- package/lib/public/modules/filebrowser.js +2 -0
- package/lib/public/modules/input.js +158 -16
- package/lib/public/modules/mate-knowledge.js +2 -0
- package/lib/public/modules/mate-memory.js +353 -0
- package/lib/public/modules/mention.js +77 -2
- package/lib/public/modules/notifications.js +0 -8
- package/lib/public/modules/server-settings.js +11 -4
- package/lib/public/modules/sidebar.js +507 -21
- package/lib/public/modules/theme.js +10 -12
- package/lib/public/modules/tools.js +84 -12
- package/lib/public/modules/user-settings.js +45 -12
- package/lib/sdk-bridge.js +122 -61
- package/lib/server.js +209 -13
- package/lib/sessions.js +4 -4
- package/lib/users.js +89 -0
- 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
|
-
|
|
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
|
|
1412
|
+
// Mate mode: restore sparkle activity row, hide thinking header
|
|
1349
1413
|
if (el.classList.contains("mate-thinking")) {
|
|
1350
|
-
var
|
|
1351
|
-
if (
|
|
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
|
-
|
|
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="
|
|
1373
|
-
'<
|
|
1374
|
-
'<span class="
|
|
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.
|
|
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
|
|
1497
|
+
// In mate mode: hide sparkle activity, show compact expandable thinking header
|
|
1426
1498
|
if (currentThinking.el.classList.contains("mate-thinking")) {
|
|
1427
|
-
var
|
|
1428
|
-
if (
|
|
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
|
-
|
|
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({
|
|
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 = '
|
|
193
|
+
pinSave.textContent = 'Change PIN';
|
|
161
194
|
if (pinMsg) {
|
|
162
|
-
pinMsg.textContent = 'PIN
|
|
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
|
|
199
|
+
showToast('PIN changed');
|
|
167
200
|
} else {
|
|
168
201
|
pinSave.disabled = false;
|
|
169
|
-
pinSave.textContent = '
|
|
202
|
+
pinSave.textContent = 'Change PIN';
|
|
170
203
|
if (pinMsg) {
|
|
171
|
-
pinMsg.textContent = data.error || '
|
|
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 = '
|
|
211
|
+
pinSave.textContent = 'Change PIN';
|
|
179
212
|
if (pinMsg) {
|
|
180
|
-
pinMsg.textContent = '
|
|
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(
|
|
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
|
-
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
|
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
|
-
&&
|
|
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
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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
|
|
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
|
-
&&
|
|
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
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
-
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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) {}
|