clay-server 2.32.0-beta.2 → 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.
- package/lib/project-debate.js +8 -0
- package/lib/project-mate-interaction.js +1 -0
- package/lib/project-sessions.js +1 -0
- package/lib/public/app.js +7 -6
- package/lib/public/modules/markdown.js +5 -1
- package/lib/sdk-bridge.js +49 -3
- package/lib/sdk-message-processor.js +18 -1
- package/lib/sessions.js +4 -0
- package/lib/yoke/adapters/claude.js +52 -0
- package/lib/yoke/adapters/codex.js +42 -0
- package/lib/yoke/adapters/gemini.js +41 -0
- package/lib/yoke/interface.js +6 -0
- package/package.json +1 -1
package/lib/project-debate.js
CHANGED
|
@@ -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,
|
package/lib/project-sessions.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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
|
-
|
|
424
|
-
|
|
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 () {
|
|
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 (
|
|
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,
|
|
@@ -23,7 +23,11 @@ export function updateMermaidTheme(vars) {
|
|
|
23
23
|
var mermaidIdCounter = 0;
|
|
24
24
|
|
|
25
25
|
export function renderMarkdown(text) {
|
|
26
|
-
|
|
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,10 +171,50 @@ 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,
|
|
177
216
|
slug: slug,
|
|
217
|
+
cwd: cwd,
|
|
178
218
|
isMate: isMate,
|
|
179
219
|
mateDisplayName: mateDisplayName,
|
|
180
220
|
pushModule: pushModule,
|
|
@@ -182,6 +222,7 @@ function createSDKBridge(opts) {
|
|
|
182
222
|
adapter: adapter,
|
|
183
223
|
onProcessingChanged: onProcessingChanged,
|
|
184
224
|
onTurnDone: onTurnDone,
|
|
225
|
+
onAutoTitle: function (session) { autoGenerateTitle(session); },
|
|
185
226
|
opts: opts,
|
|
186
227
|
discoverSkillDirs: discoverSkillDirs,
|
|
187
228
|
mergeSkills: mergeSkills,
|
|
@@ -586,7 +627,9 @@ function createSDKBridge(opts) {
|
|
|
586
627
|
console.log("[sdk-bridge] processQueryStream: starting for-await loop, vendor=" + (session.vendor || adapter.vendor));
|
|
587
628
|
try {
|
|
588
629
|
for await (var msg of myQueryInstance) {
|
|
589
|
-
|
|
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
|
+
}
|
|
590
633
|
// Handle worker meta events (context_usage, model_changed, etc.)
|
|
591
634
|
if (msg && msg.type === "_worker_meta") {
|
|
592
635
|
var metaData = msg.data || {};
|
|
@@ -1317,7 +1360,7 @@ function createSDKBridge(opts) {
|
|
|
1317
1360
|
// Creates a mention session that can be reused across multiple mentions
|
|
1318
1361
|
// within a conversation flow (session continuity).
|
|
1319
1362
|
async function createMentionSession(opts) {
|
|
1320
|
-
// opts: { claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
1363
|
+
// opts: { vendor, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
1321
1364
|
var abortController = new AbortController();
|
|
1322
1365
|
|
|
1323
1366
|
// Current response callbacks (swapped on each pushMessage)
|
|
@@ -1330,9 +1373,12 @@ function createSDKBridge(opts) {
|
|
|
1330
1373
|
var mentionBlocks = {};
|
|
1331
1374
|
var alive = true;
|
|
1332
1375
|
|
|
1376
|
+
// Use the mate's vendor adapter if specified, otherwise default
|
|
1377
|
+
var mentionAdapter = (opts.vendor && adapters[opts.vendor]) || adapter;
|
|
1378
|
+
|
|
1333
1379
|
var handle;
|
|
1334
1380
|
try {
|
|
1335
|
-
handle = await
|
|
1381
|
+
handle = await mentionAdapter.createQuery({
|
|
1336
1382
|
cwd: cwd,
|
|
1337
1383
|
systemPrompt: opts.claudeMd,
|
|
1338
1384
|
model: opts.model || undefined,
|
|
@@ -9,13 +9,17 @@ 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;
|
|
12
|
+
var adapter = ctx.adapter;
|
|
13
|
+
var cwd = ctx.cwd;
|
|
13
14
|
var onProcessingChanged = ctx.onProcessingChanged;
|
|
14
15
|
var onTurnDone = ctx.onTurnDone;
|
|
16
|
+
var onAutoTitle = ctx.onAutoTitle;
|
|
15
17
|
var opts = ctx.opts;
|
|
16
18
|
var discoverSkillDirs = ctx.discoverSkillDirs;
|
|
17
19
|
var mergeSkills = ctx.mergeSkills;
|
|
18
20
|
|
|
21
|
+
var AUTO_TITLE_TURN_THRESHOLD = 3;
|
|
22
|
+
|
|
19
23
|
function sendAndRecord(session, obj) {
|
|
20
24
|
sm.sendAndRecord(session, obj);
|
|
21
25
|
}
|
|
@@ -405,10 +409,23 @@ function attachMessageProcessor(ctx) {
|
|
|
405
409
|
}
|
|
406
410
|
// Reset for next turn in the same query
|
|
407
411
|
session.lastActivityAt = Date.now();
|
|
412
|
+
session.turnCount = (session.turnCount || 0) + 1;
|
|
408
413
|
var donePreview = session.responsePreview || "";
|
|
409
414
|
session.responsePreview = "";
|
|
410
415
|
session.streamedText = false;
|
|
411
416
|
sm.broadcastSessionList();
|
|
417
|
+
|
|
418
|
+
// Auto-generate title after N turns (skip if loop or already auto-generated)
|
|
419
|
+
if (session.turnCount === AUTO_TITLE_TURN_THRESHOLD
|
|
420
|
+
&& !session.titleAutoGenerated
|
|
421
|
+
&& !session.titleManuallySet
|
|
422
|
+
&& !session.loop
|
|
423
|
+
&& onAutoTitle) {
|
|
424
|
+
try { onAutoTitle(session); } catch (e) {
|
|
425
|
+
console.error("[auto-title] onAutoTitle threw:", e.message || e);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
412
429
|
if (onTurnDone) {
|
|
413
430
|
try { onTurnDone(session, donePreview); } catch (e) {}
|
|
414
431
|
}
|
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: [],
|
|
@@ -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([]); },
|
package/lib/yoke/interface.js
CHANGED
|
@@ -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>
|