@downcity/agent 1.1.157 → 1.1.161

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.
Files changed (32) hide show
  1. package/bin/config/Paths.d.ts +1 -1
  2. package/bin/config/Paths.js +1 -1
  3. package/bin/executor/core-engine/CoreEngineMessageState.d.ts +1 -1
  4. package/bin/executor/core-engine/CoreEngineMessageState.js +1 -1
  5. package/bin/executor/messages/AssistantFileResource.d.ts +35 -4
  6. package/bin/executor/messages/AssistantFileResource.d.ts.map +1 -1
  7. package/bin/executor/messages/AssistantFileResource.js +151 -23
  8. package/bin/executor/messages/AssistantFileResource.js.map +1 -1
  9. package/bin/executor/messages/SessionAttachmentMapper.d.ts +3 -3
  10. package/bin/executor/messages/SessionAttachmentMapper.d.ts.map +1 -1
  11. package/bin/executor/messages/SessionAttachmentMapper.js +17 -18
  12. package/bin/executor/messages/SessionAttachmentMapper.js.map +1 -1
  13. package/bin/executor/messages/SessionMessageCodec.d.ts.map +1 -1
  14. package/bin/executor/messages/SessionMessageCodec.js +1 -1
  15. package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
  16. package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
  17. package/bin/executor/tools/plugin/PluginToolBridge.js +14 -25
  18. package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -1
  19. package/bin/executor/tools/plugin/types/PluginTool.d.ts +3 -3
  20. package/bin/executor/tools/plugin/types/PluginTool.d.ts.map +1 -1
  21. package/package.json +3 -3
  22. package/scripts/assistant-file-resource.test.mjs +77 -11
  23. package/scripts/image-plugin-job.test.mjs +75 -1
  24. package/scripts/plugin-tool-bridge.test.mjs +4 -4
  25. package/src/config/Paths.ts +1 -1
  26. package/src/executor/core-engine/CoreEngineMessageState.ts +1 -1
  27. package/src/executor/messages/AssistantFileResource.ts +184 -26
  28. package/src/executor/messages/SessionAttachmentMapper.ts +16 -17
  29. package/src/executor/messages/SessionMessageCodec.ts +4 -1
  30. package/src/executor/tools/plugin/PluginToolBridge.ts +17 -27
  31. package/src/executor/tools/plugin/types/PluginTool.ts +3 -3
  32. package/tsconfig.tsbuildinfo +1 -1
@@ -5,7 +5,7 @@
5
5
  * - 兼容 Telegram / Feishu / TUI 等统一的 `<file>` 协议入口。
6
6
  * - 仅在本轮执行的内存消息上追加 file parts,不修改持久化历史。
7
7
  * - 当前只为图片与 PDF 注入 file part,保持多模态模型可直接消费。
8
- * - 历史中的 `resources://` 与旧版 `file://` 会在喂给模型前临时 hydrate。
8
+ * - 历史中的相对路径与旧版 `file://` 会在喂给模型前临时 hydrate。
9
9
  */
10
10
 
11
11
  import fs from "fs-extra";
@@ -63,18 +63,17 @@ function buildDataUrl(mediaType: string, buffer: Buffer): string {
63
63
  return `data:${safeType};base64,${base64}`;
64
64
  }
65
65
 
