clay-server 2.19.0 → 2.20.0-beta.1

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.
@@ -0,0 +1,353 @@
1
+ import { iconHtml, refreshIcons } from './icons.js';
2
+ import { escapeHtml } from './utils.js';
3
+
4
+ var getMateWs = null;
5
+ var visible = false;
6
+ var cachedEntries = [];
7
+ var cachedSummary = "";
8
+
9
+ // DOM elements
10
+ var sidebarBtn = null;
11
+ var countBadge = null;
12
+ var viewerEl = null;
13
+ var closeBtn = null;
14
+ var summaryContentEl = null;
15
+ var digestListEl = null;
16
+ var digestDetailEl = null;
17
+ var tabSummary = null;
18
+ var tabDigests = null;
19
+ var tabBodySummary = null;
20
+ var tabBodyDigests = null;
21
+
22
+ // Confirm overlay
23
+ var confirmOverlay = null;
24
+
25
+ var _onShow = null;
26
+
27
+ export function initMateMemory(mateWsGetter, opts) {
28
+ getMateWs = mateWsGetter;
29
+ if (opts && opts.onShow) _onShow = opts.onShow;
30
+
31
+ sidebarBtn = document.getElementById("mate-memory-btn");
32
+ countBadge = document.getElementById("mate-memory-count");
33
+ viewerEl = document.getElementById("mate-memory-viewer");
34
+ closeBtn = document.getElementById("mate-memory-viewer-close-btn");
35
+ summaryContentEl = document.getElementById("mate-memory-summary-content");
36
+ digestListEl = document.getElementById("mate-memory-digest-list");
37
+ digestDetailEl = document.getElementById("mate-memory-digest-detail");
38
+ tabBodySummary = document.getElementById("mate-memory-tab-summary");
39
+ tabBodyDigests = document.getElementById("mate-memory-tab-digests");
40
+
41
+ // Tab buttons
42
+ var tabs = viewerEl ? viewerEl.querySelectorAll(".mate-memory-tab") : [];
43
+ for (var i = 0; i < tabs.length; i++) {
44
+ if (tabs[i].dataset.tab === "summary") tabSummary = tabs[i];
45
+ if (tabs[i].dataset.tab === "digests") tabDigests = tabs[i];
46
+ (function (tab) {
47
+ tab.addEventListener("click", function () {
48
+ switchTab(tab.dataset.tab);
49
+ });
50
+ })(tabs[i]);
51
+ }
52
+
53
+ if (sidebarBtn) {
54
+ sidebarBtn.addEventListener("click", function () {
55
+ if (visible) { hideMemory(); } else { showMemory(); }
56
+ });
57
+ }
58
+
59
+ if (closeBtn) {
60
+ closeBtn.addEventListener("click", hideMemory);
61
+ }
62
+ }
63
+
64
+ function switchTab(tabName) {
65
+ if (tabSummary) tabSummary.classList.toggle("active", tabName === "summary");
66
+ if (tabDigests) tabDigests.classList.toggle("active", tabName === "digests");
67
+ if (tabBodySummary) tabBodySummary.classList.toggle("hidden", tabName !== "summary");
68
+ if (tabBodyDigests) tabBodyDigests.classList.toggle("hidden", tabName !== "digests");
69
+
70
+ // When switching to digests, hide detail and show list
71
+ if (tabName === "digests") {
72
+ if (digestDetailEl) digestDetailEl.classList.add("hidden");
73
+ if (digestListEl) digestListEl.classList.remove("hidden");
74
+ }
75
+ }
76
+
77
+ export function showMemory() {
78
+ if (_onShow) _onShow();
79
+ visible = true;
80
+ if (sidebarBtn) sidebarBtn.classList.add("active");
81
+ if (viewerEl) viewerEl.classList.remove("hidden");
82
+
83
+ // Default to summary tab
84
+ switchTab("summary");
85
+ requestMemoryList();
86
+ }
87
+
88
+ export function hideMemory() {
89
+ visible = false;
90
+ if (sidebarBtn) sidebarBtn.classList.remove("active");
91
+ if (viewerEl) viewerEl.classList.add("hidden");
92
+ dismissConfirm();
93
+ cachedEntries = [];
94
+ cachedSummary = "";
95
+ }
96
+
97
+ export function isMemoryVisible() {
98
+ return visible;
99
+ }
100
+
101
+ function requestMemoryList() {
102
+ var ws = getMateWs ? getMateWs() : null;
103
+ if (ws && ws.readyState === 1) {
104
+ ws.send(JSON.stringify({ type: "memory_list" }));
105
+ }
106
+ }
107
+
108
+ export function renderMemoryList(entries, summary) {
109
+ cachedEntries = entries || [];
110
+ cachedSummary = summary || "";
111
+
112
+ // Update badge
113
+ if (countBadge) {
114
+ if (cachedEntries.length > 0) {
115
+ countBadge.textContent = cachedEntries.length;
116
+ countBadge.classList.remove("hidden");
117
+ } else {
118
+ countBadge.textContent = "";
119
+ countBadge.classList.add("hidden");
120
+ }
121
+ }
122
+
123
+ // Render summary tab
124
+ renderSummary();
125
+
126
+ // Render digest list
127
+ renderDigestList();
128
+
129
+ refreshIcons();
130
+ }
131
+
132
+ function renderSummary() {
133
+ if (!summaryContentEl) return;
134
+
135
+ if (!cachedSummary) {
136
+ summaryContentEl.innerHTML =
137
+ '<div class="mate-memory-empty">No memory summary yet. Memories will be created automatically as you chat.</div>';
138
+ return;
139
+ }
140
+
141
+ // Render markdown summary (simple conversion)
142
+ var html = escapeHtml(cachedSummary);
143
+ // Convert markdown headers
144
+ html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
145
+ html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
146
+ html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
147
+ // Convert bullet points
148
+ html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
149
+ html = html.replace(/(<li>.*<\/li>\n?)+/g, function (m) { return '<ul>' + m + '</ul>'; });
150
+ // Convert bold
151
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
152
+ // Line breaks
153
+ html = html.replace(/\n\n/g, '</p><p>');
154
+ html = '<p>' + html + '</p>';
155
+ html = html.replace(/<p>\s*<(h[123]|ul)/g, '<$1');
156
+ html = html.replace(/<\/(h[123]|ul)>\s*<\/p>/g, '</$1>');
157
+
158
+ summaryContentEl.innerHTML = html;
159
+ }
160
+
161
+ function renderDigestList() {
162
+ if (!digestListEl) return;
163
+ digestListEl.innerHTML = "";
164
+
165
+ if (cachedEntries.length === 0) {
166
+ var empty = document.createElement("div");
167
+ empty.className = "mate-memory-empty";
168
+ empty.textContent = "No session logs yet";
169
+ digestListEl.appendChild(empty);
170
+ return;
171
+ }
172
+
173
+ for (var i = 0; i < cachedEntries.length; i++) {
174
+ digestListEl.appendChild(renderDigestItem(cachedEntries[i], i));
175
+ }
176
+ }
177
+
178
+ function renderDigestItem(entry, listIndex) {
179
+ var item = document.createElement("div");
180
+ item.className = "mate-memory-item";
181
+
182
+ // Top row: date + type badge + delete
183
+ var topRow = document.createElement("div");
184
+ topRow.className = "mate-memory-item-top";
185
+
186
+ var dateEl = document.createElement("span");
187
+ dateEl.className = "mate-memory-date";
188
+ dateEl.textContent = entry.date || "?";
189
+ topRow.appendChild(dateEl);
190
+
191
+ if (entry.type) {
192
+ var badge = document.createElement("span");
193
+ badge.className = "mate-memory-type-badge";
194
+ badge.textContent = entry.type;
195
+ topRow.appendChild(badge);
196
+ }
197
+
198
+ if (entry.tags && entry.tags.length > 0) {
199
+ for (var t = 0; t < entry.tags.length && t < 3; t++) {
200
+ var tag = document.createElement("span");
201
+ tag.className = "mate-memory-tag";
202
+ tag.textContent = entry.tags[t];
203
+ topRow.appendChild(tag);
204
+ }
205
+ }
206
+
207
+ var delBtn = document.createElement("button");
208
+ delBtn.className = "mate-memory-delete-btn";
209
+ delBtn.title = "Delete";
210
+ delBtn.innerHTML = iconHtml("trash-2");
211
+ delBtn.addEventListener("click", function (e) {
212
+ e.stopPropagation();
213
+ confirmDelete(entry.index);
214
+ });
215
+ topRow.appendChild(delBtn);
216
+
217
+ item.appendChild(topRow);
218
+
219
+ // Topic
220
+ var topicEl = document.createElement("div");
221
+ topicEl.className = "mate-memory-topic";
222
+ topicEl.textContent = entry.topic || "(no topic)";
223
+ item.appendChild(topicEl);
224
+
225
+ // Position preview
226
+ if (entry.my_position) {
227
+ var posEl = document.createElement("div");
228
+ posEl.className = "mate-memory-position";
229
+ var preview = entry.my_position;
230
+ if (preview.length > 120) preview = preview.substring(0, 120) + "...";
231
+ posEl.textContent = preview;
232
+ item.appendChild(posEl);
233
+ }
234
+
235
+ item.addEventListener("click", function () {
236
+ openDigestDetail(entry, listIndex);
237
+ });
238
+
239
+ return item;
240
+ }
241
+
242
+ function openDigestDetail(entry, listIndex) {
243
+ if (!digestDetailEl || !digestListEl) return;
244
+
245
+ // Hide list, show detail
246
+ digestListEl.classList.add("hidden");
247
+ digestDetailEl.classList.remove("hidden");
248
+
249
+ // Build detail HTML
250
+ var html = '';
251
+ html += '<div class="mate-memory-detail-header">';
252
+ html += '<button class="mate-memory-detail-back">' + iconHtml("arrow-left") + ' Back</button>';
253
+ html += '<div class="mate-knowledge-toolbar-spacer"></div>';
254
+ html += '<button class="mate-memory-detail-delete mate-memory-danger-btn" title="Delete">' + iconHtml("trash-2") + '</button>';
255
+ html += '</div>';
256
+ html += '<div class="mate-memory-detail-body">';
257
+ html += renderField("Date", entry.date);
258
+ if (entry.type) html += renderField("Type", entry.type);
259
+ html += renderField("Topic", entry.topic);
260
+ html += renderField("My Position", entry.my_position);
261
+ if (entry.user_intent) html += renderField("User Intent", entry.user_intent);
262
+ if (entry.other_perspectives) html += renderField("Other Perspectives", entry.other_perspectives);
263
+ html += renderField("Decisions", entry.decisions);
264
+ html += renderField("Open Items", entry.open_items);
265
+ if (entry.user_sentiment) html += renderField("User Sentiment", entry.user_sentiment);
266
+ if (entry.my_role) html += renderField("My Role", entry.my_role);
267
+ if (entry.outcome) html += renderField("Outcome", entry.outcome);
268
+ if (entry.confidence) html += renderField("Confidence", entry.confidence);
269
+ if (entry.tags && entry.tags.length > 0) html += renderField("Tags", entry.tags.join(", "));
270
+ html += '</div>';
271
+
272
+ digestDetailEl.innerHTML = html;
273
+ refreshIcons();
274
+
275
+ // Back button
276
+ var backBtn = digestDetailEl.querySelector(".mate-memory-detail-back");
277
+ if (backBtn) {
278
+ backBtn.addEventListener("click", function () {
279
+ digestDetailEl.classList.add("hidden");
280
+ digestListEl.classList.remove("hidden");
281
+ });
282
+ }
283
+
284
+ // Delete button
285
+ var delBtn = digestDetailEl.querySelector(".mate-memory-detail-delete");
286
+ if (delBtn) {
287
+ delBtn.addEventListener("click", function () {
288
+ confirmDelete(entry.index);
289
+ });
290
+ }
291
+ }
292
+
293
+ function renderField(label, value) {
294
+ if (!value || value === "null") return "";
295
+ return '<div class="mate-memory-detail">' +
296
+ '<div class="mate-memory-detail-label">' + escapeHtml(label) + '</div>' +
297
+ '<div class="mate-memory-detail-value">' + escapeHtml(String(value)) + '</div>' +
298
+ '</div>';
299
+ }
300
+
301
+ function confirmDelete(index) {
302
+ dismissConfirm();
303
+
304
+ confirmOverlay = document.createElement("div");
305
+ confirmOverlay.className = "mate-memory-confirm-overlay";
306
+
307
+ var dialog = document.createElement("div");
308
+ dialog.className = "mate-memory-confirm-dialog";
309
+
310
+ var msg = document.createElement("div");
311
+ msg.className = "mate-memory-confirm-msg";
312
+ msg.textContent = "Delete this memory?";
313
+ dialog.appendChild(msg);
314
+
315
+ var actions = document.createElement("div");
316
+ actions.className = "mate-memory-confirm-actions";
317
+
318
+ var cancelBtn = document.createElement("button");
319
+ cancelBtn.className = "mate-memory-confirm-cancel";
320
+ cancelBtn.textContent = "Cancel";
321
+ cancelBtn.addEventListener("click", dismissConfirm);
322
+ actions.appendChild(cancelBtn);
323
+
324
+ var deleteBtn = document.createElement("button");
325
+ deleteBtn.className = "mate-memory-confirm-delete";
326
+ deleteBtn.textContent = "Delete";
327
+ deleteBtn.addEventListener("click", function () {
328
+ var ws = getMateWs ? getMateWs() : null;
329
+ if (ws && ws.readyState === 1) {
330
+ ws.send(JSON.stringify({ type: "memory_delete", index: index }));
331
+ }
332
+ dismissConfirm();
333
+ });
334
+ actions.appendChild(deleteBtn);
335
+
336
+ dialog.appendChild(actions);
337
+ confirmOverlay.appendChild(dialog);
338
+ confirmOverlay.addEventListener("click", function (e) {
339
+ if (e.target === confirmOverlay) dismissConfirm();
340
+ });
341
+ document.body.appendChild(confirmOverlay);
342
+ }
343
+
344
+ function dismissConfirm() {
345
+ if (confirmOverlay && confirmOverlay.parentNode) {
346
+ confirmOverlay.parentNode.removeChild(confirmOverlay);
347
+ }
348
+ confirmOverlay = null;
349
+ }
350
+
351
+ export function handleMemoryDeleted() {
352
+ // List update follows via memory_list
353
+ }
@@ -233,10 +233,11 @@ export function clearMentionState() {
233
233
  removeInputMentionChip();
234
234
  }
