clay-server 2.36.1 → 2.36.2-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/lib/project-sessions.js +2 -1
- package/lib/public/css/messages.css +32 -0
- package/lib/public/modules/app-messages.js +21 -3
- package/lib/public/modules/app-panels.js +7 -3
- package/lib/public/modules/markdown.js +11 -0
- package/lib/public/modules/tools.js +39 -1
- package/lib/sdk-bridge.js +21 -2
- package/lib/sessions.js +5 -2
- package/package.json +1 -1
package/lib/project-sessions.js
CHANGED
|
@@ -330,10 +330,11 @@ function attachSessions(ctx) {
|
|
|
330
330
|
var targetVendor = switchTargetSess.vendor || sm.defaultVendor || null;
|
|
331
331
|
var tvModels = (targetVendor && sm.modelsByVendor && sm.modelsByVendor[targetVendor]) || [];
|
|
332
332
|
var found = false;
|
|
333
|
+
var _curLc = sm.currentModel.toLowerCase();
|
|
333
334
|
for (var tvi = 0; tvi < tvModels.length; tvi++) {
|
|
334
335
|
var tvEntry = tvModels[tvi];
|
|
335
336
|
var tvVal = typeof tvEntry === "string" ? tvEntry : (tvEntry && (tvEntry.value || tvEntry.id)) || "";
|
|
336
|
-
if (tvVal === sm.currentModel) { found = true; break; }
|
|
337
|
+
if (tvVal === sm.currentModel || (tvVal && (tvVal.toLowerCase().indexOf(_curLc) !== -1 || _curLc.indexOf(tvVal.toLowerCase()) !== -1))) { found = true; break; }
|
|
337
338
|
}
|
|
338
339
|
if (tvModels.length > 0 && !found) {
|
|
339
340
|
sm.currentModel = "";
|
|
@@ -1346,6 +1346,38 @@ pre.mermaid-error {
|
|
|
1346
1346
|
padding: 0 20px;
|
|
1347
1347
|
}
|
|
1348
1348
|
|
|
1349
|
+
/* Dead-session compaction: when a session is resumed without a live SDK
|
|
1350
|
+
process and the todo widget still has pending/in_progress items, the
|
|
1351
|
+
item list is hidden so the widget doesn't anchor visual position
|
|
1352
|
+
mid-page. Click the header to toggle the .todo-widget-dead-expanded
|
|
1353
|
+
override class which restores the full view. */
|
|
1354
|
+
.todo-widget-dead-compact .todo-header {
|
|
1355
|
+
cursor: pointer;
|
|
1356
|
+
border-radius: 12px;
|
|
1357
|
+
}
|
|
1358
|
+
.todo-widget-dead-compact .todo-header::after {
|
|
1359
|
+
content: "paused";
|
|
1360
|
+
font-size: 10px;
|
|
1361
|
+
color: var(--text-muted);
|
|
1362
|
+
background: rgba(var(--overlay-rgb), 0.06);
|
|
1363
|
+
padding: 2px 6px;
|
|
1364
|
+
border-radius: 4px;
|
|
1365
|
+
margin-left: 6px;
|
|
1366
|
+
letter-spacing: 0.04em;
|
|
1367
|
+
text-transform: uppercase;
|
|
1368
|
+
}
|
|
1369
|
+
.todo-widget-dead-compact .todo-progress,
|
|
1370
|
+
.todo-widget-dead-compact .todo-items {
|
|
1371
|
+
display: none;
|
|
1372
|
+
}
|
|
1373
|
+
.todo-widget-dead-compact.todo-widget-dead-expanded .todo-header {
|
|
1374
|
+
border-radius: 12px 12px 0 0;
|
|
1375
|
+
}
|
|
1376
|
+
.todo-widget-dead-compact.todo-widget-dead-expanded .todo-progress,
|
|
1377
|
+
.todo-widget-dead-compact.todo-widget-dead-expanded .todo-items {
|
|
1378
|
+
display: block;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1349
1381
|
.todo-header {
|
|
1350
1382
|
display: flex;
|
|
1351
1383
|
align-items: center;
|
|
@@ -8,7 +8,7 @@ import { getWs } from './ws-ref.js';
|
|
|
8
8
|
// --- Leaf module imports ---
|
|
9
9
|
import { showToast } from './utils.js';
|
|
10
10
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
11
|
-
import { renderMarkdown } from './markdown.js';
|
|
11
|
+
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
12
12
|
import { updatePageTitle } from './sidebar.js';
|
|
13
13
|
import { renderSessionList, updateSessionPresence, populateCliSessionList, handleSearchResults, updateSessionBadge } from './sidebar-sessions.js';
|
|
14
14
|
import { updateDmBadge, renderSidebarPresence, setMentionActive, renderUserStrip } from './sidebar-mates.js';
|
|
@@ -20,7 +20,7 @@ import { renderMemoryList } from './mate-memory.js';
|
|
|
20
20
|
import { handlePaletteSessionSwitch, setPaletteVersion } from './command-palette.js';
|
|
21
21
|
import { handleFindInSessionResults } from './session-search.js';
|
|
22
22
|
import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled } from './input.js';
|
|
23
|
-
import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved } from './tools.js';
|
|
23
|
+
import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved } from './tools.js';
|
|
24
24
|
import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
|
|
25
25
|
import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch } from './filebrowser.js';
|
|
26
26
|
import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
|
|
@@ -149,6 +149,20 @@ export function processMessage(msg) {
|
|
|
149
149
|
|
|
150
150
|
case "history_done":
|
|
151
151
|
store.set({ replayingHistory: false });
|
|
152
|
+
// Batched syntax highlight + mermaid pass for the entire replayed
|
|
153
|
+
// transcript. Per-message highlights are skipped during replay
|
|
154
|
+
// (see markdown.js) to avoid cascading reflows that the sticky-
|
|
155
|
+
// bottom observer chases for several seconds on long sessions.
|
|
156
|
+
if (messagesEl) {
|
|
157
|
+
highlightCodeBlocks(messagesEl);
|
|
158
|
+
renderMermaidBlocks(messagesEl);
|
|
159
|
+
}
|
|
160
|
+
// Compact dead-session todo widgets (unfinished items will never
|
|
161
|
+
// resolve — the agent isn't coming back) so they don't anchor
|
|
162
|
+
// visual position mid-page on resume.
|
|
163
|
+
if (!store.get('sessionIsProcessing')) {
|
|
164
|
+
applyDeadSessionTodoCompaction();
|
|
165
|
+
}
|
|
152
166
|
// Hide vendor toggle if session has history (vendor already locked)
|
|
153
167
|
var _hTotal = store.get('historyTotal') || 0;
|
|
154
168
|
var _vtw2 = document.getElementById("vendor-toggle-wrap");
|
|
@@ -558,7 +572,7 @@ export function processMessage(msg) {
|
|
|
558
572
|
} else if (_prevSid) {
|
|
559
573
|
delete store.get('sessionDrafts')[_prevSid];
|
|
560
574
|
}
|
|
561
|
-
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, vendorCapabilities: msg.capabilities || {} });
|
|
575
|
+
store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing });
|
|
562
576
|
if (msg.vendor) {
|
|
563
577
|
if (!store.get('vendorSelectionLocked') || msg.hasHistory) {
|
|
564
578
|
store.set({ currentVendor: msg.vendor });
|
|
@@ -715,6 +729,10 @@ export function processMessage(msg) {
|
|
|
715
729
|
case "status":
|
|
716
730
|
if (msg.status === "processing") {
|
|
717
731
|
setStatus("processing");
|
|
732
|
+
// Session became live — undo any dead-session todo compaction
|
|
733
|
+
// applied at history_done time.
|
|
734
|
+
store.set({ sessionIsProcessing: true });
|
|
735
|
+
applyDeadSessionTodoCompaction();
|
|
718
736
|
if (!(store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) && !store.get('matePreThinkingEl')) {
|
|
719
737
|
setActivity("thinking");
|
|
720
738
|
}
|
|
@@ -749,8 +749,9 @@ export function toggleStatusPanel() {
|
|
|
749
749
|
// --- Context panel ---
|
|
750
750
|
|
|
751
751
|
export function resolveContextWindow(model, sdkValue) {
|
|
752
|
-
if (sdkValue) return sdkValue;
|
|
753
752
|
var lc = (model || "").toLowerCase();
|
|
753
|
+
if (lc.includes("[1m]")) return 1000000;
|
|
754
|
+
if (sdkValue) return sdkValue;
|
|
754
755
|
for (var key in KNOWN_CONTEXT_WINDOWS) {
|
|
755
756
|
if (lc.includes(key)) return KNOWN_CONTEXT_WINDOWS[key];
|
|
756
757
|
}
|
|
@@ -851,8 +852,11 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
|
|
|
851
852
|
if (models.length > 0) {
|
|
852
853
|
var m = models[0];
|
|
853
854
|
var mu = modelUsage[m];
|
|
854
|
-
|
|
855
|
-
|
|
855
|
+
// Prefer the user-configured model name over the API-reported one
|
|
856
|
+
// (e.g. CLI reports "claude-sonnet-4-6" even when running as opus[1m])
|
|
857
|
+
var displayModel = store.get('currentModel') || m;
|
|
858
|
+
contextData.model = displayModel;
|
|
859
|
+
contextData.contextWindow = resolveContextWindow(displayModel, mu.contextWindow);
|
|
856
860
|
if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
|
|
857
861
|
}
|
|
858
862
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { copyToClipboard, escapeHtml } from './utils.js';
|
|
2
2
|
import { refreshIcons } from './icons.js';
|
|
3
3
|
import { getMermaidThemeVars } from './theme.js';
|
|
4
|
+
import { store } from './store.js';
|
|
4
5
|
|
|
5
6
|
// Initialize markdown parser
|
|
6
7
|
marked.use({ gfm: true, breaks: false });
|
|
@@ -57,6 +58,13 @@ export function parseEmojis(el) {
|
|
|
57
58
|
var langAliases = { jsonl: "json", dotenv: "bash" };
|
|
58
59
|
|
|
59
60
|
export function highlightCodeBlocks(el) {
|
|
61
|
+
// Defer all syntax highlighting + copy-button injection while replaying
|
|
62
|
+
// history. Doing this per-message during replay triggers a long tail of
|
|
63
|
+
// sequential reflows that the sticky-bottom scroll observer chases for
|
|
64
|
+
// 4-8s. After history_done, app-messages.js calls this once on the full
|
|
65
|
+
// messages container, so unhighlighted blocks (selector :not(.hljs))
|
|
66
|
+
// are picked up in a single pass.
|
|
67
|
+
if (store.get('replayingHistory')) return;
|
|
60
68
|
el.querySelectorAll("pre code:not(.hljs):not(.language-mermaid)").forEach(function (block) {
|
|
61
69
|
var cls = Array.from(block.classList).find(function (c) { return c.startsWith("language-"); });
|
|
62
70
|
if (cls) {
|
|
@@ -93,6 +101,9 @@ export function highlightCodeBlocks(el) {
|
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
export function renderMermaidBlocks(el) {
|
|
104
|
+
// Same rationale as highlightCodeBlocks: defer mermaid renders until
|
|
105
|
+
// history_done so they don't cause cascading layout shifts during replay.
|
|
106
|
+
if (store.get('replayingHistory')) return;
|
|
96
107
|
var blocks = el.querySelectorAll("pre code.language-mermaid");
|
|
97
108
|
blocks.forEach(function (codeEl) {
|
|
98
109
|
var pre = codeEl.parentElement;
|
|
@@ -30,6 +30,11 @@ var todoItems = [];
|
|
|
30
30
|
var todoWidgetEl = null;
|
|
31
31
|
var todoWidgetVisible = true; // whether in-chat widget is in viewport
|
|
32
32
|
var todoObserver = null;
|
|
33
|
+
// When a session is resumed without a live SDK process and still has
|
|
34
|
+
// pending/in_progress items, the widget is rendered in compact mode
|
|
35
|
+
// (header + count only) so it doesn't anchor visual position mid-page
|
|
36
|
+
// or extend the sticky-bottom settle window with a tall element.
|
|
37
|
+
var todoDeadCompact = false;
|
|
33
38
|
var todoMeta = {
|
|
34
39
|
variant: "tasks",
|
|
35
40
|
title: "Tasks",
|
|
@@ -1289,7 +1294,12 @@ export function handleTodoWrite(input) {
|
|
|
1289
1294
|
activeForm: t.activeForm || "",
|
|
1290
1295
|
};
|
|
1291
1296
|
});
|
|
1297
|
+
// A fresh TodoWrite during replay is just historical state. After replay
|
|
1298
|
+
// ends, applyDeadSessionTodoCompaction (called from history_done) decides
|
|
1299
|
+
// whether to compact. During live operation, sessionIsProcessing is true
|
|
1300
|
+
// and applyDeadSessionTodoCompaction is a no-op for compaction.
|
|
1292
1301
|
renderTodoWidget();
|
|
1302
|
+
applyDeadSessionTodoCompaction();
|
|
1293
1303
|
}
|
|
1294
1304
|
|
|
1295
1305
|
export function handleTaskCreate(input) {
|
|
@@ -1359,7 +1369,9 @@ function renderTodoWidget() {
|
|
|
1359
1369
|
todoWidgetEl = document.createElement("div");
|
|
1360
1370
|
todoWidgetEl.className = "todo-widget";
|
|
1361
1371
|
}
|
|
1362
|
-
todoWidgetEl.className = "todo-widget"
|
|
1372
|
+
todoWidgetEl.className = "todo-widget"
|
|
1373
|
+
+ (todoMeta.variant === "plan" ? " todo-widget-plan" : "")
|
|
1374
|
+
+ (todoDeadCompact ? " todo-widget-dead-compact" : "");
|
|
1363
1375
|
|
|
1364
1376
|
var completed = 0;
|
|
1365
1377
|
for (var i = 0; i < todoItems.length; i++) {
|
|
@@ -1395,12 +1407,38 @@ function renderTodoWidget() {
|
|
|
1395
1407
|
if (isNew) {
|
|
1396
1408
|
ctx.addToMessages(todoWidgetEl);
|
|
1397
1409
|
setupTodoObserver();
|
|
1410
|
+
// Click-to-expand for compact mode. Toggling the override class lets
|
|
1411
|
+
// the user inspect items without permanently un-compacting the widget.
|
|
1412
|
+
todoWidgetEl.addEventListener("click", function (e) {
|
|
1413
|
+
if (!todoWidgetEl.classList.contains("todo-widget-dead-compact")) return;
|
|
1414
|
+
// Only expand on header click, not on items inside an already-expanded view
|
|
1415
|
+
var header = e.target.closest(".todo-header");
|
|
1416
|
+
if (!header) return;
|
|
1417
|
+
todoWidgetEl.classList.toggle("todo-widget-dead-expanded");
|
|
1418
|
+
});
|
|
1398
1419
|
}
|
|
1399
1420
|
updateTodoSticky();
|
|
1400
1421
|
refreshIcons();
|
|
1401
1422
|
maybeScrollToBottom();
|
|
1402
1423
|
}
|
|
1403
1424
|
|
|
1425
|
+
export function applyDeadSessionTodoCompaction() {
|
|
1426
|
+
// Called after history_done and on status changes. Decides whether the
|
|
1427
|
+
// current session is "dead" (resumed, no live SDK process) and whether
|
|
1428
|
+
// the todo widget has unfinished items that will never resolve.
|
|
1429
|
+
var isLive = !!store.get('sessionIsProcessing');
|
|
1430
|
+
var hasUnfinished = false;
|
|
1431
|
+
for (var i = 0; i < todoItems.length; i++) {
|
|
1432
|
+
var s = todoItems[i].status;
|
|
1433
|
+
if (s === "in_progress" || s === "pending") { hasUnfinished = true; break; }
|
|
1434
|
+
}
|
|
1435
|
+
todoDeadCompact = !isLive && hasUnfinished;
|
|
1436
|
+
if (todoWidgetEl) {
|
|
1437
|
+
todoWidgetEl.classList.toggle("todo-widget-dead-compact", todoDeadCompact);
|
|
1438
|
+
if (!todoDeadCompact) todoWidgetEl.classList.remove("todo-widget-dead-expanded");
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1404
1442
|
function setupTodoObserver() {
|
|
1405
1443
|
if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
|
|
1406
1444
|
if (!todoWidgetEl) return;
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -198,6 +198,24 @@ function createSDKBridge(opts) {
|
|
|
198
198
|
return false;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// Resolve a shorthand model name (e.g. "opus[1m]") to its full ID
|
|
202
|
+
// in the vendor model list (e.g. "claude-opus-4.6[1m]").
|
|
203
|
+
function resolveModelInList(list, modelId) {
|
|
204
|
+
if (!list || !modelId) return null;
|
|
205
|
+
var lc = modelId.toLowerCase();
|
|
206
|
+
for (var mi = 0; mi < list.length; mi++) {
|
|
207
|
+
var val = modelEntryValue(list[mi]);
|
|
208
|
+
if (val === modelId) return val;
|
|
209
|
+
}
|
|
210
|
+
for (var mi = 0; mi < list.length; mi++) {
|
|
211
|
+
var val = modelEntryValue(list[mi]);
|
|
212
|
+
if (!val || val === "default") continue;
|
|
213
|
+
var vlc = val.toLowerCase();
|
|
214
|
+
if (vlc.indexOf(lc) !== -1 || lc.indexOf(vlc) !== -1) return val;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
201
219
|
function sendModelInfoForVendor(vendor, model) {
|
|
202
220
|
send({
|
|
203
221
|
type: "model_info",
|
|
@@ -1234,12 +1252,13 @@ function createSDKBridge(opts) {
|
|
|
1234
1252
|
// Claude would reject the unknown model. We validate against the
|
|
1235
1253
|
// session vendor's model list regardless of which vendor happens to be
|
|
1236
1254
|
// the project's default adapter.
|
|
1237
|
-
var queryModel = ls.model || sm.currentModel || undefined;
|
|
1255
|
+
var queryModel = (ls.model && ls.model !== "default" ? ls.model : null) || sm.currentModel || undefined;
|
|
1238
1256
|
var sessionVendor = session.vendor || (adapter && adapter.vendor) || null;
|
|
1239
1257
|
if (sessionVendor) {
|
|
1240
1258
|
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[sessionVendor]) || [];
|
|
1241
1259
|
if (vendorModels.length > 0 && queryModel && !modelListContains(vendorModels, queryModel)) {
|
|
1242
|
-
|
|
1260
|
+
var resolved = resolveModelInList(vendorModels, queryModel);
|
|
1261
|
+
queryModel = resolved || modelEntryValue(vendorModels[0]);
|
|
1243
1262
|
}
|
|
1244
1263
|
}
|
|
1245
1264
|
// Guard against anything upstream having set queryModel to an object
|
package/lib/sessions.js
CHANGED
|
@@ -345,7 +345,10 @@ function createSessionManager(opts) {
|
|
|
345
345
|
return session;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
|
|
348
|
+
// Initial replay payload size. Lowered from 200 to reduce client-side
|
|
349
|
+
// layout work on resume — older items are loaded progressively on
|
|
350
|
+
// scroll-up via the existing pagination path.
|
|
351
|
+
var HISTORY_PAGE_SIZE = 100;
|
|
349
352
|
|
|
350
353
|
function findTurnBoundary(history, targetIndex) {
|
|
351
354
|
for (var i = targetIndex; i >= 0; i--) {
|
|
@@ -420,7 +423,7 @@ function createSessionManager(opts) {
|
|
|
420
423
|
var _capsByVendor = capabilitiesByVendor || {};
|
|
421
424
|
var _sessionVendor = session.vendor || defaultVendor || "claude";
|
|
422
425
|
var _vendorCaps = _capsByVendor[_sessionVendor] || {};
|
|
423
|
-
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null, vendor: session.vendor || null, hasHistory: (session.history && session.history.length > 0), capabilities: _vendorCaps });
|
|
426
|
+
_send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null, vendor: session.vendor || null, hasHistory: (session.history && session.history.length > 0), capabilities: _vendorCaps, isProcessing: !!session.isProcessing });
|
|
424
427
|
// Send vendor-specific slash commands
|
|
425
428
|
var _vendorCmds = slashCommandsByVendor[_sessionVendor] || slashCommands || [];
|
|
426
429
|
_send({ type: "slash_commands", commands: _vendorCmds, vendor: _sessionVendor });
|
package/package.json
CHANGED