66
- function resolveResourcesUrlPath(
66
+ function resolveHydratableFilePath(
67
67
  projectRoot: string | undefined,
68
- rawUrl: string,
68
+ rawPath: string,
69
69
  ): string | null {
70
- const prefix = "resources://";
71
- const raw = String(rawUrl || "").trim();
72
- if (!raw.startsWith(prefix)) return null;
73
- const relative = raw.slice(prefix.length).replace(/^\/+/, "");
74
- if (!relative) return null;
70
+ const raw = String(rawPath || "").trim();
71
+ if (!raw || raw.startsWith("data:") || /^https?:\/\//i.test(raw)) return null;
72
+ if (raw.startsWith("file://")) return fileURLToPath(raw);
73
+ if (path.isAbsolute(raw)) return path.resolve(raw);
75
74
 
76
75
  const root = path.resolve(String(projectRoot || "").trim() || process.cwd());
77
- const absPath = path.resolve(root, relative);
76
+ const absPath = path.resolve(root, raw);
78
77
  const rel = path.relative(root, absPath);
79
78
  if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return null;
80
79
  return absPath;
@@ -85,14 +84,13 @@ async function hydrateFileUrlPart(
85
84
  projectRoot?: string,
86
85
  ): Promise<FileUIPart> {
87
86
  const url = String(part.url || "").trim();
88
- const resourcesPath = resolveResourcesUrlPath(projectRoot, url);
89
- if (!url.startsWith("file://") && !resourcesPath) return part;
87
+ const filePath = resolveHydratableFilePath(projectRoot, url);
88
+ if (!filePath) return part;
90
89
  try {
91
- const absPath = resourcesPath || fileURLToPath(url);
92
- const buffer = await fs.readFile(absPath);
90
+ const buffer = await fs.readFile(filePath);
93
91
  const mediaType =
94
92
  String(part.mediaType || "").trim() ||
95
- guessAttachmentMediaTypeFromPath(absPath) ||
93
+ guessAttachmentMediaTypeFromPath(filePath) ||
96
94
  "application/octet-stream";
97
95
  return {
98
96
  ...part,
@@ -109,7 +107,7 @@ async function hydrateFileUrlPart(
109
107
  *
110
108
  * 关键点(中文)
111
109
  * - 该函数只修改本轮内存消息,不回写历史。
112
- * - 新历史保留 `resources://` 相对 URL,旧历史的 `file://` 仍继续兼容。
110
+ * - 新历史保留 Agent 根目录相对路径,旧历史的 `file://` 仍继续兼容。
113
111
  */
114
112
  export async function hydrateFileUrlPartsForModel(
115
113
  messages: SessionMessageV1[],
@@ -148,10 +146,11 @@ export async function hydrateFileUrlPartsForModel(
148
146
  */
149
147
  export async function injectFilePartsFromAttachments(
150
148
  messages: SessionMessageV1[],
149
+ projectRoot?: string,
151
150
  ): Promise<SessionMessageV1[]> {
152
151
  if (!Array.isArray(messages) || messages.length === 0) return messages;
153
152
 
154
- const cwd = process.cwd();
153
+ const root = path.resolve(String(projectRoot || "").trim() || process.cwd());
155
154
  const out: SessionMessageV1[] = [];
156
155
 
157
156
  for (const message of messages) {
@@ -203,7 +202,7 @@ export async function injectFilePartsFromAttachments(
203
202
 
204
203
  const absPath = path.isAbsolute(attachment.path)
205
204
  ? attachment.path
206
- : path.resolve(cwd, attachment.path);
205
+ : path.resolve(root, attachment.path);
207
206
  try {
208
207
  const exists = await fs.pathExists(absPath);
209
208
  if (!exists) continue;
@@ -78,7 +78,10 @@ export async function toModelMessages(
78
78
  if (!Array.isArray(messages) || messages.length === 0) return [];
79
79
 
80
80
  // 第一步(中文):在 user 消息上注入 file parts(多模态附件)。
81
- const enrichedMessages = await injectFilePartsFromAttachments(messages);
81
+ const enrichedMessages = await injectFilePartsFromAttachments(
82
+ messages,
83
+ projectRoot,
84
+ );
82
85
 
83
86
  // 第二步(中文):把历史里的资源 URL 在内存中 hydrate 成模型可消费的 data URL。
84
87
  const hydratedMessages = await hydrateFileUrlPartsForModel(
@@ -18,7 +18,10 @@ import type {
18
18
  PluginReadInput,
19
19
  PluginReadToolResult,
20
20
  } from "@/executor/tools/plugin/types/PluginTool.js";
21
- import { materializeAssistantFileParts } from "@executor/messages/AssistantFileResource.js";
21
+ import {
22
+ materializeAssistantFileParts,
23
+ resolveAgentFilePath,
24
+ } from "@executor/messages/AssistantFileResource.js";
22
25
  import {
23
26
  enqueueAssistantFileParts,
24
27
  getSessionRunContext,
@@ -81,25 +84,6 @@ function resolve_project_root(project_root: string | undefined): string {
81
84
  return path.resolve(raw || process.cwd());
82
85
  }
83
86
 
84
- /**
85
- * 将 `resources://` URL 转成本机绝对路径。
86
- */
87
- function resolve_resources_file_path(
88
- project_root: string,
89
- raw_url: string,
90
- ): string {
91
- const prefix = "resources://";
92
- const raw = String(raw_url || "").trim();
93
- if (!raw.startsWith(prefix)) return "";
94
- const relative = raw.slice(prefix.length).replace(/^\/+/, "");
95
- if (!relative) return "";
96
-
97
- const file_path = path.resolve(project_root, relative);
98
- const rel = path.relative(project_root, file_path);
99
- if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) return "";
100
- return file_path;
101
- }
102
-
103
87
  /**
104
88
  * 构建返回给模型和用户可见的文件摘要。
105
89
  */
@@ -107,13 +91,19 @@ function summarize_materialized_files(
107
91
  parts: FileUIPart[],
108
92
  project_root: string,
109
93
  ): PluginCallToolFileResult[] {
110
- return parts.map((part, index) => ({
111
- index,
112
- media_type: part.mediaType,
113
- filename: typeof part.filename === "string" ? part.filename : "",
114
- url: String(part.url || ""),
115
- path: resolve_resources_file_path(project_root, String(part.url || "")),
116
- }));
94
+ return parts.map((part, index) => {
95
+ const relative_path = String(part.url || "");
96
+ return {
97
+ index,
98
+ media_type: part.mediaType,
99
+ filename: typeof part.filename === "string" ? part.filename : "",
100
+ relative_path,
101
+ path: resolveAgentFilePath({
102
+ projectRoot: project_root,
103
+ filePath: relative_path,
104
+ }),
105
+ };
106
+ });
117
107
  }
118
108
 
119
109
  /**
@@ -40,9 +40,9 @@ export interface PluginCallToolFileResult {
40
40
  media_type: string;
41
41
  /** 原始文件名;若上游未提供则为空字符串。 */
42
42
  filename: string;
43
- /** 持久化到历史消息中的资源 URL,通常为 `resources://.downcity/resources/...`。 */
44
- url: string;
45
- /** 当前机器可直接打开的绝对文件路径。 */
43
+ /** 基于 Agent 项目根目录的相对路径,例如 `.downcity/resources/xxx.png`。 */
44
+ relative_path: string;
45
+ /** 当前机器可直接打开的绝对文件路径,按 Agent 项目根目录解析。 */
46
46
  path: string;
47
47
  }
48
48