@downcity/agent 1.1.135 → 1.1.148

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 (180) hide show
  1. package/README.md +2 -2
  2. package/bin/agent/local/Agent.d.ts +3 -21
  3. package/bin/agent/local/Agent.d.ts.map +1 -1
  4. package/bin/agent/local/Agent.js +3 -30
  5. package/bin/agent/local/Agent.js.map +1 -1
  6. package/bin/agent/local/ProjectSetup.js +2 -2
  7. package/bin/agent/local/ProjectSetup.js.map +1 -1
  8. package/bin/agent/local/services/AgentAssemblyService.d.ts +1 -6
  9. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
  10. package/bin/agent/local/services/AgentAssemblyService.js +13 -20
  11. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
  12. package/bin/agent/local/services/AgentSessionManager.d.ts +16 -7
  13. package/bin/agent/local/services/AgentSessionManager.d.ts.map +1 -1
  14. package/bin/agent/local/services/AgentSessionManager.js +83 -8
  15. package/bin/agent/local/services/AgentSessionManager.js.map +1 -1
  16. package/bin/agent/remote/RemoteAgent.d.ts +9 -13
  17. package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
  18. package/bin/agent/remote/RemoteAgent.js +23 -24
  19. package/bin/agent/remote/RemoteAgent.js.map +1 -1
  20. package/bin/agent/remote/RemoteSession.d.ts +11 -4
  21. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  22. package/bin/agent/remote/RemoteSession.js +15 -3
  23. package/bin/agent/remote/RemoteSession.js.map +1 -1
  24. package/bin/agent/remote/RemoteTransport.d.ts +9 -1
  25. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  26. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +7 -3
  27. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  28. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +65 -2
  29. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  30. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +5 -1
  31. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  32. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +12 -0
  33. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  34. package/bin/config/AgentInitializer.d.ts +2 -2
  35. package/bin/config/AgentInitializer.js +2 -2
  36. package/bin/executor/Executor.d.ts +5 -0
  37. package/bin/executor/Executor.d.ts.map +1 -1
  38. package/bin/executor/Executor.js +34 -0
  39. package/bin/executor/Executor.js.map +1 -1
  40. package/bin/executor/composer/system/default/InitPrompts.d.ts +1 -1
  41. package/bin/executor/composer/system/default/InitPrompts.js +1 -1
  42. package/bin/executor/composer/system/default/SystemDomain.js +2 -2
  43. package/bin/executor/composer/system/default/SystemDomain.js.map +1 -1
  44. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
  45. package/bin/executor/core-engine/CoreEngineRunner.js +16 -0
  46. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
  47. package/bin/executor/messages/ChatMessageMarkupTypes.d.ts +1 -1
  48. package/bin/executor/messages/ChatMessageMarkupTypes.js +1 -1
  49. package/bin/executor/types/SessionExecutor.d.ts +8 -0
  50. package/bin/executor/types/SessionExecutor.d.ts.map +1 -1
  51. package/bin/index.d.ts +2 -2
  52. package/bin/index.d.ts.map +1 -1
  53. package/bin/index.js.map +1 -1
  54. package/bin/plugin/core/Activation.d.ts +0 -2
  55. package/bin/plugin/core/Activation.d.ts.map +1 -1
  56. package/bin/plugin/core/Activation.js +1 -1
  57. package/bin/plugin/core/Activation.js.map +1 -1
  58. package/bin/plugin/core/PluginCommandRequest.d.ts +1 -1
  59. package/bin/plugin/core/PluginCommandRequest.js +1 -1
  60. package/bin/plugin/core/PluginStateController.d.ts +0 -5
  61. package/bin/plugin/core/PluginStateController.d.ts.map +1 -1
  62. package/bin/plugin/core/PluginStateController.js +1 -5
  63. package/bin/plugin/core/PluginStateController.js.map +1 -1
  64. package/bin/plugin/types/PluginApi.d.ts +1 -1
  65. package/bin/plugin/types/PluginApi.js +1 -1
  66. package/bin/rpc/Client.d.ts +17 -1
  67. package/bin/rpc/Client.d.ts.map +1 -1
  68. package/bin/rpc/Client.js +47 -0
  69. package/bin/rpc/Client.js.map +1 -1
  70. package/bin/session/Session.d.ts +5 -0
  71. package/bin/session/Session.d.ts.map +1 -1
  72. package/bin/session/Session.js +7 -0
  73. package/bin/session/Session.js.map +1 -1
  74. package/bin/session/browse/Browse.d.ts +9 -1
  75. package/bin/session/browse/Browse.d.ts.map +1 -1
  76. package/bin/session/browse/Browse.js +81 -3
  77. package/bin/session/browse/Browse.js.map +1 -1
  78. package/bin/session/index.d.ts +3 -3
  79. package/bin/session/index.d.ts.map +1 -1
  80. package/bin/session/index.js +3 -3
  81. package/bin/session/index.js.map +1 -1
  82. package/bin/session/runtime/SessionPromptRuntime.d.ts +13 -0
  83. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  84. package/bin/session/runtime/SessionPromptRuntime.js +82 -5
  85. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  86. package/bin/session/services/SessionTurnService.d.ts +5 -0
  87. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  88. package/bin/session/services/SessionTurnService.js +10 -1
  89. package/bin/session/services/SessionTurnService.js.map +1 -1
  90. package/bin/session/storage/Metadata.d.ts +15 -0
  91. package/bin/session/storage/Metadata.d.ts.map +1 -1
  92. package/bin/session/storage/Metadata.js +15 -1
  93. package/bin/session/storage/Metadata.js.map +1 -1
  94. package/bin/session/storage/Paths.d.ts +24 -0
  95. package/bin/session/storage/Paths.d.ts.map +1 -1
  96. package/bin/session/storage/Paths.js +34 -0
  97. package/bin/session/storage/Paths.js.map +1 -1
  98. package/bin/session/storage/RuntimeSessionPort.d.ts +5 -0
  99. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  100. package/bin/session/storage/RuntimeSessionPort.js +4 -0
  101. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  102. package/bin/types/agent/AgentTypes.d.ts +2 -1
  103. package/bin/types/agent/AgentTypes.d.ts.map +1 -1
  104. package/bin/types/agent/SessionActor.d.ts +13 -4
  105. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  106. package/bin/types/agent/SessionTypes.d.ts +62 -0
  107. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  108. package/bin/types/config/DowncityConfig.d.ts +1 -1
  109. package/bin/types/executor/SessionRunContext.d.ts +8 -0
  110. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  111. package/bin/types/platform/Store.d.ts +2 -2
  112. package/bin/types/platform/Store.js +2 -2
  113. package/bin/types/rpc/RpcProtocol.d.ts +37 -5
  114. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  115. package/bin/types/rpc/RpcProtocol.js +1 -1
  116. package/bin/types/runtime/agent/AgentContext.d.ts +36 -11
  117. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  118. package/bin/types/runtime/agent/AgentContext.js +26 -8
  119. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  120. package/bin/types/runtime/platform/Platform.d.ts +8 -8
  121. package/bin/types/runtime/platform/PlatformGateway.d.ts +2 -2
  122. package/bin/types/sdk/AgentSessionStop.d.ts +36 -0
  123. package/bin/types/sdk/AgentSessionStop.d.ts.map +1 -0
  124. package/bin/types/sdk/AgentSessionStop.js +9 -0
  125. package/bin/types/sdk/AgentSessionStop.js.map +1 -0
  126. package/package.json +3 -3
  127. package/scripts/city-model-tool-loop.test.mjs +2 -2
  128. package/scripts/session-prompt-runtime.test.mjs +59 -0
  129. package/src/agent/local/Agent.ts +3 -41
  130. package/src/agent/local/ProjectSetup.ts +2 -2
  131. package/src/agent/local/services/AgentAssemblyService.ts +17 -28
  132. package/src/agent/local/services/AgentSessionManager.ts +127 -13
  133. package/src/agent/remote/RemoteAgent.ts +25 -34
  134. package/src/agent/remote/RemoteSession.ts +21 -6
  135. package/src/agent/remote/RemoteTransport.ts +14 -0
  136. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +100 -2
  137. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +26 -0
  138. package/src/config/AgentInitializer.ts +2 -2
  139. package/src/executor/Executor.ts +33 -0
  140. package/src/executor/composer/system/default/InitPrompts.ts +1 -1
  141. package/src/executor/composer/system/default/SystemDomain.ts +2 -2
  142. package/src/executor/core-engine/CoreEngineRunner.ts +21 -0
  143. package/src/executor/messages/ChatMessageMarkupTypes.ts +1 -1
  144. package/src/executor/types/SessionExecutor.ts +2 -1
  145. package/src/index.ts +6 -1
  146. package/src/plugin/core/Activation.ts +1 -3
  147. package/src/plugin/core/PluginCommandRequest.ts +1 -1
  148. package/src/plugin/core/PluginStateController.ts +1 -10
  149. package/src/plugin/types/PluginApi.ts +1 -1
  150. package/src/rpc/Client.ts +61 -0
  151. package/src/session/Session.ts +9 -0
  152. package/src/session/browse/Browse.ts +103 -3
  153. package/src/session/index.ts +7 -0
  154. package/src/session/runtime/SessionPromptRuntime.ts +100 -5
  155. package/src/session/services/SessionTurnService.ts +13 -1
  156. package/src/session/storage/Metadata.ts +23 -1
  157. package/src/session/storage/Paths.ts +70 -0
  158. package/src/session/storage/RuntimeSessionPort.ts +9 -0
  159. package/src/types/agent/AgentTypes.ts +6 -0
  160. package/src/types/agent/SessionActor.ts +21 -3
  161. package/src/types/agent/SessionTypes.ts +68 -0
  162. package/src/types/config/DowncityConfig.ts +1 -1
  163. package/src/types/executor/SessionRunContext.ts +9 -0
  164. package/src/types/platform/Store.ts +2 -2
  165. package/src/types/rpc/RpcProtocol.ts +46 -4
  166. package/src/types/runtime/agent/AgentContext.ts +41 -14
  167. package/src/types/runtime/platform/Platform.ts +8 -8
  168. package/src/types/runtime/platform/PlatformGateway.ts +2 -2
  169. package/src/types/sdk/AgentSessionStop.ts +39 -0
  170. package/tsconfig.tsbuildinfo +1 -1
  171. package/bin/agent/local/AgentRuntimeFactory.d.ts +0 -47
  172. package/bin/agent/local/AgentRuntimeFactory.d.ts.map +0 -1
  173. package/bin/agent/local/AgentRuntimeFactory.js +0 -34
  174. package/bin/agent/local/AgentRuntimeFactory.js.map +0 -1
  175. package/bin/types/runtime/agent/AgentRuntime.d.ts +0 -79
  176. package/bin/types/runtime/agent/AgentRuntime.d.ts.map +0 -1
  177. package/bin/types/runtime/agent/AgentRuntime.js +0 -13
  178. package/bin/types/runtime/agent/AgentRuntime.js.map +0 -1
  179. package/src/agent/local/AgentRuntimeFactory.ts +0 -78
  180. package/src/types/runtime/agent/AgentRuntime.ts +0 -84
