clay-server 2.26.0-beta.8 → 2.26.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/lib/browser-mcp-server.js +4 -4
- package/lib/daemon.js +1 -1
- package/lib/debate-mcp-server.js +94 -0
- package/lib/mates.js +12 -24
- package/lib/project-debate.js +304 -166
- package/lib/project-mate-interaction.js +10 -5
- package/lib/project.js +128 -39
- package/lib/public/app.js +452 -127
- package/lib/public/css/debate.css +230 -2
- package/lib/public/css/filebrowser.css +41 -4
- package/lib/public/css/icon-strip.css +10 -10
- package/lib/public/css/input.css +107 -19
- package/lib/public/css/mates.css +56 -57
- package/lib/public/css/mention.css +7 -4
- package/lib/public/css/messages.css +17 -0
- package/lib/public/css/mobile-nav.css +3 -1
- package/lib/public/css/rewind.css +17 -4
- package/lib/public/index.html +23 -15
- package/lib/public/modules/context-sources.js +21 -6
- package/lib/public/modules/debate.js +298 -97
- package/lib/public/modules/input.js +18 -1
- package/lib/public/modules/mate-knowledge.js +11 -11
- package/lib/public/modules/mate-memory.js +5 -5
- package/lib/public/modules/mate-sidebar.js +13 -9
- package/lib/public/modules/mention.js +40 -2
- package/lib/public/modules/sidebar.js +105 -26
- package/lib/public/modules/terminal.js +62 -6
- package/lib/public/modules/theme.js +2 -1
- package/lib/public/modules/tools.js +47 -23
- package/lib/sdk-bridge.js +134 -21
- package/lib/sessions.js +2 -2
- package/package.json +1 -1
package/lib/sdk-bridge.js
CHANGED
|
@@ -437,9 +437,38 @@ function createSDKBridge(opts) {
|
|
|
437
437
|
session.pendingAskUser = {};
|
|
438
438
|
session.activeTaskToolIds = {};
|
|
439
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
|
+
|
|
440
465
|
session.isProcessing = false;
|
|
441
|
-
session.rateLimitResetsAt = null; // clear on success
|
|
442
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);
|
|
443
472
|
// Fetch rich context usage breakdown (fire-and-forget, non-blocking)
|
|
444
473
|
if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
|
|
445
474
|
session.queryInstance.getContextUsage().then(function(ctxUsage) {
|
|
@@ -465,10 +494,6 @@ function createSDKBridge(opts) {
|
|
|
465
494
|
}
|
|
466
495
|
// Detect "Not logged in · Please run /login" from SDK.
|
|
467
496
|
// This is a short canned response with zero cost, not actual AI output.
|
|
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
497
|
if (isLoginPrompt) {
|
|
473
498
|
var authUser = session.ownerId ? usersModule.findUserById(session.ownerId) : null;
|
|
474
499
|
var authLinuxUser = authUser && authUser.linuxUser ? authUser.linuxUser : null;
|
|
@@ -573,6 +598,7 @@ function createSDKBridge(opts) {
|
|
|
573
598
|
|
|
574
599
|
} else if (parsed.type === "rate_limit_event" && parsed.rate_limit_info) {
|
|
575
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);
|
|
576
602
|
|
|
577
603
|
// Broadcast reset time for top-bar usage link
|
|
578
604
|
if (info.rateLimitType && info.resetsAt) {
|
|
@@ -597,6 +623,33 @@ function createSDKBridge(opts) {
|
|
|
597
623
|
// Track rejection for auto-continue / scheduled message support
|
|
598
624
|
if (info.status === "rejected" && info.resetsAt) {
|
|
599
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
|
+
}
|
|
600
653
|
}
|
|
601
654
|
}
|
|
602
655
|
|
|
@@ -1230,7 +1283,24 @@ function createSDKBridge(opts) {
|
|
|
1230
1283
|
sm.broadcastSessionList();
|
|
1231
1284
|
}
|
|
1232
1285
|
cleanupSessionWorker(session, worker);
|
|
1233
|
-
|
|
1286
|
+
// Mark session as done so late rate_limit_event can detect race condition
|
|
1287
|
+
session.isProcessing = false;
|
|
1288
|
+
// Auto-continue on rate limit (scheduler sessions, or user setting)
|
|
1289
|
+
var doneDidScheduleAC = false;
|
|
1290
|
+
var doneACEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
|
|
1291
|
+
console.log("[sdk-bridge] query_done: session " + session.localId + " rateLimitResetsAt=" + session.rateLimitResetsAt + " acEnabled=" + doneACEnabled + " destroying=" + session.destroying + " scheduledMessage=" + !!session.scheduledMessage);
|
|
1292
|
+
if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()
|
|
1293
|
+
&& doneACEnabled && !session.destroying) {
|
|
1294
|
+
var doneResetsAt = session.rateLimitResetsAt;
|
|
1295
|
+
session.rateLimitResetsAt = null;
|
|
1296
|
+
session.rateLimitAutoContinuePending = true;
|
|
1297
|
+
doneDidScheduleAC = true;
|
|
1298
|
+
console.log("[sdk-bridge] Rate limited (worker/query_done), scheduling auto-continue for session " + session.localId);
|
|
1299
|
+
if (typeof opts.scheduleMessage === "function") {
|
|
1300
|
+
opts.scheduleMessage(session, "continue", doneResetsAt);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (session.onQueryComplete && !doneDidScheduleAC) {
|
|
1234
1304
|
try { session.onQueryComplete(session); } catch (err) {
|
|
1235
1305
|
console.error("[sdk-bridge] onQueryComplete error:", err.message || err);
|
|
1236
1306
|
}
|
|
@@ -1321,6 +1391,8 @@ function createSDKBridge(opts) {
|
|
|
1321
1391
|
sm.broadcastSessionList();
|
|
1322
1392
|
}
|
|
1323
1393
|
cleanupSessionWorker(session, worker);
|
|
1394
|
+
// Mark session as done so late rate_limit_event can detect race condition
|
|
1395
|
+
session.isProcessing = false;
|
|
1324
1396
|
// Auto-continue on rate limit (scheduler sessions, or user setting)
|
|
1325
1397
|
var workerDidScheduleAC = false;
|
|
1326
1398
|
var workerACEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
|
|
@@ -1495,24 +1567,34 @@ function createSDKBridge(opts) {
|
|
|
1495
1567
|
|
|
1496
1568
|
// --- SDK query lifecycle ---
|
|
1497
1569
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
if (toolName === "AskUserQuestion") {
|
|
1503
|
-
return Promise.resolve({ behavior: "deny", message: "Autonomous mode. Make your own decision." });
|
|
1504
|
-
}
|
|
1505
|
-
if (toolName === "EnterPlanMode") {
|
|
1506
|
-
return Promise.resolve({ behavior: "deny", message: "Do not enter plan mode. Execute directly." });
|
|
1507
|
-
}
|
|
1508
|
-
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1570
|
+
// Check if a tool should be auto-approved based on whitelist rules.
|
|
1571
|
+
// Returns { behavior: "allow", updatedInput } if whitelisted, or null if not.
|
|
1572
|
+
// Shared by handleCanUseTool and mate mention canUseTool handlers.
|
|
1573
|
+
function checkToolWhitelist(toolName, input) {
|
|
1511
1574
|
// Auto-approve read-only tools for ALL sessions.
|
|
1512
1575
|
// These tools only inspect files and fetch data — no side effects.
|
|
1513
1576
|
var readOnlyTools = { Read: true, Glob: true, Grep: true, WebFetch: true, WebSearch: true };
|
|
1514
1577
|
if (readOnlyTools[toolName]) {
|
|
1515
|
-
return
|
|
1578
|
+
return { behavior: "allow", updatedInput: input };
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Auto-approve safe browser MCP tools.
|
|
1582
|
+
// Only watch/unwatch: user explicitly chose which tab to share.
|
|
1583
|
+
// Everything else (screenshot, read_page, list_tabs, etc.) can expose
|
|
1584
|
+
// content from tabs the user didn't intend to share, so require approval.
|
|
1585
|
+
var safeBrowserTools = { browser_watch_tab: true, browser_unwatch_tab: true };
|
|
1586
|
+
if (toolName.indexOf("mcp__") === 0 && toolName.indexOf("__browser_") !== -1) {
|
|
1587
|
+
var mcpToolName = toolName.substring(toolName.lastIndexOf("__") + 2);
|
|
1588
|
+
if (safeBrowserTools[mcpToolName]) {
|
|
1589
|
+
return { behavior: "allow", updatedInput: input };
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// Auto-approve debate MCP tools (propose_debate).
|
|
1594
|
+
// These are user-facing tools that show inline approval cards,
|
|
1595
|
+
// so the permission prompt is redundant.
|
|
1596
|
+
if (toolName.indexOf("mcp__clay-debate__") === 0) {
|
|
1597
|
+
return { behavior: "allow", updatedInput: input };
|
|
1516
1598
|
}
|
|
1517
1599
|
|
|
1518
1600
|
// Auto-approve safe Bash commands (read-only, non-destructive)
|
|
@@ -1579,8 +1661,30 @@ function createSDKBridge(opts) {
|
|
|
1579
1661
|
if (!safeBashCommands[firstWord]) { allSafe = false; break; }
|
|
1580
1662
|
}
|
|
1581
1663
|
if (allSafe) {
|
|
1582
|
-
return
|
|
1664
|
+
return { behavior: "allow", updatedInput: input };
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
return null; // Not whitelisted
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
function handleCanUseTool(session, toolName, input, opts) {
|
|
1672
|
+
// Ralph Loop execution: auto-approve all tools, deny interactive ones.
|
|
1673
|
+
// Crafting sessions are interactive — user and Claude collaborate to build PROMPT.md / JUDGE.md.
|
|
1674
|
+
if (session.loop && session.loop.active && session.loop.role !== "crafting") {
|
|
1675
|
+
if (toolName === "AskUserQuestion") {
|
|
1676
|
+
return Promise.resolve({ behavior: "deny", message: "Autonomous mode. Make your own decision." });
|
|
1583
1677
|
}
|
|
1678
|
+
if (toolName === "EnterPlanMode") {
|
|
1679
|
+
return Promise.resolve({ behavior: "deny", message: "Do not enter plan mode. Execute directly." });
|
|
1680
|
+
}
|
|
1681
|
+
return Promise.resolve({ behavior: "allow", updatedInput: input });
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// Check shared whitelist (read-only tools, safe browser tools, safe bash commands)
|
|
1685
|
+
var whitelisted = checkToolWhitelist(toolName, input);
|
|
1686
|
+
if (whitelisted) {
|
|
1687
|
+
return Promise.resolve(whitelisted);
|
|
1584
1688
|
}
|
|
1585
1689
|
|
|
1586
1690
|
// AskUserQuestion: wait for user answers via WebSocket
|
|
@@ -1824,6 +1928,10 @@ function createSDKBridge(opts) {
|
|
|
1824
1928
|
session.pendingElicitations = {};
|
|
1825
1929
|
|
|
1826
1930
|
// Auto-continue on rate limit (scheduler sessions, or user setting)
|
|
1931
|
+
// Mark session as done processing so the late rate_limit_event handler
|
|
1932
|
+
// can detect the race condition and schedule auto-continue itself.
|
|
1933
|
+
session.isProcessing = false;
|
|
1934
|
+
|
|
1827
1935
|
var didScheduleAutoContinue = false;
|
|
1828
1936
|
var acEnabled = session.onQueryComplete || (typeof opts.getAutoContinueSetting === "function" && opts.getAutoContinueSetting(session));
|
|
1829
1937
|
if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()
|
|
@@ -1836,6 +1944,10 @@ function createSDKBridge(opts) {
|
|
|
1836
1944
|
if (typeof opts.scheduleMessage === "function") {
|
|
1837
1945
|
opts.scheduleMessage(session, "continue", acResetsAt);
|
|
1838
1946
|
}
|
|
1947
|
+
} else if (acEnabled && !session.destroying) {
|
|
1948
|
+
// Log why auto-continue was not scheduled (for debugging)
|
|
1949
|
+
console.log("[sdk-bridge] Query done, auto-continue enabled but not scheduled: rateLimitResetsAt=" +
|
|
1950
|
+
session.rateLimitResetsAt + " (will rely on late rate_limit_event handler)");
|
|
1839
1951
|
}
|
|
1840
1952
|
|
|
1841
1953
|
// Ralph Loop: notify completion so loop orchestrator can proceed
|
|
@@ -2437,6 +2549,7 @@ function createSDKBridge(opts) {
|
|
|
2437
2549
|
return {
|
|
2438
2550
|
createMessageQueue: createMessageQueue,
|
|
2439
2551
|
processSDKMessage: processSDKMessage,
|
|
2552
|
+
checkToolWhitelist: checkToolWhitelist,
|
|
2440
2553
|
handleCanUseTool: handleCanUseTool,
|
|
2441
2554
|
handleElicitation: handleElicitation,
|
|
2442
2555
|
processQueryStream: processQueryStream,
|
package/lib/sessions.js
CHANGED
|
@@ -237,10 +237,8 @@ function createSessionManager(opts) {
|
|
|
237
237
|
return [...sessions.values()].filter(function (s) {
|
|
238
238
|
if (s.hidden) return false;
|
|
239
239
|
if (!multiUser) {
|
|
240
|
-
// Single-user mode: only show sessions without ownerId
|
|
241
240
|
return !s.ownerId;
|
|
242
241
|
}
|
|
243
|
-
// Multi-user mode: include all sessions (per-user filtering done by canAccessSession)
|
|
244
242
|
return true;
|
|
245
243
|
});
|
|
246
244
|
}
|
|
@@ -507,6 +505,8 @@ function createSessionManager(opts) {
|
|
|
507
505
|
}
|
|
508
506
|
|
|
509
507
|
function doSendAndRecord(session, obj) {
|
|
508
|
+
// Stamp every recorded message so history replay preserves original times
|
|
509
|
+
if (!obj._ts) obj._ts = Date.now();
|
|
510
510
|
session.history.push(obj);
|
|
511
511
|
appendToSessionFile(session, obj);
|
|
512
512
|
if (sendEach) {
|