@co0ontty/wand 1.58.2 → 1.59.0-beta.g50569ba
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/dist/build-info.json +4 -4
- package/dist/process-manager.d.ts +3 -0
- package/dist/process-manager.js +33 -1
- package/dist/server.js +5 -5
- package/dist/session-topic.d.ts +5 -0
- package/dist/session-topic.js +41 -0
- package/dist/storage.js +19 -6
- package/dist/structured-session-manager.d.ts +3 -0
- package/dist/structured-session-manager.js +27 -0
- package/dist/types.d.ts +4 -0
- package/dist/web-ui/content/scripts.js +33 -33
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-06-
|
|
4
|
-
"version": "1.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "50569baa20702980747742a04fa6ea05fcab4d33",
|
|
3
|
+
"builtAt": "2026-06-13T04:26:00.178Z",
|
|
4
|
+
"version": "1.59.0-beta.g50569ba",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
|
@@ -49,6 +49,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
49
49
|
private readonly lastPersistedMessageState;
|
|
50
50
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数(旧服务器进程已死) */
|
|
51
51
|
private orphanRecoveredCount;
|
|
52
|
+
private readonly topicRequests;
|
|
52
53
|
constructor(config: WandConfig, storage: WandStorage, configDir?: string);
|
|
53
54
|
on(event: "process", listener: ProcessEventHandler): this;
|
|
54
55
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数量(仅用于启动摘要展示)。 */
|
|
@@ -109,6 +110,8 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
109
110
|
/** Lightweight snapshot for list views — omits output and messages. */
|
|
110
111
|
private snapshotSlim;
|
|
111
112
|
private isPermissionBlocked;
|
|
113
|
+
setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
|
|
114
|
+
private maybeGenerateSessionTopic;
|
|
112
115
|
private defaultAutonomyPolicy;
|
|
113
116
|
resolveEscalation(id: string, requestId: string, resolution?: "approve_once" | "approve_turn" | "deny"): SessionSnapshot;
|
|
114
117
|
approvePermission(id: string): SessionSnapshot;
|
package/dist/process-manager.js
CHANGED
|
@@ -14,6 +14,8 @@ import { buildLanguageDirective, buildManagedAutonomyDirective } from "./languag
|
|
|
14
14
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
15
15
|
import { getCodexResumeCommandSessionId, getResumeCommandSessionId } from "./resume-policy.js";
|
|
16
16
|
import { applyThinkingEffortToPrompt, normalizeThinkingEffort } from "./structured-session-manager.js";
|
|
17
|
+
import { generateSessionTopic } from "./session-topic.js";
|
|
18
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
17
19
|
function resolveProviderFromCommand(command) {
|
|
18
20
|
return /^codex\b/.test(command.trim()) ? "codex" : "claude";
|
|
19
21
|
}
|
|
@@ -568,6 +570,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
568
570
|
lastPersistedMessageState = new Map();
|
|
569
571
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数(旧服务器进程已死) */
|
|
570
572
|
orphanRecoveredCount = 0;
|
|
573
|
+
topicRequests = new Set();
|
|
571
574
|
constructor(config, storage, configDir) {
|
|
572
575
|
super();
|
|
573
576
|
this.config = config;
|
|
@@ -930,6 +933,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
930
933
|
});
|
|
931
934
|
}
|
|
932
935
|
this.emitEvent({ type: "started", sessionId: id, data: this.snapshot(record) });
|
|
936
|
+
if (initialInput)
|
|
937
|
+
this.maybeGenerateSessionTopic(id, initialInput);
|
|
933
938
|
let initialInputSent = false;
|
|
934
939
|
const sendInitialInput = () => {
|
|
935
940
|
if (initialInputSent || !initialInput)
|
|
@@ -1231,6 +1236,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
1231
1236
|
console.error(`[ProcessManager] Rejecting input: session ${id} has no PTY`);
|
|
1232
1237
|
throw new SessionInputError("Session is not running.", "SESSION_NO_PTY", id, record.status);
|
|
1233
1238
|
}
|
|
1239
|
+
if (view !== "terminal")
|
|
1240
|
+
this.maybeGenerateSessionTopic(id, input);
|
|
1234
1241
|
// Log shortcut key interactions for auto-confirm and mode analysis
|
|
1235
1242
|
if (shortcutKey) {
|
|
1236
1243
|
const outputLines = record.output.split("\n");
|
|
@@ -1522,7 +1529,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
1522
1529
|
autoRecovered: record.autoRecovered ?? false,
|
|
1523
1530
|
autoApprovePermissions: record.autoApprovePermissions || undefined,
|
|
1524
1531
|
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
|
|
1525
|
-
summary: deriveSessionSummary(messages, record.currentTask?.title ?? null),
|
|
1532
|
+
summary: record.description ?? deriveSessionSummary(messages, record.currentTask?.title ?? null),
|
|
1533
|
+
title: record.title,
|
|
1534
|
+
description: record.description,
|
|
1526
1535
|
currentTaskTitle: record.status === "running" ? record.currentTask?.title ?? undefined : undefined,
|
|
1527
1536
|
selectedModel: record.selectedModel ?? null,
|
|
1528
1537
|
thinkingEffort: record.thinkingEffort ?? null,
|
|
@@ -1542,6 +1551,29 @@ export class ProcessManager extends EventEmitter {
|
|
|
1542
1551
|
isPermissionBlocked(record) {
|
|
1543
1552
|
return record.ptyPermissionBlocked || record.pendingEscalation !== null;
|
|
1544
1553
|
}
|
|
1554
|
+
setSessionTopic(id, title, description) {
|
|
1555
|
+
const record = this.mustGet(id);
|
|
1556
|
+
record.title = title;
|
|
1557
|
+
record.description = description;
|
|
1558
|
+
const snapshot = this.snapshot(record);
|
|
1559
|
+
this.storage.saveSessionMetadata(snapshot);
|
|
1560
|
+
this.emitEvent({ type: "output", sessionId: id, data: { title, description, summary: description } });
|
|
1561
|
+
return snapshot;
|
|
1562
|
+
}
|
|
1563
|
+
maybeGenerateSessionTopic(id, input) {
|
|
1564
|
+
const prompt = input.trim();
|
|
1565
|
+
const record = this.sessions.get(id);
|
|
1566
|
+
if (!prompt || !record || record.title || this.topicRequests.has(id))
|
|
1567
|
+
return;
|
|
1568
|
+
this.topicRequests.add(id);
|
|
1569
|
+
void generateSessionTopic(prompt, record.cwd, this.config.language)
|
|
1570
|
+
.then(({ title, description }) => {
|
|
1571
|
+
if (this.sessions.has(id))
|
|
1572
|
+
this.setSessionTopic(id, title, description);
|
|
1573
|
+
})
|
|
1574
|
+
.catch((error) => console.error(`[ProcessManager] Failed to generate session topic ${id}:`, getErrorMessage(error)))
|
|
1575
|
+
.finally(() => this.topicRequests.delete(id));
|
|
1576
|
+
}
|
|
1545
1577
|
defaultAutonomyPolicy(mode) {
|
|
1546
1578
|
if (mode === "agent" || mode === "agent-max" || mode === "managed" || mode === "native" || mode === "full-access") {
|
|
1547
1579
|
return "agent";
|
package/dist/server.js
CHANGED
|
@@ -89,8 +89,8 @@ const DISPLAY_VERSION = BUILD_INFO.version || PKG_VERSION;
|
|
|
89
89
|
let cachedGitHubApk = null;
|
|
90
90
|
let gitHubApkCacheTs = 0;
|
|
91
91
|
const GITHUB_APK_CACHE_TTL = 10 * 60 * 1000; // 10 minutes
|
|
92
|
-
//
|
|
93
|
-
//
|
|
92
|
+
// 按时间倒序遍历最近的 releases,取第一个带对应产物的。正常 tag release
|
|
93
|
+
// 会为每个平台出包;这里保留回退以兼容旧 release 或某次平台构建失败。
|
|
94
94
|
async function fetchGitHubReleaseAssetByExt(ext) {
|
|
95
95
|
const apiUrl = PKG_REPO_URL.replace("github.com", "api.github.com/repos") + "/releases?per_page=30";
|
|
96
96
|
const resp = await fetch(apiUrl, {
|
|
@@ -118,8 +118,8 @@ async function fetchGitHubLatestApk(forceRefresh = false) {
|
|
|
118
118
|
const hit = await fetchGitHubReleaseAssetByExt(".apk");
|
|
119
119
|
if (!hit)
|
|
120
120
|
return cachedGitHubApk ?? null;
|
|
121
|
-
//
|
|
122
|
-
//
|
|
121
|
+
// 版本号优先从文件名提取;回退到旧 release asset 时不能把当前 release tag
|
|
122
|
+
// 误当成产物版本。
|
|
123
123
|
const version = extractAndroidApkVersion(hit.asset.name)
|
|
124
124
|
?? extractAndroidApkVersion(hit.tagName)
|
|
125
125
|
?? hit.tagName.replace(/^v/, "");
|
|
@@ -193,7 +193,7 @@ async function fetchGitHubLatestDmg(forceRefresh = false) {
|
|
|
193
193
|
const hit = await fetchGitHubReleaseAssetByExt(".dmg");
|
|
194
194
|
if (!hit)
|
|
195
195
|
return cachedGitHubDmg ?? null;
|
|
196
|
-
// 同 APK
|
|
196
|
+
// 同 APK:版本号优先从文件名提取,避免回退到旧 asset 时把 release tag 当成新版本。
|
|
197
197
|
const version = extractMacosDmgVersion(hit.asset.name)
|
|
198
198
|
?? extractMacosDmgVersion(hit.tagName)
|
|
199
199
|
?? hit.tagName.replace(/^v/, "");
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { runClaudePrint } from "./claude-sdk-runner.js";
|
|
2
|
+
const TOPIC_TIMEOUT_MS = 45_000;
|
|
3
|
+
const MAX_PROMPT_LENGTH = 12_000;
|
|
4
|
+
function cleanTopicText(value, maxLength) {
|
|
5
|
+
if (typeof value !== "string")
|
|
6
|
+
return "";
|
|
7
|
+
return value.replace(/\s+/g, " ").trim().slice(0, maxLength);
|
|
8
|
+
}
|
|
9
|
+
function parseTopic(raw) {
|
|
10
|
+
const json = raw.match(/\{[\s\S]*\}/)?.[0];
|
|
11
|
+
if (!json)
|
|
12
|
+
return null;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(json);
|
|
15
|
+
const title = cleanTopicText(parsed.title, 40);
|
|
16
|
+
const description = cleanTopicText(parsed.description, 120);
|
|
17
|
+
return title && description ? { title, description } : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function generateSessionTopic(userMessage, cwd, language) {
|
|
24
|
+
const input = userMessage.trim().slice(0, MAX_PROMPT_LENGTH);
|
|
25
|
+
const outputLanguage = language?.trim() || "与用户消息相同的语言";
|
|
26
|
+
const prompt = [
|
|
27
|
+
"请总结下面这条用户发给编码助手的首条消息,用于会话列表展示。",
|
|
28
|
+
`使用${outputLanguage}输出。`,
|
|
29
|
+
"只输出一个 JSON 对象,不要 Markdown、解释或额外文字。",
|
|
30
|
+
'格式:{"title":"不超过20个字的具体主题标题","description":"不超过60个字的一句话任务描述"}',
|
|
31
|
+
"标题避免使用“关于”“请求”“任务”等空泛词,描述保留最重要的目标和对象。",
|
|
32
|
+
"",
|
|
33
|
+
"用户消息:",
|
|
34
|
+
input,
|
|
35
|
+
].join("\n");
|
|
36
|
+
const raw = await runClaudePrint(prompt, { cwd, timeoutMs: TOPIC_TIMEOUT_MS, language });
|
|
37
|
+
const topic = parseTopic(raw);
|
|
38
|
+
if (!topic)
|
|
39
|
+
throw new Error("模型返回的会话主题格式无效。");
|
|
40
|
+
return topic;
|
|
41
|
+
}
|
package/dist/storage.js
CHANGED
|
@@ -54,12 +54,12 @@ function mapWorktreeMergeFields(row) {
|
|
|
54
54
|
}
|
|
55
55
|
function sessionSelectFields() {
|
|
56
56
|
return `id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, output, archived, archived_at, claude_session_id, messages, queued_messages, structured_state
|
|
57
|
-
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info`;
|
|
57
|
+
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description`;
|
|
58
58
|
}
|
|
59
59
|
function sessionPersistFields() {
|
|
60
60
|
return `id, command, cwd, mode, status, exit_code, started_at, ended_at, output
|
|
61
61
|
, archived, archived_at, claude_session_id, provider, session_kind, runner, messages, queued_messages, structured_state
|
|
62
|
-
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info`;
|
|
62
|
+
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description`;
|
|
63
63
|
}
|
|
64
64
|
function sessionPersistAssignments() {
|
|
65
65
|
return `command = excluded.command,
|
|
@@ -84,7 +84,9 @@ function sessionPersistAssignments() {
|
|
|
84
84
|
worktree_enabled = excluded.worktree_enabled,
|
|
85
85
|
worktree_info = excluded.worktree_info,
|
|
86
86
|
worktree_merge_status = excluded.worktree_merge_status,
|
|
87
|
-
worktree_merge_info = excluded.worktree_merge_info
|
|
87
|
+
worktree_merge_info = excluded.worktree_merge_info,
|
|
88
|
+
title = excluded.title,
|
|
89
|
+
description = excluded.description`;
|
|
88
90
|
}
|
|
89
91
|
function sessionMetadataAssignments() {
|
|
90
92
|
return `command = ?, cwd = ?, mode = ?, status = ?, exit_code = ?,
|
|
@@ -92,7 +94,8 @@ function sessionMetadataAssignments() {
|
|
|
92
94
|
archived = ?, archived_at = ?, claude_session_id = ?,
|
|
93
95
|
provider = ?, session_kind = ?, runner = ?, structured_state = ?,
|
|
94
96
|
resumed_from_session_id = ?, auto_recovered = ?,
|
|
95
|
-
worktree_enabled = ?, worktree_info = ?, worktree_merge_status = ?, worktree_merge_info =
|
|
97
|
+
worktree_enabled = ?, worktree_info = ?, worktree_merge_status = ?, worktree_merge_info = ?,
|
|
98
|
+
title = ?, description = ?`;
|
|
96
99
|
}
|
|
97
100
|
function sessionPersistValues(snapshot) {
|
|
98
101
|
return [
|
|
@@ -120,6 +123,8 @@ function sessionPersistValues(snapshot) {
|
|
|
120
123
|
serializeWorktreeInfo(snapshot.worktree),
|
|
121
124
|
snapshot.worktreeMergeStatus ?? null,
|
|
122
125
|
serializeWorktreeMergeInfo(snapshot.worktreeMergeInfo),
|
|
126
|
+
snapshot.title ?? null,
|
|
127
|
+
snapshot.description ?? null,
|
|
123
128
|
];
|
|
124
129
|
}
|
|
125
130
|
function sessionMetadataValues(snapshot) {
|
|
@@ -145,6 +150,8 @@ function sessionMetadataValues(snapshot) {
|
|
|
145
150
|
serializeWorktreeInfo(snapshot.worktree),
|
|
146
151
|
snapshot.worktreeMergeStatus ?? null,
|
|
147
152
|
serializeWorktreeMergeInfo(snapshot.worktreeMergeInfo),
|
|
153
|
+
snapshot.title ?? null,
|
|
154
|
+
snapshot.description ?? null,
|
|
148
155
|
snapshot.id,
|
|
149
156
|
];
|
|
150
157
|
}
|
|
@@ -173,6 +180,8 @@ function mapSessionCore(row) {
|
|
|
173
180
|
autoRecovered: Boolean(row.auto_recovered),
|
|
174
181
|
worktreeEnabled: Boolean(row.worktree_enabled),
|
|
175
182
|
worktree: parseWorktreeInfo(row.worktree_info) ?? null,
|
|
183
|
+
title: row.title ?? undefined,
|
|
184
|
+
description: row.description ?? undefined,
|
|
176
185
|
...mapWorktreeMergeFields(row),
|
|
177
186
|
};
|
|
178
187
|
}
|
|
@@ -214,7 +223,9 @@ const INIT_SQL = `
|
|
|
214
223
|
worktree_enabled INTEGER NOT NULL DEFAULT 0,
|
|
215
224
|
worktree_info TEXT,
|
|
216
225
|
worktree_merge_status TEXT,
|
|
217
|
-
worktree_merge_info TEXT
|
|
226
|
+
worktree_merge_info TEXT,
|
|
227
|
+
title TEXT,
|
|
228
|
+
description TEXT
|
|
218
229
|
);
|
|
219
230
|
|
|
220
231
|
CREATE TABLE IF NOT EXISTS app_config (
|
|
@@ -342,7 +353,7 @@ export class WandStorage {
|
|
|
342
353
|
this.db
|
|
343
354
|
.prepare(`INSERT INTO command_sessions (
|
|
344
355
|
${sessionPersistFields()}
|
|
345
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
356
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
346
357
|
ON CONFLICT(id) DO UPDATE SET
|
|
347
358
|
${sessionPersistAssignments()}`)
|
|
348
359
|
.run(...sessionPersistValues(snapshot));
|
|
@@ -428,6 +439,8 @@ const SCHEMA_MIGRATIONS = [
|
|
|
428
439
|
["worktree_info", "ALTER TABLE command_sessions ADD COLUMN worktree_info TEXT"],
|
|
429
440
|
["worktree_merge_status", "ALTER TABLE command_sessions ADD COLUMN worktree_merge_status TEXT"],
|
|
430
441
|
["worktree_merge_info", "ALTER TABLE command_sessions ADD COLUMN worktree_merge_info TEXT"],
|
|
442
|
+
["title", "ALTER TABLE command_sessions ADD COLUMN title TEXT"],
|
|
443
|
+
["description", "ALTER TABLE command_sessions ADD COLUMN description TEXT"],
|
|
431
444
|
];
|
|
432
445
|
function ensureCommandSessionSchema(db) {
|
|
433
446
|
const columns = db.prepare("PRAGMA table_info(command_sessions)").all();
|
|
@@ -68,6 +68,7 @@ export declare class StructuredSessionManager {
|
|
|
68
68
|
private readonly seenIdempotencyKeys;
|
|
69
69
|
private emitEvent;
|
|
70
70
|
private archiveTimer;
|
|
71
|
+
private readonly topicRequests;
|
|
71
72
|
constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null);
|
|
72
73
|
private archiveExpiredSessions;
|
|
73
74
|
setEventEmitter(emitEvent: (event: ProcessEvent) => void): void;
|
|
@@ -82,6 +83,8 @@ export declare class StructuredSessionManager {
|
|
|
82
83
|
/** Return lightweight snapshots for the session list (no output/messages). */
|
|
83
84
|
listSlim(): SessionSnapshot[];
|
|
84
85
|
get(id: string): SessionSnapshot | null;
|
|
86
|
+
setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
|
|
87
|
+
private maybeGenerateSessionTopic;
|
|
85
88
|
createSession(options: CreateStructuredSessionOptions): SessionSnapshot;
|
|
86
89
|
sendMessage(id: string, input: string, opts?: {
|
|
87
90
|
interrupt?: boolean;
|
|
@@ -10,6 +10,7 @@ import { buildChildEnv, isRunningAsRoot } from "./env-utils.js";
|
|
|
10
10
|
import { getErrorMessage } from "./error-utils.js";
|
|
11
11
|
import { resolveSdkClaudeBinary } from "./claude-sdk-runner.js";
|
|
12
12
|
import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
|
|
13
|
+
import { generateSessionTopic } from "./session-topic.js";
|
|
13
14
|
function defaultStructuredRunner(provider) {
|
|
14
15
|
return provider === "codex" ? "codex-cli-exec" : "claude-cli-print";
|
|
15
16
|
}
|
|
@@ -358,6 +359,9 @@ function buildStructuredOutputPayload(snapshot) {
|
|
|
358
359
|
queuedMessages: snapshot.queuedMessages,
|
|
359
360
|
sessionKind: "structured",
|
|
360
361
|
structuredState: snapshot.structuredState,
|
|
362
|
+
title: snapshot.title,
|
|
363
|
+
description: snapshot.description,
|
|
364
|
+
summary: snapshot.description ?? snapshot.summary,
|
|
361
365
|
};
|
|
362
366
|
}
|
|
363
367
|
function buildIncrementalStructuredPayload(snapshot, cardDefaults) {
|
|
@@ -410,6 +414,7 @@ export class StructuredSessionManager {
|
|
|
410
414
|
seenIdempotencyKeys = new Map();
|
|
411
415
|
emitEvent = null;
|
|
412
416
|
archiveTimer = null;
|
|
417
|
+
topicRequests = new Set();
|
|
413
418
|
constructor(storage, config, logger = null) {
|
|
414
419
|
this.storage = storage;
|
|
415
420
|
this.config = config;
|
|
@@ -503,6 +508,27 @@ export class StructuredSessionManager {
|
|
|
503
508
|
const s = this.sessions.get(id);
|
|
504
509
|
return s ? withSummary(s) : null;
|
|
505
510
|
}
|
|
511
|
+
setSessionTopic(id, title, description) {
|
|
512
|
+
const current = this.requireSession(id);
|
|
513
|
+
const updated = { ...current, title, description, summary: description };
|
|
514
|
+
this.sessions.set(id, updated);
|
|
515
|
+
this.storage.saveSessionMetadata(updated);
|
|
516
|
+
this.emitStructuredSnapshot(updated);
|
|
517
|
+
return updated;
|
|
518
|
+
}
|
|
519
|
+
maybeGenerateSessionTopic(id, input) {
|
|
520
|
+
const session = this.sessions.get(id);
|
|
521
|
+
if (!session || session.title || this.topicRequests.has(id))
|
|
522
|
+
return;
|
|
523
|
+
this.topicRequests.add(id);
|
|
524
|
+
void generateSessionTopic(input, session.cwd, this.config.language)
|
|
525
|
+
.then(({ title, description }) => {
|
|
526
|
+
if (this.sessions.has(id))
|
|
527
|
+
this.setSessionTopic(id, title, description);
|
|
528
|
+
})
|
|
529
|
+
.catch((error) => console.error(`[StructuredSessionManager] Failed to generate session topic ${id}:`, getErrorMessage(error)))
|
|
530
|
+
.finally(() => this.topicRequests.delete(id));
|
|
531
|
+
}
|
|
506
532
|
createSession(options) {
|
|
507
533
|
const id = randomUUID();
|
|
508
534
|
const startedAt = new Date().toISOString();
|
|
@@ -562,6 +588,7 @@ export class StructuredSessionManager {
|
|
|
562
588
|
const prompt = input.trim();
|
|
563
589
|
if (!prompt)
|
|
564
590
|
return session;
|
|
591
|
+
this.maybeGenerateSessionTopic(id, prompt);
|
|
565
592
|
if (opts?.idempotencyKey) {
|
|
566
593
|
const mapKey = `${id}:${opts.idempotencyKey}`;
|
|
567
594
|
if (this.seenIdempotencyKeys.has(mapKey)) {
|
package/dist/types.d.ts
CHANGED
|
@@ -426,6 +426,10 @@ export interface SessionSnapshot {
|
|
|
426
426
|
};
|
|
427
427
|
/** 会话摘要:从首条用户消息或当前任务提取 */
|
|
428
428
|
summary?: string;
|
|
429
|
+
/** 由模型根据首条用户消息生成的短标题。 */
|
|
430
|
+
title?: string;
|
|
431
|
+
/** 由模型根据首条用户消息生成的一句话描述。 */
|
|
432
|
+
description?: string;
|
|
429
433
|
/** 当前正在执行的任务标题(用于会话列表展示) */
|
|
430
434
|
currentTaskTitle?: string;
|
|
431
435
|
/** 用户为此会话选定的 Claude 模型(别名或完整 ID)。结构化会话下次 spawn 时使用;PTY 会话仅用于展示。 */
|