@@ -230,7 +230,7 @@ test("CityModel uses direct LanguageModel path and sends tool result back", asyn
230
230
  const city = new City({
231
231
  role: "user",
232
232
  city_url: `http://127.0.0.1:${String(address.port)}`,
233
- town_id: "town_demo",
233
+ city_id: "city_demo",
234
234
  user_token: "ub_test",
235
235
  });
236
236
  const catalog = await city.ai.listModels();
@@ -266,7 +266,7 @@ test("CityModel uses direct LanguageModel path and sends tool result back", asyn
266
266
  assert.equal(tool_executed, true);
267
267
  assert.equal(stream_requests, 0);
268
268
  assert.equal(agent_requests.length, 2);
269
- assert.equal(requests.every((request) => request?.town_id === "town_demo"), true);
269
+ assert.equal(requests.every((request) => request?.city_id === "city_demo"), true);
270
270
  assert.equal(agent_requests[0]?.model, "mock-model");
271
271
 
272
272
  const second_request_messages = Array.isArray(agent_requests[1]?.messages)
@@ -98,6 +98,7 @@ test("SessionPromptRuntime merges queued prompts at the next step boundary", asy
98
98
  assistantMessage: createAssistantMessage("done", 1),
99
99
  };
100
100
  },
101
+ stopTurn: () => false,
101
102
  });
