clay-server 2.32.0-beta.1 → 2.32.0-beta.3

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.
@@ -280,6 +280,7 @@ function attachSessions(ctx) {
280
280
  if (msg.id && sm.sessions.has(msg.id) && msg.title) {
281
281
  var s = sm.sessions.get(msg.id);
282
282
  s.title = String(msg.title).substring(0, 100);
283
+ s.titleManuallySet = true;
283
284
  sm.saveSessionFile(s);
284
285
  sm.broadcastSessionList();
285
286
  // Sync title to SDK session
package/lib/public/app.js CHANGED
@@ -386,7 +386,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
386
386
  openDm: function (userId) { openDm(userId); },
387
387
  openMateWizard: function () { requireClayMateInterview(function () { openMateWizard(); }); },
388
388
  openAddProjectModal: function () { openAddProjectModal(); },
389
- sendWs: function (msg) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg)); },
389
+ sendWs: function (msg) { var _ws = _getWsRef(); if (_ws && _ws.readyState === 1) _ws.send(JSON.stringify(msg)); },
390
390
  onDmRemoveUser: function (userId) { var dr = Object.assign({}, store.get('dmRemovedUsers')); dr[userId] = true; store.set({ dmRemovedUsers: dr }); },
391
391
  getHistoryFrom: function () { return store.get('historyFrom'); },
392
392
  get permissions() { return store.get('permissions'); },
@@ -399,7 +399,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
399
399
  initMateKnowledge(wsGetter);
400
400
  initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
401
401
  initMateWizard(
402
- function (msg) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg)); },
402
+ function (msg) { var _ws = _getWsRef(); if (_ws && _ws.readyState === 1) _ws.send(JSON.stringify(msg)); },
403
403
  function (mate) { handleMateCreatedInApp(mate); }
404
404
  );
405
405
 
@@ -420,8 +420,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
420
420
  var stickyBtn = document.getElementById("sticky-notes-sidebar-btn");
421
421
  if (stickyBtn) stickyBtn.click();
422
422
  }
423
- if (ws && ws.readyState === 1) {
424
- ws.send(JSON.stringify({ type: "switch_session", id: id }));
423
+ var _ws = _getWsRef();
424
+ if (_ws && _ws.readyState === 1) {
425
+ _ws.send(JSON.stringify({ type: "switch_session", id: id }));
425
426
  }
426
427
  },
427
428
  openDm: function (userId) { openDm(userId); },
@@ -700,7 +701,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
700
701
  isDebateFloorMode: function () { return store.get('debateFloorMode'); },
701
702
  handleDebateFloorSend: function () { handleDebateFloorSend(); },
702
703
  isMateDm: function () { return store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate; },
703
- getDmMateId: function () { return (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) ? dmTargetUser.id : null; },
704
+ getDmMateId: function () { var _dmt = store.get('dmTargetUser'); return (store.get('dmMode') && _dmt && _dmt.isMate) ? _dmt.id : null; },
704
705
  getMateName: function () { return store.get('dmTargetUser') ? (store.get('dmTargetUser').displayName || "Mate") : "Mate"; },
705
706
  getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
706
707
  showMatePreThinking: function () { showMatePreThinking(); },
@@ -726,7 +727,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
726
727
  // --- Debate module ---
727
728
  initDebate({
728
729
  get ws() { return _getWsRef(); },
729
- sendWs: function (obj) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)); },
730
+ sendWs: function (obj) { var _ws = _getWsRef(); if (_ws && _ws.readyState === 1) _ws.send(JSON.stringify(obj)); },
730
731
  messagesEl: messagesEl,
731
732
  addToMessages: function (el) { addToMessages(el); },
732
733
  scrollToBottom: scrollToBottom,
@@ -18,6 +18,7 @@ var badgeEl = null;
18
18
  // --- Update available banner state ---
19
19
  var pendingUpdateMsg = null;
20
20
  var updateReshowTimer = null;
