@downcity/agent 1.1.51 → 1.1.62
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/README.md +5 -4
- package/bin/agent/Agent.d.ts.map +1 -1
- package/bin/agent/Agent.js.map +1 -1
- package/bin/config/AgentInitializer.d.ts +1 -1
- package/bin/config/AgentInitializer.js +1 -1
- package/bin/config/DowncitySchema.js +1 -1
- package/bin/config/DowncitySchema.js.map +1 -1
- package/bin/config/ExecutionBinding.d.ts +1 -1
- package/bin/config/ExecutionBinding.js +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +0 -4
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
- package/bin/executor/composer/system/default/InitPrompts.d.ts +1 -1
- package/bin/executor/composer/system/default/InitPrompts.js +1 -1
- package/bin/executor/composer/system/default/SystemDomain.js +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js +1 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.js +1 -1
- package/bin/executor/composer/system/default/assets/task.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/task.prompt.js +1 -1
- package/bin/executor/messages/ChatMessageMarkupTypes.d.ts +1 -1
- package/bin/executor/messages/ChatMessageMarkupTypes.js +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +1 -2
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +17 -56
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/tools/shell/ShellToolBridge.js +1 -1
- package/bin/executor/tools/shell/ShellToolBridge.js.map +1 -1
- package/bin/executor/tools/shell/ShellToolDefinition.d.ts +2 -2
- package/bin/executor/tools/shell/ShellToolFormatting.d.ts +1 -1
- package/bin/executor/tools/shell/ShellToolFormatting.js +7 -7
- package/bin/executor/tools/shell/ShellToolFormatting.js.map +1 -1
- package/bin/executor/tools/shell/ShellToolSchemas.d.ts +7 -75
- package/bin/executor/tools/shell/ShellToolSchemas.d.ts.map +1 -1
- package/bin/executor/types/SessionHistoryMeta.d.ts +5 -24
- package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
- package/bin/executor/types/SessionHistoryMeta.js +1 -1
- package/bin/index.d.ts +3 -2
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -0
- package/bin/index.js.map +1 -1
- package/bin/model/CityModelAdapter.d.ts +23 -0
- package/bin/model/CityModelAdapter.d.ts.map +1 -0
- package/bin/model/CityModelAdapter.js +338 -0
- package/bin/model/CityModelAdapter.js.map +1 -0
- package/bin/runtime/host/daemon/Paths.d.ts +1 -1
- package/bin/runtime/host/daemon/Paths.js +1 -1
- package/bin/runtime/host/daemon/ProjectSetup.js +2 -2
- package/bin/session/Session.d.ts +1 -0
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +32 -6
- package/bin/session/Session.js.map +1 -1
- package/bin/session/SessionTitle.d.ts +49 -0
- package/bin/session/SessionTitle.d.ts.map +1 -0
- package/bin/session/SessionTitle.js +130 -0
- package/bin/session/SessionTitle.js.map +1 -0
- package/bin/session/browse/Browse.d.ts +2 -1
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +18 -15
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/index.d.ts +2 -1
- package/bin/session/index.d.ts.map +1 -1
- package/bin/session/index.js +2 -1
- package/bin/session/index.js.map +1 -1
- package/bin/session/storage/Metadata.d.ts +4 -0
- package/bin/session/storage/Metadata.d.ts.map +1 -1
- package/bin/session/storage/Metadata.js +12 -25
- package/bin/session/storage/Metadata.js.map +1 -1
- package/bin/session/storage/Persistence.d.ts.map +1 -1
- package/bin/session/storage/Persistence.js +1 -4
- package/bin/session/storage/Persistence.js.map +1 -1
- package/bin/types/agent/AgentTypes.d.ts +9 -5
- package/bin/types/agent/AgentTypes.d.ts.map +1 -1
- package/bin/types/config/AgentProject.d.ts +1 -1
- package/bin/types/config/DowncityConfig.d.ts +3 -3
- package/bin/types/config/ExecutionBinding.d.ts +4 -4
- package/bin/types/config/ExecutionBinding.js +1 -1
- package/bin/types/runtime/auth/AuthPermission.js +2 -2
- package/bin/types/runtime/auth/AuthPermission.js.map +1 -1
- package/bin/types/runtime/host/Store.d.ts +3 -177
- package/bin/types/runtime/host/Store.d.ts.map +1 -1
- package/bin/types/runtime/host/Store.js +7 -0
- package/bin/types/runtime/host/Store.js.map +1 -1
- package/bin/types/runtime/http/InlineInstant.d.ts +1 -1
- package/bin/types/runtime/platform/Platform.d.ts +1 -1
- package/package.json +19 -18
- package/src/agent/Agent.ts +3 -2
- package/src/config/AgentInitializer.ts +1 -1
- package/src/config/DowncitySchema.ts +1 -1
- package/src/config/ExecutionBinding.ts +1 -1
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +0 -4
- package/src/executor/composer/system/default/InitPrompts.ts +1 -1
- package/src/executor/composer/system/default/SystemDomain.ts +1 -1
- package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/core.prompt.ts.txt +1 -1
- package/src/executor/composer/system/default/assets/plugin.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/plugin.prompt.ts.txt +18 -18
- package/src/executor/composer/system/default/assets/task.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/task.prompt.ts.txt +1 -1
- package/src/executor/messages/ChatMessageMarkupTypes.ts +1 -1
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +17 -57
- package/src/executor/tools/shell/ShellToolBridge.ts +1 -1
- package/src/executor/tools/shell/ShellToolFormatting.ts +7 -7
- package/src/executor/types/SessionHistoryMeta.ts +5 -25
- package/src/index.ts +5 -5
- package/src/model/CityModelAdapter.ts +384 -0
- package/src/runtime/host/daemon/Paths.ts +1 -1
- package/src/runtime/host/daemon/ProjectSetup.ts +2 -2
- package/src/session/Session.ts +40 -6
- package/src/session/SessionTitle.ts +192 -0
- package/src/session/browse/Browse.ts +22 -13
- package/src/session/index.ts +5 -0
- package/src/session/storage/Metadata.ts +14 -29
- package/src/session/storage/Persistence.ts +1 -4
- package/src/types/agent/AgentTypes.ts +10 -5
- package/src/types/config/AgentProject.ts +1 -1
- package/src/types/config/DowncityConfig.ts +3 -3
- package/src/types/config/ExecutionBinding.ts +4 -4
- package/src/types/runtime/auth/AuthPermission.ts +2 -2
- package/src/types/runtime/host/Store.ts +3 -182
- package/src/types/runtime/http/InlineInstant.ts +1 -1
- package/src/types/runtime/platform/Platform.ts +1 -1
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/session/Session.ts
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
SessionSystemBuilder,
|
|
31
31
|
} from "@/session/SessionSystemBuilder.js";
|
|
32
32
|
import {
|
|
33
|
-
inferModelLabel,
|
|
34
33
|
buildSessionHistoryPage,
|
|
35
34
|
buildSessionInfo,
|
|
36
35
|
patchSessionModelLabel,
|
|
@@ -62,6 +61,11 @@ import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
|
|
|
62
61
|
import type { SessionUserMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
63
62
|
import { SessionEventHub } from "@/session/runtime/SessionEventHub.js";
|
|
64
63
|
import { SessionPromptRuntime } from "@/session/runtime/SessionPromptRuntime.js";
|
|
64
|
+
import {
|
|
65
|
+
inferAgentModelLabel,
|
|
66
|
+
normalizeAgentModel,
|
|
67
|
+
} from "@/model/CityModelAdapter.js";
|
|
68
|
+
import { ensureSessionTitle } from "@/session/SessionTitle.js";
|
|
65
69
|
|
|
66
70
|
type SessionOptions = {
|
|
67
71
|
/**
|
|
@@ -257,8 +261,8 @@ export class Session implements AgentSession {
|
|
|
257
261
|
this.createdAt = createdAt;
|
|
258
262
|
this.timezone = timezone;
|
|
259
263
|
this.sessionConfig = {
|
|
260
|
-
...(metadata.
|
|
261
|
-
? { modelLabel: metadata.
|
|
264
|
+
...(metadata.modelLabel
|
|
265
|
+
? { modelLabel: metadata.modelLabel }
|
|
262
266
|
: {}),
|
|
263
267
|
};
|
|
264
268
|
return this;
|
|
@@ -280,8 +284,8 @@ export class Session implements AgentSession {
|
|
|
280
284
|
*/
|
|
281
285
|
async set(input: AgentSessionSetInput): Promise<void> {
|
|
282
286
|
if (input.model) {
|
|
283
|
-
this.sessionConfig.model = input.model;
|
|
284
|
-
this.sessionConfig.modelLabel =
|
|
287
|
+
this.sessionConfig.model = normalizeAgentModel(input.model);
|
|
288
|
+
this.sessionConfig.modelLabel = inferAgentModelLabel(input.model);
|
|
285
289
|
this.executor.clearExecutor();
|
|
286
290
|
}
|
|
287
291
|
await patchSessionModelLabel({
|
|
@@ -333,6 +337,7 @@ export class Session implements AgentSession {
|
|
|
333
337
|
await this.executor.appendUserMessage({
|
|
334
338
|
text: String(input.text || "").trim(),
|
|
335
339
|
});
|
|
340
|
+
await this.ensureTitleFromHistory({ generate: true });
|
|
336
341
|
await this.touchMetadata();
|
|
337
342
|
}
|
|
338
343
|
|
|
@@ -363,11 +368,19 @@ export class Session implements AgentSession {
|
|
|
363
368
|
}),
|
|
364
369
|
this.historyStore.list(),
|
|
365
370
|
]);
|
|
371
|
+
const metadataWithTitle = metadata.title
|
|
372
|
+
? metadata
|
|
373
|
+
: await ensureSessionTitle({
|
|
374
|
+
projectRoot: this.projectRoot,
|
|
375
|
+
agentId: this.agentId,
|
|
376
|
+
sessionId: this.id,
|
|
377
|
+
messages,
|
|
378
|
+
});
|
|
366
379
|
return buildSessionInfo({
|
|
367
380
|
projectRoot: this.projectRoot,
|
|
368
381
|
agentId: this.agentId,
|
|
369
382
|
sessionId: this.id,
|
|
370
|
-
metadata,
|
|
383
|
+
metadata: metadataWithTitle,
|
|
371
384
|
messages,
|
|
372
385
|
executing: this.isExecuting(),
|
|
373
386
|
});
|
|
@@ -477,6 +490,7 @@ export class Session implements AgentSession {
|
|
|
477
490
|
for (const message of forkMessages) {
|
|
478
491
|
await forked.historyStore.append(message);
|
|
479
492
|
}
|
|
493
|
+
await forked.ensureTitleFromHistory({ generate: true });
|
|
480
494
|
await forked.touchMetadata();
|
|
481
495
|
return forked;
|
|
482
496
|
}
|
|
@@ -503,6 +517,8 @@ export class Session implements AgentSession {
|
|
|
503
517
|
},
|
|
504
518
|
appendUserMessage: async (messageParams) => {
|
|
505
519
|
await this.executor.appendUserMessage(messageParams);
|
|
520
|
+
await this.ensureTitleFromHistory({ generate: true });
|
|
521
|
+
await this.touchMetadata();
|
|
506
522
|
},
|
|
507
523
|
appendAssistantMessage: async (messageParams) => {
|
|
508
524
|
await this.executor.appendAssistantMessage(messageParams);
|
|
@@ -558,6 +574,23 @@ export class Session implements AgentSession {
|
|
|
558
574
|
});
|
|
559
575
|
}
|
|
560
576
|
|
|
577
|
+
private async ensureTitleFromHistory(input?: {
|
|
578
|
+
/**
|
|
579
|
+
* 是否允许调用模型生成标题。
|
|
580
|
+
*/
|
|
581
|
+
generate?: boolean;
|
|
582
|
+
}): Promise<void> {
|
|
583
|
+
const messages = await this.historyStore.list();
|
|
584
|
+
await ensureSessionTitle({
|
|
585
|
+
projectRoot: this.projectRoot,
|
|
586
|
+
agentId: this.agentId,
|
|
587
|
+
sessionId: this.id,
|
|
588
|
+
messages,
|
|
589
|
+
...(input?.generate ? { model: this.sessionConfig.model } : {}),
|
|
590
|
+
generate: input?.generate === true,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
561
594
|
private async persistAssistantResult(
|
|
562
595
|
assistantMessage: SessionMessageV1,
|
|
563
596
|
): Promise<void> {
|
|
@@ -583,6 +616,7 @@ export class Session implements AgentSession {
|
|
|
583
616
|
await this.executor.appendUserMessage({
|
|
584
617
|
message,
|
|
585
618
|
});
|
|
619
|
+
await this.ensureTitleFromHistory({ generate: true });
|
|
586
620
|
await this.touchMetadata();
|
|
587
621
|
return message;
|
|
588
622
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Session 标题生成与持久化辅助。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - session title 是 `meta.json` 顶层字段,列表与详情都以它为准。
|
|
6
|
+
* - 首次用户消息出现后优先使用模型生成标题,失败时回退到首条用户消息截断。
|
|
7
|
+
* - 老 session 缺失 title 时可在读取列表/详情时补写 fallback title。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { generateText, type LanguageModel } from "ai";
|
|
11
|
+
import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
|
|
12
|
+
import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
13
|
+
import {
|
|
14
|
+
normalizeSessionTitle,
|
|
15
|
+
readSessionMetadata,
|
|
16
|
+
writeSessionMetadata,
|
|
17
|
+
} from "@/session/storage/Metadata.js";
|
|
18
|
+
|
|
19
|
+
const FALLBACK_SESSION_TITLE_MAX_CHARS = 60;
|
|
20
|
+
const GENERATED_SESSION_TITLE_MAX_CHARS = 24;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 标题持久化参数。
|
|
24
|
+
*/
|
|
25
|
+
export interface EnsureSessionTitleParams {
|
|
26
|
+
/**
|
|
27
|
+
* 当前项目根目录。
|
|
28
|
+
*/
|
|
29
|
+
projectRoot: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 当前 agent 稳定标识。
|
|
33
|
+
*/
|
|
34
|
+
agentId: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 当前 sessionId。
|
|
38
|
+
*/
|
|
39
|
+
sessionId: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 当前 session 已落盘消息。
|
|
43
|
+
*/
|
|
44
|
+
messages: SessionMessageV1[];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 可选模型实例;传入时会尝试生成更短标题。
|
|
48
|
+
*/
|
|
49
|
+
model?: LanguageModel;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 是否允许调用模型生成标题。
|
|
53
|
+
*/
|
|
54
|
+
generate?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function truncateTitle(input: string, maxChars: number): string {
|
|
58
|
+
const title = String(input || "").replace(/\s+/g, " ").trim();
|
|
59
|
+
if (!title) return "";
|
|
60
|
+
if (title.length <= maxChars) return title;
|
|
61
|
+
return title.slice(0, maxChars).trimEnd();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractTextFromMessage(message: SessionMessageV1): string {
|
|
65
|
+
if (!Array.isArray(message.parts)) return "";
|
|
66
|
+
const texts: string[] = [];
|
|
67
|
+
for (const part of message.parts) {
|
|
68
|
+
if (!part || typeof part !== "object") continue;
|
|
69
|
+
const textPart = part as { type?: unknown; text?: unknown };
|
|
70
|
+
if (textPart.type !== "text" || typeof textPart.text !== "string") continue;
|
|
71
|
+
const text = textPart.text.trim();
|
|
72
|
+
if (!text) continue;
|
|
73
|
+
texts.push(text);
|
|
74
|
+
}
|
|
75
|
+
return texts.join("\n").trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveFirstUserText(messages: SessionMessageV1[]): string {
|
|
79
|
+
for (const message of messages) {
|
|
80
|
+
if (message.role !== "user") continue;
|
|
81
|
+
const text = extractTextFromMessage(message);
|
|
82
|
+
if (text) return text;
|
|
83
|
+
}
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeGeneratedTitle(input: string): string | undefined {
|
|
88
|
+
const firstLine = String(input || "")
|
|
89
|
+
.split("\n")
|
|
90
|
+
.map((line) => line.trim())
|
|
91
|
+
.find(Boolean);
|
|
92
|
+
const title = normalizeSessionTitle(
|
|
93
|
+
String(firstLine || "")
|
|
94
|
+
.replace(/^["'`“”‘’]+|["'`“”‘’]+$/g, "")
|
|
95
|
+
.replace(/^标题[::]\s*/i, "")
|
|
96
|
+
.trim(),
|
|
97
|
+
);
|
|
98
|
+
return title
|
|
99
|
+
? truncateTitle(title, GENERATED_SESSION_TITLE_MAX_CHARS)
|
|
100
|
+
: undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 从首条用户消息推导 fallback 标题。
|
|
105
|
+
*/
|
|
106
|
+
export function resolveFallbackSessionTitle(
|
|
107
|
+
messages: SessionMessageV1[],
|
|
108
|
+
): string | undefined {
|
|
109
|
+
const firstUserText = resolveFirstUserText(messages);
|
|
110
|
+
const title = truncateTitle(
|
|
111
|
+
firstUserText,
|
|
112
|
+
FALLBACK_SESSION_TITLE_MAX_CHARS,
|
|
113
|
+
);
|
|
114
|
+
return normalizeSessionTitle(title);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function generateSessionTitle(input: {
|
|
118
|
+
/**
|
|
119
|
+
* 当前模型实例。
|
|
120
|
+
*/
|
|
121
|
+
model: LanguageModel;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 首条用户消息文本。
|
|
125
|
+
*/
|
|
126
|
+
firstUserText: string;
|
|
127
|
+
}): Promise<string | undefined> {
|
|
128
|
+
try {
|
|
129
|
+
const result = await generateText({
|
|
130
|
+
model: input.model,
|
|
131
|
+
system:
|
|
132
|
+
"你负责为一段会话生成极简标题。只输出标题本身,不要解释,不要使用引号。",
|
|
133
|
+
prompt: [
|
|
134
|
+
"根据下面这条用户首条消息,生成一个简短的会话标题。",
|
|
135
|
+
"要求:3 到 12 个汉字或 2 到 6 个英文词;不要句号;不要前缀。",
|
|
136
|
+
"",
|
|
137
|
+
input.firstUserText,
|
|
138
|
+
].join("\n"),
|
|
139
|
+
});
|
|
140
|
+
return normalizeGeneratedTitle(result.text);
|
|
141
|
+
} catch {
|
|
142
|
+
// 关键点(中文):标题生成失败不能影响 session 主流程。
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 确保当前 session meta 中持久化 title。
|
|
149
|
+
*/
|
|
150
|
+
export async function ensureSessionTitle(
|
|
151
|
+
input: EnsureSessionTitleParams,
|
|
152
|
+
): Promise<SessionHistoryMetaV1> {
|
|
153
|
+
const current = await readSessionMetadata(input);
|
|
154
|
+
if (current.title) return current;
|
|
155
|
+
|
|
156
|
+
const firstUserText = resolveFirstUserText(input.messages);
|
|
157
|
+
const fallbackTitle = resolveFallbackSessionTitle(input.messages);
|
|
158
|
+
if (!fallbackTitle) return current;
|
|
159
|
+
|
|
160
|
+
const fallbackMeta: SessionHistoryMetaV1 = {
|
|
161
|
+
...current,
|
|
162
|
+
title: fallbackTitle,
|
|
163
|
+
};
|
|
164
|
+
await writeSessionMetadata({
|
|
165
|
+
projectRoot: input.projectRoot,
|
|
166
|
+
agentId: input.agentId,
|
|
167
|
+
sessionId: input.sessionId,
|
|
168
|
+
meta: fallbackMeta,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (input.generate !== true || !input.model || !firstUserText) {
|
|
172
|
+
return fallbackMeta;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const generatedTitle = await generateSessionTitle({
|
|
176
|
+
model: input.model,
|
|
177
|
+
firstUserText,
|
|
178
|
+
});
|
|
179
|
+
if (!generatedTitle) return fallbackMeta;
|
|
180
|
+
|
|
181
|
+
const generatedMeta: SessionHistoryMetaV1 = {
|
|
182
|
+
...fallbackMeta,
|
|
183
|
+
title: generatedTitle,
|
|
184
|
+
};
|
|
185
|
+
await writeSessionMetadata({
|
|
186
|
+
projectRoot: input.projectRoot,
|
|
187
|
+
agentId: input.agentId,
|
|
188
|
+
sessionId: input.sessionId,
|
|
189
|
+
meta: generatedMeta,
|
|
190
|
+
});
|
|
191
|
+
return generatedMeta;
|
|
192
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* SDK Session 浏览辅助。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* - 统一负责 session 列表摘要、session 详情与 history
|
|
5
|
+
* - 统一负责 session 列表摘要、session 详情与 history 分页的投影逻辑。
|
|
6
|
+
* - 老 session 缺失持久化 title 时,会在列表读取阶段补写 fallback title。
|
|
6
7
|
* - 面向 SDK / RemoteAgent / HTTP route 复用,避免在多个入口重复拼列表与分页语义。
|
|
7
8
|
* - 这里不持有运行态状态;执行状态等动态信息通过调用参数显式注入。
|
|
8
9
|
*/
|
|
@@ -33,6 +34,10 @@ import { pickLastSuccessfulChatSendText } from "@/executor/messages/UserVisibleT
|
|
|
33
34
|
import { getSdkAgentSessionMessagesPath } from "@/session/storage/Paths.js";
|
|
34
35
|
import { getSdkAgentSessionsRootDirPath } from "@/session/storage/Paths.js";
|
|
35
36
|
import { readSessionMetadata } from "@/session/storage/Metadata.js";
|
|
37
|
+
import {
|
|
38
|
+
ensureSessionTitle,
|
|
39
|
+
resolveFallbackSessionTitle,
|
|
40
|
+
} from "@/session/SessionTitle.js";
|
|
36
41
|
|
|
37
42
|
type AnyUiPart = UIMessagePart<Record<string, never>, Record<string, never>>;
|
|
38
43
|
|
|
@@ -184,14 +189,7 @@ export function resolveSessionMessagePreview(message: SessionMessageV1): string
|
|
|
184
189
|
* 推导当前 session 的可读标题。
|
|
185
190
|
*/
|
|
186
191
|
export function resolveSessionTitle(messages: SessionMessageV1[]): string | undefined {
|
|
187
|
-
|
|
188
|
-
if (message.role !== "user") continue;
|
|
189
|
-
const preview = resolveSessionMessagePreview(message);
|
|
190
|
-
if (!preview) continue;
|
|
191
|
-
return truncateText(preview, 80);
|
|
192
|
-
}
|
|
193
|
-
const fallback = messages[0] ? resolveSessionMessagePreview(messages[0]) : "";
|
|
194
|
-
return fallback ? truncateText(fallback, 80) : undefined;
|
|
192
|
+
return resolveFallbackSessionTitle(messages);
|
|
195
193
|
}
|
|
196
194
|
|
|
197
195
|
function resolveToolName(part: ToolPartCompatShape, aiToolName?: string): string {
|
|
@@ -361,7 +359,10 @@ export function buildSessionInfo(
|
|
|
361
359
|
180,
|
|
362
360
|
)
|
|
363
361
|
: undefined;
|
|
364
|
-
const title =
|
|
362
|
+
const title =
|
|
363
|
+
typeof input.metadata.title === "string" && input.metadata.title.trim()
|
|
364
|
+
? input.metadata.title.trim()
|
|
365
|
+
: undefined;
|
|
365
366
|
return {
|
|
366
367
|
agentId: input.agentId,
|
|
367
368
|
sessionId: input.sessionId,
|
|
@@ -374,8 +375,8 @@ export function buildSessionInfo(
|
|
|
374
375
|
...(typeof input.metadata.updatedAt === "number"
|
|
375
376
|
? { updatedAt: input.metadata.updatedAt }
|
|
376
377
|
: {}),
|
|
377
|
-
...(input.metadata.
|
|
378
|
-
? { modelLabel: input.metadata.
|
|
378
|
+
...(input.metadata.modelLabel
|
|
379
|
+
? { modelLabel: input.metadata.modelLabel }
|
|
379
380
|
: {}),
|
|
380
381
|
...(typeof input.metadata.timezone === "string" && input.metadata.timezone.trim()
|
|
381
382
|
? { timezone: input.metadata.timezone.trim() }
|
|
@@ -470,11 +471,19 @@ export async function listAgentSessionSummaryPage(params: {
|
|
|
470
471
|
const messages = await loadSessionMessagesFromPath(
|
|
471
472
|
getSdkAgentSessionMessagesPath(params.projectRoot, params.agentId, sessionId),
|
|
472
473
|
);
|
|
474
|
+
const metadataWithTitle = metadata.title
|
|
475
|
+
? metadata
|
|
476
|
+
: await ensureSessionTitle({
|
|
477
|
+
projectRoot: params.projectRoot,
|
|
478
|
+
agentId: params.agentId,
|
|
479
|
+
sessionId,
|
|
480
|
+
messages,
|
|
481
|
+
});
|
|
473
482
|
const info = buildSessionInfo({
|
|
474
483
|
projectRoot: params.projectRoot,
|
|
475
484
|
agentId: params.agentId,
|
|
476
485
|
sessionId,
|
|
477
|
-
metadata,
|
|
486
|
+
metadata: metadataWithTitle,
|
|
478
487
|
messages,
|
|
479
488
|
executing: params.executingSessionIds?.has(sessionId),
|
|
480
489
|
});
|
package/src/session/index.ts
CHANGED
|
@@ -21,11 +21,16 @@ export {
|
|
|
21
21
|
} from "./storage/Paths.js";
|
|
22
22
|
export {
|
|
23
23
|
inferModelLabel,
|
|
24
|
+
normalizeSessionTitle,
|
|
24
25
|
patchSessionModelLabel,
|
|
25
26
|
readSessionMetadata,
|
|
26
27
|
resolveSystemTimezone,
|
|
27
28
|
writeSessionMetadata,
|
|
28
29
|
} from "./storage/Metadata.js";
|
|
30
|
+
export {
|
|
31
|
+
ensureSessionTitle,
|
|
32
|
+
resolveFallbackSessionTitle,
|
|
33
|
+
} from "./SessionTitle.js";
|
|
29
34
|
export {
|
|
30
35
|
persistSdkAssistantResult,
|
|
31
36
|
touchSessionMetadata,
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import fs from "fs-extra";
|
|
10
10
|
import type { LanguageModel } from "ai";
|
|
11
|
-
import type {
|
|
12
|
-
SessionHistoryMetaV1,
|
|
13
|
-
SessionHistorySdkConfigV1,
|
|
14
|
-
} from "@/executor/types/SessionHistoryMeta.js";
|
|
11
|
+
import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
|
|
15
12
|
import { getSdkAgentSessionMetaPath } from "@/session/storage/Paths.js";
|
|
16
13
|
|
|
17
14
|
type ReadSessionMetadataInput = {
|
|
@@ -36,6 +33,14 @@ function normalizeModelLabel(input: unknown): string | undefined {
|
|
|
36
33
|
return label || undefined;
|
|
37
34
|
}
|
|
38
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 归一化 session 标题。
|
|
38
|
+
*/
|
|
39
|
+
export function normalizeSessionTitle(input: unknown): string | undefined {
|
|
40
|
+
const title = typeof input === "string" ? input.trim() : "";
|
|
41
|
+
return title || undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
/**
|
|
40
45
|
* 读取当前系统时区。
|
|
41
46
|
*/
|
|
@@ -103,27 +108,11 @@ export async function readSessionMetadata(
|
|
|
103
108
|
typeof raw.updatedAt === "number" && Number.isFinite(raw.updatedAt)
|
|
104
109
|
? raw.updatedAt
|
|
105
110
|
: 0,
|
|
106
|
-
|
|
107
|
-
? raw.
|
|
108
|
-
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
109
|
-
.filter(Boolean)
|
|
110
|
-
: [],
|
|
111
|
-
...(typeof raw.lastArchiveId === "string" && raw.lastArchiveId.trim()
|
|
112
|
-
? { lastArchiveId: raw.lastArchiveId.trim() }
|
|
113
|
-
: {}),
|
|
114
|
-
...(typeof raw.keepLastMessages === "number" &&
|
|
115
|
-
Number.isFinite(raw.keepLastMessages)
|
|
116
|
-
? { keepLastMessages: raw.keepLastMessages }
|
|
117
|
-
: {}),
|
|
118
|
-
...(typeof raw.maxInputTokensApprox === "number" &&
|
|
119
|
-
Number.isFinite(raw.maxInputTokensApprox)
|
|
120
|
-
? { maxInputTokensApprox: raw.maxInputTokensApprox }
|
|
121
|
-
: {}),
|
|
122
|
-
...(typeof raw.compactRatio === "number" && Number.isFinite(raw.compactRatio)
|
|
123
|
-
? { compactRatio: raw.compactRatio }
|
|
111
|
+
...(normalizeSessionTitle(raw.title)
|
|
112
|
+
? { title: normalizeSessionTitle(raw.title) }
|
|
124
113
|
: {}),
|
|
125
|
-
...(raw.
|
|
126
|
-
? {
|
|
114
|
+
...(normalizeModelLabel(raw.modelLabel)
|
|
115
|
+
? { modelLabel: normalizeModelLabel(raw.modelLabel) }
|
|
127
116
|
: {}),
|
|
128
117
|
};
|
|
129
118
|
} catch {
|
|
@@ -134,7 +123,6 @@ export async function readSessionMetadata(
|
|
|
134
123
|
createdAt: Date.now(),
|
|
135
124
|
timezone: resolveSystemTimezone(),
|
|
136
125
|
updatedAt: 0,
|
|
137
|
-
pinnedSkillIds: [],
|
|
138
126
|
};
|
|
139
127
|
}
|
|
140
128
|
}
|
|
@@ -181,10 +169,7 @@ export async function patchSessionModelLabel(
|
|
|
181
169
|
updatedAt: Date.now(),
|
|
182
170
|
...(modelLabel
|
|
183
171
|
? {
|
|
184
|
-
|
|
185
|
-
...(current.sdkConfig || {}),
|
|
186
|
-
modelLabel,
|
|
187
|
-
},
|
|
172
|
+
modelLabel,
|
|
188
173
|
}
|
|
189
174
|
: {}),
|
|
190
175
|
};
|
|
@@ -83,10 +83,7 @@ export async function touchSessionMetadata(
|
|
|
83
83
|
updatedAt: Date.now(),
|
|
84
84
|
...(params.sessionConfig.modelLabel
|
|
85
85
|
? {
|
|
86
|
-
|
|
87
|
-
...(current.sdkConfig || {}),
|
|
88
|
-
modelLabel: params.sessionConfig.modelLabel,
|
|
89
|
-
},
|
|
86
|
+
modelLabel: params.sessionConfig.modelLabel,
|
|
90
87
|
}
|
|
91
88
|
: {}),
|
|
92
89
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { LanguageModel, Tool } from "ai";
|
|
11
11
|
import type { BasePlugin } from "@/plugin/core/BasePlugin.js";
|
|
12
|
+
import type { AgentModel } from "@/model/CityModelAdapter.js";
|
|
12
13
|
import type { RpcServerInstance } from "@/rpc/Server.js";
|
|
13
14
|
import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
14
15
|
import type {
|
|
@@ -18,6 +19,8 @@ import type {
|
|
|
18
19
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
19
20
|
import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
|
|
20
21
|
|
|
22
|
+
export type { AgentModel } from "@/model/CityModelAdapter.js";
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* 本地 Agent 构造参数。
|
|
23
26
|
*/
|
|
@@ -59,10 +62,11 @@ export interface AgentOptions {
|
|
|
59
62
|
* 当前 agent 为新建 session 提供的默认模型实例。
|
|
60
63
|
*
|
|
61
64
|
* 关键点(中文)
|
|
62
|
-
* - SDK
|
|
65
|
+
* - SDK 仍不负责“选择哪个模型”,这里只接收宿主已经创建好的模型实例。
|
|
66
|
+
* - 支持 AI SDK `LanguageModel`,也支持 City City 返回的 `CityModel`。
|
|
63
67
|
* - 该模型会作为 session 首次执行前的默认注入值。
|
|
64
68
|
*/
|
|
65
|
-
model?:
|
|
69
|
+
model?: AgentModel;
|
|
66
70
|
|
|
67
71
|
/**
|
|
68
72
|
* 当前 agent 显式持有的插件实例集合。
|
|
@@ -252,7 +256,7 @@ export interface AgentSessionSetInput {
|
|
|
252
256
|
* - 这里接受运行中的模型实例,而不是模型 ID。
|
|
253
257
|
* - 由于模型实例通常不可序列化,落盘只保留轻量可读标签用于展示。
|
|
254
258
|
*/
|
|
255
|
-
model?:
|
|
259
|
+
model?: AgentModel;
|
|
256
260
|
}
|
|
257
261
|
|
|
258
262
|
/**
|
|
@@ -441,8 +445,9 @@ export interface AgentSessionSummary {
|
|
|
441
445
|
* 当前 session 可读标题。
|
|
442
446
|
*
|
|
443
447
|
* 说明(中文)
|
|
444
|
-
* -
|
|
445
|
-
* -
|
|
448
|
+
* - 标题持久化在 session `meta.json` 顶层。
|
|
449
|
+
* - 首条用户消息出现后,SDK 会优先生成标题,失败时回退到首条用户消息截断。
|
|
450
|
+
* - 空 session 可能暂时没有标题,调用方可回退到 `sessionId`。
|
|
446
451
|
*/
|
|
447
452
|
title?: string;
|
|
448
453
|
|
|
@@ -159,7 +159,7 @@ export interface DowncityConfig {
|
|
|
159
159
|
id: string;
|
|
160
160
|
version: string;
|
|
161
161
|
/**
|
|
162
|
-
* Runtime startup configuration used by `
|
|
162
|
+
* Runtime startup configuration used by `town agent start`.
|
|
163
163
|
* CLI flags (if provided) take precedence over this config.
|
|
164
164
|
*/
|
|
165
165
|
start?: {
|
|
@@ -181,7 +181,7 @@ export interface DowncityConfig {
|
|
|
181
181
|
*
|
|
182
182
|
* 关键点(中文)
|
|
183
183
|
* - 项目只有一种执行模式:`api`。
|
|
184
|
-
* -
|
|
184
|
+
* - 绑定 City AIService 暴露的模型 ID。
|
|
185
185
|
*/
|
|
186
186
|
execution?: ExecutionBindingConfig;
|
|
187
187
|
/**
|
|
@@ -197,7 +197,7 @@ export interface DowncityConfig {
|
|
|
197
197
|
*
|
|
198
198
|
* 关键点(中文)
|
|
199
199
|
* - `@downcity/agent` 本地 SDK 不直接消费该字段。
|
|
200
|
-
* - 宿主侧(例如
|
|
200
|
+
* - 宿主侧(例如 `downcity`)可读取该字段控制模型工厂行为,例如 `llm.logMessages`。
|
|
201
201
|
* - 对于项目内 `downcity.json`,通常不需要显式写 provider/model 明细。
|
|
202
202
|
*/
|
|
203
203
|
llm?: LlmConfig;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 项目运行入口只有一种执行模式:`api`。
|
|
6
|
-
* -
|
|
6
|
+
* - 绑定 City AIService 暴露的模型 ID。
|
|
7
7
|
* - 该类型是项目 `downcity.json` 中唯一的执行配置。
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -17,11 +17,11 @@ export interface ExecutionBindingConfig {
|
|
|
17
17
|
type: "api";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* City AIService 中的模型 ID。
|
|
21
21
|
*
|
|
22
22
|
* 说明(中文)
|
|
23
|
-
* -
|
|
24
|
-
* - 例如:`
|
|
23
|
+
* - 必须能通过 City 的 `/v1/ai/models` 目录查询到。
|
|
24
|
+
* - 例如:`deepseek-v4-flash`、`fast`、`quality`。
|
|
25
25
|
*/
|
|
26
26
|
modelId: string;
|
|
27
27
|
}
|
|
@@ -44,8 +44,8 @@ export const AUTH_PERMISSION_DESCRIPTIONS: Record<AuthPermissionKey, string> = {
|
|
|
44
44
|
"agent.execute": "触发 agent 执行与会话运行。",
|
|
45
45
|
"task.read": "查看 task 定义与运行结果。",
|
|
46
46
|
"task.run": "创建或手动执行 task。",
|
|
47
|
-
"model.read": "
|
|
48
|
-
"model.write": "
|
|
47
|
+
"model.read": "查看 City AIService 模型与模型绑定。",
|
|
48
|
+
"model.write": "修改模型绑定。",
|
|
49
49
|
"env.read": "查看环境变量配置。",
|
|
50
50
|
"env.write": "修改环境变量配置。",
|
|
51
51
|
"channel.read": "查看渠道账号与渠道状态。",
|