235
235
 
236
- export function sendMention(mateId, text, pastes) {
236
+ export function sendMention(mateId, text, pastes, images) {
237
237
  if (!ctx.ws || !ctx.connected) return;
238
238
  var payload = { type: "mention", mateId: mateId, text: text };
239
239
  if (pastes && pastes.length > 0) payload.pastes = pastes;
240
+ if (images && images.length > 0) payload.images = images;
240
241
  ctx.ws.send(JSON.stringify(payload));
241
242
  }
242
243
 
@@ -298,6 +299,17 @@ export function handleMentionStart(msg) {
298
299
  badge.className = "mention-badge";
299
300
  badge.textContent = "@MENTION";
300
301
  header.appendChild(badge);
302
+
303
+ var stopBtn = document.createElement("button");
304
+ stopBtn.className = "mention-stop-btn";
305
+ stopBtn.title = "Stop";
306
+ stopBtn.innerHTML = iconHtml("square");
307
+ stopBtn.addEventListener("click", function () {
308
+ if (ctx.ws && ctx.connected) {
309
+ ctx.ws.send(JSON.stringify({ type: "mention_stop", mateId: msg.mateId }));
310
+ }
311
+ });
312
+ header.appendChild(stopBtn);
301
313
  contentWrap.appendChild(header);
302
314
 
303
315
  // Activity indicator
@@ -336,6 +348,17 @@ export function handleMentionStart(msg) {
336
348
  nameSpan.textContent = msg.mateName || "Mate";
337
349
  header.appendChild(nameSpan);
338
350
 
351
+ var stopBtn = document.createElement("button");
352
+ stopBtn.className = "mention-stop-btn";
353
+ stopBtn.title = "Stop";
354
+ stopBtn.innerHTML = iconHtml("square");
355
+ stopBtn.addEventListener("click", function () {
356
+ if (ctx.ws && ctx.connected) {
357
+ ctx.ws.send(JSON.stringify({ type: "mention_stop", mateId: msg.mateId }));
358
+ }
359
+ });
360
+ header.appendChild(stopBtn);
361
+
339
362
  currentMentionEl.appendChild(header);
340
363
 
341
364
  // Activity indicator
@@ -457,10 +480,12 @@ function flushMentionStream() {
457
480
 
458
481
  export function handleMentionDone(msg) {
459
482
  flushMentionStream();
460
- // Hide activity bar
461
483
  if (currentMentionEl) {
462
484
  var bar = currentMentionEl.querySelector(".mention-activity-bar");
463
485
  if (bar) bar.style.display = "none";
486
+ // Remove stop button
487
+ var stopBtn = currentMentionEl.querySelector(".mention-stop-btn");
488
+ if (stopBtn) stopBtn.remove();
464
489
  // Add copy handler so user can "click to grab this"
465
490
  if (ctx.addCopyHandler && mentionFullText) {
466
491
  ctx.addCopyHandler(currentMentionEl, mentionFullText);
@@ -477,6 +502,8 @@ export function handleMentionError(msg) {
477
502
  if (currentMentionEl) {
478
503
  var bar = currentMentionEl.querySelector(".mention-activity-bar");
479
504
  if (bar) bar.style.display = "none";
505
+ var stopBtn = currentMentionEl.querySelector(".mention-stop-btn");
506
+ if (stopBtn) stopBtn.remove();
480
507
  var contentEl = currentMentionEl.querySelector(".mention-content");
481
508
  if (contentEl) {
482
509
  contentEl.innerHTML = '<div class="mention-error">Error: ' + escapeHtml(msg.error || "Unknown error") + '</div>';
@@ -511,6 +538,54 @@ export function renderMentionUser(entry) {
511
538
  bubble.className = "bubble";
512
539
  bubble.dir = "auto";
513
540
 
541
+ // Render images (same pattern as addUserMessage in app.js)
542
+ if (entry.images && entry.images.length > 0) {
543
+ var imgRow = document.createElement("div");
544
+ imgRow.className = "bubble-images";
545
+ for (var ii = 0; ii < entry.images.length; ii++) {
546
+ var img = document.createElement("img");
547
+ if (entry.images[ii].url) {
548
+ img.src = entry.images[ii].url;
549
+ } else if (entry.images[ii].data) {
550
+ img.src = "data:" + entry.images[ii].mediaType + ";base64," + entry.images[ii].data;
551
+ }
552
+ img.loading = "lazy";
553
+ img.className = "bubble-img";
554
+ img.addEventListener("click", function () {
555
+ if (ctx.showImageModal) ctx.showImageModal(this.src);
556
+ });
557
+ img.addEventListener("error", function () {
558
+ var placeholder = document.createElement("div");
559
+ placeholder.className = "bubble-img-expired";
560
+ placeholder.textContent = "Image deleted";
561
+ this.parentNode.replaceChild(placeholder, this);
562
+ });
563
+ imgRow.appendChild(img);
564
+ }
565
+ bubble.appendChild(imgRow);
566
+ }
567
+
568
+ // Render pastes
569
+ if (entry.pastes && entry.pastes.length > 0) {
570
+ var pasteRow = document.createElement("div");
571
+ pasteRow.className = "bubble-pastes";
572
+ for (var pi = 0; pi < entry.pastes.length; pi++) {
573
+ (function (pasteText) {
574
+ var chip = document.createElement("div");
575
+ chip.className = "bubble-paste";
576
+ var preview = pasteText.substring(0, 60).replace(/\n/g, " ");
577
+ if (pasteText.length > 60) preview += "...";
578
+ chip.innerHTML = '<span class="bubble-paste-preview">' + escapeHtml(preview) + '</span><span class="bubble-paste-label">PASTED</span>';
579
+ chip.addEventListener("click", function (e) {
580
+ e.stopPropagation();
581
+ if (ctx.showPasteModal) ctx.showPasteModal(pasteText);
582
+ });
583
+ pasteRow.appendChild(chip);
584
+ })(entry.pastes[pi]);
585
+ }
586
+ bubble.appendChild(pasteRow);
587
+ }
588
+
514
589
  var textEl = document.createElement("span");
515
590
  textEl.innerHTML = '<span class="mention-chip">@' + escapeHtml(entry.mateName || "Mate") + '</span> ' + escapeHtml(entry.text || "");
516
591
  bubble.appendChild(textEl);
@@ -184,14 +184,6 @@ export function initNotifications(_ctx) {
184
184
  ctx.ws.send(JSON.stringify({ type: "check_update" }));
185
185
  }
186
186
  setUpdateBtn("Checking…", true, true);
187
- setTimeout(function () {
188
- if (settingsUpdateCheck.disabled) {
189
- setUpdateBtn("Up to date", false, true);
190
- setTimeout(function () {
191
- setUpdateBtn("Check for updates", false, false);
192
- }, 1500);
193
- }
194
- }, 2000);
195
187
  });
196
188
  }
197
189
 
@@ -144,6 +144,8 @@ export function initServerSettings(appCtx) {
144
144
  if (e.key === "Escape") { e.preventDefault(); hidePinForm(); }
145
145
  });
146
146
 
147
+ // Auto-continue moved to User Settings > Behavior
148
+
147
149
  // Keep awake toggle
148
150
  var keepAwakeToggle = document.getElementById("settings-keep-awake");
149
151
  if (keepAwakeToggle) {
@@ -318,10 +320,8 @@ function populateSettings() {
318
320
  // Nav header defaults to hostname (updated by updateDaemonConfig)
319
321
  if (nameEl && !nameEl.textContent) nameEl.textContent = "Server";
320
322
 
321
- var footerVersion = document.getElementById("footer-version");
322
- if (versionEl && footerVersion) {
323
- versionEl.textContent = footerVersion.textContent || "-";
324
- }
323
+ // Version is set from WebSocket "info" message in app.js
324
+ if (versionEl && !versionEl.textContent) versionEl.textContent = "-";
325
325
 
326
326
  if (slugEl) slugEl.textContent = ctx.currentSlug || "(default)";
327
327
  if (wsPathEl) wsPathEl.textContent = ctx.wsPath || "/ws";
@@ -475,6 +475,9 @@ export function updateDaemonConfig(config) {
475
475
  // PIN status
476
476
  updatePinStatus(!!config.pinEnabled);
477
477
 
478
+ // Auto-continue on rate limit
479
+ // Auto-continue is now per-user (User Settings > Behavior)
480
+
478
481
  // Keep awake
479
482
  var keepAwakeToggle = document.getElementById("settings-keep-awake");
480
483
  if (keepAwakeToggle) keepAwakeToggle.checked = !!config.keepAwake;
@@ -529,6 +532,10 @@ export function handleKeepAwakeChanged(msg) {
529
532
  if (keepAwakeToggle) keepAwakeToggle.checked = !!msg.keepAwake;
530
533
  }
531
534
 
535
+ export function handleAutoContinueChanged(msg) {
536
+ // Auto-continue is now per-user; server broadcast no longer updates UI
537
+ }
538
+
532
539
  export function handleRestartResult(msg) {
533
540
  var restartBtn = document.getElementById("settings-restart-btn");
534
541
  var errorEl = document.getElementById("settings-restart-error");