21
+ var updateDismissedAt = 0;
21
22
  var UPDATE_RESHOW_INTERVAL = 60 * 60 * 1000; // 1 hour
22
23
 
23
24
  // ========================================================
@@ -331,6 +332,9 @@ export function showUpdateBanner(msg) {
331
332
  pendingUpdateMsg = msg;
332
333
  if (!bannerContainer) return;
333
334
 
335
+ // If user dismissed recently, skip until reshow timer fires
336
+ if (updateDismissedAt && (Date.now() - updateDismissedAt) < UPDATE_RESHOW_INTERVAL) return;
337
+
334
338
  // Remove any existing update banner
335
339
  var existing = bannerContainer.querySelector('[data-notif-id="_update"]');
336
340
  if (existing) removeBanner(existing);
@@ -395,9 +399,11 @@ export function showUpdateBanner(msg) {
395
399
  }
396
400
 
397
401
  function scheduleUpdateReshow() {
402
+ updateDismissedAt = Date.now();
398
403
  if (updateReshowTimer) clearTimeout(updateReshowTimer);
399
404
  updateReshowTimer = setTimeout(function () {
400
405
  updateReshowTimer = null;
406
+ updateDismissedAt = 0;
401
407
  if (pendingUpdateMsg) showUpdateBanner(pendingUpdateMsg);
402
408
  }, UPDATE_RESHOW_INTERVAL);
403
409
  }
package/lib/sdk-bridge.js CHANGED
@@ -175,6 +175,7 @@ function createSDKBridge(opts) {
175
175
  sm: sm,
176
176
  send: send,
177
177
  slug: slug,
178
+ cwd: cwd,
178
179
  isMate: isMate,
179
180
  mateDisplayName: mateDisplayName,
180
181
  pushModule: pushModule,
@@ -9,13 +9,16 @@ function attachMessageProcessor(ctx) {
9
9
  var pushModule = ctx.pushModule;
10
10
  var getNotificationsModule = ctx.getNotificationsModule || function () { return null; };
11
11
  var getSDK = ctx.getSDK;
12
- var adapter = ctx.adapter; // YOKE adapter (unused currently, available for future use)
12
+ var adapter = ctx.adapter;
13
+ var cwd = ctx.cwd;
13
14
  var onProcessingChanged = ctx.onProcessingChanged;
14
15
  var onTurnDone = ctx.onTurnDone;
15
16
  var opts = ctx.opts;
16
17
  var discoverSkillDirs = ctx.discoverSkillDirs;
17
18
  var mergeSkills = ctx.mergeSkills;
18
19
 
20
+ var AUTO_TITLE_TURN_THRESHOLD = 3;
21
+
19
22
  function sendAndRecord(session, obj) {
20
23
  sm.sendAndRecord(session, obj);
21
24
  }
@@ -24,6 +27,75 @@ function attachMessageProcessor(ctx) {
24
27
  sm.sendToSession(session, obj);
25
28
  }
26
29
 
30
+ /**
31
+ * Auto-generate a session title using a lightweight SDK query.
32
+ * Fires after AUTO_TITLE_TURN_THRESHOLD turns and runs async (non-blocking).
33
+ */
34
+ function autoGenerateTitle(session) {
35
+ // Collect user messages from history for context
36
+ var userMessages = [];
37
+ for (var i = 0; i < session.history.length; i++) {
38
+ var entry = session.history[i];
39
+ if (entry.type === "user_message" && entry.text) {
40
+ userMessages.push(entry.text.substring(0, 200));
41
+ if (userMessages.length >= 5) break;
42
+ }
43
+ }
44
+ if (userMessages.length === 0) return;
45
+
46
+ var prompt = "Below is a conversation between a user and an AI assistant. Generate a short, descriptive title (3-8 words) that captures the main topic. Reply with ONLY the title, nothing else.\n\n";
47
+ for (var j = 0; j < userMessages.length; j++) {
48
+ prompt += "User message " + (j + 1) + ": " + userMessages[j] + "\n";
49
+ }
50
+
51
+ var ac = new AbortController();
52
+ adapter.createQuery({
53
+ cwd: cwd,
54
+ systemPrompt: "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.",
55
+ model: "claude-haiku-4-5-20250901",
56
+ effort: "low",
57
+ abortController: ac,
58
+ adapterOptions: {
59
+ CLAUDE: {
60
+ permissionMode: "bypassPermissions",
61
+ }
62
+ }
63
+ }).then(function(handle) {
64
+ handle.pushMessage(prompt);
65
+ var title = "";
66
+ (async function() {
67
+ try {
68
+ for await (var msg of handle) {
69
+ if (msg.yokeType === "text_delta" && msg.text) {
70
+ title += msg.text;
71
+ } else if (msg.yokeType === "result") {
72
+ break;
73
+ }
74
+ }
75
+ } finally {
76
+ handle.close();
77
+ }
78
+ title = title.replace(/[\r\n]+/g, " ").replace(/^["'\s]+|["'\s.]+$/g, "").trim();
79
+ if (!title || title.length < 2) return;
80
+ title = title.substring(0, 100);
81
+
82
+ // Only update if user hasn't manually renamed the session
83
+ if (!session.titleManuallySet) {
84
+ session.title = title;
85
+ session.titleAutoGenerated = true;
86
+ sm.saveSessionFile(session);
87
+ sm.broadcastSessionList();
88
+ // Sync to SDK
89
+ if (session.cliSessionId) {
90
+ adapter.renameSession(session.cliSessionId, title, { dir: cwd }).catch(function() {});
91
+ }
92
+ }
93
+ })();
94
+ }).catch(function(e) {
95
+ console.error("[auto-title] Failed to generate title:", e.message || e);
96
+ });
97
+ }
98
+
27
99
  function toolActivityTextForSubagent(name, input) {
28
100
  if (name === "Bash" && input && input.description) return input.description;
29
101
  if (name === "Read" && input && input.file_path) return "Reading " + input.file_path.split("/").pop();
@@ -405,10 +477,22 @@ function attachMessageProcessor(ctx) {
405
477
  }
406
478
  // Reset for next turn in the same query
407
479
  session.lastActivityAt = Date.now();
480
+ session.turnCount = (session.turnCount || 0) + 1;
408
481
  var donePreview = session.responsePreview || "";
409
482
  session.responsePreview = "";
410
483
  session.streamedText = false;
411
484
  sm.broadcastSessionList();
485
+
486
+ // Auto-generate title after N turns (skip if loop, mate, or already auto-generated)
487
+ if (session.turnCount === AUTO_TITLE_TURN_THRESHOLD
488
+ && !session.titleAutoGenerated
489
+ && !session.titleManuallySet
490
+ && !session.loop
491
+ && !isMate
492
+ && adapter) {
493
+ try { autoGenerateTitle(session); } catch (e) {}
494
+ }
495
+
412
496
  if (onTurnDone) {
413
497
  try { onTurnDone(session, donePreview); } catch (e) {}
414
498
  }
package/lib/sessions.js CHANGED
@@ -286,6 +286,8 @@ function createSessionManager(opts) {
286
286
  allowedTools: {},
287
287
  isProcessing: false,
288
288
  title: "",
289
+ titleAutoGenerated: false,
290
+ turnCount: 0,
289
291
  createdAt: Date.now(),
290
292
  lastActivity: Date.now(),
291
293
  history: [],
@@ -314,6 +316,8 @@ function createSessionManager(opts) {
314
316
  allowedTools: {},
315
317
  isProcessing: false,
316
318
  title: "",
319
+ titleAutoGenerated: false,
320
+ turnCount: 0,
317
321
  createdAt: Date.now(),
318
322
  lastActivity: Date.now(),
319
323
  history: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.32.0-beta.1",
3
+ "version": "2.32.0-beta.3",
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",