@downcity/agent 1.1.150 → 1.1.153
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/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js +8 -6
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.d.ts +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.js +45 -17
- package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -1
- package/bin/executor/tools/plugin/types/PluginTool.d.ts +17 -0
- package/bin/executor/tools/plugin/types/PluginTool.d.ts.map +1 -1
- package/bin/executor/types/SessionRun.d.ts +6 -2
- package/bin/executor/types/SessionRun.d.ts.map +1 -1
- package/bin/executor/types/SessionRun.js +1 -1
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +10 -20
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts +1 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.js +3 -1
- package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +1 -1
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +6 -2
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/session/storage/Persistence.d.ts +5 -1
- package/bin/session/storage/Persistence.d.ts.map +1 -1
- package/bin/session/storage/Persistence.js +3 -1
- package/bin/session/storage/Persistence.js.map +1 -1
- package/bin/types/agent/SessionTypes.d.ts +2 -2
- package/package.json +3 -3
- package/scripts/image-plugin-job.test.mjs +138 -3
- package/scripts/plugin-tool-bridge.test.mjs +88 -0
- package/scripts/session-list-title.test.mjs +141 -0
- package/scripts/session-prompt-runtime.test.mjs +38 -0
- package/src/executor/core-engine/CoreEngineRunner.ts +16 -17
- package/src/executor/tools/plugin/PluginToolBridge.ts +54 -18
- package/src/executor/tools/plugin/types/PluginTool.ts +18 -0
- package/src/executor/types/SessionRun.ts +6 -2
- package/src/session/browse/Browse.ts +18 -22
- package/src/session/runtime/SessionPromptRuntime.ts +4 -2
- package/src/session/services/SessionStateService.ts +1 -1
- package/src/session/services/SessionTurnService.ts +7 -3
- package/src/session/storage/Persistence.ts +8 -2
- package/src/types/agent/SessionTypes.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - tool 层不直接持有 agent 或 plugin registry,只通过装配期注入的 AgentPlugins 调用 action。
|
|
6
6
|
* - 如果 action 返回 AI SDK UIMessage,则抽取 file parts 并入最终 assistant 消息。
|
|
7
|
-
* - 返回给模型的 tool result
|
|
7
|
+
* - 返回给模型的 tool result 只保留短摘要和本地绝对路径,避免 data URL 等大内容污染上下文。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import path from "node:path";
|
|
10
11
|
import type { FileUIPart } from "ai";
|
|
11
12
|
import type { JsonObject, JsonValue } from "@/types/common/Json.js";
|
|
12
13
|
import type { AgentPlugins } from "@/plugin/types/Plugin.js";
|
|
13
14
|
import type {
|
|
14
15
|
PluginCallInput,
|
|
16
|
+
PluginCallToolFileResult,
|
|
15
17
|
PluginCallToolResult,
|
|
16
18
|
} from "@/executor/tools/plugin/types/PluginTool.js";
|
|
17
19
|
import { materializeAssistantFileParts } from "@executor/messages/AssistantFileResource.js";
|
|
@@ -69,27 +71,57 @@ function extract_assistant_file_parts(data: JsonValue | undefined): FileUIPart[]
|
|
|
69
71
|
return parts.filter(is_file_part);
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* 解析当前项目根目录,保持与 assistant 文件落盘逻辑一致。
|
|
76
|
+
*/
|
|
77
|
+
function resolve_project_root(project_root: string | undefined): string {
|
|
78
|
+
const raw = String(project_root || "").trim();
|
|
79
|
+
return path.resolve(raw || process.cwd());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 将 `resources://` URL 转成本机绝对路径。
|
|
84
|
+
*/
|
|
85
|
+
function resolve_resources_file_path(
|
|
86
|
+
project_root: string,
|
|
87
|
+
raw_url: string,
|
|
88
|
+
): string {
|
|
89
|
+
const prefix = "resources://";
|
|
90
|
+
const raw = String(raw_url || "").trim();
|
|
91
|
+
if (!raw.startsWith(prefix)) return "";
|
|
92
|
+
const relative = raw.slice(prefix.length).replace(/^\/+/, "");
|
|
93
|
+
if (!relative) return "";
|
|
94
|
+
|
|
95
|
+
const file_path = path.resolve(project_root, relative);
|
|
96
|
+
const rel = path.relative(project_root, file_path);
|
|
97
|
+
if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return "";
|
|
98
|
+
return file_path;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 构建返回给模型和用户可见的文件摘要。
|
|
103
|
+
*/
|
|
104
|
+
function summarize_materialized_files(
|
|
105
|
+
parts: FileUIPart[],
|
|
106
|
+
project_root: string,
|
|
107
|
+
): PluginCallToolFileResult[] {
|
|
108
|
+
return parts.map((part, index) => ({
|
|
109
|
+
index,
|
|
110
|
+
media_type: part.mediaType,
|
|
111
|
+
filename: typeof part.filename === "string" ? part.filename : "",
|
|
112
|
+
url: String(part.url || ""),
|
|
113
|
+
path: resolve_resources_file_path(project_root, String(part.url || "")),
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
72
117
|
/**
|
|
73
118
|
* 生成给模型读取的短摘要。
|
|
74
119
|
*/
|
|
75
|
-
function summarize_action_data(data: JsonValue | undefined): JsonObject {
|
|
120
|
+
function summarize_action_data(data: JsonValue | undefined): JsonObject | undefined {
|
|
76
121
|
const message = to_json_object(data);
|
|
77
122
|
const parts: unknown[] = Array.isArray(message?.parts) ? message.parts : [];
|
|
78
|
-
const file_parts = parts.filter(is_file_part);
|
|
79
123
|
if (parts.length > 0) {
|
|
80
|
-
return
|
|
81
|
-
kind: "ui_message",
|
|
82
|
-
role: typeof message?.role === "string" ? message.role : "assistant",
|
|
83
|
-
part_count: parts.length,
|
|
84
|
-
file_count: file_parts.length,
|
|
85
|
-
files: file_parts.map((part, index) => ({
|
|
86
|
-
index,
|
|
87
|
-
mediaType: part.mediaType,
|
|
88
|
-
filename: typeof part.filename === "string" ? part.filename : "",
|
|
89
|
-
// 关键点(中文):不把 data URL 或长 URL 原样返回给模型,完整内容只进入 assistant file part。
|
|
90
|
-
has_url: Boolean(part.url),
|
|
91
|
-
})),
|
|
92
|
-
};
|
|
124
|
+
return undefined;
|
|
93
125
|
}
|
|
94
126
|
if (data === undefined) return {};
|
|
95
127
|
return {
|
|
@@ -135,29 +167,33 @@ export async function invokePluginCallTool(
|
|
|
135
167
|
action,
|
|
136
168
|
payload,
|
|
137
169
|
});
|
|
170
|
+
const project_root = resolve_project_root(getSessionRunContext()?.projectRoot);
|
|
138
171
|
const raw_file_parts = result.success
|
|
139
172
|
? extract_assistant_file_parts(result.data)
|
|
140
173
|
: [];
|
|
141
174
|
const file_parts =
|
|
142
175
|
raw_file_parts.length > 0
|
|
143
176
|
? await materializeAssistantFileParts({
|
|
144
|
-
projectRoot:
|
|
177
|
+
projectRoot: project_root,
|
|
145
178
|
parts: raw_file_parts,
|
|
146
179
|
})
|
|
147
180
|
: [];
|
|
181
|
+
const files = summarize_materialized_files(file_parts, project_root);
|
|
148
182
|
if (file_parts.length > 0) {
|
|
149
183
|
enqueueAssistantFileParts(file_parts);
|
|
150
184
|
}
|
|
185
|
+
const data = summarize_action_data(result.data);
|
|
151
186
|
return {
|
|
152
187
|
success: result.success,
|
|
153
188
|
plugin,
|
|
154
189
|
action,
|
|
155
190
|
assistant_file_count: file_parts.length,
|
|
191
|
+
...(files.length > 0 ? { files } : {}),
|
|
156
192
|
message:
|
|
157
193
|
String(result.message || result.error || "").trim() ||
|
|
158
194
|
(result.success ? "plugin action completed" : "plugin action failed"),
|
|
159
195
|
...(result.error ? { error: result.error } : {}),
|
|
160
|
-
data:
|
|
196
|
+
...(data === undefined ? {} : { data }),
|
|
161
197
|
};
|
|
162
198
|
} catch (error) {
|
|
163
199
|
return {
|
|
@@ -20,6 +20,22 @@ export interface PluginCallInput {
|
|
|
20
20
|
payload?: JsonObject;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* plugin_call 产生的 assistant 文件摘要。
|
|
25
|
+
*/
|
|
26
|
+
export interface PluginCallToolFileResult {
|
|
27
|
+
/** 文件在本轮 assistant 文件列表中的顺序。 */
|
|
28
|
+
index: number;
|
|
29
|
+
/** 文件 MIME 类型,例如 `image/png`。 */
|
|
30
|
+
media_type: string;
|
|
31
|
+
/** 原始文件名;若上游未提供则为空字符串。 */
|
|
32
|
+
filename: string;
|
|
33
|
+
/** 持久化到历史消息中的资源 URL,通常为 `resources://.downcity/resources/...`。 */
|
|
34
|
+
url: string;
|
|
35
|
+
/** 当前机器可直接打开的绝对文件路径。 */
|
|
36
|
+
path: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
/**
|
|
24
40
|
* plugin_call 返回给模型的摘要结果。
|
|
25
41
|
*/
|
|
@@ -32,6 +48,8 @@ export interface PluginCallToolResult {
|
|
|
32
48
|
action: string;
|
|
33
49
|
/** 本次 action 产生并写入 assistant 消息的 file part 数量。 */
|
|
34
50
|
assistant_file_count: number;
|
|
51
|
+
/** 本次 action 产生的文件摘要,包含可直接打开的绝对路径。 */
|
|
52
|
+
files?: PluginCallToolFileResult[];
|
|
35
53
|
/** 人类可读消息。 */
|
|
36
54
|
message: string;
|
|
37
55
|
/** 错误信息。 */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - `SessionRunInput` 表示上层会话入口输入(例如 context query)。
|
|
6
6
|
* - `SessionExecuteInput` 表示 Executor 通过 Composer 装配后的中间运行态。
|
|
7
|
-
* -
|
|
7
|
+
* - 输出暴露可选 assistantMessage(UIMessage)。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Tool, UIMessageChunk } from "ai";
|
|
@@ -94,8 +94,12 @@ export interface SessionRunResult {
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* 最终 assistant 消息。
|
|
97
|
+
*
|
|
98
|
+
* 关键点(中文)
|
|
99
|
+
* - stop/abort 且没有任何 assistant 内容时可以为空。
|
|
100
|
+
* - turn 状态通过 `success` / `error` 表达,不应伪造成 assistant 正文。
|
|
97
101
|
*/
|
|
98
|
-
assistantMessage
|
|
102
|
+
assistantMessage?: SessionMessageV1 | null;
|
|
99
103
|
|
|
100
104
|
/**
|
|
101
105
|
* 本轮执行结束后待写入长期历史的 user 消息。
|
|
@@ -32,15 +32,12 @@ import type {
|
|
|
32
32
|
import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
|
|
33
33
|
import { pickLastSuccessfulChatSendText } from "@/executor/messages/UserVisibleText.js";
|
|
34
34
|
import { getSdkAgentSessionMessagesPath } from "@/session/storage/Paths.js";
|
|
35
|
+
import { getSdkAgentSessionMetaPath } from "@/session/storage/Paths.js";
|
|
35
36
|
import { getSdkAgentSessionsRootDirPath } from "@/session/storage/Paths.js";
|
|
36
37
|
import { getSdkAgentArchivedSessionsDirPath } from "@/session/storage/Paths.js";
|
|
37
38
|
import { getSdkAgentArchivedSessionMessagesPath } from "@/session/storage/Paths.js";
|
|
38
39
|
import { getSdkAgentArchivedSessionMetaPath } from "@/session/storage/Paths.js";
|
|
39
|
-
import { readSessionMetadata } from "@/session/storage/Metadata.js";
|
|
40
40
|
import { readSessionMetadataFromPath } from "@/session/storage/Metadata.js";
|
|
41
|
-
import {
|
|
42
|
-
ensureSessionTitle,
|
|
43
|
-
} from "@/session/SessionTitle.js";
|
|
44
41
|
|
|
45
42
|
type AnyUiPart = UIMessagePart<Record<string, never>, Record<string, never>>;
|
|
46
43
|
|
|
@@ -479,7 +476,7 @@ export async function listAgentSessionSummaryPage(params: {
|
|
|
479
476
|
const sessionId = decodeMaybe(entry.name);
|
|
480
477
|
if (!sessionId) continue;
|
|
481
478
|
const metadata = await readSessionMetadataFromPath({
|
|
482
|
-
filePath:
|
|
479
|
+
filePath: getSdkAgentSessionMetaPath(
|
|
483
480
|
params.projectRoot,
|
|
484
481
|
params.agentId,
|
|
485
482
|
sessionId,
|
|
@@ -488,19 +485,17 @@ export async function listAgentSessionSummaryPage(params: {
|
|
|
488
485
|
agentId: params.agentId,
|
|
489
486
|
});
|
|
490
487
|
const messages = await loadSessionMessagesFromPath(
|
|
491
|
-
|
|
488
|
+
getSdkAgentSessionMessagesPath(
|
|
492
489
|
params.projectRoot,
|
|
493
490
|
params.agentId,
|
|
494
491
|
sessionId,
|
|
495
492
|
),
|
|
496
493
|
);
|
|
497
|
-
// 关键点(中文):归档 session 不再生成新 title,仅读取已有 meta。
|
|
498
|
-
const metadataWithTitle = metadata;
|
|
499
494
|
const info = buildSessionInfo({
|
|
500
495
|
projectRoot: params.projectRoot,
|
|
501
496
|
agentId: params.agentId,
|
|
502
497
|
sessionId,
|
|
503
|
-
metadata
|
|
498
|
+
metadata,
|
|
504
499
|
messages,
|
|
505
500
|
executing: params.executingSessionIds?.has(sessionId),
|
|
506
501
|
});
|
|
@@ -575,27 +570,28 @@ export async function listArchivedAgentSessionSummaryPage(params: {
|
|
|
575
570
|
if (!entry.isDirectory()) continue;
|
|
576
571
|
const sessionId = decodeMaybe(entry.name);
|
|
577
572
|
if (!sessionId) continue;
|
|
578
|
-
const metadata = await
|
|
579
|
-
|
|
580
|
-
|
|
573
|
+
const metadata = await readSessionMetadataFromPath({
|
|
574
|
+
filePath: getSdkAgentArchivedSessionMetaPath(
|
|
575
|
+
params.projectRoot,
|
|
576
|
+
params.agentId,
|
|
577
|
+
sessionId,
|
|
578
|
+
),
|
|
581
579
|
sessionId,
|
|
580
|
+
agentId: params.agentId,
|
|
582
581
|
});
|
|
583
582
|
const messages = await loadSessionMessagesFromPath(
|
|
584
|
-
|
|
583
|
+
getSdkAgentArchivedSessionMessagesPath(
|
|
584
|
+
params.projectRoot,
|
|
585
|
+
params.agentId,
|
|
586
|
+
sessionId,
|
|
587
|
+
),
|
|
585
588
|
);
|
|
586
|
-
|
|
587
|
-
? metadata
|
|
588
|
-
: await ensureSessionTitle({
|
|
589
|
-
projectRoot: params.projectRoot,
|
|
590
|
-
agentId: params.agentId,
|
|
591
|
-
sessionId,
|
|
592
|
-
messages,
|
|
593
|
-
});
|
|
589
|
+
// 关键点(中文):归档 session 不再生成新 title,仅读取归档目录内已有 meta。
|
|
594
590
|
const info = buildSessionInfo({
|
|
595
591
|
projectRoot: params.projectRoot,
|
|
596
592
|
agentId: params.agentId,
|
|
597
593
|
sessionId,
|
|
598
|
-
metadata
|
|
594
|
+
metadata,
|
|
599
595
|
messages,
|
|
600
596
|
executing: false,
|
|
601
597
|
});
|
|
@@ -106,7 +106,7 @@ export interface SessionPromptRuntimeOptions {
|
|
|
106
106
|
}) => Promise<{
|
|
107
107
|
text: string;
|
|
108
108
|
success: boolean;
|
|
109
|
-
assistantMessage
|
|
109
|
+
assistantMessage?: SessionMessageV1 | null;
|
|
110
110
|
error?: string;
|
|
111
111
|
}>;
|
|
112
112
|
|
|
@@ -233,7 +233,9 @@ export class SessionPromptRuntime {
|
|
|
233
233
|
turnId,
|
|
234
234
|
text: result.text,
|
|
235
235
|
success: stopped ? false : result.success,
|
|
236
|
-
|
|
236
|
+
...(result.assistantMessage
|
|
237
|
+
? { assistantMessage: result.assistantMessage }
|
|
238
|
+
: {}),
|
|
237
239
|
...(stopped
|
|
238
240
|
? { error: TURN_STOPPED_MESSAGE }
|
|
239
241
|
: result.error ? { error: result.error } : {}),
|
|
@@ -289,7 +289,7 @@ export class SessionStateService {
|
|
|
289
289
|
* 持久化最终 assistant 结果。
|
|
290
290
|
*/
|
|
291
291
|
async persist_assistant_result(
|
|
292
|
-
assistant_message
|
|
292
|
+
assistant_message?: SessionMessageV1 | null,
|
|
293
293
|
): Promise<void> {
|
|
294
294
|
await persistSdkAssistantResult({
|
|
295
295
|
projectRoot: this.project_root,
|
|
@@ -146,7 +146,7 @@ export class SessionTurnService {
|
|
|
146
146
|
}): Promise<{
|
|
147
147
|
text: string;
|
|
148
148
|
success: boolean;
|
|
149
|
-
assistantMessage
|
|
149
|
+
assistantMessage?: SessionMessageV1 | null;
|
|
150
150
|
error?: string;
|
|
151
151
|
}> {
|
|
152
152
|
const tool_name_by_call_id = new Map<string, string>();
|
|
@@ -214,9 +214,13 @@ export class SessionTurnService {
|
|
|
214
214
|
result.deferredPersistedUserMessages,
|
|
215
215
|
);
|
|
216
216
|
return {
|
|
217
|
-
text:
|
|
217
|
+
text: result.assistantMessage
|
|
218
|
+
? extractTextFromUiMessage(result.assistantMessage)
|
|
219
|
+
: "",
|
|
218
220
|
success: result.success,
|
|
219
|
-
|
|
221
|
+
...(result.assistantMessage
|
|
222
|
+
? { assistantMessage: result.assistantMessage }
|
|
223
|
+
: {}),
|
|
220
224
|
...(result.error ? { error: result.error } : {}),
|
|
221
225
|
};
|
|
222
226
|
}
|
|
@@ -60,8 +60,12 @@ export interface PersistSdkAssistantResultParams
|
|
|
60
60
|
};
|
|
61
61
|
/**
|
|
62
62
|
* 本轮执行得到的 assistant 消息。
|
|
63
|
+
*
|
|
64
|
+
* 关键点(中文)
|
|
65
|
+
* - stop/abort 且没有 assistant 内容时允许为空。
|
|
66
|
+
* - 为空时仅刷新 metadata,不写入伪造 assistant 正文。
|
|
63
67
|
*/
|
|
64
|
-
assistantMessage
|
|
68
|
+
assistantMessage?: SessionMessageV1 | null;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
/**
|
|
@@ -104,7 +108,9 @@ export async function persistSdkAssistantResult(
|
|
|
104
108
|
await persistAssistantResult({
|
|
105
109
|
writer: params.executor,
|
|
106
110
|
assistantMessage: params.assistantMessage,
|
|
107
|
-
fallbackText:
|
|
111
|
+
fallbackText: params.assistantMessage
|
|
112
|
+
? extractTextFromUiMessage(params.assistantMessage)
|
|
113
|
+
: undefined,
|
|
108
114
|
});
|
|
109
115
|
await touchSessionMetadata(params);
|
|
110
116
|
}
|
|
@@ -212,8 +212,8 @@ export interface AgentSessionSummary {
|
|
|
212
212
|
*
|
|
213
213
|
* 说明(中文)
|
|
214
214
|
* - 标题持久化在 session `meta.json` 顶层。
|
|
215
|
-
* -
|
|
216
|
-
* -
|
|
215
|
+
* - SDK 只在模型成功生成标题时写入,不再从首条用户消息生成 fallback。
|
|
216
|
+
* - 标题允许为空,调用方需要展示占位文案时可自行回退到 `sessionId`。
|
|
217
217
|
*/
|
|
218
218
|
title?: string;
|
|
219
219
|
/**
|