clay-server 2.32.0-beta.5 → 2.32.0-beta.6

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.
@@ -136,12 +136,19 @@ function attachMateInteraction(ctx) {
136
136
  } catch (e) {}
137
137
 
138
138
  // Combined gate + digest in one prompt (saves a full round-trip vs separate gate)
139
- var prompt = [
139
+ var promptParts = [
140
140
  "[SYSTEM: Memory Gate + Digest]",
141
141
  "You are a memory system for an AI Mate (role: " + (mateRole || "assistant") + ").",
142
142
  "",
143
- "Conversation (" + job.type + "):",
144
- job.conversationContent,
143
+ ];
144
+ if (job.priorSummary) {
145
+ promptParts.push("Prior conversation context (memory summary so far):");
146
+ promptParts.push(job.priorSummary);
147
+ promptParts.push("");
148
+ }
149
+ promptParts.push("Conversation (" + job.type + "):");
150
+ promptParts.push(job.conversationContent);
151
+ var prompt = promptParts.concat([
145
152
  "",
146
153
  "STEP 1: Should this be saved to memory?",
147
154
  'Answer "no" ONLY if the entire conversation is trivial (e.g. just "hi"/"hello").',
@@ -171,7 +178,7 @@ function attachMateInteraction(ctx) {
171
178
  "user_observations: OPTIONAL array. Include ONLY if you noticed meaningful patterns about the USER themselves (not the topic).",
172
179
  "Categories: pattern (repeated behavior 2+ times), decision (explicit choice with reasoning), reaction (emotional/attitude signal), preference (tool/style/communication preference).",
173
180
  "Omit the field entirely if nothing notable about the user.",
174
- ].join("\n");
181
+ ]).join("\n");
175
182
 
176
183
  function handleResult(text) {
177
184
  var cleaned = text.trim();
@@ -225,9 +232,14 @@ function attachMateInteraction(ctx) {
225
232
  }
226
233
  }
227
234
 
228
- updateMemorySummary(job.mateCtx, job.mateId, digestObj);
229
- maybeSynthesizeUserProfile(job.mateCtx, job.mateId);
230
- if (job.onDone) job.onDone();
235
+ // Skip summary update for failed parses -- the fallback digest would degrade quality
236
+ if (digestObj.topic !== "parse_failed") {
237
+ updateMemorySummary(job.mateCtx, job.mateId, digestObj);
238
+ maybeSynthesizeUserProfile(job.mateCtx, job.mateId);
239
+ if (job.onDone) job.onDone();
240
+ } else {
241
+ if (job.onError) job.onError(new Error("parse_failed"));
242
+ }
231
243
  processDigestQueue();
232
244
  }
233
245
 
@@ -249,7 +261,7 @@ function attachMateInteraction(ctx) {
249
261
  console.error("[digest-worker] Error:", err);
250
262
  _digestWorker = null;
251
263
  _digestWorkerTurns = 0;
252
- if (job.onDone) job.onDone();
264
+ if (job.onError) job.onError(err);
253
265
  processDigestQueue();
254
266
  },
255
267
  });
@@ -265,11 +277,11 @@ function attachMateInteraction(ctx) {
265
277
  onError: function (err) {
266
278
  console.error("[digest-worker] Create error:", err);
267
279
  _digestWorker = null;
268
- if (job.onDone) job.onDone();
280
+ if (job.onError) job.onError(err);
269
281
  processDigestQueue();
270
282
  },
271
- }).then(function (ws) { _digestWorker = ws; _digestWorkerTurns = 1; }).catch(function () {
272
- if (job.onDone) job.onDone();
283
+ }).then(function (ws) { _digestWorker = ws; _digestWorkerTurns = 1; }).catch(function (err) {
284
+ if (job.onError) job.onError(err || new Error("digest worker creation failed"));
273
285
  processDigestQueue();
274
286
  });
275
287
  }
@@ -303,10 +315,14 @@ function attachMateInteraction(ctx) {
303
315
  type: "mention",
304
316
  conversationContent: conversationContent,
305
317
  onDone: function () { mentionSession._digesting = false; },
318
+ onError: function () { mentionSession._digesting = false; },
306
319
  });
307
320
  }
308
321
 
