clay-server 2.34.0-beta.8 → 2.34.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.
@@ -94,12 +94,14 @@ export function initSidebar(_ctx) {
94
94
  }
95
95
  } catch (e) {}
96
96
 
97
- ctx.newSessionBtn.addEventListener("click", function () {
98
- if (ctx.ws && ctx.connected) {
99
- ctx.ws.send(JSON.stringify({ type: "new_session" }));
100
- closeSidebar();
101
- }
102
- });
97
+ if (ctx.newSessionBtn) {
98
+ ctx.newSessionBtn.addEventListener("click", function () {
99
+ if (ctx.ws && ctx.connected) {
100
+ ctx.ws.send(JSON.stringify({ type: "new_session" }));
101
+ closeSidebar();
102
+ }
103
+ });
104
+ }
103
105
 
104
106
  // --- Loop (Ralph wizard) tool-palette tile ---
105
107
  // The tile is rendered by tool-palette.js at the stable id
@@ -1,7 +1,7 @@
1
1
  import { escapeHtml, copyToClipboard } from './utils.js';
2
2
  import { iconHtml, refreshIcons } from './icons.js';
3
3
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
4
- import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
4
+ import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff, reconstructPatchSources } from './diff.js';
5
5
  import { openFile } from './filebrowser.js';
6
6
  import { mateAvatarUrl } from './avatar.js';
7
7
  import { getChatLayout } from './theme.js';
@@ -1740,12 +1740,12 @@ export function updateToolExecuting(id, name, input) {
1740
1740
  ctx.scrollToBottom();
1741
1741
  }
1742
1742
 
1743
- function renderEditDiff(oldStr, newStr, filePath) {
1743
+ // Shared chrome (filename header + unified/split toggle) for diff renderings.
1744
+ // makeUnified and makeSplit are factories that return a fresh body element.
1745
+ function buildDiffChrome(filePath, linkOldStr, linkNewStr, makeUnified, makeSplit) {
1744
1746
  var wrapper = document.createElement("div");
1745
1747
  wrapper.className = "edit-diff";
1746
- var lang = getLanguageFromPath(filePath);
1747
1748
 
1748
- // Header with file path and split toggle (desktop only)
1749
1749
  var header = document.createElement("div");
1750
1750
  header.className = "edit-diff-header";
1751
1751
 
@@ -1758,7 +1758,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
1758
1758
  e.stopPropagation();
1759
1759
  openFile(fp, { diff: { oldStr: os || "", newStr: ns || "" } });
1760
1760
  });
1761
- })(filePath, oldStr, newStr);
1761
+ })(filePath, linkOldStr, linkNewStr);
1762
1762
  }
1763
1763
  header.appendChild(pathSpan);
1764
1764
 
@@ -1784,7 +1784,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
1784
1784
 
1785
1785
  wrapper.appendChild(header);
1786
1786
 
1787
- var currentBody = renderUnifiedDiff(oldStr, newStr, lang);
1787
+ var currentBody = makeUnified();
1788
1788
  wrapper.appendChild(currentBody);
1789
1789
 
1790
1790
  unifiedBtn.addEventListener("click", function (e) {
@@ -1794,7 +1794,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
1794
1794
  unifiedBtn.classList.add("active");
1795
1795
  splitBtn.classList.remove("active");
1796
1796
  wrapper.removeChild(currentBody);
1797
- currentBody = renderUnifiedDiff(oldStr, newStr, lang);
1797
+ currentBody = makeUnified();
1798
1798
  wrapper.appendChild(currentBody);
1799
1799
  refreshIcons();
1800
1800
  });
@@ -1806,7 +1806,7 @@ function renderEditDiff(oldStr, newStr, filePath) {
1806
1806
  splitBtn.classList.add("active");
1807
1807
  unifiedBtn.classList.remove("active");
1808
1808
  wrapper.removeChild(currentBody);
1809
- currentBody = renderSplitDiff(oldStr, newStr, lang);
1809
+ currentBody = makeSplit();
1810
1810
  wrapper.appendChild(currentBody);
1811
1811
  refreshIcons();
1812
1812
  });
@@ -1814,6 +1814,29 @@ function renderEditDiff(oldStr, newStr, filePath) {
1814
1814
  return wrapper;
1815
1815
  }
1816
1816
 