102
103
 
103
104
  const firstTurn = await runtime.prompt({ query: "first" });
@@ -159,6 +160,7 @@ test("SessionPromptRuntime moves unmerged prompts into the next turn", async ()
159
160
  ),
160
161
  };
161
162
  },
163
+ stopTurn: () => false,
162
164
  });
163
165
 
164
166
  const firstTurn = await runtime.prompt({ query: "first" });
@@ -185,3 +187,60 @@ test("SessionPromptRuntime moves unmerged prompts into the next turn", async ()
185
187
  ["turn-start", "turn-finish", "turn-start", "turn-finish"],
186
188
  );
187
189
  });
190
+
191
+ test("SessionPromptRuntime stops current turn and cancels unmerged queued prompts", async () => {
192
+ const events = [];
193
+ const executionFinished = createDeferred();
194
+ let stopRequested = false;
195
+
196
+ const runtime = new SessionPromptRuntime({
197
+ sessionId: "test",
198
+ publish: (event) => {
199
+ events.push(event);
200
+ },
201
+ createAndPersistUserMessage: async (input) => {
202
+ return createUserMessage(input.query, events.length + 1);
203
+ },
204
+ executeTurn: async (input) => {
205
+ await new Promise((resolve) => {
206
+ input.abortSignal.addEventListener("abort", resolve, { once: true });
207
+ });
208
+ await executionFinished.promise;
209
+ return {
210
+ text: "should-not-succeed",
211
+ success: true,
212
+ assistantMessage: createAssistantMessage("should-not-succeed", 1),
213
+ };
214
+ },
215
+ stopTurn: () => {
216
+ stopRequested = true;
217
+ executionFinished.resolve();
218
+ return true;
219
+ },
220
+ });
221
+
222
+ const firstTurn = await runtime.prompt({ query: "first" });
223
+ const secondTurnPromise = runtime.prompt({ query: "second" });
224
+ await waitUntil(() => runtime.isActive());
225
+
226
+ const stopResult = runtime.stop();
227
+ const secondTurn = await secondTurnPromise;
228
+ const firstResult = await firstTurn.finished;
229
+ const secondResult = await secondTurn.finished;
230
+
231
+ assert.equal(stopRequested, true);
232
+ assert.equal(stopResult.stopped, true);
233
+ assert.equal(stopResult.turnId, firstTurn.id);
234
+ assert.equal(stopResult.cancelledQueuedPrompts, 1);
235
+ assert.equal(firstResult.success, false);
236
+ assert.equal(firstResult.error, "Turn stopped");
237
+ assert.equal(secondResult.success, false);
238
+ assert.equal(
239
+ secondResult.error,
240
+ "Prompt cancelled because session was stopped",
241
+ );
242
+ assert.deepEqual(
243
+ events.map((event) => event.type),
244
+ ["turn-start", "turn-start", "turn-finish", "turn-finish"],
245
+ );
246
+ });
@@ -9,17 +9,12 @@
9
9
 
