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

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.
@@ -601,7 +601,9 @@ function attachDebate(ctx) {
601
601
  var digests = ctx.loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
602
602
 
603
603
  var briefText = "";
604
+ var _modMate = matesModule.getMate(mateCtx, debate.moderatorId);
604
605
  ctx.sdk.createMentionSession({
606
+ vendor: _modMate ? _modMate.vendor : null,
605
607
  claudeMd: claudeMd,
606
608
  initialContext: digests,
607
609
  initialMessage: quickBriefPrompt,
@@ -794,7 +796,9 @@ function attachDebate(ctx) {
794
796
  var digests = ctx.loadMateDigests(mateCtx, debate.moderatorId, debate.topic);
795
797
  var moderatorContext = buildModeratorContext(debate) + digests;
796
798
 
799
+ var _modMate2 = matesModule.getMate(mateCtx, debate.moderatorId);
797
800
  ctx.sdk.createMentionSession({
801
+ vendor: _modMate2 ? _modMate2.vendor : null,
798
802
  claudeMd: claudeMd,
799
803
  initialContext: moderatorContext,
800
804
  initialMessage: "Begin the debate on: " + debate.topic,
@@ -987,7 +991,9 @@ function attachDebate(ctx) {
987
991
  historyContext += "---";
988
992
  }
989
993
 
994
+ var _panMate = matesModule.getMate(debate.mateCtx, mateId);
990
995
  ctx.sdk.createMentionSession({
996
+ vendor: _panMate ? _panMate.vendor : null,
991
997
  claudeMd: claudeMd,
992
998
  initialContext: panelistContext + historyContext,
993
999
  initialMessage: "The moderator addresses you:\n\n" + moderatorText,
@@ -1521,7 +1527,9 @@ function attachDebate(ctx) {
1521
1527
  }
1522
1528
  moderatorContext += "---\n";
1523
1529
 
1530
+ var _modMate3 = matesModule.getMate(mateCtx, debate.moderatorId);
1524
1531
  ctx.sdk.createMentionSession({
1532
+ vendor: _modMate3 ? _modMate3.vendor : null,
1525
1533
  claudeMd: claudeMd,
1526
1534
  initialContext: moderatorContext,
1527
1535
  initialMessage: resumePrompt,
@@ -560,6 +560,7 @@ function attachMateInteraction(ctx) {
560
560
 
561
561
  // Create new persistent mention session
562
562
  sdk.createMentionSession({
563
+ vendor: mate.vendor || null,
563
564
  claudeMd: claudeMd,
564
565
  initialContext: mentionContext,
565
566
  initialMessage: mentionFullInput,
@@ -23,7 +23,11 @@ export function updateMermaidTheme(vars) {
23
23
  var mermaidIdCounter = 0;
24
24
 
25
25
  export function renderMarkdown(text) {
26
- return DOMPurify.sanitize(marked.parse(text));
26
+ // Normalize smart quotes so bold/italic delimiters flanking them parse correctly
27
+ var normalized = text
28
+ .replace(/[\u201C\u201D]/g, '"')
29
+ .replace(/[\u2018\u2019]/g, "'");
30
+ return DOMPurify.sanitize(marked.parse(normalized));
27
31
  }
28
32
 
29
33
  /**
package/lib/sdk-bridge.js CHANGED
@@ -171,6 +171,45 @@ function createSDKBridge(opts) {
171
171
  var mergeSkills = skills.mergeSkills;
172
172
 
173
173
  // --- Message processing (extracted to sdk-message-processor.js) ---
174
+ // Auto-generate a session title via YOKE adapter.generateTitle().
175
+ // Triggered by sdk-message-processor after AUTO_TITLE_TURN_THRESHOLD turns.
176
+ function autoGenerateTitle(session) {
177
+ if (typeof adapter.generateTitle !== "function") {
178
+ console.log("[auto-title] adapter.generateTitle not available for vendor=" + adapter.vendor);
179
+ return;
180
+ }
181
+ var userMessages = [];
182
+ for (var i = 0; i < session.history.length; i++) {
183
+ var entry = session.history[i];
184
+ if (entry.type === "user_message" && entry.text) {
185
+ userMessages.push(entry.text.substring(0, 200));
186
+ if (userMessages.length >= 5) break;
187
+ }
188
+ }
189
+ if (userMessages.length === 0) {
190
+ console.log("[auto-title] No user messages found in session " + session.localId);
191
+ return;
192
+ }
193
+ console.log("[auto-title] Calling adapter.generateTitle with " + userMessages.length + " messages for session " + session.localId);
194
+
195
+ adapter.generateTitle(userMessages, { cwd: cwd }).then(function(title) {
196
+ if (!title || title.length < 2) return;
197
+ title = title.substring(0, 100);
198
+ if (!session.titleManuallySet) {
199
+ session.title = title;
200
+ session.titleAutoGenerated = true;
201
+ sm.saveSessionFile(session);
202
+ sm.broadcastSessionList();
203
+ if (session.cliSessionId && typeof adapter.renameSession === "function") {
204
+ adapter.renameSession(session.cliSessionId, title, { dir: cwd }).catch(function () {});
205
+ }
206
+ console.log("[auto-title] Generated title for session " + session.localId + ": " + title);
207
+ }
208
+ }).catch(function(e) {
209
+ console.error("[auto-title] Failed:", e.message || e);
210
+ });
211
+ }
212
+
174
213
  var msgProcessor = attachMessageProcessor({
175
214
  sm: sm,
176
215
  send: send,
@@ -183,6 +222,7 @@ function createSDKBridge(opts) {
183
222
  adapter: adapter,
184
223
  onProcessingChanged: onProcessingChanged,
185
224
  onTurnDone: onTurnDone,
225
+ onAutoTitle: function (session) { autoGenerateTitle(session); },
186
226
  opts: opts,
187
227
  discoverSkillDirs: discoverSkillDirs,
188
228
  mergeSkills: mergeSkills,
@@ -587,7 +627,9 @@ function createSDKBridge(opts) {
587
627
  console.log("[sdk-bridge] processQueryStream: starting for-await loop, vendor=" + (session.vendor || adapter.vendor));
588
628
  try {
589
629
  for await (var msg of myQueryInstance) {
590
- console.log("[sdk-bridge] processQueryStream: received event yokeType=" + (msg && msg.yokeType) + " type=" + (msg && msg.type) + (msg && msg.text ? " text=" + msg.text.substring(0, 200) : ""));
630
+ if (msg && msg.yokeType !== "text_delta" && msg.yokeType !== "thinking_delta" && msg.yokeType !== "tool_input_delta") {
631
+ console.log("[sdk-bridge] processQueryStream: received event yokeType=" + msg.yokeType);
632
+ }
591
633
  // Handle worker meta events (context_usage, model_changed, etc.)
592
634
  if (msg && msg.type === "_worker_meta") {
593
635
  var metaData = msg.data || {};
@@ -1318,7 +1360,7 @@ function createSDKBridge(opts) {
1318
1360
  // Creates a mention session that can be reused across multiple mentions
1319
1361
  // within a conversation flow (session continuity).
1320
1362
  async function createMentionSession(opts) {
1321
- // opts: { claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
1363
+ // opts: { vendor, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
1322
1364
  var abortController = new AbortController();
1323
1365
 
1324
1366
  // Current response callbacks (swapped on each pushMessage)
@@ -1331,9 +1373,12 @@ function createSDKBridge(opts) {
1331
1373
  var mentionBlocks = {};
1332
1374
  var alive = true;
1333
1375
 
1376
+ // Use the mate's vendor adapter if specified, otherwise default
1377
+ var mentionAdapter = (opts.vendor && adapters[opts.vendor]) || adapter;
1378
+
1334
1379
  var handle;
1335
1380
  try {
1336
- handle = await adapter.createQuery({
1381
+ handle = await mentionAdapter.createQuery({
1337
1382
  cwd: cwd,
1338
1383
  systemPrompt: opts.claudeMd,
1339
1384
  model: opts.model || undefined,
@@ -13,6 +13,7 @@ function attachMessageProcessor(ctx) {
13
13
  var cwd = ctx.cwd;
14
14
  var onProcessingChanged = ctx.onProcessingChanged;
15
15
  var onTurnDone = ctx.onTurnDone;
16
+ var onAutoTitle = ctx.onAutoTitle;
16
17
  var opts = ctx.opts;
17
18
  var discoverSkillDirs = ctx.discoverSkillDirs;
18
19
  var mergeSkills = ctx.mergeSkills;
@@ -27,75 +28,6 @@ function attachMessageProcessor(ctx) {
27
28
  sm.sendToSession(session, obj);
28
29
  }
29
30
 
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
-
99
31
  function toolActivityTextForSubagent(name, input) {
100
32
  if (name === "Bash" && input && input.description) return input.description;
101
33
  if (name === "Read" && input && input.file_path) return "Reading " + input.file_path.split("/").pop();
@@ -483,14 +415,15 @@ function attachMessageProcessor(ctx) {
483
415
  session.streamedText = false;
484
416
  sm.broadcastSessionList();
485
417
 
486
- // Auto-generate title after N turns (skip if loop, mate, or already auto-generated)
418
+ // Auto-generate title after N turns (skip if loop or already auto-generated)
487
419
  if (session.turnCount === AUTO_TITLE_TURN_THRESHOLD
488
420
  && !session.titleAutoGenerated
489
421
  && !session.titleManuallySet
490
422
  && !session.loop
491
- && !isMate
492
- && adapter) {
493
- try { autoGenerateTitle(session); } catch (e) {}
423
+ && onAutoTitle) {
424
+ try { onAutoTitle(session); } catch (e) {
425
+ console.error("[auto-title] onAutoTitle threw:", e.message || e);
426
+ }
494
427
  }
495
428
 
496
429
  if (onTurnDone) {
@@ -1096,6 +1096,58 @@ function createClaudeAdapter(opts) {
1096
1096
  return createQueryHandle(rawQuery, mq, ac);
1097
1097
  },
1098
1098
 
1099
+ // --- Title generation ---
1100
+ generateTitle: async function(messages, opts) {
1101
+ console.log("[auto-title/claude] generateTitle called with " + messages.length + " messages");
1102
+ var systemPrompt = "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.";
1103
+ 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";
1104
+ for (var i = 0; i < messages.length; i++) {
1105
+ prompt += "User message " + (i + 1) + ": " + messages[i] + "\n";
1106
+ }
1107
+ var ac = new AbortController();
1108
+ console.log("[auto-title/claude] Creating query with model=haiku...");
1109
+ var handle = await adapter.createQuery({
1110
+ cwd: (opts && opts.cwd) || _cwd,
1111
+ systemPrompt: systemPrompt,
1112
+ model: "haiku",
1113
+ adapterOptions: {
1114
+ CLAUDE: {
1115
+ settingSources: ["user"],
1116
+ permissionMode: "bypassPermissions",
1117
+ }
1118
+ },
1119
+ abortController: ac,
1120
+ });
1121
+ console.log("[auto-title/claude] Query created, pushing message...");
1122
+ handle.pushMessage(prompt);
1123
+ var title = "";
1124
+ var streamed = false;
1125
+ try {
1126
+ for await (var msg of handle) {
1127
+ if (msg.yokeType === "text_delta" && msg.text) {
1128
+ streamed = true;
1129
+ title += msg.text;
1130
+ } else if (msg.yokeType === "message" && msg.messageRole === "assistant" && !streamed && msg.content) {
1131
+ // Fallback: extract text from non-streamed message content
1132
+ var content = msg.content;
1133
+ if (Array.isArray(content)) {
1134
+ for (var ci = 0; ci < content.length; ci++) {
1135
+ if (content[ci].type === "text" && content[ci].text) {
1136
+ title += content[ci].text;
1137
+ }
1138
+ }
1139
+ }
1140
+ } else if (msg.yokeType === "result") {
1141
+ break;
1142
+ }
1143
+ }
1144
+ } finally {
1145
+ handle.close();
1146
+ }
1147
+ console.log("[auto-title/claude] Generated: " + title.substring(0, 80));
1148
+ return title.replace(/[\r\n]+/g, " ").replace(/^["'\s]+|["'\s.]+$/g, "").trim();
1149
+ },
1150
+
1099
1151
  // --- Session management ---
1100
1152
  // These delegate to SDK module-level functions.
1101
1153
 
@@ -932,6 +932,48 @@ function createCodexAdapter(opts) {
932
932
  return handle;
933
933
  },
934
934
 
935
+ // --- Title generation ---
936
+ generateTitle: async function(messages, opts) {
937
+ var systemPrompt = "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.";
938
+ 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";
939
+ for (var i = 0; i < messages.length; i++) {
940
+ prompt += "User message " + (i + 1) + ": " + messages[i] + "\n";
941
+ }
942
+ var ac = new AbortController();
943
+ var handle = await adapter.createQuery({
944
+ cwd: (opts && opts.cwd) || _cwd,
945
+ systemPrompt: systemPrompt,
946
+ model: "gpt-5.4-mini",
947
+ abortController: ac,
948
+ canUseTool: function() { return Promise.resolve({ behavior: "deny", message: "No tools." }); },
949
+ });
950
+ handle.pushMessage(prompt);
951
+ var title = "";
952
+ var streamed = false;
953
+ try {
954
+ for await (var msg of handle) {
955
+ if (msg.yokeType === "text_delta" && msg.text) {
956
+ streamed = true;
957
+ title += msg.text;
958
+ } else if (msg.yokeType === "message" && msg.messageRole === "assistant" && !streamed && msg.content) {
959
+ var content = msg.content;
960
+ if (Array.isArray(content)) {
961
+ for (var ci = 0; ci < content.length; ci++) {
962
+ if (content[ci].type === "text" && content[ci].text) {
963
+ title += content[ci].text;
964
+ }
965
+ }
966
+ }
967
+ } else if (msg.yokeType === "result") {
968
+ break;
969
+ }
970
+ }
971
+ } finally {
972
+ handle.close();
973
+ }
974
+ return title.replace(/[\r\n]+/g, " ").replace(/^["'\s]+|["'\s.]+$/g, "").trim();
975
+ },
976
+
935
977
  // Codex has session persistence via thread IDs
936
978
  getSessionInfo: function(sessionId) {
937
979
  return Promise.resolve(null);
@@ -653,6 +653,47 @@ function createGeminiAdapter(opts) {
653
653
  return handle;
654
654
  },
655
655
 
656
+ // --- Title generation ---
657
+ generateTitle: async function(messages, opts) {
658
+ var systemPrompt = "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.";
659
+ 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";
660
+ for (var i = 0; i < messages.length; i++) {
661
+ prompt += "User message " + (i + 1) + ": " + messages[i] + "\n";
662
+ }
663
+ var ac = new AbortController();
664
+ var handle = await adapter.createQuery({
665
+ cwd: (opts && opts.cwd) || undefined,
666
+ systemPrompt: systemPrompt,
667
+ model: "gemini-2.5-flash",
668
+ abortController: ac,
669
+ });
670
+ handle.pushMessage(prompt);
671
+ var title = "";
672
+ var streamed = false;
673
+ try {
674
+ for await (var msg of handle) {
675
+ if (msg.yokeType === "text_delta" && msg.text) {
676
+ streamed = true;
677
+ title += msg.text;
678
+ } else if (msg.yokeType === "message" && msg.messageRole === "assistant" && !streamed && msg.content) {
679
+ var content = msg.content;
680
+ if (Array.isArray(content)) {
681
+ for (var ci = 0; ci < content.length; ci++) {
682
+ if (content[ci].type === "text" && content[ci].text) {
683
+ title += content[ci].text;
684
+ }
685
+ }
686
+ }
687
+ } else if (msg.yokeType === "result") {
688
+ break;
689
+ }
690
+ }
691
+ } finally {
692
+ handle.close();
693
+ }
694
+ return title.replace(/[\r\n]+/g, " ").replace(/^["'\s]+|["'\s.]+$/g, "").trim();
695
+ },
696
+
656
697
  // Gemini has no session management (stateless chat)
657
698
  getSessionInfo: function() { return Promise.resolve(null); },
658
699
  listSessions: function() { return Promise.resolve([]); },
@@ -21,6 +21,12 @@ var TOOL_POLICIES = ["ask", "allow-all"];
21
21
  * .createToolServer(def): ToolServer (opaque)
22
22
  * .createQuery(opts): QueryHandle
23
23
  *
24
+ * Lightweight utilities:
25
+ * .generateTitle(messages, opts) : Promise<string> - generate a short session title
26
+ * messages: string[] - user messages to derive the title from
27
+ * opts: { cwd }
28
+ * Returns a short (3-8 word) title string.
29
+ *
24
30
  * Additional session management (Claude SDK specific, may vary per adapter):
25
31
  * .getSessionInfo(sessionId, opts): Promise<object|null>
26
32
  * .listSessions(opts) : Promise<Array>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.32.0-beta.3",
3
+ "version": "2.32.0-beta.4",
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",