309
- // Digest DM turn for mate projects - uses shared digest worker
322
+ // Digest DM turn for mate projects - uses shared digest worker.
323
+ // Delta-based: only collects new turns since the last successful digest.
324
+ // Concurrency debounce: turns that arrive while a digest is in-flight
325
+ // are naturally batched into the next flush.
310
326
  var _dmDigestPending = false;
311
327
  function digestDmTurn(session, responsePreview) {
312
328
  if (!isMate || _dmDigestPending) return;
@@ -314,11 +330,26 @@ function attachMateInteraction(ctx) {
314
330
  var mateCtx = matesModule.buildMateCtx(projectOwnerId);
315
331
  if (!matesModule.isMate(mateCtx, mateId)) return;
316
332
 
317
- // Collect full conversation from session history (all user + mate turns)
333
+ // Track digest index per session so switching sessions doesn't misalign.
334
+ // On resumed sessions (after restart), recover index from the last
335
+ // digest_checkpoint entry in history so undigested turns aren't lost.
336
+ if (typeof session._dmLastDigestedIndex !== "number") {
337
+ session._dmLastDigestedIndex = 0;
338
+ for (var ci = session.history.length - 1; ci >= 0; ci--) {
339
+ if (session.history[ci].type === "digest_checkpoint") {
340
+ session._dmLastDigestedIndex = session.history[ci].digestedIndex;
341
+ break;
342
+ }
343
+ }
344
+ }
345
+
346
+ // Collect only new turns since the last successful digest
318
347
  var conversationParts = [];
319
348
  var totalLen = 0;
320
349
  var CONV_CAP = 6000;
321
- for (var hi = 0; hi < session.history.length; hi++) {
350
+ var startIndex = session._dmLastDigestedIndex;
351
+ for (var hi = startIndex; hi < session.history.length; hi++) {
352
+ if (totalLen >= CONV_CAP) break;
322
353
  var entry = session.history[hi];
323
354
  if (entry.type === "user_message" && entry.text) {
324
355
  var uText = entry.text;
@@ -335,8 +366,8 @@ function attachMateInteraction(ctx) {
335
366
  conversationParts.push("Mate: " + aText);
336
367
  totalLen += aText.length;
337
368
  }
338
- if (totalLen >= CONV_CAP) break;
339
369
  }
370
+ // If the latest response hasn't landed in history yet, append it
340
371
  var lastResponseText = responsePreview || "";
341
372
  if (lastResponseText && conversationParts.length > 0) {
342
373
  var lastPart = conversationParts[conversationParts.length - 1];
@@ -362,14 +393,34 @@ function attachMateInteraction(ctx) {
362
393
  });
363
394
  }
364
395
 
396
+ // Read existing summary to give the digest worker context for delta content
397
+ var priorSummary = "";
398
+ try {
399
+ if (fs.existsSync(summaryFile)) {
400
+ priorSummary = fs.readFileSync(summaryFile, "utf8").trim();
401
+ }
402
+ } catch (e) {}
403
+
365
404
  _dmDigestPending = true;
405
+ var snapshotIndex = session.history.length;
366
406
 
367
407
  enqueueDigest({
368
408
  mateCtx: mateCtx,
369
409
  mateId: mateId,
370
410
  type: "dm",
411
+ priorSummary: priorSummary || "",
371
412
  conversationContent: conversationParts.join("\n"),
372
- onDone: function () { _dmDigestPending = false; },
413
+ onDone: function () {
414
+ session._dmLastDigestedIndex = snapshotIndex;
415
+ // Persist checkpoint so resumed sessions know where to continue
416
+ var checkpoint = { type: "digest_checkpoint", digestedIndex: snapshotIndex };
417
+ session.history.push(checkpoint);
418
+ sm.appendToSessionFile(session, checkpoint);
419
+ _dmDigestPending = false;
420
+ },
421
+ onError: function () {
422
+ _dmDigestPending = false;
423
+ },
373
424
  });
374
425
  }
375
426
 
@@ -445,6 +496,7 @@ function attachMateInteraction(ctx) {
445
496
  }
446
497
 
447
498
  session._mentionInProgress = true;
499
+ session._mentionActiveMateId = msg.mateId;
448
500
 
449
501
  // Send mention start indicator
450
502
  sendToSession(session.localId, {
@@ -478,6 +530,7 @@ function attachMateInteraction(ctx) {
478
530
  },
479
531
  onDone: function (fullText) {
480
532
  session._mentionInProgress = false;
533
+ session._mentionActiveMateId = null;
481
534
 
482
535
  // Save mention response to session history
483
536
  var mentionResponseEntry = {
@@ -512,6 +565,7 @@ function attachMateInteraction(ctx) {
512
565
  },
513
566
  onError: function (errMsg) {
514
567
  session._mentionInProgress = false;
568
+ session._mentionActiveMateId = null;
515
569
  // Clean up dead session
516
570
  if (session._mentionSessions && session._mentionSessions[msg.mateId]) {
517
571
  delete session._mentionSessions[msg.mateId];
@@ -615,6 +669,7 @@ function attachMateInteraction(ctx) {
615
669
  }
616
670
  }).catch(function (err) {
617
671
  session._mentionInProgress = false;
672
+ session._mentionActiveMateId = null;
618
673
  console.error("[mention] Failed to create session for mate " + msg.mateId + ":", err.message || err);
619
674
  sendToSession(session.localId, { type: "mention_error", mateId: msg.mateId, error: "Failed to create mention session." });
620
675
  });
@@ -597,6 +597,10 @@ function attachSessions(ctx) {
597
597
  }
598
598
  session.history = session.history.slice(0, trimTo);
599
599
  session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
600
+ // Reset digest checkpoint if it points past the trimmed history
601
+ if (typeof session._dmLastDigestedIndex === "number" && session._dmLastDigestedIndex > trimTo) {
602
+ session._dmLastDigestedIndex = trimTo;
603
+ }
600
604
  }
601
605
 
602
606
  // Notify adapter of conversation rollback (e.g. Codex thread/rollback)
package/lib/project.js CHANGED
@@ -726,6 +726,7 @@ function createProjectContext(opts) {
726
726
  delete session._mentionSessions[mateId];
727
727
  }
728
728
  session._mentionInProgress = false;
729
+ session._mentionActiveMateId = null;
729
730
  sendToSession(session.localId, { type: "mention_done", mateId: mateId, stopped: true });
730
731
  send({ type: "mention_processing", mateId: mateId, active: false });
731
732
  }
package/lib/public/app.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  } from './modules/sidebar-projects.js';
13
13
  import {
14
14
  renderUserStrip, closeDmUserPicker, setCurrentDmUser,
15
- updateDmBadge, renderSidebarPresence
15
+ updateDmBadge, renderSidebarPresence, setMentionActive, clearAllMentionActive
16
16
  } from './modules/sidebar-mates.js';
17
17
  import {
18
18
  openMobileSheet, setMobileSheetMateData, refreshMobileChatSheet
@@ -11,7 +11,7 @@ import { refreshIcons, iconHtml } from './icons.js';
11
11
  import { renderMarkdown } from './markdown.js';
12
12
  import { updatePageTitle } from './sidebar.js';
13
13
  import { renderSessionList, updateSessionPresence, populateCliSessionList, handleSearchResults, updateSessionBadge } from './sidebar-sessions.js';
14
- import { updateDmBadge, renderSidebarPresence } from './sidebar-mates.js';
14
+ import { updateDmBadge, renderSidebarPresence, setMentionActive } from './sidebar-mates.js';
15
15
  import { refreshMobileChatSheet } from './sidebar-mobile.js';
16
16
  import { renderMateSessionList, handleMateSearchResults, updateMateSidebarProfile } from './mate-sidebar.js';
17
17
  import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js';
@@ -38,7 +38,7 @@ import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunF
38
38
 
39
39
  // --- App module imports ---
40
40
  import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, addAuthRequiredMessage, showSuggestionChips } from './app-rendering.js';
41
- import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot } from './app-favicon.js';
41
+ import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, updateCrossProjectBlink } from './app-favicon.js';
42
42
  import { setStatus } from './app-connection.js';
43
43
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
44
44
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
@@ -1324,6 +1324,7 @@ export function processMessage(msg) {
1324
1324
  case "mention_processing":
1325
1325
  // Broadcast: show/hide activity dot on mate avatar across all tabs
1326
1326
  if (msg.mateId) {
1327
+ setMentionActive(msg.mateId, msg.active);
1327
1328
  var mateContainers = document.querySelectorAll('.icon-strip-mate[data-user-id="' + msg.mateId + '"]');
1328
1329
  for (var mi = 0; mi < mateContainers.length; mi++) {
1329
1330
  var dot = mateContainers[mi].querySelector(".icon-strip-status");
@@ -1335,6 +1336,7 @@ export function processMessage(msg) {
1335
1336
  mateContainers[mi].classList.remove("mention-active");
1336
1337
  }
1337
1338
  }
1339
+ updateCrossProjectBlink();
1338
1340
  }
1339
1341
  break;
1340
1342
 
@@ -24,6 +24,7 @@ import { connect, cancelReconnect, setStatus } from './app-connection.js';
24
24
  import { setTurnCounter, setPrependAnchor, setActivityEl, setIsUserScrolledUp, hideSuggestionChips } from './app-rendering.js';
25
25
  import { resetToolState, enableMainInput, resetTurnMetaCost } from './tools.js';
26
26
  import { clearPendingImages } from './input.js';
27
+ import { clearAllMentionActive } from './sidebar-mates.js';
27
28
  import { setRewindMode } from './rewind.js';
28
29
  import { resetUsage, resetContext } from './app-panels.js';
29
30
  import { resetRateLimitState } from './app-rate-limit.js';
@@ -366,6 +367,7 @@ export function resetClientState() {
366
367
  store.set({ currentFullText: "" });
367
368
  resetToolState();
368
369
  clearPendingImages();
370
+ clearAllMentionActive();
369
371
  setActivityEl(null);
370
372
  store.set({ processing: false });
371
373
  setTurnCounter(0);
@@ -29,6 +29,16 @@ var currentDmUserId = null;
29
29
  var dmPickerOpen = false;
30
30
  var cachedDmRemovedUsers = {};
31
31
  var cachedMates = [];
32
+ var activeMentionMateIds = {};
33
+
34
+ export function setMentionActive(mateId, active) {
35
+ if (active) { activeMentionMateIds[mateId] = true; }
36
+ else { delete activeMentionMateIds[mateId]; }
37
+ }
38
+
39
+ export function clearAllMentionActive() {
40
+ activeMentionMateIds = {};
41
+ }
32
42
  var _lastUserStripJson = "";
33
43
 
34
44
  // --- Icon strip tooltip ---
@@ -413,7 +423,9 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
413
423
  // Processing status dot (IO blink) - top-left
414
424
  var statusDot = document.createElement("span");
415
425
  statusDot.className = "icon-strip-status";
416
- if (mateProj.isProcessing) statusDot.classList.add("processing");
426
+ var isMentionActive = !!activeMentionMateIds[mate.id];
427
+ if (mateProj.isProcessing || isMentionActive) statusDot.classList.add("processing");
428
+ if (isMentionActive) el.classList.add("mention-active");
417
429
  el.appendChild(statusDot);
418
430
 
419
431
  // Mate badge (bot icon)
package/lib/sessions.js CHANGED
@@ -354,6 +354,8 @@ function createSessionManager(opts) {
354
354
 
355
355
  for (var i = fromIndex; i < total; i++) {
356
356
  var _item = session.history[i];
357
+ // Skip internal bookkeeping entries not meant for the UI
358
+ if (_item && _item.type === "digest_checkpoint") continue;
357
359
  if (_item && (_item.type === "mention_user" || _item.type === "mention_response")) {
358
360
  console.log("[DEBUG replayHistory] sending mention at index=" + i + " from=" + fromIndex + " total=" + total + " type=" + _item.type + " mate=" + (_item.mateName || ""));
359
361
  }
@@ -423,6 +425,11 @@ function createSessionManager(opts) {
423
425
  decisionReason: p.decisionReason,
424
426
  });
425
427
  }
428
+
429
+ // Re-send active mention indicator so returning clients restore the mate avatar state
430
+ if (session._mentionInProgress && session._mentionActiveMateId) {
431
+ _send({ type: "mention_processing", mateId: session._mentionActiveMateId, active: true });
432
+ }
426
433
  }
427
434
 
428
435
  function cleanupMentionSessions(session) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.32.0-beta.5",
3
+ "version": "2.32.0-beta.6",
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",