10
10
  import type { Tool } from "ai";
11
11
  import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
12
- import type { AgentRuntime } from "@/types/runtime/agent/AgentRuntime.js";
13
12
  import type { DowncityConfig } from "@/types/config/DowncityConfig.js";
14
13
  import type { AgentPlugins } from "@/plugin/types/Plugin.js";
15
14
  import type {
16
- AgentCreateSessionInput,
17
- AgentListSessionsInput,
18
15
  AgentModel,
19
16
  AgentOptions,
20
- AgentSession,
21
17
  AgentSessionCollection,
22
- AgentSessionSummaryPage,
23
18
  } from "@/types/agent/AgentTypes.js";
24
19
  import type {
25
20
  ShellApprovalMode,
@@ -50,7 +45,6 @@ export class Agent {
50
45
  readonly plugins: AgentPlugins;
51
46
 
52
47
  private readonly logger: Logger;
53
- private readonly runtime: AgentRuntime;
54
48
  private readonly agentContext: AgentContext;
55
49
  private readonly pluginRegistry: PluginRegistry;
56
50
  private readonly config: DowncityConfig;
@@ -89,7 +83,6 @@ export class Agent {
89
83
  this.tools = assembly.tools;
90
84
  this.plugins = assembly.plugins;
91
85
  this.logger = assembly.logger;
92
- this.runtime = assembly.runtime;
93
86
  this.agentContext = assembly.agent_context;
94
87
  this.pluginRegistry = assembly.plugin_registry;
95
88
  this.config = assembly.config;
@@ -107,29 +100,6 @@ export class Agent {
107
100
  });
108
101
  }
109
102
 
110
- /**
111
- * 新建一个 session。
112
- */
113
- async createSession(input?: AgentCreateSessionInput): Promise<AgentSession> {
114
- return await this.sessionManager.create_session(input);
115
- }
116
-
117
- /**
118
- * 获取一个已存在的 session。
119
- */
120
- async getSession(sessionId: string): Promise<AgentSession> {
121
- return await this.sessionManager.get_session(sessionId);
122
- }
123
-
124
- /**
125
- * 列出当前 agent 的 session 摘要页。
126
- */
127
- async listSessions(
128
- input?: AgentListSessionsInput,
129
- ): Promise<AgentSessionSummaryPage> {
130
- return await this.sessionManager.list_sessions(input);
131
- }
132
-
133
103
  /**
134
104
  * 等待 Agent 后台能力启动完成。
135
105
  *
@@ -267,13 +237,6 @@ export class Agent {
267
237
  return this.logger;
268
238
  }
269
239
 
270
- /**
271
- * 返回当前 agent runtime。
272
- */
273
- getRuntime(): AgentRuntime {
274
- return this.runtime;
275
- }
276
-
277
240
  /**
278
241
  * 返回当前 agent context。
279
242
  */
@@ -282,9 +245,9 @@ export class Agent {
282
245
  }
283
246
 
284
247
  /**
285
- * 返回当前 session collection
248
+ * 返回当前 session collection 入口。
286
249
  */
287
- getSessionCollection(): AgentSessionCollection {
250
+ session_collection(): AgentSessionCollection {
288
251
  return this.sessionManager.get_session_collection();
289
252
  }
290
253
 
@@ -303,8 +266,7 @@ export class Agent {
303
266
  project_root: this.path || assembly.path,
304
267
  tools: this.tools || assembly.tools,
305
268
  logger: this.logger || assembly.logger,
306
- runtime: this.runtime || assembly.runtime,
307
- get_agent_context: () => this.agentContext || assembly.agent_context,
269
+ get_agent_context: () => this.agentContext ?? assembly.agent_context,
308
270
  get_instruction: () => this.instruction,
309
271
  plugin_instances: assembly.plugin_instances,
310
272
  default_model: this.defaultModel,
@@ -30,14 +30,14 @@ function ensureContextFiles(projectRoot: string): void {
30
30
  // Check if initialized(启动入口一次性确认工程根目录与关键文件)
31
31
  if (!fs.existsSync(getProfileMdPath(projectRoot))) {
32
32
  console.error(
33
- '❌ Project not initialized. Please run "town agent create" first',
33
+ '❌ Project not initialized. Please run "downcity agent create" first',
34
34
  );
35
35
  process.exit(1);
36
36
  }
37
37
 
38
38
  if (!fs.existsSync(getDowncityJsonPath(projectRoot))) {
39
39
  console.error(
40
- '❌ downcity.json does not exist. Please run "town agent create" first',
40
+ '❌ downcity.json does not exist. Please run "downcity agent create" first',
41
41
  );
42
42
  process.exit(1);
43
43
  }
@@ -2,7 +2,7 @@
2
2
  * AgentAssemblyService:本地 Agent 装配服务。
3
3
  *
4
4
  * 关键点(中文)
5
- * - 统一装配 logger、config、env、runtime、context 与 plugin registry。
5
+ * - 统一装配 logger、config、env、context 与 plugin registry。
6
6
  * - 该服务只负责一次性长期对象装配,不负责 session 缓存和生命周期启动。
7
7
  * - session/lifecycle service 通过它暴露的长期对象协作,避免在 facade 中重复拼装。
8
8
  */
@@ -10,7 +10,6 @@
10
10
  import type { LanguageModel, Tool } from "ai";
11
11
  import type { BasePlugin } from "@/plugin/core/BasePlugin.js";
12
12
  import { AgentContext } from "@/types/runtime/agent/AgentContext.js";
13
- import type { AgentRuntime } from "@/types/runtime/agent/AgentRuntime.js";
14
13
  import type { DowncityConfig } from "@/types/config/DowncityConfig.js";
15
14
  import type { AgentPlugins } from "@/plugin/types/Plugin.js";
16
15
  import type { AgentOptions } from "@/types/agent/AgentTypes.js";
@@ -25,8 +24,9 @@ import {
25
24
  createAgentPluginRegistry,
26
25
  } from "@/agent/local/AgentPluginFactory.js";
27
26
  import {
28
- createAgentRuntime,
29
- } from "@/agent/local/AgentRuntimeFactory.js";
27
+ createAgentPathRuntime,
28
+ createAgentPluginConfigRuntime,
29
+ } from "@/agent/local/AgentRuntimeAssembly.js";
30
30
  import {
31
31
  plugin_tools,
32
32
  setPluginToolRuntime,
@@ -113,11 +113,6 @@ export interface AgentAssemblyResult {
113
113
  */
114
114
  plugins: AgentPlugins;
115
115
 
116
- /**
117
- * 当前 agent runtime。
118
- */
119
- runtime: AgentRuntime;
120
-
121
116
  /**
122
117
  * 当前 agent context。
123
118
  */
@@ -166,24 +161,12 @@ export class AgentAssemblyService {
166
161
  logger.bindProjectRoot(path);
167
162
  // 关键点(中文)
168
163
  // - 这里产出的 env 是 agent 全生命周期共享的 mutable 对象引用。
169
- // - runtime / context / shell 都持有同一引用;后续 `agent.setEnv()` 会原地修改它。
164
+ // - context / shell 都持有同一引用;后续 `agent.setEnv()` 会原地修改它。
170
165
  const env = resolveAgentEnv(path, this.options.env);
171
166
  const instruction = normalizeInstructionInput(this.options.instruction);
172
167
  const config = this.load_config(id, path);
173
168
  const plugin_instances = new Map<string, BasePlugin>();
174
169
 
175
- const runtime = createAgentRuntime({
176
- agent_id: id,
177
- project_root: path,
178
- logger,
179
- config,
180
- env,
181
- systems: instruction,
182
- plugin_instances,
183
- get_session_port: this.get_session_port,
184
- list_cached_sessions: this.list_cached_sessions,
185
- });
186
-
187
170
  this.register_plugins(plugin_instances, this.options.plugins || []);
188
171
 
189
172
  // 关键点(中文)
@@ -204,20 +187,27 @@ export class AgentAssemblyService {
204
187
  tools.plugin_call = tools.plugin_call || plugin_tools.plugin_call;
205
188
  }
206
189
  const resolve_session_model = this.resolve_session_model;
190
+ const paths = createAgentPathRuntime(path, id);
191
+ const pluginConfig = createAgentPluginConfigRuntime(path);
207
192
  agent_context = new AgentContext({
208
- agent: runtime,
209
193
  cwd: path,
210
194
  rootPath: path,
211
195
  logger,
212
196
  config,
213
197
  env,
214
198
  systems: instruction,
215
- paths: runtime.paths,
216
- pluginConfig: runtime.pluginConfig,
199
+ paths,
200
+ pluginConfig,
201
+ pluginInstances: plugin_instances,
217
202
  session: {
218
203
  get: (session_id) => this.get_session_port(session_id),
219
- listExecutingSessionIds: () => runtime.listExecutingSessionIds(),
220
- getExecutingSessionCount: () => runtime.getExecutingSessionCount(),
204
+ listExecutingSessionIds: () =>
205
+ this.list_cached_sessions()
206
+ .filter((session) => session.isExecuting())
207
+ .map((session) => session.id),
208
+ getExecutingSessionCount: () =>
209
+ this.list_cached_sessions()
210
+ .filter((session) => session.isExecuting()).length,
221
211
  resolveModel: async (session_id) =>
222
212
  await resolve_session_model(session_id),
223
213
  },
@@ -251,7 +241,6 @@ export class AgentAssemblyService {
251
241
  plugin_instances,
252
242
  plugin_registry,
253
243
  plugins,
254
- runtime,
255
244
  agent_context: agent_context!,
256
245
  ...(shell ? { shell } : {}),
257
246
  };
@@ -13,6 +13,11 @@ import type { Tool } from "ai";
13
13
  import type { Logger } from "@/utils/logger/Logger.js";
14
14
  import type {
15
15
  AgentCreateSessionInput,
16
+ AgentArchiveSessionInput,
17
+ AgentArchiveSessionsInput,
18
+ AgentArchiveSessionResult,
19
+ AgentArchiveSessionsResult,
20
+ AgentCleanArchiveResult,
16
21
  AgentListSessionsInput,
17
22
  AgentManagedSession,
18
23
  AgentModel,
@@ -24,16 +29,26 @@ import type {
24
29
  } from "@/types/agent/AgentTypes.js";
25
30
  import { Session } from "@/session/Session.js";
26
31
  import {
32
+ getSdkAgentArchivedSessionDirPath,
33
+ getSdkAgentArchivedSessionsDirPath,
27
34
  getSdkAgentSessionDirPath,
35
+ listArchivedAgentSessionSummaryPage,
28
36
  listAgentSessionSummaryPage,
29
37
  } from "@/session/index.js";
30
- import type { AgentRuntime } from "@/types/runtime/agent/AgentRuntime.js";
31
38
  import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
32
39
  import type { SessionPort } from "@/types/runtime/agent/AgentContext.js";
33
40
  import type { BasePlugin } from "@/plugin/core/BasePlugin.js";
34
41
  import { isPluginEnabled } from "@/plugin/core/Activation.js";
35
42
  import { createInstructionSystemBlocks } from "@/agent/local/AgentInstructions.js";
36
43
 
44
+ function decodeMaybe(input: string): string {
45
+ try {
46
+ return decodeURIComponent(input);
47
+ } catch {
48
+ return input;
49
+ }
50
+ }
51
+
37
52
  type AgentSessionManagerOptions = {
38
53
  /**
39
54
  * 当前 agent 稳定标识。
@@ -55,13 +70,11 @@ type AgentSessionManagerOptions = {
55
70
  */
56
71
  logger: Logger;
57
72
 
58
- /**
59
- * 当前 agent runtime。
60
- */
61
- runtime: AgentRuntime;
62
-
63
73
  /**
64
74
  * 延迟读取当前 agent context。
75
+ *
76
+ * 关键点(中文)
77
+ * - 装配期通过 getter 拿到 context,避免循环引用。
65
78
  */
66
79
  get_agent_context: () => AgentContext;
67
80
 
@@ -94,7 +107,6 @@ export class AgentSessionManager {
94
107
  private readonly project_root: string;
95
108
  private readonly tools: Record<string, Tool>;
96
109
  private readonly logger: Logger;
97
- private readonly runtime: AgentRuntime;
98
110
  private readonly get_agent_context: AgentSessionManagerOptions["get_agent_context"];
99
111
  private readonly get_instruction: AgentSessionManagerOptions["get_instruction"];
100
112
  private readonly plugin_instances: Map<string, BasePlugin>;
@@ -109,16 +121,18 @@ export class AgentSessionManager {
109
121
  this.project_root = options.project_root;
110
122
  this.tools = options.tools;
111
123
  this.logger = options.logger;
112
- this.runtime = options.runtime;
113
124
  this.get_agent_context = options.get_agent_context;
114
125
  this.get_instruction = options.get_instruction;
115
126
  this.plugin_instances = options.plugin_instances;
116
127
  this.default_model = options.default_model;
117
128
  this.SessionClass = options.SessionClass || Session;
118
129
  this.session_collection = {
119
- createSession: async (input) => await this.create_session(input),
120
- getSession: async (session_id) => await this.get_session(session_id),
121
- listSessions: async (input) => await this.list_sessions(input),
130
+ create_session: async (input) => await this.create_session(input),
131
+ get_session: async (session_id) => await this.get_session(session_id),
132
+ list_sessions: async (input) => await this.list_sessions(input),
133
+ archive_session: async (input) => await this.archive_session(input),
134
+ archive_sessions: async (input) => await this.archive_sessions(input),
135
+ clean_archive: async () => await this.clean_archive(),
122
136
  };
123
137
  }
124
138
 
@@ -180,7 +194,7 @@ export class AgentSessionManager {
180
194
  async get_session(session_id: string): Promise<AgentSession> {
181
195
  const resolved_session_id = String(session_id || "").trim();
182
196
  if (!resolved_session_id) {
183
- throw new Error("getSession requires a non-empty sessionId");
197
+ throw new Error("get_session requires a non-empty sessionId");
184
198
  }
185
199
  const session_dir_path = getSdkAgentSessionDirPath(
186
200
  this.project_root,
@@ -211,10 +225,110 @@ export class AgentSessionManager {
211
225
  projectRoot: this.project_root,
212
226
  agentId: this.agent_id,
213
227
  input,
214
- executingSessionIds: new Set(this.runtime.listExecutingSessionIds()),
228
+ executingSessionIds: new Set(this.get_agent_context().listExecutingSessionIds()),
215
229
  });
216
230
  }
217
231
 
232
+ /**
233
+ * 归档单个 session。
234
+ */
235
+ async archive_session(
236
+ input: AgentArchiveSessionInput,
237
+ ): Promise<AgentArchiveSessionResult> {
238
+ const session_id = String(input?.id || "").trim();
239
+ if (!session_id) {
240
+ throw new Error("archive_session requires a non-empty id");
241
+ }
242
+
243
+ const executing_session_ids = new Set(
244
+ this.get_agent_context().listExecutingSessionIds(),
245
+ );
246
+ if (executing_session_ids.has(session_id)) {
247
+ throw new Error(`Session "${session_id}" is currently executing`);
248
+ }
249
+
250
+ const source_path = getSdkAgentSessionDirPath(
251
+ this.project_root,
252
+ this.agent_id,
253
+ session_id,
254
+ );
255
+ if (!(await fs.pathExists(source_path))) {
256
+ throw new Error(`Session "${session_id}" not found`);
257
+ }
258
+
259
+ const target_path = getSdkAgentArchivedSessionDirPath(
260
+ this.project_root,
261
+ this.agent_id,
262
+ session_id,
263
+ );
264
+ if (await fs.pathExists(target_path)) {
265
+ throw new Error(`Archived session "${session_id}" already exists`);
266
+ }
267
+
268
+ await fs.ensureDir(getSdkAgentArchivedSessionsDirPath(
269
+ this.project_root,
270
+ this.agent_id,
271
+ ));
272
+ await fs.move(source_path, target_path);
273
+
274
+ // 关键点(中文):归档后清理缓存,避免后续操作访问已移动目录。
275
+ this.sessions_by_id.delete(session_id);
276
+ this.configured_session_ids.delete(session_id);
277
+
278
+ return {
279
+ sessionId: session_id,
280
+ archivedAt: Date.now(),
281
+ };
282
+ }
283
+
284
+ /**
285
+ * 列出当前 agent 的已归档 session 摘要页。
286
+ */
287
+ async archive_sessions(
288
+ input?: AgentArchiveSessionsInput,
289
+ ): Promise<AgentArchiveSessionsResult> {
290
+ return await listArchivedAgentSessionSummaryPage({
291
+ projectRoot: this.project_root,
292
+ agentId: this.agent_id,
293
+ input,
294
+ });
295
+ }
296
+
297
+ /**
298
+ * 永久清空已归档 session。
299
+ */
300
+ async clean_archive(): Promise<AgentCleanArchiveResult> {
301
+ const archived_root = getSdkAgentArchivedSessionsDirPath(
302
+ this.project_root,
303
+ this.agent_id,
304
+ );
305
+ if (!(await fs.pathExists(archived_root))) {
306
+ return {
307
+ removedSessionIds: [],
308
+ };
309
+ }
310
+
311
+ const entries = await fs.readdir(archived_root, { withFileTypes: true });
312
+ const removed_session_ids: string[] = [];
313
+
314
+ for (const entry of entries) {
315
+ if (!entry.isDirectory()) continue;
316
+ const session_id = decodeMaybe(entry.name);
317
+ if (!session_id) continue;
318
+ const session_path = getSdkAgentArchivedSessionDirPath(
319
+ this.project_root,
320
+ this.agent_id,
321
+ session_id,
322
+ );
323
+ await fs.remove(session_path);
324
+ removed_session_ids.push(session_id);
325
+ }
326
+
327
+ return {
328
+ removedSessionIds: removed_session_ids,
329
+ };
330
+ }
331
+
218
332
  private get_or_create_session(input?: {
219
333
  /**
220
334
  * 可选指定 session id。
@@ -8,9 +8,7 @@
8
8
  */
9
9
 
10
10
  import type {
11
- AgentCreateSessionInput,
12
- AgentListSessionsInput,
13
- AgentSessionSummaryPage,
11
+ AgentSessionCollection,
14
12
  RemoteAgentPluginActionInput,
15
13
  RemoteAgentPluginActionResult,
16
14
  RemoteAgentOptions,
@@ -42,37 +40,6 @@ export class RemoteAgent {
42
40
  this.transport = create_remote_agent_transport(url, options.token);
43
41
  }
44
42
 
45
- /**
46
- * 新建一个远程 session。
47
- */
48
- async createSession(
49
- input?: AgentCreateSessionInput,
50
- ): Promise<RemoteAgentSession> {
51
- const info = await this.transport.create_session(input);
52
- return new RemoteSession(this.transport, info.sessionId);
53
- }
54
-
55
- /**
56
- * 获取一个已存在的远程 session。
57
- */
58
- async getSession(sessionId: string): Promise<RemoteAgentSession> {
59
- const resolved_session_id = String(sessionId || "").trim();
60
- if (!resolved_session_id) {
61
- throw new Error("getSession requires a non-empty sessionId");
62
- }
63
- const info = await this.transport.get_info(resolved_session_id);
64
- return new RemoteSession(this.transport, info.sessionId);
65
- }
66
-
67
- /**
68
- * 列出远程 agent 的 session 摘要页。
69
- */
70
- async listSessions(
71
- input?: AgentListSessionsInput,
72
- ): Promise<AgentSessionSummaryPage> {
73
- return await this.transport.list_sessions(input);
74
- }
75
-
76
43
  /**
77
44
  * 执行远程 Agent runtime 内的 plugin action。
78
45
  *
@@ -153,4 +120,28 @@ export class RemoteAgent {
153
120
  async close(): Promise<void> {
154
121
  await this.transport.close?.();
155
122
  }
123
+
124
+ /**
125
+ * 返回当前远程 session collection 入口。
126
+ *
127
+ * 关键点(中文)
128
+ * - `RemoteAgentTransport` 只负责协议传输,不直接等价于 `AgentSessionCollection`。
129
+ * - 这里把 transport 包装为 `AgentSessionCollection`,`create_session` / `get_session` 返回 `RemoteSession` 实例。
130
+ */
131
+ session_collection(): AgentSessionCollection {
132
+ return {
133
+ create_session: async (input) => {
134
+ const info = await this.transport.create_session(input);
135
+ return new RemoteSession(this.transport, info);
136
+ },
137
+ get_session: async (session_id) => {
138
+ const info = await this.transport.get_info(session_id);
139
+ return new RemoteSession(this.transport, info);
140
+ },
141
+ list_sessions: async (input) => await this.transport.list_sessions(input),
142
+ archive_session: async (input) => await this.transport.archive_session(input),
143
+ archive_sessions: async (input) => await this.transport.archive_sessions(input),
144
+ clean_archive: async () => await this.transport.clean_archive(),
145
+ };
146
+ }
156
147
  }
@@ -7,14 +7,16 @@
7
7
  */
8
8
 
9
9
  import type {
10
+ AgentSession,
11
+ AgentSessionConfigSnapshot,
10
12
  AgentSessionForkInput,
11
13
  AgentSessionHistoryInput,
12
14
  AgentSessionHistoryPage,
13
15
  AgentSessionInfo,
14
16
  AgentSessionSetInput,
15
17
  AgentSessionSystemSnapshot,
16
- RemoteAgentSession,
17
18
  } from "@/types/agent/AgentTypes.js";
19
+ import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
18
20
  import type {
19
21
  AgentSessionEvent,
20
22
  AgentSessionSubscriber,
@@ -50,8 +52,10 @@ type RemoteTurnLifecycle = {
50
52
  /**
51
53
  * 远程 Session 客户端。
52
54
  */
53
- export class RemoteSession implements RemoteAgentSession {
55
+ export class RemoteSession implements AgentSession {
54
56
  readonly id: string;
57
+ readonly agentId: string;
58
+ readonly config: AgentSessionConfigSnapshot;
55
59
 
56
60
  private readonly transport: RemoteSessionTransport;
57
61
  private readonly event_hub = new SessionEventHub();
@@ -62,9 +66,12 @@ export class RemoteSession implements RemoteAgentSession {
62
66
  private event_subscriber_count = 0;
63
67
  private event_subscription: TransportSubscription | null = null;
64
68
 
65
- constructor(transport: RemoteSessionTransport, session_id: string) {
69
+ constructor(transport: RemoteSessionTransport, info: AgentSessionInfo) {
66
70
  this.transport = transport;
67
- this.id = session_id;
71
+ this.id = info.sessionId;
72
+ this.agentId = info.agentId;
73
+ // 远程 session 不暴露服务端模型实例,config 返回空快照。
74
+ this.config = {} as AgentSessionConfigSnapshot;
68
75
  }
69
76
 
70
77
  /**
@@ -98,6 +105,14 @@ export class RemoteSession implements RemoteAgentSession {
98
105
  return create_turn_handle(lifecycle);
99
106
  }
100
107
 
108
+ /**
109
+ * 停止当前远程 session turn,并取消未吸收队列。
110
+ */
111
+ async stop(): Promise<AgentSessionStopResult> {
112
+ await this.ensure_event_pump();
113
+ return await this.transport.stop(this.id);
114
+ }
115
+
101
116
  /**
102
117
  * 订阅当前远程 session 的 future 事件。
103
118
  */
@@ -134,9 +149,9 @@ export class RemoteSession implements RemoteAgentSession {
134
149
  /**
135
150
  * 分叉远程 session。
136
151
  */
137
- async fork(input?: AgentSessionForkInput | string): Promise<RemoteAgentSession> {
152
+ async fork(input?: AgentSessionForkInput | string): Promise<AgentSession> {
138
153
  const info = await this.transport.fork(this.id, input);
139
- return new RemoteSession(this.transport, info.sessionId);
154
+ return new RemoteSession(this.transport, info);
140
155
  }
141
156
 
142
157
  private async ensure_event_pump(): Promise<void> {