1817
+ function renderEditDiff(oldStr, newStr, filePath) {
1818
+ var lang = getLanguageFromPath(filePath);
1819
+ return buildDiffChrome(
1820
+ filePath,
1821
+ oldStr,
1822
+ newStr,
1823
+ function () { return renderUnifiedDiff(oldStr, newStr, lang); },
1824
+ function () { return renderSplitDiff(oldStr, newStr, lang); }
1825
+ );
1826
+ }
1827
+
1828
+ function renderPatchDiffBlock(patchText, filePath) {
1829
+ var lang = getLanguageFromPath(filePath);
1830
+ var sources = reconstructPatchSources(patchText);
1831
+ return buildDiffChrome(
1832
+ filePath,
1833
+ sources.oldStr,
1834
+ sources.newStr,
1835
+ function () { return renderPatchDiff(patchText, lang); },
1836
+ function () { return renderSplitDiff(sources.oldStr, sources.newStr, lang); }
1837
+ );
1838
+ }
1839
+
1817
1840
  function isDiffContent(text) {
1818
1841
  var lines = text.split("\n");
1819
1842
  var hasHunkHeader = false;
@@ -1921,8 +1944,12 @@ export function updateToolResult(id, content, isError, images) {
1921
1944
  if (hasEditDiff) {
1922
1945
  resultBlock.appendChild(renderEditDiff(tool.input.old_string, tool.input.new_string, tool.input.file_path));
1923
1946
  } else if (!isError && isDiffContent(displayContent)) {
1924
- var patchLang = tool.input && tool.input.file_path ? getLanguageFromPath(tool.input.file_path) : null;
1925
- resultBlock.appendChild(renderPatchDiff(displayContent, patchLang));
1947
+ var patchFilePath = tool.input && tool.input.file_path ? tool.input.file_path : null;
1948
+ if (patchFilePath) {
1949
+ resultBlock.appendChild(renderPatchDiffBlock(displayContent, patchFilePath));
1950
+ } else {
1951
+ resultBlock.appendChild(renderPatchDiff(displayContent, null));
1952
+ }
1926
1953
  } else if (!isError && tool.name === "Read" && tool.input && tool.input.file_path && isImagePath(tool.input.file_path)) {
1927
1954
  // Image file: show inline preview
1928
1955
  var imgWrap = document.createElement("div");
package/lib/public/sw.js CHANGED
@@ -1,4 +1,4 @@
1
- var CACHE_NAME = "clay-offline-v2";
1
+ var CACHE_NAME = "clay-offline-v3";
2
2
 
3
3
  self.addEventListener("install", function (event) {
4
4
  event.waitUntil(self.skipWaiting());
package/lib/sessions.js CHANGED
@@ -96,6 +96,7 @@ function createSessionManager(opts) {
96
96
  if (session.vendor) metaObj.vendor = session.vendor;
97
97
  if (session.sessionVisibility) metaObj.sessionVisibility = session.sessionVisibility;
98
98
  if (session.bookmarked) metaObj.bookmarked = true;
99
+ if (typeof session.favoriteOrder === "number") metaObj.favoriteOrder = session.favoriteOrder;
99
100
  if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
100
101
  if (session.loop) metaObj.loop = session.loop;
101
102
  if (session.debateState) metaObj.debateState = session.debateState;
@@ -202,6 +203,7 @@ function createSessionManager(opts) {
202
203
  if (m.ownerId) session.ownerId = m.ownerId;
203
204
  session.sessionVisibility = m.sessionVisibility || "shared";
204
205
  session.bookmarked = !!m.bookmarked;
206
+ session.favoriteOrder = typeof m.favoriteOrder === "number" ? m.favoriteOrder : null;
205
207
  sessions.set(localId, session);
206
208
  }
207
209
  }
@@ -241,6 +243,7 @@ function createSessionManager(opts) {
241
243
  ownerId: s.ownerId || null,
242
244
  sessionVisibility: s.sessionVisibility || "shared",
243
245
  bookmarked: !!s.bookmarked,
246
+ favoriteOrder: typeof s.favoriteOrder === "number" ? s.favoriteOrder : null,
244
247
  unread: unreadMap[s.localId] || 0,
245
248
  vendor: s.vendor || null,
246
249
  };
@@ -303,6 +306,7 @@ function createSessionManager(opts) {
303
306
  ownerId: (sessionOpts && sessionOpts.ownerId) || null,
304
307
  sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility) || "shared",
305
308
  bookmarked: false,
309
+ favoriteOrder: null,
306
310
  vendor: (sessionOpts && sessionOpts.vendor) || null,
307
311
  };
308
312
  sessions.set(localId, session);
@@ -334,6 +338,7 @@ function createSessionManager(opts) {
334
338
  ownerId: (sessionOpts && sessionOpts.ownerId) || null,
335
339
  sessionVisibility: (sessionOpts && sessionOpts.sessionVisibility) || "shared",
336
340
  bookmarked: false,
341
+ favoriteOrder: null,
337
342
  vendor: (sessionOpts && sessionOpts.vendor) || null,
338
343
  };
339
344
  sessions.set(localId, session);
@@ -525,6 +530,38 @@ function createSessionManager(opts) {
525
530
  sessions.delete(localId);
526
531
  }
527
532
 
533
+ function deleteSessionsBulk(localIds, targetWs) {
534
+ if (!Array.isArray(localIds) || localIds.length === 0) return;
535
+
536
+ var seen = {};
537
+ var ids = [];
538
+ for (var i = 0; i < localIds.length; i++) {
539
+ var id = localIds[i];
540
+ if (typeof id !== "number" || seen[id] || !sessions.has(id)) continue;
541
+ seen[id] = true;
542
+ ids.push(id);
543
+ }
544
+ if (ids.length === 0) return;
545
+
546
+ var deletedActive = false;
547
+ for (var j = 0; j < ids.length; j++) {
548
+ if (ids[j] === activeSessionId) deletedActive = true;
549
+ deleteSessionQuiet(ids[j]);
550
+ }
551
+
552
+ if (sessions.size === 0) {
553
+ createSession(null, targetWs);
554
+ return;
555
+ }
556
+
557
+ if (deletedActive) {
558
+ var remaining = [...sessions.keys()];
559
+ switchSession(remaining[remaining.length - 1], targetWs);
560
+ } else {
561
+ broadcastSessionList();
562
+ }
563
+ }
564
+
528
565
  function doSendToSession(session, obj) {
529
566
  // Send to active clients without recording to history/disk (ephemeral data)
530
567
  if (sendEach) {
@@ -622,6 +659,7 @@ function createSessionManager(opts) {
622
659
  history: cliHistory,
623
660
  messageUUIDs: [],
624
661
  bookmarked: false,
662
+ favoriteOrder: null,
625
663
  };
626
664
  sessions.set(localId, session);
627
665
  saveSessionFile(session);
@@ -843,6 +881,7 @@ function createSessionManager(opts) {
843
881
  switchSession: switchSession,
844
882
  deleteSession: deleteSession,
845
883
  deleteSessionQuiet: deleteSessionQuiet,
884
+ deleteSessionsBulk: deleteSessionsBulk,
846
885
  resumeSession: resumeSession,
847
886
  broadcastSessionList: broadcastSessionList,
848
887
  getTotalUnread: function (ws) {
@@ -876,10 +915,61 @@ function createSessionManager(opts) {
876
915
  var session = sessions.get(localId);
877
916
  if (!session) return { error: "Session not found" };
878
917
  session.bookmarked = !!bookmarked;
918
+ if (session.bookmarked) {
919
+ var maxOrder = -1;
920
+ sessions.forEach(function (s) {
921
+ if (s.bookmarked && typeof s.favoriteOrder === "number" && s.favoriteOrder > maxOrder) {
922
+ maxOrder = s.favoriteOrder;
923
+ }
924
+ });
925
+ session.favoriteOrder = maxOrder + 1;
926
+ } else {
927
+ session.favoriteOrder = null;
928
+ }
879
929
  saveSessionFile(session);
880
930
  broadcastSessionList();
881
931
  return { ok: true };
882
932
  },
933
+ reorderBookmarkedSessions: function (sourceId, targetId, insertBefore) {
934
+ var source = sessions.get(sourceId);
935
+ var target = sessions.get(targetId);
936
+ if (!source || !target) return { error: "Session not found" };
937
+ if (!source.bookmarked || !target.bookmarked) return { error: "Only favorites can be reordered" };
938
+
939
+ var favorites = [];
940
+ sessions.forEach(function (s) {
941
+ if (s.bookmarked) favorites.push(s);
942
+ });
943
+ favorites.sort(function (a, b) {
944
+ var ao = typeof a.favoriteOrder === "number" ? a.favoriteOrder : Number.MAX_SAFE_INTEGER;
945
+ var bo = typeof b.favoriteOrder === "number" ? b.favoriteOrder : Number.MAX_SAFE_INTEGER;
946
+ if (ao !== bo) return ao - bo;
947
+ return (b.lastActivity || 0) - (a.lastActivity || 0);
948
+ });
949
+
950
+ var reordered = [];
951
+ for (var i = 0; i < favorites.length; i++) {
952
+ if (favorites[i].localId !== sourceId) reordered.push(favorites[i]);
953
+ }
954
+
955
+ var targetIdx = -1;
956
+ for (var j = 0; j < reordered.length; j++) {
957
+ if (reordered[j].localId === targetId) {
958
+ targetIdx = j;
959
+ break;
960
+ }
961
+ }
962
+ if (targetIdx === -1) return { error: "Target favorite not found" };
963
+ if (!insertBefore) targetIdx++;
964
+ reordered.splice(targetIdx, 0, source);
965
+
966
+ for (var k = 0; k < reordered.length; k++) {
967
+ reordered[k].favoriteOrder = k;
968
+ saveSessionFile(reordered[k]);
969
+ }
970
+ broadcastSessionList();
971
+ return { ok: true };
972
+ },
883
973
  setSessionOwner: function (localId, ownerId) {
884
974
  var session = sessions.get(localId);
885
975
  if (!session) return { error: "Session not found" };
package/lib/ws-schema.js CHANGED
@@ -21,6 +21,8 @@ var schema = {
21
21
  "delete_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a session by ID" },
22
22
  "rename_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Rename a session" },
23
23
  "set_session_bookmark": { direction: "c2s", handler: "lib/project-sessions.js", description: "Bookmark or unbookmark a session in the sidebar" },
24
+ "reorder_session_bookmarks": { direction: "c2s", handler: "lib/project-sessions.js", description: "Reorder favorited sessions within the favorites area" },
25
+ "bulk_delete_sessions": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a group of sessions at once" },
24
26
  "resume_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Resume a CLI session by its CLI session ID" },
25
27
  "set_session_visibility": { direction: "c2s", handler: "lib/project-sessions.js", description: "Show or hide a session in the sidebar" },
26
28
  "search_sessions": { direction: "c2s", handler: "lib/project-sessions.js", description: "Search session titles" },
@@ -793,10 +793,37 @@ function createCodexQueryHandle(appServer, queryOpts) {
793
793
 
794
794
  // --- Main query loop ---
795
795
  async function runQueryLoop(initialMessage) {
796
- // Prepend system prompt (project instructions from YOKE layer) to first message
797
- var currentMessage = systemPrompt
798
- ? systemPrompt + "\n\n" + initialMessage
799
- : initialMessage;
796
+ // Prepend system prompt (project instructions from YOKE layer) to first message.
797
+ // initialMessage may be a string (text-only) or an array of content items
798
+ // (e.g. [{ type: "text", text: "..." }, ...] when images/attachments are present).
799
+ // Naive string concatenation on an array coerces it via toString(), producing
800
+ // "[object Object]" inside the prompt, so we must branch on the shape.
801
+ var currentMessage;
802
+ if (!systemPrompt) {
803
+ currentMessage = initialMessage;
804
+ } else if (typeof initialMessage === "string") {
805
+ currentMessage = systemPrompt + "\n\n" + initialMessage;
806
+ } else if (Array.isArray(initialMessage)) {
807
+ // Prepend systemPrompt to the first text item; if none exists, insert one.
808
+ var cloned = initialMessage.slice();
809
+ var injected = false;
810
+ for (var i = 0; i < cloned.length; i++) {
811
+ if (cloned[i] && cloned[i].type === "text") {
812
+ cloned[i] = {
813
+ type: "text",
814
+ text: systemPrompt + "\n\n" + (cloned[i].text || ""),
815
+ };
816
+ injected = true;
817
+ break;
818
+ }
819
+ }
820
+ if (!injected) {
821
+ cloned.unshift({ type: "text", text: systemPrompt });
822
+ }
823
+ currentMessage = cloned;
824
+ } else {
825
+ currentMessage = initialMessage;
826
+ }
800
827
 
801
828
  try {
802
829
  // Set event handler on app-server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.34.0-beta.8",
3
+ "version": "2.34.0",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",