@downcity/agent 1.1.207 → 1.1.210
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/agent/local/Agent.d.ts +3 -3
- package/bin/agent/local/Agent.d.ts.map +1 -1
- package/bin/agent/local/Agent.js +0 -3
- package/bin/agent/local/Agent.js.map +1 -1
- package/bin/agent/local/AgentInstructions.d.ts +2 -1
- package/bin/agent/local/AgentInstructions.d.ts.map +1 -1
- package/bin/agent/local/AgentInstructions.js +10 -10
- package/bin/agent/local/AgentInstructions.js.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.d.ts +3 -8
- package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.js +0 -1
- package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
- package/bin/agent/local/services/AgentSessions.d.ts +5 -7
- package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
- package/bin/agent/local/services/AgentSessions.js +2 -3
- package/bin/agent/local/services/AgentSessions.js.map +1 -1
- package/bin/agent/remote/RemoteAgent.d.ts +3 -1
- package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
- package/bin/agent/remote/RemoteAgent.js.map +1 -1
- package/bin/agent/remote/RemoteSession.d.ts +10 -1
- package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
- package/bin/agent/remote/RemoteSession.js +18 -0
- package/bin/agent/remote/RemoteSession.js.map +1 -1
- package/bin/agent/remote/RemoteTransport.d.ts +5 -1
- package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +4 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +7 -0
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +4 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +1 -0
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
- package/bin/executor/Executor.d.ts +0 -16
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +0 -16
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +1 -3
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +1 -5
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
- package/bin/executor/composer/history/SessionHistoryWriter.d.ts +0 -10
- package/bin/executor/composer/history/SessionHistoryWriter.d.ts.map +1 -1
- package/bin/executor/composer/history/SessionHistoryWriter.js +31 -61
- package/bin/executor/composer/history/SessionHistoryWriter.js.map +1 -1
- package/bin/executor/composer/system/default/StaticPromptCatalog.d.ts +4 -0
- package/bin/executor/composer/system/default/StaticPromptCatalog.d.ts.map +1 -1
- package/bin/executor/composer/system/default/StaticPromptCatalog.js +4 -0
- package/bin/executor/composer/system/default/StaticPromptCatalog.js.map +1 -1
- package/bin/executor/composer/system/default/SystemDomain.d.ts +1 -1
- package/bin/executor/composer/system/default/SystemDomain.d.ts.map +1 -1
- package/bin/executor/composer/system/default/SystemDomain.js +2 -7
- package/bin/executor/composer/system/default/SystemDomain.js.map +1 -1
- package/bin/executor/composer/system/default/SystemPromptAssets.d.ts +0 -4
- package/bin/executor/composer/system/default/SystemPromptAssets.d.ts.map +1 -1
- package/bin/executor/composer/system/default/SystemPromptAssets.js +0 -5
- package/bin/executor/composer/system/default/SystemPromptAssets.js.map +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.d.ts +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.d.ts.map +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js +1 -1
- package/bin/executor/composer/system/default/assets/core.prompt.js.map +1 -1
- package/bin/executor/messages/AssistantFileResource.d.ts.map +1 -1
- package/bin/executor/messages/AssistantFileResource.js +58 -15
- package/bin/executor/messages/AssistantFileResource.js.map +1 -1
- package/bin/executor/services/ExecutorInflightService.d.ts +0 -5
- package/bin/executor/services/ExecutorInflightService.d.ts.map +1 -1
- package/bin/executor/services/ExecutorInflightService.js +0 -6
- package/bin/executor/services/ExecutorInflightService.js.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +4 -0
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +115 -3
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/types/SessionHistoryMeta.d.ts +6 -0
- package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
- package/bin/index.d.ts +38 -6
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +14 -1
- package/bin/index.js.map +1 -1
- package/bin/plugin/core/ActionScheduleExecutor.js +1 -1
- package/bin/plugin/core/ActionScheduleExecutor.js.map +1 -1
- package/bin/plugin/core/BasePlugin.d.ts +7 -9
- package/bin/plugin/core/BasePlugin.d.ts.map +1 -1
- package/bin/plugin/core/BasePlugin.js +1 -14
- package/bin/plugin/core/BasePlugin.js.map +1 -1
- package/bin/plugin/core/HookRegistry.d.ts +1 -1
- package/bin/plugin/core/HookRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionFactory.d.ts +1 -5
- package/bin/plugin/core/PluginActionFactory.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionFactory.js +0 -3
- package/bin/plugin/core/PluginActionFactory.js.map +1 -1
- package/bin/plugin/core/PluginActionRunner.d.ts +6 -4
- package/bin/plugin/core/PluginActionRunner.d.ts.map +1 -1
- package/bin/plugin/core/PluginActionRunner.js +2 -2
- package/bin/plugin/core/PluginActionRunner.js.map +1 -1
- package/bin/plugin/core/PluginCatalog.d.ts +2 -1
- package/bin/plugin/core/PluginCatalog.d.ts.map +1 -1
- package/bin/plugin/core/PluginCatalog.js.map +1 -1
- package/bin/plugin/core/PluginCommand.d.ts +1 -1
- package/bin/plugin/core/PluginCommand.d.ts.map +1 -1
- package/bin/plugin/core/PluginCommand.js.map +1 -1
- package/bin/plugin/core/PluginHttpRoutes.d.ts +1 -1
- package/bin/plugin/core/PluginHttpRoutes.d.ts.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.d.ts +4 -1
- package/bin/plugin/core/PluginLocalExecution.d.ts.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.js +0 -1
- package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
- package/bin/plugin/core/PluginRegistry.d.ts +4 -2
- package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginRegistry.js +7 -6
- package/bin/plugin/core/PluginRegistry.js.map +1 -1
- package/bin/plugin/core/PluginStateController.d.ts +7 -27
- package/bin/plugin/core/PluginStateController.d.ts.map +1 -1
- package/bin/plugin/core/PluginStateController.js +5 -39
- package/bin/plugin/core/PluginStateController.js.map +1 -1
- package/bin/plugin/types/PluginApi.d.ts +3 -79
- package/bin/plugin/types/PluginApi.d.ts.map +1 -1
- package/bin/rpc/Client.d.ts +11 -6
- package/bin/rpc/Client.d.ts.map +1 -1
- package/bin/rpc/Client.js +2 -0
- package/bin/rpc/Client.js.map +1 -1
- package/bin/session/Session.d.ts +2 -5
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +3 -16
- package/bin/session/Session.js.map +1 -1
- package/bin/session/SessionSystemBuilder.d.ts +1 -1
- package/bin/session/SessionSystemBuilder.d.ts.map +1 -1
- package/bin/session/browse/Browse.d.ts +4 -6
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +71 -71
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/preview/SessionMessagePreview.d.ts +13 -0
- package/bin/session/preview/SessionMessagePreview.d.ts.map +1 -0
- package/bin/session/preview/SessionMessagePreview.js +61 -0
- package/bin/session/preview/SessionMessagePreview.js.map +1 -0
- 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 +2 -2
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionViewService.d.ts +2 -1
- package/bin/session/services/SessionViewService.d.ts.map +1 -1
- package/bin/session/services/SessionViewService.js +4 -1
- package/bin/session/services/SessionViewService.js.map +1 -1
- package/bin/session/storage/Metadata.d.ts.map +1 -1
- package/bin/session/storage/Metadata.js +23 -0
- package/bin/session/storage/Metadata.js.map +1 -1
- package/bin/session/storage/Persistence.d.ts +1 -1
- package/bin/session/storage/Persistence.d.ts.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.d.ts +0 -12
- package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.js +0 -8
- package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
- package/bin/types/plugin/PluginAction.d.ts +0 -10
- package/bin/types/plugin/PluginAction.d.ts.map +1 -1
- package/bin/types/plugin/PluginCommand.d.ts +2 -3
- package/bin/types/plugin/PluginCommand.d.ts.map +1 -1
- package/bin/types/plugin/PluginControl.d.ts +74 -0
- package/bin/types/plugin/PluginControl.d.ts.map +1 -0
- package/bin/types/plugin/PluginControl.js +9 -0
- package/bin/types/plugin/PluginControl.js.map +1 -0
- package/bin/types/plugin/PluginDefinition.d.ts +1 -1
- package/bin/types/plugin/PluginDefinition.d.ts.map +1 -1
- package/bin/types/plugin/PluginRuntime.d.ts +1 -3
- package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
- package/bin/types/plugin/PluginState.d.ts +3 -16
- package/bin/types/plugin/PluginState.d.ts.map +1 -1
- package/bin/types/rpc/RpcProtocol.d.ts +3 -3
- package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.d.ts +1 -9
- package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.js.map +1 -1
- package/bin/types/session/SessionLocalState.d.ts +1 -1
- package/bin/types/session/SessionLocalState.d.ts.map +1 -1
- package/package.json +4 -7
- package/scripts/agent-ready.test.mjs +38 -0
- package/scripts/assistant-file-resource.test.mjs +39 -0
- package/scripts/city-model-tool-loop.test.mjs +18 -30
- package/scripts/image-plugin-job.test.mjs +13 -13
- package/scripts/plugin-tool-bridge.test.mjs +2 -3
- package/scripts/remote-session-reconnect.test.mjs +67 -0
- package/scripts/session-history-writer-error.test.mjs +57 -0
- package/scripts/session-list-title.test.mjs +114 -23
- package/scripts/session-system-blocks.test.mjs +73 -0
- package/src/agent/local/Agent.ts +4 -10
- package/src/agent/local/AgentInstructions.ts +11 -11
- package/src/agent/local/services/AgentAssemblyService.ts +3 -9
- package/src/agent/local/services/AgentSessions.ts +8 -15
- package/src/agent/remote/RemoteAgent.ts +5 -4
- package/src/agent/remote/RemoteSession.ts +21 -2
- package/src/agent/remote/RemoteTransport.ts +6 -2
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +13 -3
- package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +6 -2
- package/src/executor/Executor.ts +0 -23
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +2 -8
- package/src/executor/composer/history/SessionHistoryWriter.ts +33 -64
- package/src/executor/composer/system/default/StaticPromptCatalog.ts +4 -0
- package/src/executor/composer/system/default/SystemDomain.ts +1 -9
- package/src/executor/composer/system/default/SystemPromptAssets.ts +0 -6
- package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
- package/src/executor/composer/system/default/assets/core.prompt.ts.txt +37 -15
- package/src/executor/messages/AssistantFileResource.ts +61 -17
- package/src/executor/services/ExecutorInflightService.ts +0 -10
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +130 -4
- package/src/executor/types/SessionHistoryMeta.ts +6 -0
- package/src/index.ts +100 -29
- package/src/plugin/core/ActionScheduleExecutor.ts +1 -1
- package/src/plugin/core/BasePlugin.ts +8 -22
- package/src/plugin/core/HookRegistry.ts +1 -1
- package/src/plugin/core/PluginActionFactory.ts +1 -8
- package/src/plugin/core/PluginActionRunner.ts +8 -11
- package/src/plugin/core/PluginCatalog.ts +2 -5
- package/src/plugin/core/PluginCommand.ts +2 -1
- package/src/plugin/core/PluginHttpRoutes.ts +1 -1
- package/src/plugin/core/PluginLocalExecution.ts +4 -7
- package/src/plugin/core/PluginRegistry.ts +10 -10
- package/src/plugin/core/PluginStateController.ts +12 -60
- package/src/plugin/types/PluginApi.ts +6 -81
- package/src/rpc/Client.ts +22 -16
- package/src/session/Session.ts +5 -21
- package/src/session/SessionSystemBuilder.ts +1 -1
- package/src/session/browse/Browse.ts +103 -84
- package/src/session/preview/SessionMessagePreview.ts +75 -0
- package/src/session/services/SessionStateService.ts +3 -4
- package/src/session/services/SessionViewService.ts +6 -6
- package/src/session/storage/Metadata.ts +26 -0
- package/src/session/storage/Persistence.ts +1 -1
- package/src/session/storage/RuntimeSessionPort.ts +0 -20
- package/src/types/plugin/PluginAction.ts +0 -10
- package/src/types/plugin/PluginCommand.ts +2 -3
- package/src/types/plugin/PluginControl.ts +80 -0
- package/src/types/plugin/PluginDefinition.ts +1 -1
- package/src/types/plugin/PluginRuntime.ts +1 -3
- package/src/types/plugin/PluginState.ts +3 -20
- package/src/types/rpc/RpcProtocol.ts +4 -4
- package/src/types/runtime/agent/AgentContext.ts +1 -9
- package/src/types/session/SessionLocalState.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/config/DowncitySchema.d.ts +0 -20
- package/bin/config/DowncitySchema.d.ts.map +0 -1
- package/bin/config/DowncitySchema.js +0 -160
- package/bin/config/DowncitySchema.js.map +0 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts +0 -7
- package/bin/executor/composer/system/default/assets/plugin.prompt.d.ts.map +0 -1
- package/bin/executor/composer/system/default/assets/plugin.prompt.js +0 -8
- package/bin/executor/composer/system/default/assets/plugin.prompt.js.map +0 -1
- package/bin/plugin/core/Activation.d.ts +0 -18
- package/bin/plugin/core/Activation.d.ts.map +0 -1
- package/bin/plugin/core/Activation.js +0 -21
- package/bin/plugin/core/Activation.js.map +0 -1
- package/bin/plugin/core/Manager.d.ts +0 -7
- package/bin/plugin/core/Manager.d.ts.map +0 -1
- package/bin/plugin/core/Manager.js +0 -6
- package/bin/plugin/core/Manager.js.map +0 -1
- package/bin/plugin/types/Plugin.d.ts +0 -15
- package/bin/plugin/types/Plugin.d.ts.map +0 -1
- package/bin/plugin/types/Plugin.js +0 -9
- package/bin/plugin/types/Plugin.js.map +0 -1
- package/bin/plugin/types/PluginLifecycle.d.ts +0 -34
- package/bin/plugin/types/PluginLifecycle.d.ts.map +0 -1
- package/bin/plugin/types/PluginLifecycle.js +0 -9
- package/bin/plugin/types/PluginLifecycle.js.map +0 -1
- package/bin/plugin/types/PluginState.d.ts +0 -5
- package/bin/plugin/types/PluginState.d.ts.map +0 -1
- package/bin/plugin/types/PluginState.js +0 -5
- package/bin/plugin/types/PluginState.js.map +0 -1
- package/bin/plugin/types/Plugins.d.ts +0 -61
- package/bin/plugin/types/Plugins.d.ts.map +0 -1
- package/bin/plugin/types/Plugins.js +0 -5
- package/bin/plugin/types/Plugins.js.map +0 -1
- package/bin/session/index.d.ts +0 -15
- package/bin/session/index.d.ts.map +0 -1
- package/bin/session/index.js +0 -15
- package/bin/session/index.js.map +0 -1
- package/bin/types/agent/AgentTypes.d.ts +0 -18
- package/bin/types/agent/AgentTypes.d.ts.map +0 -1
- package/bin/types/agent/AgentTypes.js +0 -10
- package/bin/types/agent/AgentTypes.js.map +0 -1
- package/bin/utils/logger/Format.d.ts +0 -11
- package/bin/utils/logger/Format.d.ts.map +0 -1
- package/bin/utils/logger/Format.js +0 -10
- package/bin/utils/logger/Format.js.map +0 -1
- package/src/config/DowncitySchema.ts +0 -175
- package/src/executor/composer/system/default/assets/plugin.prompt.ts +0 -9
- package/src/executor/composer/system/default/assets/plugin.prompt.ts.txt +0 -20
- package/src/plugin/core/Activation.ts +0 -26
- package/src/plugin/core/Manager.ts +0 -17
- package/src/plugin/types/Plugin.ts +0 -63
- package/src/plugin/types/PluginLifecycle.ts +0 -35
- package/src/plugin/types/PluginState.ts +0 -12
- package/src/plugin/types/Plugins.ts +0 -67
- package/src/session/index.ts +0 -57
- package/src/types/agent/AgentTypes.ts +0 -58
- package/src/utils/logger/Format.ts +0 -15
|
@@ -101,6 +101,45 @@ test("materializeAssistantFileParts downloads remote file URLs into resources",
|
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test("materializeAssistantFileParts rejects oversized remote resources before download", async () => {
|
|
105
|
+
const project_root = await fs.mkdtemp(
|
|
106
|
+
path.join(os.tmpdir(), "downcity-agent-assistant-large-resource-"),
|
|
107
|
+
);
|
|
108
|
+
const server = http.createServer((_, response) => {
|
|
109
|
+
response.writeHead(200, {
|
|
110
|
+
"content-type": "application/octet-stream",
|
|
111
|
+
"content-length": String(26 * 1024 * 1024),
|
|
112
|
+
});
|
|
113
|
+
response.end("oversized");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
117
|
+
try {
|
|
118
|
+
const address = server.address();
|
|
119
|
+
assert.equal(typeof address, "object");
|
|
120
|
+
assert.ok(address);
|
|
121
|
+
const remote_url = `http://127.0.0.1:${address.port}/large.bin`;
|
|
122
|
+
const parts = await materializeAssistantFileParts({
|
|
123
|
+
projectRoot: project_root,
|
|
124
|
+
parts: [
|
|
125
|
+
{
|
|
126
|
+
type: "file",
|
|
127
|
+
mediaType: "application/octet-stream",
|
|
128
|
+
url: remote_url,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
assert.equal(parts[0].url, remote_url);
|
|
134
|
+
const resources_path = path.join(project_root, ".downcity", "resources");
|
|
135
|
+
await assert.rejects(fs.access(resources_path));
|
|
136
|
+
} finally {
|
|
137
|
+
await new Promise((resolve, reject) =>
|
|
138
|
+
server.close((error) => (error ? reject(error) : resolve())),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
104
143
|
test("materializeAssistantFileParts resolves relative local file URLs from agent project root", async () => {
|
|
105
144
|
const project_root = await fs.mkdtemp(
|
|
106
145
|
path.join(os.tmpdir(), "downcity-agent-assistant-relative-resource-"),
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @file 验证 CityModel 会优先走 OpenAI-compatible LanguageModel 并完成 tool loop。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* - 这里直接走编译后的 Agent
|
|
5
|
+
* - 这里直接走编译后的 Agent 产物,避免测试只覆盖源码级辅助函数。
|
|
6
|
+
* - CityModel 使用 @downcity/type 的共享协议构造,避免反向依赖 City SDK 实现。
|
|
6
7
|
* - 重点锁住 CityModel -> LanguageModel -> tool-call -> 本地执行 -> tool-result 回传链路。
|
|
7
8
|
* - 新路径不应再调用 `/v1/ai/stream`,避免 UIMessage stream 反向适配丢失 finish 语义。
|
|
8
9
|
*/
|
|
@@ -15,7 +16,7 @@ import path from "node:path";
|
|
|
15
16
|
import fs from "node:fs/promises";
|
|
16
17
|
import { Agent } from "../bin/index.js";
|
|
17
18
|
import { createAction, createPlugin } from "../bin/plugin/core/PluginActionFactory.js";
|
|
18
|
-
import {
|
|
19
|
+
import { CITY_MODEL_INVOKER, CITY_MODEL_KIND } from "@downcity/type";
|
|
19
20
|
import { tool } from "ai";
|
|
20
21
|
import { z } from "zod";
|
|
21
22
|
|
|
@@ -45,7 +46,6 @@ async function read_json_body(req) {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
test("CityModel uses direct LanguageModel path and sends tool result back", async () => {
|
|
48
|
-
const requests = [];
|
|
49
49
|
const agent_requests = [];
|
|
50
50
|
let stream_requests = 0;
|
|
51
51
|
let tool_executed = false;
|
|
@@ -53,24 +53,6 @@ test("CityModel uses direct LanguageModel path and sends tool result back", asyn
|
|
|
53
53
|
const server = http.createServer(async (req, res) => {
|
|
54
54
|
const url = new URL(String(req.url || "/"), "http://127.0.0.1");
|
|
55
55
|
|
|
56
|
-
if (req.method === "GET" && url.pathname === "/v1/ai/models") {
|
|
57
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
58
|
-
res.end(JSON.stringify({
|
|
59
|
-
items: [
|
|
60
|
-
{
|
|
61
|
-
id: "mock-model",
|
|
62
|
-
name: "Mock Model",
|
|
63
|
-
description: "mock",
|
|
64
|
-
modalities: ["text", "stream"],
|
|
65
|
-
tags: [],
|
|
66
|
-
meta: {},
|
|
67
|
-
env: {},
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
}));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
56
|
if (req.method === "POST" && url.pathname === "/v1/ai/stream") {
|
|
75
57
|
stream_requests += 1;
|
|
76
58
|
res.writeHead(500, { "content-type": "application/json" });
|
|
@@ -80,7 +62,6 @@ test("CityModel uses direct LanguageModel path and sends tool result back", asyn
|
|
|
80
62
|
|
|
81
63
|
if (req.method === "POST" && url.pathname === "/v1/ai/chat/completions") {
|
|
82
64
|
const body = await read_json_body(req);
|
|
83
|
-
requests.push(body);
|
|
84
65
|
|
|
85
66
|
if (!Array.isArray(body.tools)) {
|
|
86
67
|
write_openai_sse(res, [
|
|
@@ -229,15 +210,22 @@ test("CityModel uses direct LanguageModel path and sends tool result back", asyn
|
|
|
229
210
|
try {
|
|
230
211
|
const address = server.address();
|
|
231
212
|
assert.ok(address && typeof address === "object");
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
213
|
+
const model = Object.freeze({
|
|
214
|
+
id: "mock-model",
|
|
215
|
+
name: "Mock Model",
|
|
216
|
+
description: "mock",
|
|
217
|
+
modalities: ["text", "stream"],
|
|
218
|
+
tags: [],
|
|
219
|
+
meta: {},
|
|
220
|
+
kind: CITY_MODEL_KIND,
|
|
221
|
+
[CITY_MODEL_INVOKER]: {
|
|
222
|
+
connection: () => ({
|
|
223
|
+
base_url: `http://127.0.0.1:${String(address.port)}/v1/ai`,
|
|
224
|
+
api_key: "ub_test",
|
|
225
|
+
model_id: "mock-model",
|
|
226
|
+
}),
|
|
227
|
+
},
|
|
237
228
|
});
|
|
238
|
-
const catalog = await city.ai.listModels();
|
|
239
|
-
const model = catalog.get("mock-model");
|
|
240
|
-
assert.ok(model);
|
|
241
229
|
|
|
242
230
|
const agent_path = await fs.mkdtemp(
|
|
243
231
|
path.join(os.tmpdir(), "downcity-agent-city-model-tool-loop-"),
|
|
@@ -38,6 +38,7 @@ function create_context(rootPath = process.cwd()) {
|
|
|
38
38
|
function create_registry(plugin, rootPath = process.cwd()) {
|
|
39
39
|
return createAgentPluginRegistry({
|
|
40
40
|
plugins: [plugin],
|
|
41
|
+
plugin_instances: new Map(),
|
|
41
42
|
get_context: () => create_context(rootPath),
|
|
42
43
|
});
|
|
43
44
|
}
|
|
@@ -67,7 +68,7 @@ test("ImagePlugin image_create returns image job", async () => {
|
|
|
67
68
|
|
|
68
69
|
const result = await plugin.actions.image_create.execute({
|
|
69
70
|
context: create_context(),
|
|
70
|
-
|
|
71
|
+
input: { prompt: "draw" },
|
|
71
72
|
pluginName: "image",
|
|
72
73
|
actionName: "image_create",
|
|
73
74
|
});
|
|
@@ -104,13 +105,12 @@ test("ImagePlugin models lists image-capable models", async () => {
|
|
|
104
105
|
|
|
105
106
|
const result = await plugin.actions.models.execute({
|
|
106
107
|
context: create_context(),
|
|
107
|
-
|
|
108
|
+
input: {},
|
|
108
109
|
pluginName: "image",
|
|
109
110
|
actionName: "models",
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
assert.equal(result.success, true);
|
|
113
|
-
assert.equal(result.data.default_model_id, "image_1");
|
|
114
114
|
assert.deepEqual(result.data.items.map((item) => item.id), ["image_1"]);
|
|
115
115
|
assert.equal(result.data.items[0].meta.provider, "test");
|
|
116
116
|
});
|
|
@@ -153,7 +153,7 @@ test("ImagePlugin image_result reads pending state once", async () => {
|
|
|
153
153
|
|
|
154
154
|
const result = await plugin.actions.image_result.execute({
|
|
155
155
|
context: create_context(),
|
|
156
|
-
|
|
156
|
+
input: { job_id: "img_1" },
|
|
157
157
|
pluginName: "image",
|
|
158
158
|
actionName: "image_result",
|
|
159
159
|
});
|
|
@@ -179,7 +179,7 @@ test("ImagePlugin image_result returns final message when succeeded", async () =
|
|
|
179
179
|
|
|
180
180
|
const result = await plugin.actions.image_result.execute({
|
|
181
181
|
context: create_context(),
|
|
182
|
-
|
|
182
|
+
input: { job_id: "img_1" },
|
|
183
183
|
pluginName: "image",
|
|
184
184
|
actionName: "image_result",
|
|
185
185
|
});
|
|
@@ -202,7 +202,7 @@ test("ImagePlugin image_result reports failed terminal job", async () => {
|
|
|
202
202
|
|
|
203
203
|
const result = await plugin.actions.image_result.execute({
|
|
204
204
|
context: create_context(),
|
|
205
|
-
|
|
205
|
+
input: { job_id: "img_1" },
|
|
206
206
|
pluginName: "image",
|
|
207
207
|
actionName: "image_result",
|
|
208
208
|
});
|
|
@@ -244,7 +244,7 @@ test("ImagePlugin image_create converts local content image paths", async () =>
|
|
|
244
244
|
|
|
245
245
|
const result = await plugin.actions.image_create.execute({
|
|
246
246
|
context: create_context(tempDir),
|
|
247
|
-
|
|
247
|
+
input: {
|
|
248
248
|
content: [
|
|
249
249
|
{ type: "text", text: "change background" },
|
|
250
250
|
{ type: "image", url: "./input.png" },
|
|
@@ -277,7 +277,7 @@ test("ImagePlugin image_create uses content instead of prompt when both exist",
|
|
|
277
277
|
|
|
278
278
|
const result = await plugin.actions.image_create.execute({
|
|
279
279
|
context: create_context(),
|
|
280
|
-
|
|
280
|
+
input: {
|
|
281
281
|
prompt: "ignore this prompt",
|
|
282
282
|
content: [{ type: "text", text: "use this content" }],
|
|
283
283
|
},
|
|
@@ -302,7 +302,7 @@ test("ImagePlugin image_create keeps remote content image URLs", async () => {
|
|
|
302
302
|
|
|
303
303
|
const result = await plugin.actions.image_create.execute({
|
|
304
304
|
context: create_context(),
|
|
305
|
-
|
|
305
|
+
input: {
|
|
306
306
|
content: [
|
|
307
307
|
{ type: "text", text: "use this style" },
|
|
308
308
|
{ type: "image", url: "https://example.com/input.webp", media_type: "image/webp" },
|
|
@@ -325,7 +325,7 @@ test("ImagePlugin image_create rejects legacy messages and data URLs", async ()
|
|
|
325
325
|
|
|
326
326
|
const messages_result = await plugin.actions.image_create.execute({
|
|
327
327
|
context: create_context(),
|
|
328
|
-
|
|
328
|
+
input: {
|
|
329
329
|
messages: [
|
|
330
330
|
{
|
|
331
331
|
role: "user",
|
|
@@ -342,7 +342,7 @@ test("ImagePlugin image_create rejects legacy messages and data URLs", async ()
|
|
|
342
342
|
|
|
343
343
|
const data_url_result = await plugin.actions.image_create.execute({
|
|
344
344
|
context: create_context(),
|
|
345
|
-
|
|
345
|
+
input: {
|
|
346
346
|
content: [
|
|
347
347
|
{ type: "text", text: "edit this" },
|
|
348
348
|
{ type: "image", url: "data:image/png;base64,cG5n" },
|
|
@@ -386,7 +386,7 @@ test("ImagePlugin image_result polls until terminal when until_done=true", async
|
|
|
386
386
|
|
|
387
387
|
const result = await plugin.actions.image_result.execute({
|
|
388
388
|
context: create_context(),
|
|
389
|
-
|
|
389
|
+
input: {
|
|
390
390
|
job_id: "img_wait",
|
|
391
391
|
until_done: true,
|
|
392
392
|
max_wait_ms: 500,
|
|
@@ -414,7 +414,7 @@ test("ImagePlugin image_result returns last status when max_wait_ms elapses", as
|
|
|
414
414
|
|
|
415
415
|
const result = await plugin.actions.image_result.execute({
|
|
416
416
|
context: create_context(),
|
|
417
|
-
|
|
417
|
+
input: {
|
|
418
418
|
job_id: "img_timeout",
|
|
419
419
|
until_done: true,
|
|
420
420
|
max_wait_ms: 30,
|
|
@@ -132,7 +132,6 @@ test("invokePluginReadTool returns plugin action metadata", async () => {
|
|
|
132
132
|
payload: { prompt: "draw" },
|
|
133
133
|
},
|
|
134
134
|
],
|
|
135
|
-
allow_when_disabled: false,
|
|
136
135
|
has_command: false,
|
|
137
136
|
has_api: false,
|
|
138
137
|
},
|
|
@@ -303,7 +302,7 @@ test("PluginRegistry keeps plugin ready after action business failure", async ()
|
|
|
303
302
|
payload: { name: "missing" },
|
|
304
303
|
});
|
|
305
304
|
assert.equal(failed.success, false);
|
|
306
|
-
assert.equal(registry.status("skill").
|
|
305
|
+
assert.equal(registry.status("skill").status, "ready");
|
|
307
306
|
|
|
308
307
|
const retry = await registry.runAction({
|
|
309
308
|
plugin: "skill",
|
|
@@ -312,5 +311,5 @@ test("PluginRegistry keeps plugin ready after action business failure", async ()
|
|
|
312
311
|
});
|
|
313
312
|
assert.equal(retry.success, true);
|
|
314
313
|
assert.equal(retry.data.loaded, true);
|
|
315
|
-
assert.equal(registry.status("skill").
|
|
314
|
+
assert.equal(registry.status("skill").status, "ready");
|
|
316
315
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 RemoteSession 在事件连接断开后能够重新建立订阅。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 断流必须结束当前 pending turn,避免 finished 永久等待。
|
|
6
|
+
* - 下一次 prompt 必须重新调用 transport.subscribe。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import { RemoteSession } from "../bin/agent/remote/RemoteSession.js";
|
|
13
|
+
|
|
14
|
+
test("RemoteSession reconnects the event pump after transport close", async () => {
|
|
15
|
+
const subscriptions = [];
|
|
16
|
+
let prompt_count = 0;
|
|
17
|
+
const transport = {
|
|
18
|
+
async subscribe(input) {
|
|
19
|
+
subscriptions.push(input);
|
|
20
|
+
input.on_ready();
|
|
21
|
+
return { close: async () => {} };
|
|
22
|
+
},
|
|
23
|
+
async prompt() {
|
|
24
|
+
prompt_count += 1;
|
|
25
|
+
return { id: `turn_${prompt_count}` };
|
|
26
|
+
},
|
|
27
|
+
async get_info() {
|
|
28
|
+
throw new Error("unused");
|
|
29
|
+
},
|
|
30
|
+
async stop() {
|
|
31
|
+
return { stopped: false, cancelledQueuedPrompts: 0, reason: "idle" };
|
|
32
|
+
},
|
|
33
|
+
async records() {
|
|
34
|
+
throw new Error("unused");
|
|
35
|
+
},
|
|
36
|
+
async system() {
|
|
37
|
+
throw new Error("unused");
|
|
38
|
+
},
|
|
39
|
+
async fork() {
|
|
40
|
+
throw new Error("unused");
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const session = new RemoteSession(transport, {
|
|
44
|
+
agentId: "agent_test",
|
|
45
|
+
sessionId: "session_test",
|
|
46
|
+
messageCount: 0,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const first_turn = await session.prompt({ query: "first" });
|
|
50
|
+
subscriptions[0].on_close(new Error("stream dropped"));
|
|
51
|
+
assert.deepEqual(await first_turn.finished, {
|
|
52
|
+
turnId: "turn_1",
|
|
53
|
+
text: "",
|
|
54
|
+
success: false,
|
|
55
|
+
error: "stream dropped",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const second_turn = await session.prompt({ query: "second" });
|
|
59
|
+
assert.equal(subscriptions.length, 2);
|
|
60
|
+
subscriptions[1].on_event({
|
|
61
|
+
type: "turn-finish",
|
|
62
|
+
turnId: second_turn.id,
|
|
63
|
+
text: "done",
|
|
64
|
+
success: true,
|
|
65
|
+
});
|
|
66
|
+
assert.equal((await second_turn.finished).text, "done");
|
|
67
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 SessionHistoryWriter 不会掩盖事实源写入失败。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - user 消息写入失败必须向调用方抛出。
|
|
6
|
+
* - assistant inflight 落盘失败也必须向调用方抛出。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import { SessionHistoryWriter } from "../bin/executor/composer/history/SessionHistoryWriter.js";
|
|
13
|
+
|
|
14
|
+
function create_failing_history_store() {
|
|
15
|
+
return {
|
|
16
|
+
userText() {
|
|
17
|
+
throw new Error("unused");
|
|
18
|
+
},
|
|
19
|
+
assistantText() {
|
|
20
|
+
throw new Error("unused");
|
|
21
|
+
},
|
|
22
|
+
async write_record() {
|
|
23
|
+
throw new Error("disk full");
|
|
24
|
+
},
|
|
25
|
+
async finalize_inflight() {
|
|
26
|
+
throw new Error("disk full");
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("append_user_message propagates history write failures", async () => {
|
|
32
|
+
const writer = new SessionHistoryWriter({
|
|
33
|
+
sessionId: "writer_error_session",
|
|
34
|
+
getHistoryStore: create_failing_history_store,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await assert.rejects(
|
|
38
|
+
writer.append_user_message({
|
|
39
|
+
message: { v: 1, id: "user_1", role: "user", parts: [] },
|
|
40
|
+
}),
|
|
41
|
+
/disk full/,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("append_assistant_message propagates history write failures", async () => {
|
|
46
|
+
const writer = new SessionHistoryWriter({
|
|
47
|
+
sessionId: "writer_error_session",
|
|
48
|
+
getHistoryStore: create_failing_history_store,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await assert.rejects(
|
|
52
|
+
writer.append_assistant_message({
|
|
53
|
+
message: { v: 1, id: "assistant_1", role: "assistant", parts: [] },
|
|
54
|
+
}),
|
|
55
|
+
/disk full/,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
@@ -19,28 +19,37 @@ import { Agent } from "../bin/index.js";
|
|
|
19
19
|
function create_mock_title_model(title_text) {
|
|
20
20
|
return new MockLanguageModelV3({
|
|
21
21
|
modelId: "mock-session-list-title-model",
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
type: "
|
|
26
|
-
text:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
doStream: async () => ({
|
|
23
|
+
stream: new ReadableStream({
|
|
24
|
+
start(controller) {
|
|
25
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
26
|
+
controller.enqueue({ type: "text-start", id: "title" });
|
|
27
|
+
controller.enqueue({
|
|
28
|
+
type: "text-delta",
|
|
29
|
+
id: "title",
|
|
30
|
+
delta: title_text,
|
|
31
|
+
});
|
|
32
|
+
controller.enqueue({ type: "text-end", id: "title" });
|
|
33
|
+
controller.enqueue({
|
|
34
|
+
type: "finish",
|
|
35
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
36
|
+
usage: {
|
|
37
|
+
inputTokens: {
|
|
38
|
+
total: 0,
|
|
39
|
+
noCache: 0,
|
|
40
|
+
cacheRead: 0,
|
|
41
|
+
cacheWrite: 0,
|
|
42
|
+
},
|
|
43
|
+
outputTokens: {
|
|
44
|
+
total: 0,
|
|
45
|
+
text: 0,
|
|
46
|
+
reasoning: 0,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
controller.close();
|
|
36
51
|
},
|
|
37
|
-
|
|
38
|
-
total: 0,
|
|
39
|
-
text: 0,
|
|
40
|
-
reasoning: 0,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
warnings: [],
|
|
52
|
+
}),
|
|
44
53
|
}),
|
|
45
54
|
});
|
|
46
55
|
}
|
|
@@ -81,8 +90,38 @@ test("list_sessions returns persisted title from active session metadata", async
|
|
|
81
90
|
|
|
82
91
|
try {
|
|
83
92
|
const page = await collection.list();
|
|
93
|
+
const meta = JSON.parse(
|
|
94
|
+
await fs.readFile(
|
|
95
|
+
path.join(
|
|
96
|
+
agent.path,
|
|
97
|
+
".downcity",
|
|
98
|
+
"agents",
|
|
99
|
+
encodeURIComponent(agent.id),
|
|
100
|
+
"sessions",
|
|
101
|
+
encodeURIComponent(session.id),
|
|
102
|
+
"messages",
|
|
103
|
+
"meta.json",
|
|
104
|
+
),
|
|
105
|
+
"utf8",
|
|
106
|
+
),
|
|
107
|
+
);
|
|
84
108
|
|
|
85
109
|
assert.equal(page.total, 1);
|
|
110
|
+
assert.equal(meta.messageCount, 1);
|
|
111
|
+
assert.equal(meta.previewText, "Need the session list to show the generated title");
|
|
112
|
+
const history_stat = await fs.stat(
|
|
113
|
+
path.join(
|
|
114
|
+
agent.path,
|
|
115
|
+
".downcity",
|
|
116
|
+
"agents",
|
|
117
|
+
encodeURIComponent(agent.id),
|
|
118
|
+
"sessions",
|
|
119
|
+
encodeURIComponent(session.id),
|
|
120
|
+
"messages",
|
|
121
|
+
"messages.jsonl",
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
assert.equal(meta.historyBytes, history_stat.size);
|
|
86
125
|
assert.deepEqual(
|
|
87
126
|
page.items.map((item) => ({
|
|
88
127
|
sessionId: item.sessionId,
|
|
@@ -93,7 +132,7 @@ test("list_sessions returns persisted title from active session metadata", async
|
|
|
93
132
|
{
|
|
94
133
|
sessionId: session.id,
|
|
95
134
|
title: "列表标题",
|
|
96
|
-
messageCount:
|
|
135
|
+
messageCount: 1,
|
|
97
136
|
},
|
|
98
137
|
],
|
|
99
138
|
);
|
|
@@ -102,6 +141,58 @@ test("list_sessions returns persisted title from active session metadata", async
|
|
|
102
141
|
}
|
|
103
142
|
});
|
|
104
143
|
|
|
144
|
+
test("list_sessions rebuilds stale metadata after external history changes", async () => {
|
|
145
|
+
const { agent, collection, session } = await create_agent_with_titled_session({
|
|
146
|
+
tmp_prefix: "downcity-agent-session-summary-repair-",
|
|
147
|
+
agent_id: "summary_repair_agent",
|
|
148
|
+
session_id: "summary_repair_session",
|
|
149
|
+
title: "摘要修复",
|
|
150
|
+
first_user_text: "Initial history",
|
|
151
|
+
});
|
|
152
|
+
const messages_dir = path.join(
|
|
153
|
+
agent.path,
|
|
154
|
+
".downcity",
|
|
155
|
+
"agents",
|
|
156
|
+
encodeURIComponent(agent.id),
|
|
157
|
+
"sessions",
|
|
158
|
+
encodeURIComponent(session.id),
|
|
159
|
+
"messages",
|
|
160
|
+
);
|
|
161
|
+
const messages_path = path.join(messages_dir, "messages.jsonl");
|
|
162
|
+
const meta_path = path.join(messages_dir, "meta.json");
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await fs.appendFile(
|
|
166
|
+
messages_path,
|
|
167
|
+
`${JSON.stringify({
|
|
168
|
+
id: "external_assistant",
|
|
169
|
+
role: "assistant",
|
|
170
|
+
metadata: {
|
|
171
|
+
v: 1,
|
|
172
|
+
ts: Date.now(),
|
|
173
|
+
sessionId: session.id,
|
|
174
|
+
source: "egress",
|
|
175
|
+
kind: "normal",
|
|
176
|
+
},
|
|
177
|
+
parts: [{ type: "text", text: "Externally appended history" }],
|
|
178
|
+
})}\n`,
|
|
179
|
+
"utf8",
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const page = await collection.list();
|
|
183
|
+
const repaired_meta = JSON.parse(await fs.readFile(meta_path, "utf8"));
|
|
184
|
+
const history_stat = await fs.stat(messages_path);
|
|
185
|
+
|
|
186
|
+
assert.equal(page.items[0].messageCount, 2);
|
|
187
|
+
assert.equal(page.items[0].previewText, "Externally appended history");
|
|
188
|
+
assert.equal(repaired_meta.messageCount, 2);
|
|
189
|
+
assert.equal(repaired_meta.previewText, "Externally appended history");
|
|
190
|
+
assert.equal(repaired_meta.historyBytes, history_stat.size);
|
|
191
|
+
} finally {
|
|
192
|
+
await agent.dispose();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
105
196
|
test("archive_sessions returns title from archived session metadata", async () => {
|
|
106
197
|
const { agent, collection, session } = await create_agent_with_titled_session({
|
|
107
198
|
tmp_prefix: "downcity-agent-archive-list-title-",
|
|
@@ -131,7 +222,7 @@ test("archive_sessions returns title from archived session metadata", async () =
|
|
|
131
222
|
{
|
|
132
223
|
sessionId: session.id,
|
|
133
224
|
title: "归档标题",
|
|
134
|
-
messageCount:
|
|
225
|
+
messageCount: 1,
|
|
135
226
|
},
|
|
136
227
|
],
|
|
137
228
|
);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 SDK session system blocks 的稳定分层顺序。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 测试编译后的 bin 输出,避免测试文件依赖 TS 源码加载器。
|
|
6
|
+
* - 自定义 instruction 不能替代 Downcity core;core 必须包含 Shell 与 plugin 总规则。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import { createInstructionSystemBlocks } from "../bin/agent/local/AgentInstructions.js";
|
|
13
|
+
import { buildSessionSystemBlocks } from "../bin/session/SessionSystemBuilder.js";
|
|
14
|
+
|
|
15
|
+
test("instruction blocks keep Downcity core after custom instruction", () => {
|
|
16
|
+
const blocks = createInstructionSystemBlocks(
|
|
17
|
+
["你是这个项目的工程 agent。"],
|
|
18
|
+
"/tmp/downcity-project",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
assert.deepEqual(
|
|
22
|
+
blocks.map((block) => `${block.source}:${block.name}`),
|
|
23
|
+
["instruction:agent", "core:default"],
|
|
24
|
+
);
|
|
25
|
+
assert.match(blocks[1].content, /# Downcity Agent/);
|
|
26
|
+
assert.match(blocks[1].content, /# Harness Design/);
|
|
27
|
+
assert.match(blocks[1].content, /human-owned workspace/);
|
|
28
|
+
assert.match(blocks[1].content, /project structure is the control surface/);
|
|
29
|
+
assert.match(blocks[1].content, /# Shell Commands/);
|
|
30
|
+
assert.match(blocks[1].content, /# Plugin System/);
|
|
31
|
+
assert.doesNotMatch(blocks[1].content, /\/tmp\/downcity-project/);
|
|
32
|
+
assert.doesNotMatch(blocks[1].content, /current year/i);
|
|
33
|
+
assert.doesNotMatch(blocks[1].content, /# Project Runtime/);
|
|
34
|
+
assert.doesNotMatch(blocks[1].content, /\.downcity\/agents/);
|
|
35
|
+
assert.doesNotMatch(blocks[1].content, /\.downcity\/memory/);
|
|
36
|
+
assert.doesNotMatch(blocks[1].content, /\.downcity\/public/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("session system blocks are ordered as instruction, core, plugin, session", async () => {
|
|
40
|
+
const blocks = await buildSessionSystemBlocks({
|
|
41
|
+
agentId: "agent-test",
|
|
42
|
+
projectRoot: "/tmp/downcity-project",
|
|
43
|
+
sessionId: "session-test",
|
|
44
|
+
createdAt: Date.UTC(2026, 6, 9, 8, 0, 0),
|
|
45
|
+
timezone: "Asia/Shanghai",
|
|
46
|
+
getInstructionSystemBlocks: () =>
|
|
47
|
+
createInstructionSystemBlocks(
|
|
48
|
+
["使用中文回复。"],
|
|
49
|
+
"/tmp/downcity-project",
|
|
50
|
+
),
|
|
51
|
+
getManagedPluginSystemBlocks: async () => [],
|
|
52
|
+
getPluginSystemBlocks: async () => [
|
|
53
|
+
{
|
|
54
|
+
source: "plugin",
|
|
55
|
+
name: "task",
|
|
56
|
+
content: "# Task Plugin\n\n任务插件说明。",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
blocks.map((block) => `${block.source}:${block.name}`),
|
|
63
|
+
[
|
|
64
|
+
"instruction:agent",
|
|
65
|
+
"core:default",
|
|
66
|
+
"plugin:task",
|
|
67
|
+
"session:context",
|
|
68
|
+
],
|
|
69
|
+
);
|
|
70
|
+
assert.equal(blocks[0].content, "使用中文回复。");
|
|
71
|
+
assert.match(blocks[1].content, /plugin_call/);
|
|
72
|
+
assert.match(blocks[3].content, /session-test/);
|
|
73
|
+
});
|