@downcity/agent 1.1.86 → 1.1.92
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/config/AgentInitializer.d.ts.map +1 -1
- package/bin/config/AgentInitializer.js +2 -1
- package/bin/config/AgentInitializer.js.map +1 -1
- package/bin/config/Paths.d.ts +8 -0
- package/bin/config/Paths.d.ts.map +1 -1
- package/bin/config/Paths.js +10 -0
- package/bin/config/Paths.js.map +1 -1
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +3 -0
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/messages/AssistantFileResource.d.ts +31 -0
- package/bin/executor/messages/AssistantFileResource.d.ts.map +1 -0
- package/bin/executor/messages/AssistantFileResource.js +113 -0
- package/bin/executor/messages/AssistantFileResource.js.map +1 -0
- package/bin/executor/messages/SessionAttachmentMapper.d.ts +8 -0
- package/bin/executor/messages/SessionAttachmentMapper.d.ts.map +1 -1
- package/bin/executor/messages/SessionAttachmentMapper.js +54 -0
- package/bin/executor/messages/SessionAttachmentMapper.js.map +1 -1
- package/bin/executor/messages/SessionMessageCodec.d.ts.map +1 -1
- package/bin/executor/messages/SessionMessageCodec.js +5 -3
- package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.js +9 -2
- package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -1
- package/bin/index.d.ts +1 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/plugin/core/ImagePlugin.d.ts +62 -0
- package/bin/plugin/core/ImagePlugin.d.ts.map +1 -1
- package/bin/plugin/core/ImagePlugin.js +230 -7
- package/bin/plugin/core/ImagePlugin.js.map +1 -1
- package/bin/sandbox/LinuxBubblewrapSandbox.d.ts +21 -0
- package/bin/sandbox/LinuxBubblewrapSandbox.d.ts.map +1 -0
- package/bin/sandbox/LinuxBubblewrapSandbox.js +184 -0
- package/bin/sandbox/LinuxBubblewrapSandbox.js.map +1 -0
- package/bin/sandbox/SandboxConfigResolver.d.ts +5 -0
- package/bin/sandbox/SandboxConfigResolver.d.ts.map +1 -1
- package/bin/sandbox/SandboxConfigResolver.js +11 -4
- package/bin/sandbox/SandboxConfigResolver.js.map +1 -1
- package/bin/sandbox/SandboxPreflight.d.ts +73 -0
- package/bin/sandbox/SandboxPreflight.d.ts.map +1 -0
- package/bin/sandbox/SandboxPreflight.js +122 -0
- package/bin/sandbox/SandboxPreflight.js.map +1 -0
- package/bin/sandbox/SandboxRunner.d.ts +1 -1
- package/bin/sandbox/SandboxRunner.d.ts.map +1 -1
- package/bin/sandbox/SandboxRunner.js +11 -3
- package/bin/sandbox/SandboxRunner.js.map +1 -1
- package/bin/sandbox/types/SandboxRuntime.d.ts +6 -2
- package/bin/sandbox/types/SandboxRuntime.d.ts.map +1 -1
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +1 -0
- package/bin/session/Session.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts +5 -0
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +3 -0
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/types/executor/SessionRunContext.d.ts +8 -0
- package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
- package/bin/types/plugin/ImagePlugin.d.ts +79 -2
- package/bin/types/plugin/ImagePlugin.d.ts.map +1 -1
- package/package.json +2 -2
- package/scripts/assistant-file-resource.test.mjs +91 -0
- package/scripts/image-plugin-job.test.mjs +155 -0
- package/scripts/linux-bubblewrap-sandbox.test.mjs +142 -0
- package/scripts/shell-sandbox-preflight.test.mjs +88 -0
- package/src/config/AgentInitializer.ts +2 -0
- package/src/config/Paths.ts +11 -0
- package/src/executor/Executor.ts +3 -0
- package/src/executor/messages/AssistantFileResource.ts +155 -0
- package/src/executor/messages/SessionAttachmentMapper.ts +59 -0
- package/src/executor/messages/SessionMessageCodec.ts +9 -3
- package/src/executor/tools/plugin/PluginToolBridge.ts +13 -2
- package/src/index.ts +4 -0
- package/src/plugin/core/ImagePlugin.ts +284 -7
- package/src/sandbox/LinuxBubblewrapSandbox.ts +229 -0
- package/src/sandbox/SandboxConfigResolver.ts +13 -7
- package/src/sandbox/SandboxPreflight.ts +205 -0
- package/src/sandbox/SandboxRunner.ts +11 -3
- package/src/sandbox/types/SandboxRuntime.ts +7 -2
- package/src/session/Session.ts +1 -0
- package/src/session/services/SessionTurnService.ts +8 -0
- package/src/types/executor/SessionRunContext.ts +9 -0
- package/src/types/plugin/ImagePlugin.ts +79 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -78,6 +78,69 @@ export interface ImagePluginInput {
|
|
|
78
78
|
* ImagePlugin 生成结果。
|
|
79
79
|
*/
|
|
80
80
|
export type ImagePluginResult = UIMessage;
|
|
81
|
+
/**
|
|
82
|
+
* ImagePlugin 图片任务状态。
|
|
83
|
+
*/
|
|
84
|
+
export type ImagePluginJobStatus = "queued" | "running" | "succeeded" | "failed";
|
|
85
|
+
/**
|
|
86
|
+
* ImagePlugin 图片任务创建结果。
|
|
87
|
+
*/
|
|
88
|
+
export interface ImagePluginJobCreateResult {
|
|
89
|
+
/** 图片任务唯一 ID。 */
|
|
90
|
+
job_id: string;
|
|
91
|
+
/** 当前任务状态。 */
|
|
92
|
+
status: ImagePluginJobStatus;
|
|
93
|
+
/** 查询任务状态的路径或 URL。 */
|
|
94
|
+
status_path?: string;
|
|
95
|
+
/** 读取任务结果的路径或 URL。 */
|
|
96
|
+
result_path?: string;
|
|
97
|
+
/** 人类可读状态说明。 */
|
|
98
|
+
message?: string;
|
|
99
|
+
/** 建议下次轮询前等待的毫秒数。 */
|
|
100
|
+
poll_after_ms?: number;
|
|
101
|
+
/** 任务创建时间。 */
|
|
102
|
+
created_at?: string;
|
|
103
|
+
/** 任务更新时间。 */
|
|
104
|
+
updated_at?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* ImagePlugin 图片任务状态查询结果。
|
|
108
|
+
*/
|
|
109
|
+
export interface ImagePluginJobStatusResult {
|
|
110
|
+
/** 图片任务唯一 ID。 */
|
|
111
|
+
job_id: string;
|
|
112
|
+
/** 当前任务状态。 */
|
|
113
|
+
status: ImagePluginJobStatus;
|
|
114
|
+
/** 人类可读状态说明。 */
|
|
115
|
+
message?: string;
|
|
116
|
+
/** 失败时的错误信息。 */
|
|
117
|
+
error?: string;
|
|
118
|
+
/** 建议下次轮询前等待的毫秒数。 */
|
|
119
|
+
poll_after_ms?: number;
|
|
120
|
+
/** 任务创建时间。 */
|
|
121
|
+
created_at?: string;
|
|
122
|
+
/** 任务更新时间。 */
|
|
123
|
+
updated_at?: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* ImagePlugin 图片任务结果查询结果。
|
|
127
|
+
*/
|
|
128
|
+
export interface ImagePluginJobResult {
|
|
129
|
+
/** 图片任务唯一 ID。 */
|
|
130
|
+
job_id: string;
|
|
131
|
+
/** 当前任务状态。 */
|
|
132
|
+
status: ImagePluginJobStatus;
|
|
133
|
+
/** 成功时的图片结果。 */
|
|
134
|
+
result?: ImagePluginResult;
|
|
135
|
+
/** 失败时的错误信息。 */
|
|
136
|
+
error?: string;
|
|
137
|
+
/** 人类可读状态说明。 */
|
|
138
|
+
message?: string;
|
|
139
|
+
/** 任务创建时间。 */
|
|
140
|
+
created_at?: string;
|
|
141
|
+
/** 任务更新时间。 */
|
|
142
|
+
updated_at?: string;
|
|
143
|
+
}
|
|
81
144
|
/**
|
|
82
145
|
* ImagePlugin 构造参数。
|
|
83
146
|
*/
|
|
@@ -88,7 +151,21 @@ export interface ImagePluginOptions {
|
|
|
88
151
|
title?: string;
|
|
89
152
|
/** Plugin 用途说明。 */
|
|
90
153
|
description?: string;
|
|
91
|
-
/**
|
|
92
|
-
image
|
|
154
|
+
/** 可选:图片生成函数;未传 `create/status/result` 时用于本地后台任务兼容。 */
|
|
155
|
+
image?: (input: ImagePluginInput) => Promise<ImagePluginResult> | ImagePluginResult;
|
|
156
|
+
/** 可选:创建图片生成任务,通常传入 `(input) => city.ai.imageJobCreate(input)`。 */
|
|
157
|
+
create?: (input: ImagePluginInput) => Promise<ImagePluginJobCreateResult> | ImagePluginJobCreateResult;
|
|
158
|
+
/** 可选:查询图片生成任务状态,通常传入 `(input) => city.ai.imageJobStatus(input)`。 */
|
|
159
|
+
status?: (input: {
|
|
160
|
+
job_id: string;
|
|
161
|
+
}) => Promise<ImagePluginJobStatusResult> | ImagePluginJobStatusResult;
|
|
162
|
+
/** 可选:读取图片生成任务结果,通常传入 `(input) => city.ai.imageJobResult(input)`。 */
|
|
163
|
+
result?: (input: {
|
|
164
|
+
job_id: string;
|
|
165
|
+
}) => Promise<ImagePluginJobResult> | ImagePluginJobResult;
|
|
166
|
+
/** 兼容 `generate` 动作等待任务完成的最长毫秒数。 */
|
|
167
|
+
wait_timeout_ms?: number;
|
|
168
|
+
/** 兼容 `generate` 动作每次轮询间隔毫秒数。 */
|
|
169
|
+
poll_interval_ms?: number;
|
|
93
170
|
}
|
|
94
171
|
//# sourceMappingURL=ImagePlugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImagePlugin.d.ts","sourceRoot":"","sources":["../../../src/types/plugin/ImagePlugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,kBAAkB;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,gBAAgB;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,sBAAsB,GACtB,sBAAsB,CAAC;AAE3B;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,YAAY;IACZ,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,oBAAoB;IACpB,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,cAAc;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC,cAAc;IACd,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,UAAU,CAAC;IAC9B,qCAAqC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,kBAAkB,EAAE,GAAG,SAAS,CAAC;CAC7D;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,
|
|
1
|
+
{"version":3,"file":"ImagePlugin.d.ts","sourceRoot":"","sources":["../../../src/types/plugin/ImagePlugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@downcity/agent",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.92",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Downcity Agent 运行时 — 单 Agent 执行壳与本机 RPC 能力",
|
|
6
6
|
"main": "./bin/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"node-cron": "^4.2.1",
|
|
30
30
|
"ws": "^8.21.0",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
|
-
"@downcity/type": "0.1.
|
|
32
|
+
"@downcity/type": "0.1.27"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 assistant file part 会把 data URL 资源落盘为 file:// URL。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 历史消息不应长期保存图片 base64。
|
|
6
|
+
* - 送模型前可以从 file:// 临时 hydrate 回 data URL。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import fs from "node:fs/promises";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
import { materializeAssistantFileParts } from "../bin/executor/messages/AssistantFileResource.js";
|
|
17
|
+
import { hydrateFileUrlPartsForModel } from "../bin/executor/messages/SessionAttachmentMapper.js";
|
|
18
|
+
|
|
19
|
+
test("materializeAssistantFileParts stores data URL images under .downcity/resources", async () => {
|
|
20
|
+
const project_root = await fs.mkdtemp(
|
|
21
|
+
path.join(os.tmpdir(), "downcity-agent-assistant-resource-"),
|
|
22
|
+
);
|
|
23
|
+
const bytes = Buffer.from("png-bytes-for-test", "utf8");
|
|
24
|
+
const data_url = `data:image/png;base64,${bytes.toString("base64")}`;
|
|
25
|
+
|
|
26
|
+
const parts = await materializeAssistantFileParts({
|
|
27
|
+
projectRoot: project_root,
|
|
28
|
+
parts: [
|
|
29
|
+
{
|
|
30
|
+
type: "file",
|
|
31
|
+
mediaType: "image/png",
|
|
32
|
+
filename: "image-1.png",
|
|
33
|
+
url: data_url,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
assert.equal(parts.length, 1);
|
|
39
|
+
assert.equal(parts[0].type, "file");
|
|
40
|
+
assert.equal(parts[0].mediaType, "image/png");
|
|
41
|
+
assert.equal(parts[0].filename, "image-1.png");
|
|
42
|
+
assert.match(parts[0].url, /^file:\/\//);
|
|
43
|
+
assert.equal(parts[0].url.includes("base64"), false);
|
|
44
|
+
|
|
45
|
+
const resource_path = fileURLToPath(parts[0].url);
|
|
46
|
+
assert.equal(
|
|
47
|
+
path.dirname(resource_path),
|
|
48
|
+
path.join(project_root, ".downcity", "resources"),
|
|
49
|
+
);
|
|
50
|
+
assert.deepEqual(await fs.readFile(resource_path), bytes);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("hydrateFileUrlPartsForModel converts persisted file URLs back to data URLs in memory", async () => {
|
|
54
|
+
const project_root = await fs.mkdtemp(
|
|
55
|
+
path.join(os.tmpdir(), "downcity-agent-assistant-hydrate-"),
|
|
56
|
+
);
|
|
57
|
+
const bytes = Buffer.from("hydrate-bytes-for-test", "utf8");
|
|
58
|
+
const materialized = await materializeAssistantFileParts({
|
|
59
|
+
projectRoot: project_root,
|
|
60
|
+
parts: [
|
|
61
|
+
{
|
|
62
|
+
type: "file",
|
|
63
|
+
mediaType: "image/png",
|
|
64
|
+
filename: "image-1.png",
|
|
65
|
+
url: `data:image/png;base64,${bytes.toString("base64")}`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const messages = await hydrateFileUrlPartsForModel([
|
|
71
|
+
{
|
|
72
|
+
id: "a:test:1",
|
|
73
|
+
role: "assistant",
|
|
74
|
+
metadata: {
|
|
75
|
+
v: 1,
|
|
76
|
+
ts: Date.now(),
|
|
77
|
+
sessionId: "session_test",
|
|
78
|
+
},
|
|
79
|
+
parts: materialized,
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const hydrated_part = messages[0]?.parts[0];
|
|
84
|
+
assert.equal(hydrated_part?.type, "file");
|
|
85
|
+
assert.equal(hydrated_part?.mediaType, "image/png");
|
|
86
|
+
assert.equal(
|
|
87
|
+
hydrated_part?.url,
|
|
88
|
+
`data:image/png;base64,${bytes.toString("base64")}`,
|
|
89
|
+
);
|
|
90
|
+
assert.match(materialized[0].url, /^file:\/\//);
|
|
91
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 ImagePlugin 的可观察图片任务协议。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - `create` 应快速返回 job_id,而不是同步等待图片完成。
|
|
6
|
+
* - `status/result` 可查询同一个任务,成功后 result 返回 UIMessage。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import { ImagePlugin } from "../bin/index.js";
|
|
13
|
+
|
|
14
|
+
function create_image_message() {
|
|
15
|
+
return {
|
|
16
|
+
id: "msg_image_test",
|
|
17
|
+
role: "assistant",
|
|
18
|
+
parts: [
|
|
19
|
+
{
|
|
20
|
+
type: "file",
|
|
21
|
+
mediaType: "image/png",
|
|
22
|
+
filename: "image.png",
|
|
23
|
+
url: "data:image/png;base64,cG5n",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("ImagePlugin create/status/result exposes async image jobs", async () => {
|
|
30
|
+
let finish_image;
|
|
31
|
+
const image_promise = new Promise((resolve) => {
|
|
32
|
+
finish_image = () => resolve(create_image_message());
|
|
33
|
+
});
|
|
34
|
+
const plugin = new ImagePlugin({
|
|
35
|
+
image: async () => image_promise,
|
|
36
|
+
poll_interval_ms: 1,
|
|
37
|
+
wait_timeout_ms: 100,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const created = await plugin.actions.create.execute({
|
|
41
|
+
context: {},
|
|
42
|
+
payload: { prompt: "draw" },
|
|
43
|
+
pluginName: "image",
|
|
44
|
+
actionName: "create",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
assert.equal(created.success, true);
|
|
48
|
+
assert.equal(created.data.status, "running");
|
|
49
|
+
assert.match(created.data.job_id, /^img_/);
|
|
50
|
+
|
|
51
|
+
const before = await plugin.actions.status.execute({
|
|
52
|
+
context: {},
|
|
53
|
+
payload: { job_id: created.data.job_id },
|
|
54
|
+
pluginName: "image",
|
|
55
|
+
actionName: "status",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
assert.equal(before.success, true);
|
|
59
|
+
assert.equal(before.data.status, "running");
|
|
60
|
+
|
|
61
|
+
finish_image();
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
63
|
+
|
|
64
|
+
const after = await plugin.actions.status.execute({
|
|
65
|
+
context: {},
|
|
66
|
+
payload: { job_id: created.data.job_id },
|
|
67
|
+
pluginName: "image",
|
|
68
|
+
actionName: "status",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
assert.equal(after.success, true);
|
|
72
|
+
assert.equal(after.data.status, "succeeded");
|
|
73
|
+
assert.equal("result" in after.data, false);
|
|
74
|
+
|
|
75
|
+
const result = await plugin.actions.result.execute({
|
|
76
|
+
context: {},
|
|
77
|
+
payload: { job_id: created.data.job_id },
|
|
78
|
+
pluginName: "image",
|
|
79
|
+
actionName: "result",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
assert.equal(result.success, true);
|
|
83
|
+
assert.equal(result.data.role, "assistant");
|
|
84
|
+
assert.equal(result.data.parts[0].type, "file");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("ImagePlugin accepts custom job API without synchronous image function", async () => {
|
|
88
|
+
const message = create_image_message();
|
|
89
|
+
const plugin = new ImagePlugin({
|
|
90
|
+
create: () => ({
|
|
91
|
+
job_id: "img_custom",
|
|
92
|
+
status: "running",
|
|
93
|
+
poll_after_ms: 1,
|
|
94
|
+
}),
|
|
95
|
+
status: () => ({
|
|
96
|
+
job_id: "img_custom",
|
|
97
|
+
status: "succeeded",
|
|
98
|
+
}),
|
|
99
|
+
result: () => ({
|
|
100
|
+
job_id: "img_custom",
|
|
101
|
+
status: "succeeded",
|
|
102
|
+
result: message,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const created = await plugin.actions.create.execute({
|
|
107
|
+
context: {},
|
|
108
|
+
payload: { prompt: "draw" },
|
|
109
|
+
pluginName: "image",
|
|
110
|
+
actionName: "create",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
assert.equal(created.success, true);
|
|
114
|
+
assert.equal(created.data.job_id, "img_custom");
|
|
115
|
+
|
|
116
|
+
const result = await plugin.actions.result.execute({
|
|
117
|
+
context: {},
|
|
118
|
+
payload: { job_id: "img_custom" },
|
|
119
|
+
pluginName: "image",
|
|
120
|
+
actionName: "result",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
assert.equal(result.success, true);
|
|
124
|
+
assert.equal(result.data.parts[0].type, "file");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("ImagePlugin strips accidental result data from custom status responses", async () => {
|
|
128
|
+
const plugin = new ImagePlugin({
|
|
129
|
+
create: () => ({
|
|
130
|
+
job_id: "img_status_result",
|
|
131
|
+
status: "succeeded",
|
|
132
|
+
}),
|
|
133
|
+
status: () => ({
|
|
134
|
+
job_id: "img_status_result",
|
|
135
|
+
status: "succeeded",
|
|
136
|
+
result: create_image_message(),
|
|
137
|
+
}),
|
|
138
|
+
result: () => ({
|
|
139
|
+
job_id: "img_status_result",
|
|
140
|
+
status: "succeeded",
|
|
141
|
+
result: create_image_message(),
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const status = await plugin.actions.status.execute({
|
|
146
|
+
context: {},
|
|
147
|
+
payload: { job_id: "img_status_result" },
|
|
148
|
+
pluginName: "image",
|
|
149
|
+
actionName: "status",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
assert.equal(status.success, true);
|
|
153
|
+
assert.equal(status.data.status, "succeeded");
|
|
154
|
+
assert.equal("result" in status.data, false);
|
|
155
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 Linux bubblewrap sandbox 参数生成。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 测试编译后的 bin 输出,避免测试文件进入 package 源码导出面。
|
|
6
|
+
* - 不启动真实 `bwrap`,只锁住路径挂载、网络开关与 shell 调用参数。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import fs from "node:fs/promises";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
import { buildLinuxBubblewrapArgs } from "../bin/sandbox/LinuxBubblewrapSandbox.js";
|
|
16
|
+
|
|
17
|
+
async function createSandboxFixture() {
|
|
18
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "downcity-bwrap-"));
|
|
19
|
+
const projectRoot = path.join(root, "project");
|
|
20
|
+
const writablePath = path.join(projectRoot, ".downcity");
|
|
21
|
+
const shellDir = path.join(writablePath, "shell", "sh_test");
|
|
22
|
+
const shellHomeDir = path.join(shellDir, "sandbox", "home");
|
|
23
|
+
const shellTmpDir = path.join(shellDir, "sandbox", "tmp");
|
|
24
|
+
|
|
25
|
+
await fs.mkdir(shellHomeDir, { recursive: true });
|
|
26
|
+
await fs.mkdir(shellTmpDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
root,
|
|
30
|
+
projectRoot,
|
|
31
|
+
writablePath,
|
|
32
|
+
shellDir,
|
|
33
|
+
shellHomeDir,
|
|
34
|
+
shellTmpDir,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createParams(fixture, overrides = {}) {
|
|
39
|
+
return {
|
|
40
|
+
shellId: "sh_test",
|
|
41
|
+
shellDir: fixture.shellDir,
|
|
42
|
+
cmd: "printf hello",
|
|
43
|
+
cwd: fixture.projectRoot,
|
|
44
|
+
actualCwd: fixture.projectRoot,
|
|
45
|
+
shellPath: "/bin/sh",
|
|
46
|
+
login: true,
|
|
47
|
+
baseEnv: {
|
|
48
|
+
PATH: "/usr/bin:/bin",
|
|
49
|
+
LANG: "C.UTF-8",
|
|
50
|
+
DC_SESSION_ID: "session_test",
|
|
51
|
+
},
|
|
52
|
+
config: {
|
|
53
|
+
backend: "linux-bubblewrap",
|
|
54
|
+
rootPath: fixture.projectRoot,
|
|
55
|
+
envAllowlist: ["PATH", "LANG"],
|
|
56
|
+
writablePaths: [fixture.writablePath],
|
|
57
|
+
networkMode: "off",
|
|
58
|
+
},
|
|
59
|
+
shellHomeDir: fixture.shellHomeDir,
|
|
60
|
+
shellTmpDir: fixture.shellTmpDir,
|
|
61
|
+
...overrides,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function hasArg(args, value) {
|
|
66
|
+
return args.includes(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasOptionPair(args, option, sourcePath, targetPath = sourcePath) {
|
|
70
|
+
for (let index = 0; index < args.length - 2; index += 1) {
|
|
71
|
+
if (
|
|
72
|
+
args[index] === option &&
|
|
73
|
+
args[index + 1] === sourcePath &&
|
|
74
|
+
args[index + 2] === targetPath
|
|
75
|
+
) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasOptionValue(args, option, value) {
|
|
83
|
+
for (let index = 0; index < args.length - 1; index += 1) {
|
|
84
|
+
if (args[index] === option && args[index + 1] === value) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
test("Linux bubblewrap args isolate network and overlay writable project paths", async () => {
|
|
92
|
+
const fixture = await createSandboxFixture();
|
|
93
|
+
try {
|
|
94
|
+
const args = buildLinuxBubblewrapArgs(createParams(fixture));
|
|
95
|
+
|
|
96
|
+
assert.equal(hasArg(args, "--die-with-parent"), true);
|
|
97
|
+
assert.equal(hasArg(args, "--unshare-pid"), true);
|
|
98
|
+
assert.equal(hasArg(args, "--unshare-net"), true);
|
|
99
|
+
assert.equal(hasOptionPair(args, "--ro-bind", fixture.projectRoot), true);
|
|
100
|
+
assert.equal(hasOptionPair(args, "--bind", fixture.writablePath), true);
|
|
101
|
+
assert.equal(hasOptionPair(args, "--bind", fixture.projectRoot), false);
|
|
102
|
+
assert.equal(hasOptionValue(args, "--dir", fixture.writablePath), false);
|
|
103
|
+
assert.deepEqual(args.slice(-5), [
|
|
104
|
+
"--chdir",
|
|
105
|
+
fixture.projectRoot,
|
|
106
|
+
"/bin/sh",
|
|
107
|
+
"-lc",
|
|
108
|
+
"printf hello",
|
|
109
|
+
]);
|
|
110
|
+
} finally {
|
|
111
|
+
await fs.rm(fixture.root, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("Linux bubblewrap args keep root writable when sandbox writablePaths includes root", async () => {
|
|
116
|
+
const fixture = await createSandboxFixture();
|
|
117
|
+
try {
|
|
118
|
+
const args = buildLinuxBubblewrapArgs(createParams(fixture, {
|
|
119
|
+
config: {
|
|
120
|
+
backend: "linux-bubblewrap",
|
|
121
|
+
rootPath: fixture.projectRoot,
|
|
122
|
+
envAllowlist: ["PATH"],
|
|
123
|
+
writablePaths: [fixture.projectRoot],
|
|
124
|
+
networkMode: "full",
|
|
125
|
+
},
|
|
126
|
+
login: false,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
assert.equal(hasArg(args, "--unshare-net"), false);
|
|
130
|
+
assert.equal(hasOptionPair(args, "--bind", fixture.projectRoot), true);
|
|
131
|
+
assert.equal(hasOptionPair(args, "--ro-bind", fixture.projectRoot), false);
|
|
132
|
+
assert.deepEqual(args.slice(-5), [
|
|
133
|
+
"--chdir",
|
|
134
|
+
fixture.projectRoot,
|
|
135
|
+
"/bin/sh",
|
|
136
|
+
"-c",
|
|
137
|
+
"printf hello",
|
|
138
|
+
]);
|
|
139
|
+
} finally {
|
|
140
|
+
await fs.rm(fixture.root, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 shell sandbox 启动前依赖诊断。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 测试编译后的 bin 输出,避免测试文件进入 package 源码导出面。
|
|
6
|
+
* - 通过注入探针模拟 Linux 依赖状态,不要求当前测试机安装 bwrap。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
checkShellSandboxPreflightWithProbe,
|
|
14
|
+
} from "../bin/sandbox/SandboxPreflight.js";
|
|
15
|
+
|
|
16
|
+
async function withPlatform(platform, callback) {
|
|
17
|
+
const previous = Object.getOwnPropertyDescriptor(process, "platform");
|
|
18
|
+
Object.defineProperty(process, "platform", {
|
|
19
|
+
configurable: true,
|
|
20
|
+
value: platform,
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
return await callback();
|
|
24
|
+
} finally {
|
|
25
|
+
if (previous) {
|
|
26
|
+
Object.defineProperty(process, "platform", previous);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createProbe(params) {
|
|
32
|
+
return {
|
|
33
|
+
commandExists: async (command) => params.commands?.has(command) === true,
|
|
34
|
+
readProcInt: async (filePath) => params.proc?.get(filePath) ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test("Linux shell sandbox preflight reports missing bwrap and disabled userns", async () => {
|
|
39
|
+
await withPlatform("linux", async () => {
|
|
40
|
+
const result = await checkShellSandboxPreflightWithProbe(createProbe({
|
|
41
|
+
commands: new Set(),
|
|
42
|
+
proc: new Map([
|
|
43
|
+
["/proc/sys/kernel/unprivileged_userns_clone", 0],
|
|
44
|
+
["/proc/sys/user/max_user_namespaces", 0],
|
|
45
|
+
]),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
assert.equal(result.ok, false);
|
|
49
|
+
assert.equal(result.backend, "linux-bubblewrap");
|
|
50
|
+
assert.deepEqual(
|
|
51
|
+
result.issues.map((issue) => issue.code),
|
|
52
|
+
["missing-command", "userns-disabled"],
|
|
53
|
+
);
|
|
54
|
+
assert.match(result.issues[0].message, /bubblewrap/);
|
|
55
|
+
assert.match(result.issues[1].message, /user namespaces/);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("Linux shell sandbox preflight accepts bwrap with enabled userns", async () => {
|
|
60
|
+
await withPlatform("linux", async () => {
|
|
61
|
+
const result = await checkShellSandboxPreflightWithProbe(createProbe({
|
|
62
|
+
commands: new Set(["bwrap"]),
|
|
63
|
+
proc: new Map([
|
|
64
|
+
["/proc/sys/kernel/unprivileged_userns_clone", 1],
|
|
65
|
+
["/proc/sys/user/max_user_namespaces", 1024],
|
|
66
|
+
]),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
assert.equal(result.ok, true);
|
|
70
|
+
assert.equal(result.backend, "linux-bubblewrap");
|
|
71
|
+
assert.deepEqual(result.issues, []);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("Unsupported platforms fail shell sandbox preflight", async () => {
|
|
76
|
+
await withPlatform("win32", async () => {
|
|
77
|
+
const result = await checkShellSandboxPreflightWithProbe(createProbe({
|
|
78
|
+
commands: new Set(),
|
|
79
|
+
proc: new Map(),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
assert.equal(result.ok, false);
|
|
83
|
+
assert.deepEqual(
|
|
84
|
+
result.issues.map((issue) => issue.code),
|
|
85
|
+
["unsupported-platform"],
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
getDowncityProfileOtherPath,
|
|
28
28
|
getDowncityProfilePrimaryPath,
|
|
29
29
|
getDowncityPublicDirPath,
|
|
30
|
+
getDowncityResourcesDirPath,
|
|
30
31
|
getDowncitySchemaPath,
|
|
31
32
|
getDowncityTasksDirPath,
|
|
32
33
|
getSoulMdPath,
|
|
@@ -229,6 +230,7 @@ export async function initializeAgentProject(
|
|
|
229
230
|
getDowncityDataDirPath(projectRoot),
|
|
230
231
|
getDowncityAgentsRootDirPath(projectRoot),
|
|
231
232
|
getDowncityPublicDirPath(projectRoot),
|
|
233
|
+
getDowncityResourcesDirPath(projectRoot),
|
|
232
234
|
getDowncityConfigDirPath(projectRoot),
|
|
233
235
|
path.join(projectRoot, ".agents", "skills"),
|
|
234
236
|
path.join(getDowncityDirPath(projectRoot), "schema"),
|
package/src/config/Paths.ts
CHANGED
|
@@ -331,6 +331,17 @@ export function getDowncityPublicDirPath(cwd: string): string {
|
|
|
331
331
|
return path.join(getDowncityDirPath(cwd), "public");
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
/**
|
|
335
|
+
* 返回项目资源目录路径。
|
|
336
|
+
*
|
|
337
|
+
* 关键点(中文)
|
|
338
|
+
* - 该目录用于存放会话历史引用的二进制资源,例如图片生成结果。
|
|
339
|
+
* - `messages.jsonl` 只保存 `file://` 绝对 URL,避免把大段 base64 长期写入历史。
|
|
340
|
+
*/
|
|
341
|
+
export function getDowncityResourcesDirPath(cwd: string): string {
|
|
342
|
+
return path.join(getDowncityDirPath(cwd), "resources");
|
|
343
|
+
}
|
|
344
|
+
|
|
334
345
|
/**
|
|
335
346
|
* 返回项目任务目录路径。
|
|
336
347
|
*
|
package/src/executor/Executor.ts
CHANGED
|
@@ -375,6 +375,9 @@ export class Executor implements SessionExecutor {
|
|
|
375
375
|
): SessionRunContext {
|
|
376
376
|
return {
|
|
377
377
|
sessionId: String(input?.sessionId || this.sessionId).trim(),
|
|
378
|
+
...(typeof input?.projectRoot === "string" && input.projectRoot.trim()
|
|
379
|
+
? { projectRoot: input.projectRoot.trim() }
|
|
380
|
+
: {}),
|
|
378
381
|
...(typeof input?.onStepCallback === "function"
|
|
379
382
|
? { onStepCallback: input.onStepCallback }
|
|
380
383
|
: {}),
|