@downcity/agent 1.1.208 → 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.
Files changed (262) hide show
  1. package/bin/agent/local/Agent.d.ts +3 -3
  2. package/bin/agent/local/Agent.d.ts.map +1 -1
  3. package/bin/agent/local/Agent.js +0 -3
  4. package/bin/agent/local/Agent.js.map +1 -1
  5. package/bin/agent/local/AgentInstructions.d.ts +1 -1
  6. package/bin/agent/local/AgentInstructions.d.ts.map +1 -1
  7. package/bin/agent/local/services/AgentAssemblyService.d.ts +3 -8
  8. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
  9. package/bin/agent/local/services/AgentAssemblyService.js +0 -1
  10. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
  11. package/bin/agent/local/services/AgentSessions.d.ts +5 -7
  12. package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
  13. package/bin/agent/local/services/AgentSessions.js +2 -3
  14. package/bin/agent/local/services/AgentSessions.js.map +1 -1
  15. package/bin/agent/remote/RemoteAgent.d.ts +3 -1
  16. package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
  17. package/bin/agent/remote/RemoteAgent.js.map +1 -1
  18. package/bin/agent/remote/RemoteSession.d.ts +10 -1
  19. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  20. package/bin/agent/remote/RemoteSession.js +18 -0
  21. package/bin/agent/remote/RemoteSession.js.map +1 -1
  22. package/bin/agent/remote/RemoteTransport.d.ts +5 -1
  23. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  24. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +4 -1
  25. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  26. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +7 -0
  27. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  28. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +4 -1
  29. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  30. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +1 -0
  31. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  32. package/bin/executor/Executor.d.ts +0 -16
  33. package/bin/executor/Executor.d.ts.map +1 -1
  34. package/bin/executor/Executor.js +0 -16
  35. package/bin/executor/Executor.js.map +1 -1
  36. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +1 -3
  37. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
  38. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +1 -5
  39. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
  40. package/bin/executor/composer/history/SessionHistoryWriter.d.ts +0 -10
  41. package/bin/executor/composer/history/SessionHistoryWriter.d.ts.map +1 -1
  42. package/bin/executor/composer/history/SessionHistoryWriter.js +31 -61
  43. package/bin/executor/composer/history/SessionHistoryWriter.js.map +1 -1
  44. package/bin/executor/messages/AssistantFileResource.d.ts.map +1 -1
  45. package/bin/executor/messages/AssistantFileResource.js +58 -15
  46. package/bin/executor/messages/AssistantFileResource.js.map +1 -1
  47. package/bin/executor/services/ExecutorInflightService.d.ts +0 -5
  48. package/bin/executor/services/ExecutorInflightService.d.ts.map +1 -1
  49. package/bin/executor/services/ExecutorInflightService.js +0 -6
  50. package/bin/executor/services/ExecutorInflightService.js.map +1 -1
  51. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +4 -0
  52. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
  53. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +115 -3
  54. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  55. package/bin/executor/types/SessionHistoryMeta.d.ts +6 -0
  56. package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
  57. package/bin/index.d.ts +38 -6
  58. package/bin/index.d.ts.map +1 -1
  59. package/bin/index.js +14 -1
  60. package/bin/index.js.map +1 -1
  61. package/bin/plugin/core/ActionScheduleExecutor.js +1 -1
  62. package/bin/plugin/core/ActionScheduleExecutor.js.map +1 -1
  63. package/bin/plugin/core/BasePlugin.d.ts +7 -9
  64. package/bin/plugin/core/BasePlugin.d.ts.map +1 -1
  65. package/bin/plugin/core/BasePlugin.js +1 -14
  66. package/bin/plugin/core/BasePlugin.js.map +1 -1
  67. package/bin/plugin/core/HookRegistry.d.ts +1 -1
  68. package/bin/plugin/core/HookRegistry.d.ts.map +1 -1
  69. package/bin/plugin/core/PluginActionFactory.d.ts +1 -5
  70. package/bin/plugin/core/PluginActionFactory.d.ts.map +1 -1
  71. package/bin/plugin/core/PluginActionFactory.js +0 -3
  72. package/bin/plugin/core/PluginActionFactory.js.map +1 -1
  73. package/bin/plugin/core/PluginActionRunner.d.ts +6 -4
  74. package/bin/plugin/core/PluginActionRunner.d.ts.map +1 -1
  75. package/bin/plugin/core/PluginActionRunner.js +2 -2
  76. package/bin/plugin/core/PluginActionRunner.js.map +1 -1
  77. package/bin/plugin/core/PluginCatalog.d.ts +2 -1
  78. package/bin/plugin/core/PluginCatalog.d.ts.map +1 -1
  79. package/bin/plugin/core/PluginCatalog.js.map +1 -1
  80. package/bin/plugin/core/PluginCommand.d.ts +1 -1
  81. package/bin/plugin/core/PluginCommand.d.ts.map +1 -1
  82. package/bin/plugin/core/PluginCommand.js.map +1 -1
  83. package/bin/plugin/core/PluginHttpRoutes.d.ts +1 -1
  84. package/bin/plugin/core/PluginHttpRoutes.d.ts.map +1 -1
  85. package/bin/plugin/core/PluginLocalExecution.d.ts +4 -1
  86. package/bin/plugin/core/PluginLocalExecution.d.ts.map +1 -1
  87. package/bin/plugin/core/PluginLocalExecution.js +0 -1
  88. package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
  89. package/bin/plugin/core/PluginRegistry.d.ts +4 -2
  90. package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
  91. package/bin/plugin/core/PluginRegistry.js +7 -6
  92. package/bin/plugin/core/PluginRegistry.js.map +1 -1
  93. package/bin/plugin/core/PluginStateController.d.ts +7 -27
  94. package/bin/plugin/core/PluginStateController.d.ts.map +1 -1
  95. package/bin/plugin/core/PluginStateController.js +5 -39
  96. package/bin/plugin/core/PluginStateController.js.map +1 -1
  97. package/bin/plugin/types/PluginApi.d.ts +3 -79
  98. package/bin/plugin/types/PluginApi.d.ts.map +1 -1
  99. package/bin/rpc/Client.d.ts +11 -6
  100. package/bin/rpc/Client.d.ts.map +1 -1
  101. package/bin/rpc/Client.js +2 -0
  102. package/bin/rpc/Client.js.map +1 -1
  103. package/bin/session/Session.d.ts +2 -5
  104. package/bin/session/Session.d.ts.map +1 -1
  105. package/bin/session/Session.js +3 -16
  106. package/bin/session/Session.js.map +1 -1
  107. package/bin/session/SessionSystemBuilder.d.ts +1 -1
  108. package/bin/session/SessionSystemBuilder.d.ts.map +1 -1
  109. package/bin/session/browse/Browse.d.ts +4 -6
  110. package/bin/session/browse/Browse.d.ts.map +1 -1
  111. package/bin/session/browse/Browse.js +71 -71
  112. package/bin/session/browse/Browse.js.map +1 -1
  113. package/bin/session/preview/SessionMessagePreview.d.ts +13 -0
  114. package/bin/session/preview/SessionMessagePreview.d.ts.map +1 -0
  115. package/bin/session/preview/SessionMessagePreview.js +61 -0
  116. package/bin/session/preview/SessionMessagePreview.js.map +1 -0
  117. package/bin/session/services/SessionStateService.d.ts +1 -1
  118. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  119. package/bin/session/services/SessionStateService.js +2 -2
  120. package/bin/session/services/SessionStateService.js.map +1 -1
  121. package/bin/session/services/SessionViewService.d.ts +2 -1
  122. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  123. package/bin/session/services/SessionViewService.js +4 -1
  124. package/bin/session/services/SessionViewService.js.map +1 -1
  125. package/bin/session/storage/Metadata.d.ts.map +1 -1
  126. package/bin/session/storage/Metadata.js +23 -0
  127. package/bin/session/storage/Metadata.js.map +1 -1
  128. package/bin/session/storage/Persistence.d.ts +1 -1
  129. package/bin/session/storage/Persistence.d.ts.map +1 -1
  130. package/bin/session/storage/RuntimeSessionPort.d.ts +0 -12
  131. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  132. package/bin/session/storage/RuntimeSessionPort.js +0 -8
  133. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  134. package/bin/types/plugin/PluginAction.d.ts +0 -10
  135. package/bin/types/plugin/PluginAction.d.ts.map +1 -1
  136. package/bin/types/plugin/PluginCommand.d.ts +2 -3
  137. package/bin/types/plugin/PluginCommand.d.ts.map +1 -1
  138. package/bin/types/plugin/PluginControl.d.ts +74 -0
  139. package/bin/types/plugin/PluginControl.d.ts.map +1 -0
  140. package/bin/types/plugin/PluginControl.js +9 -0
  141. package/bin/types/plugin/PluginControl.js.map +1 -0
  142. package/bin/types/plugin/PluginDefinition.d.ts +1 -1
  143. package/bin/types/plugin/PluginDefinition.d.ts.map +1 -1
  144. package/bin/types/plugin/PluginRuntime.d.ts +1 -3
  145. package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
  146. package/bin/types/plugin/PluginState.d.ts +3 -16
  147. package/bin/types/plugin/PluginState.d.ts.map +1 -1
  148. package/bin/types/rpc/RpcProtocol.d.ts +3 -3
  149. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  150. package/bin/types/runtime/agent/AgentContext.d.ts +1 -9
  151. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  152. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  153. package/bin/types/session/SessionLocalState.d.ts +1 -1
  154. package/bin/types/session/SessionLocalState.d.ts.map +1 -1
  155. package/package.json +4 -7
  156. package/scripts/agent-ready.test.mjs +38 -0
  157. package/scripts/assistant-file-resource.test.mjs +39 -0
  158. package/scripts/city-model-tool-loop.test.mjs +18 -30
  159. package/scripts/image-plugin-job.test.mjs +13 -13
  160. package/scripts/plugin-tool-bridge.test.mjs +2 -3
  161. package/scripts/remote-session-reconnect.test.mjs +67 -0
  162. package/scripts/session-history-writer-error.test.mjs +57 -0
  163. package/scripts/session-list-title.test.mjs +114 -23
  164. package/src/agent/local/Agent.ts +4 -10
  165. package/src/agent/local/AgentInstructions.ts +1 -1
  166. package/src/agent/local/services/AgentAssemblyService.ts +3 -9
  167. package/src/agent/local/services/AgentSessions.ts +8 -15
  168. package/src/agent/remote/RemoteAgent.ts +5 -4
  169. package/src/agent/remote/RemoteSession.ts +21 -2
  170. package/src/agent/remote/RemoteTransport.ts +6 -2
  171. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +13 -3
  172. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +6 -2
  173. package/src/executor/Executor.ts +0 -23
  174. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +2 -8
  175. package/src/executor/composer/history/SessionHistoryWriter.ts +33 -64
  176. package/src/executor/messages/AssistantFileResource.ts +61 -17
  177. package/src/executor/services/ExecutorInflightService.ts +0 -10
  178. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +130 -4
  179. package/src/executor/types/SessionHistoryMeta.ts +6 -0
  180. package/src/index.ts +100 -29
  181. package/src/plugin/core/ActionScheduleExecutor.ts +1 -1
  182. package/src/plugin/core/BasePlugin.ts +8 -22
  183. package/src/plugin/core/HookRegistry.ts +1 -1
  184. package/src/plugin/core/PluginActionFactory.ts +1 -8
  185. package/src/plugin/core/PluginActionRunner.ts +8 -11
  186. package/src/plugin/core/PluginCatalog.ts +2 -5
  187. package/src/plugin/core/PluginCommand.ts +2 -1
  188. package/src/plugin/core/PluginHttpRoutes.ts +1 -1
  189. package/src/plugin/core/PluginLocalExecution.ts +4 -7
  190. package/src/plugin/core/PluginRegistry.ts +10 -10
  191. package/src/plugin/core/PluginStateController.ts +12 -60
  192. package/src/plugin/types/PluginApi.ts +6 -81
  193. package/src/rpc/Client.ts +22 -16
  194. package/src/session/Session.ts +5 -21
  195. package/src/session/SessionSystemBuilder.ts +1 -1
  196. package/src/session/browse/Browse.ts +103 -84
  197. package/src/session/preview/SessionMessagePreview.ts +75 -0
  198. package/src/session/services/SessionStateService.ts +3 -4
  199. package/src/session/services/SessionViewService.ts +6 -6
  200. package/src/session/storage/Metadata.ts +26 -0
  201. package/src/session/storage/Persistence.ts +1 -1
  202. package/src/session/storage/RuntimeSessionPort.ts +0 -20
  203. package/src/types/plugin/PluginAction.ts +0 -10
  204. package/src/types/plugin/PluginCommand.ts +2 -3
  205. package/src/types/plugin/PluginControl.ts +80 -0
  206. package/src/types/plugin/PluginDefinition.ts +1 -1
  207. package/src/types/plugin/PluginRuntime.ts +1 -3
  208. package/src/types/plugin/PluginState.ts +3 -20
  209. package/src/types/rpc/RpcProtocol.ts +4 -4
  210. package/src/types/runtime/agent/AgentContext.ts +1 -9
  211. package/src/types/session/SessionLocalState.ts +1 -1
  212. package/tsconfig.tsbuildinfo +1 -1
  213. package/bin/config/DowncitySchema.d.ts +0 -20
  214. package/bin/config/DowncitySchema.d.ts.map +0 -1
  215. package/bin/config/DowncitySchema.js +0 -160
  216. package/bin/config/DowncitySchema.js.map +0 -1
  217. package/bin/plugin/core/Activation.d.ts +0 -18
  218. package/bin/plugin/core/Activation.d.ts.map +0 -1
  219. package/bin/plugin/core/Activation.js +0 -21
  220. package/bin/plugin/core/Activation.js.map +0 -1
  221. package/bin/plugin/core/Manager.d.ts +0 -7
  222. package/bin/plugin/core/Manager.d.ts.map +0 -1
  223. package/bin/plugin/core/Manager.js +0 -6
  224. package/bin/plugin/core/Manager.js.map +0 -1
  225. package/bin/plugin/types/Plugin.d.ts +0 -15
  226. package/bin/plugin/types/Plugin.d.ts.map +0 -1
  227. package/bin/plugin/types/Plugin.js +0 -9
  228. package/bin/plugin/types/Plugin.js.map +0 -1
  229. package/bin/plugin/types/PluginLifecycle.d.ts +0 -34
  230. package/bin/plugin/types/PluginLifecycle.d.ts.map +0 -1
  231. package/bin/plugin/types/PluginLifecycle.js +0 -9
  232. package/bin/plugin/types/PluginLifecycle.js.map +0 -1
  233. package/bin/plugin/types/PluginState.d.ts +0 -5
  234. package/bin/plugin/types/PluginState.d.ts.map +0 -1
  235. package/bin/plugin/types/PluginState.js +0 -5
  236. package/bin/plugin/types/PluginState.js.map +0 -1
  237. package/bin/plugin/types/Plugins.d.ts +0 -61
  238. package/bin/plugin/types/Plugins.d.ts.map +0 -1
  239. package/bin/plugin/types/Plugins.js +0 -5
  240. package/bin/plugin/types/Plugins.js.map +0 -1
  241. package/bin/session/index.d.ts +0 -15
  242. package/bin/session/index.d.ts.map +0 -1
  243. package/bin/session/index.js +0 -15
  244. package/bin/session/index.js.map +0 -1
  245. package/bin/types/agent/AgentTypes.d.ts +0 -18
  246. package/bin/types/agent/AgentTypes.d.ts.map +0 -1
  247. package/bin/types/agent/AgentTypes.js +0 -10
  248. package/bin/types/agent/AgentTypes.js.map +0 -1
  249. package/bin/utils/logger/Format.d.ts +0 -11
  250. package/bin/utils/logger/Format.d.ts.map +0 -1
  251. package/bin/utils/logger/Format.js +0 -10
  252. package/bin/utils/logger/Format.js.map +0 -1
  253. package/src/config/DowncitySchema.ts +0 -175
  254. package/src/plugin/core/Activation.ts +0 -26
  255. package/src/plugin/core/Manager.ts +0 -17
  256. package/src/plugin/types/Plugin.ts +0 -63
  257. package/src/plugin/types/PluginLifecycle.ts +0 -35
  258. package/src/plugin/types/PluginState.ts +0 -12
  259. package/src/plugin/types/Plugins.ts +0 -67
  260. package/src/session/index.ts +0 -57
  261. package/src/types/agent/AgentTypes.ts +0 -58
  262. package/src/utils/logger/Format.ts +0 -15
@@ -4,7 +4,6 @@
4
4
  * 关键点(中文)
5
5
  * - 只负责当前 session 的 user / assistant 消息补写。
6
6
  * - 只依赖当前 session 的 history Store,不感知其他 session。
7
- * - 会话更新后的通知也收在这里,避免把写入细节散到 Session 主类里。
8
7
  */
9
8
 
10
9
  import type { JsonObject } from "@/types/common/Json.js";
@@ -25,10 +24,6 @@ type SessionHistoryWriterOptions = {
25
24
  */
26
25
  getHistoryStore: () => SessionHistoryStore;
27
26
 
28
- /**
29
- * session 更新后的异步回调。
30
- */
31
- runAfterSessionUpdated?: (sessionId: string) => Promise<void>;
32
27
  };
33
28
 
34
29
  /**
@@ -37,29 +32,15 @@ type SessionHistoryWriterOptions = {
37
32
  export class SessionHistoryWriter {
38
33
  private readonly sessionId: string;
39
34
  private readonly getHistoryStore: SessionHistoryWriterOptions["getHistoryStore"];
40
- private readonly runAfterSessionUpdated?: SessionHistoryWriterOptions["runAfterSessionUpdated"];
41
35
 
42
36
  constructor(options: SessionHistoryWriterOptions) {
43
37
  this.sessionId = String(options.sessionId || "").trim();
44
38
  this.getHistoryStore = options.getHistoryStore;
45
- this.runAfterSessionUpdated = options.runAfterSessionUpdated;
46
39
  if (!this.sessionId) {
47
40
  throw new Error("SessionHistoryWriter requires a non-empty sessionId");
48
41
  }
49
42
  }
50
43
 
51
- /**
52
- * 触发 session 更新后的异步回调。
53
- */
54
- async afterSessionUpdatedAsync(): Promise<void> {
55
- if (!this.runAfterSessionUpdated) return;
56
- try {
57
- await this.runAfterSessionUpdated(this.sessionId);
58
- } catch {
59
- // ignore
60
- }
61
- }
62
-
63
44
  /**
64
45
  * 追加一条 user 消息。
65
46
  */
@@ -68,29 +49,23 @@ export class SessionHistoryWriter {
68
49
  text?: string;
69
50
  extra?: JsonObject;
70
51
  }): Promise<void> {
71
- try {
72
- const historyStore = this.getHistoryStore();
73
- if (params.message && typeof params.message === "object") {
74
- await historyStore.write_record(params.message);
75
- void this.afterSessionUpdatedAsync();
76
- return;
77
- }
52
+ const historyStore = this.getHistoryStore();
53
+ if (params.message && typeof params.message === "object") {
54
+ await historyStore.write_record(params.message);
55
+ return;
56
+ }
78
57
 
79
- const fallbackText = String(params.text || "").trim();
80
- if (!fallbackText) return;
58
+ const fallbackText = String(params.text || "").trim();
59
+ if (!fallbackText) return;
81
60
 
82
- const message = historyStore.userText({
83
- text: fallbackText,
84
- metadata: {
85
- sessionId: this.sessionId,
86
- extra: params.extra,
87
- } as Omit<SessionMetadataV1, "v" | "ts">,
88
- });
89
- await historyStore.write_record(message);
90
- void this.afterSessionUpdatedAsync();
91
- } catch {
92
- // ignore
93
- }
61
+ const message = historyStore.userText({
62
+ text: fallbackText,
63
+ metadata: {
64
+ sessionId: this.sessionId,
65
+ extra: params.extra,
66
+ } as Omit<SessionMetadataV1, "v" | "ts">,
67
+ });
68
+ await historyStore.write_record(message);
94
69
  }
95
70
 
96
71
  /**
@@ -101,31 +76,25 @@ export class SessionHistoryWriter {
101
76
  fallbackText?: string;
102
77
  extra?: JsonObject;
103
78
  }): Promise<void> {
104
- try {
105
- const historyStore = this.getHistoryStore();
106
- if (params.message && typeof params.message === "object") {
107
- await historyStore.finalize_inflight(params.message);
108
- void this.afterSessionUpdatedAsync();
109
- return;
110
- }
79
+ const historyStore = this.getHistoryStore();
80
+ if (params.message && typeof params.message === "object") {
81
+ await historyStore.finalize_inflight(params.message);
82
+ return;
83
+ }
111
84
 
112
- const fallbackText = String(params.fallbackText || "").trim();
113
- if (!fallbackText) return;
85
+ const fallbackText = String(params.fallbackText || "").trim();
86
+ if (!fallbackText) return;
114
87
 
115
- await historyStore.finalize_inflight(
116
- historyStore.assistantText({
117
- text: fallbackText,
118
- metadata: {
119
- sessionId: this.sessionId,
120
- extra: params.extra,
121
- },
122
- kind: "normal",
123
- source: "egress",
124
- }),
125
- );
126
- void this.afterSessionUpdatedAsync();
127
- } catch {
128
- // ignore
129
- }
88
+ await historyStore.finalize_inflight(
89
+ historyStore.assistantText({
90
+ text: fallbackText,
91
+ metadata: {
92
+ sessionId: this.sessionId,
93
+ extra: params.extra,
94
+ },
95
+ kind: "normal",
96
+ source: "egress",
97
+ }),
98
+ );
130
99
  }
131
100
  }
@@ -15,6 +15,11 @@ import fs from "fs-extra";
15
15
  import type { FileUIPart } from "ai";
16
16
  import { getDowncityResourcesDirPath } from "@/config/Paths.js";
17
17
 
18
+ /** 单个 assistant 资源下载允许占用的最长时间。 */
19
+ const ASSISTANT_RESOURCE_TIMEOUT_MS = 30_000;
20
+ /** 单个 assistant 资源允许写入内存与磁盘的最大字节数。 */
21
+ const MAX_ASSISTANT_RESOURCE_BYTES = 25 * 1024 * 1024;
22
+
18
23
  type ParsedDataUrl = {
19
24
  /**
20
25
  * data URL 声明的媒体类型。
@@ -186,22 +191,49 @@ async function read_remote_resource(raw_url: string): Promise<{
186
191
  filename?: string;
187
192
  bytes: Buffer;
188
193
  }> {
189
- const result = await fetch_with_retry(raw_url);
190
- if (!result.ok) {
191
- throw new Error(
192
- `Failed to download assistant file resource: ${result.status} :: url=${raw_url}`,
193
- );
194
- }
195
- const array_buffer = await result.arrayBuffer();
196
- const bytes = Buffer.from(array_buffer);
197
- if (bytes.length === 0) {
198
- throw new Error(`Downloaded assistant file resource is empty :: url=${raw_url}`);
194
+ const abort_controller = new AbortController();
195
+ const timeout = setTimeout(() => {
196
+ abort_controller.abort(new Error("Assistant resource download timed out"));
197
+ }, ASSISTANT_RESOURCE_TIMEOUT_MS);
198
+ try {
199
+ const result = await fetch_with_retry(raw_url, abort_controller.signal);
200
+ if (!result.ok) {
201
+ throw new Error(
202
+ `Failed to download assistant file resource: ${result.status} :: url=${raw_url}`,
203
+ );
204
+ }
205
+ const declared_size = Number(result.headers.get("content-length"));
206
+ if (Number.isFinite(declared_size) && declared_size > MAX_ASSISTANT_RESOURCE_BYTES) {
207
+ throw new Error(`Assistant file resource exceeds 25 MiB :: url=${raw_url}`);
208
+ }
209
+ if (!result.body) {
210
+ throw new Error(`Assistant file resource has no response body :: url=${raw_url}`);
211
+ }
212
+
213
+ const reader = result.body.getReader();
214
+ const chunks: Buffer[] = [];
215
+ let total_bytes = 0;
216
+ while (true) {
217
+ const { done, value } = await reader.read();
218
+ if (done) break;
219
+ total_bytes += value.byteLength;
220
+ if (total_bytes > MAX_ASSISTANT_RESOURCE_BYTES) {
221
+ await reader.cancel();
222
+ throw new Error(`Assistant file resource exceeds 25 MiB :: url=${raw_url}`);
223
+ }
224
+ chunks.push(Buffer.from(value));
225
+ }
226
+ if (total_bytes === 0) {
227
+ throw new Error(`Downloaded assistant file resource is empty :: url=${raw_url}`);
228
+ }
229
+ return {
230
+ mediaType: result.headers.get("content-type")?.split(";")[0]?.trim(),
231
+ filename: filename_from_url(raw_url),
232
+ bytes: Buffer.concat(chunks, total_bytes),
233
+ };
234
+ } finally {
235
+ clearTimeout(timeout);
199
236
  }
200
- return {
201
- mediaType: result.headers.get("content-type")?.split(";")[0]?.trim(),
202
- filename: filename_from_url(raw_url),
203
- bytes,
204
- };
205
237
  }
206
238
 
207
239
  /**
@@ -213,13 +245,14 @@ async function read_remote_resource(raw_url: string): Promise<{
213
245
  */
214
246
  async function fetch_with_retry(
215
247
  raw_url: string,
248
+ signal: AbortSignal,
216
249
  ): Promise<Response> {
217
250
  const delays_ms = [250, 1_000];
218
251
  const attempts = delays_ms.length + 1;
219
252
  let last_error: unknown;
220
253
  for (let attempt = 0; attempt < attempts; attempt += 1) {
221
254
  try {
222
- return await fetch(raw_url);
255
+ return await fetch(raw_url, { signal });
223
256
  } catch (error) {
224
257
  last_error = error;
225
258
  if (!is_transient_fetch_error(error) || attempt === attempts - 1) {
@@ -286,7 +319,14 @@ async function read_local_resource(
286
319
  : path.isAbsolute(raw)
287
320
  ? raw
288
321
  : path.resolve(projectRoot, raw);
322
+ const file_stat = await fs.stat(file_path);
323
+ if (file_stat.size > MAX_ASSISTANT_RESOURCE_BYTES) {
324
+ throw new Error(`Assistant file resource exceeds 25 MiB: ${file_path}`);
325
+ }
289
326
  const bytes = await fs.readFile(file_path);
327
+ if (bytes.length > MAX_ASSISTANT_RESOURCE_BYTES) {
328
+ throw new Error(`Assistant file resource exceeds 25 MiB: ${file_path}`);
329
+ }
290
330
  if (bytes.length === 0) {
291
331
  throw new Error(`Assistant file resource is empty: ${file_path}`);
292
332
  }
@@ -306,6 +346,9 @@ async function materialize_file_part(params: {
306
346
  }
307
347
  const parsed_data_url = parse_data_url(raw_url);
308
348
  if (parsed_data_url) {
349
+ if (parsed_data_url.bytes.length > MAX_ASSISTANT_RESOURCE_BYTES) {
350
+ throw new Error("Assistant data URL resource exceeds 25 MiB");
351
+ }
309
352
  const media_type =
310
353
  String(params.part.mediaType || parsed_data_url.media_type || "").trim() ||
311
354
  "application/octet-stream";
@@ -352,8 +395,9 @@ async function materialize_file_part(params: {
352
395
  } catch (error) {
353
396
  // 关键点(中文):远程下载失败时保留原始 URL,不让整张图导致 action 失败。
354
397
  // 这样图片至少以链接形式存在,用户可手动点击或后续再试。
398
+ const error_message = error instanceof Error ? error.message : String(error);
355
399
  console.warn(
356
- `[AssistantFileResource] remote download failed, keeping original url :: ${describe_error_cause(error instanceof Error ? error : new Error(String(error)))} :: url=${raw_url}`,
400
+ `[AssistantFileResource] remote download failed, keeping original url :: ${error_message}`,
357
401
  );
358
402
  return params.part;
359
403
  }
@@ -23,10 +23,6 @@ interface ExecutorInflightServiceOptions {
23
23
  */
24
24
  history_store: SessionHistoryStore;
25
25
 
26
- /**
27
- * inflight 更新完成后的异步通知。
28
- */
29
- run_after_session_updated_async?: () => Promise<void>;
30
26
  }
31
27
 
32
28
  /**
@@ -35,13 +31,10 @@ interface ExecutorInflightServiceOptions {
35
31
  export class ExecutorInflightService {
36
32
  private readonly session_id: string;
37
33
  private readonly history_store: SessionHistoryStore;
38
- private readonly run_after_session_updated_async?: ExecutorInflightServiceOptions["run_after_session_updated_async"];
39
34
 
40
35
  constructor(options: ExecutorInflightServiceOptions) {
41
36
  this.session_id = String(options.session_id || "").trim();
42
37
  this.history_store = options.history_store;
43
- this.run_after_session_updated_async =
44
- options.run_after_session_updated_async;
45
38
 
46
39
  if (!this.session_id) {
47
40
  throw new Error("ExecutorInflightService requires a non-empty session_id");
@@ -98,8 +91,5 @@ export class ExecutorInflightService {
98
91
  };
99
92
 
100
93
  await this.history_store.write_inflight(next_message);
101
- if (this.run_after_session_updated_async) {
102
- await this.run_after_session_updated_async();
103
- }
104
94
  }
105
95
  }
@@ -34,6 +34,7 @@ import type {
34
34
  SessionHistoryCompactInput,
35
35
  SessionHistoryStore,
36
36
  } from "@/executor/store/history/SessionHistoryStore.js";
37
+ import { resolve_session_message_preview } from "@/session/preview/SessionMessagePreview.js";
37
38
 
38
39
  /**
39
40
  * JSONL history store 构造参数。
@@ -359,6 +360,19 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
359
360
  ...(this.normalizeText(raw.modelLabel)
360
361
  ? { modelLabel: this.normalizeText(raw.modelLabel) }
361
362
  : {}),
363
+ ...(typeof raw.messageCount === "number" &&
364
+ Number.isInteger(raw.messageCount) &&
365
+ raw.messageCount >= 0
366
+ ? { messageCount: raw.messageCount }
367
+ : {}),
368
+ ...(this.normalizeText(raw.previewText)
369
+ ? { previewText: this.normalizeText(raw.previewText) }
370
+ : {}),
371
+ ...(typeof raw.historyBytes === "number" &&
372
+ Number.isInteger(raw.historyBytes) &&
373
+ raw.historyBytes >= 0
374
+ ? { historyBytes: raw.historyBytes }
375
+ : {}),
362
376
  };
363
377
  } catch {
364
378
  return {
@@ -391,6 +405,19 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
391
405
  ...(this.normalizeText(next.modelLabel)
392
406
  ? { modelLabel: this.normalizeText(next.modelLabel) }
393
407
  : {}),
408
+ ...(typeof next.messageCount === "number" &&
409
+ Number.isInteger(next.messageCount) &&
410
+ next.messageCount >= 0
411
+ ? { messageCount: next.messageCount }
412
+ : {}),
413
+ ...(this.normalizeText(next.previewText)
414
+ ? { previewText: this.normalizeText(next.previewText) }
415
+ : {}),
416
+ ...(typeof next.historyBytes === "number" &&
417
+ Number.isInteger(next.historyBytes) &&
418
+ next.historyBytes >= 0
419
+ ? { historyBytes: next.historyBytes }
420
+ : {}),
394
421
  };
395
422
  await fs.writeJson(this.getMetaFilePath(), normalized, { spaces: 2 });
396
423
  }
@@ -432,6 +459,82 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
432
459
  );
433
460
  }
434
461
 
462
+ private async get_history_bytes_unsafe(): Promise<number> {
463
+ try {
464
+ const file_stat = await statNative(this.getMessagesFilePath());
465
+ return file_stat.size;
466
+ } catch {
467
+ return 0;
468
+ }
469
+ }
470
+
471
+ private async read_persisted_records_unsafe(): Promise<SessionRecordV1[]> {
472
+ const raw = await fs.readFile(this.getMessagesFilePath(), "utf8").catch(() => "");
473
+ const records: SessionRecordV1[] = [];
474
+ for (const line of raw.split("\n").filter(Boolean)) {
475
+ try {
476
+ const record = this.normalizePersistedMessage(JSON.parse(line));
477
+ if (record) records.push(record);
478
+ } catch {
479
+ // 关键点(中文):摘要重建跳过损坏行,与正式历史读取语义保持一致。
480
+ }
481
+ }
482
+ return records;
483
+ }
484
+
485
+ private async write_history_summary_unsafe(
486
+ records: SessionRecordV1[],
487
+ ): Promise<void> {
488
+ const current = await this.readMetaUnsafe();
489
+ const last_record = records[records.length - 1];
490
+ const preview_text = last_record
491
+ ? resolve_session_message_preview(last_record).slice(0, 180).trim()
492
+ : "";
493
+ const { previewText: _previous_preview, ...metadata_without_preview } = current;
494
+ void _previous_preview;
495
+ await this.writeMetaUnsafe({
496
+ ...metadata_without_preview,
497
+ updatedAt: Date.now(),
498
+ messageCount: records.length,
499
+ historyBytes: await this.get_history_bytes_unsafe(),
500
+ ...(preview_text ? { previewText: preview_text } : {}),
501
+ });
502
+ }
503
+
504
+ private async update_history_summary_after_append_unsafe(input: {
505
+ /** 本次追加前 messages.jsonl 的字节长度。 */
506
+ previous_history_bytes: number;
507
+ /** 本次实际追加的 records,按写入顺序排列。 */
508
+ appended_records: SessionRecordV1[];
509
+ }): Promise<void> {
510
+ if (input.appended_records.length === 0) return;
511
+ const current = await this.readMetaUnsafe();
512
+ const previous_message_count = current.messageCount;
513
+ if (
514
+ current.historyBytes !== input.previous_history_bytes ||
515
+ typeof previous_message_count !== "number"
516
+ ) {
517
+ await this.write_history_summary_unsafe(
518
+ await this.read_persisted_records_unsafe(),
519
+ );
520
+ return;
521
+ }
522
+
523
+ const last_record = input.appended_records[input.appended_records.length - 1];
524
+ const preview_text = resolve_session_message_preview(last_record)
525
+ .slice(0, 180)
526
+ .trim();
527
+ const { previewText: _previous_preview, ...metadata_without_preview } = current;
528
+ void _previous_preview;
529
+ await this.writeMetaUnsafe({
530
+ ...metadata_without_preview,
531
+ updatedAt: Date.now(),
532
+ messageCount: previous_message_count + input.appended_records.length,
533
+ historyBytes: await this.get_history_bytes_unsafe(),
534
+ ...(preview_text ? { previewText: preview_text } : {}),
535
+ });
536
+ }
537
+
435
538
  private async rewriteMessagesUnsafe(messages: SessionRecordV1[]): Promise<void> {
436
539
  const file = this.getMessagesFilePath();
437
540
  const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
@@ -445,7 +548,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
445
548
 
446
549
  private async upsert_action_record_unsafe(
447
550
  message: SessionActionRecordV1,
448
- ): Promise<void> {
551
+ ): Promise<SessionRecordV1[]> {
449
552
  const file = this.getMessagesFilePath();
450
553
  const raw = await fs.readFile(file, "utf8").catch(() => "");
451
554
  const messages: SessionRecordV1[] = [];
@@ -468,6 +571,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
468
571
 
469
572
  if (!replaced) messages.push(message);
470
573
  await this.rewriteMessagesUnsafe(messages);
574
+ return messages;
471
575
  }
472
576
 
473
577
  private async withWriteLock<T>(fn: () => Promise<T>): Promise<T> {
@@ -555,8 +659,8 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
555
659
  }),
556
660
  getArchiveDirPath: () => this.getArchiveDirPath(),
557
661
  getMessagesFilePath: () => this.getMessagesFilePath(),
558
- readMetaUnsafe: () => this.readMetaUnsafe(),
559
- writeMetaUnsafe: (next) => this.writeMetaUnsafe(next),
662
+ writeHistorySummaryUnsafe: (records) =>
663
+ this.write_history_summary_unsafe(records),
560
664
  },
561
665
  {
562
666
  model: input.model,
@@ -575,21 +679,31 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
575
679
  const normalized = this.normalizePersistedMessage(message);
576
680
  if (!normalized) return;
577
681
  if (is_session_action_record(normalized)) {
578
- await this.upsert_action_record_unsafe(normalized);
682
+ const records = await this.upsert_action_record_unsafe(normalized);
683
+ await this.write_history_summary_unsafe(records);
579
684
  return;
580
685
  }
581
686
 
687
+ const previous_history_bytes = await this.get_history_bytes_unsafe();
688
+ const appended_records: SessionRecordV1[] = [];
689
+
582
690
  // 关键点(中文):若上一次 assistant 在运行中中断,新的 user 到来前先把残留快照收口到正式历史,
583
691
  // 避免 `list_records()` 时旧 inflight 跑到新 user 后面,打乱时序。
584
692
  if (is_session_message_record(normalized) && normalized.role === "user") {
585
693
  const current_inflight = await this.read_inflight_unsafe();
586
694
  if (current_inflight) {
587
695
  await this.appendMessageUnsafe(current_inflight);
696
+ appended_records.push(current_inflight);
588
697
  await this.removeInflightUnsafe();
589
698
  }
590
699
  }
591
700
 
592
701
  await this.appendMessageUnsafe(normalized);
702
+ appended_records.push(normalized);
703
+ await this.update_history_summary_after_append_unsafe({
704
+ previous_history_bytes,
705
+ appended_records,
706
+ });
593
707
  });
594
708
  }
595
709
 
@@ -611,7 +725,12 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
611
725
  .map((message) => `${JSON.stringify(message)}\n`)
612
726
  .join("");
613
727
  await this.withWriteLock(async () => {
728
+ const previous_history_bytes = await this.get_history_bytes_unsafe();
614
729
  await fs.appendFile(this.getMessagesFilePath(), payload, "utf8");
730
+ await this.update_history_summary_after_append_unsafe({
731
+ previous_history_bytes,
732
+ appended_records: normalized_list,
733
+ });
615
734
  });
616
735
  }
617
736
 
@@ -628,6 +747,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
628
747
 
629
748
  async finalize_inflight(message?: SessionRecordV1 | null): Promise<void> {
630
749
  await this.withWriteLock(async () => {
750
+ const previous_history_bytes = await this.get_history_bytes_unsafe();
631
751
  const current_inflight = await this.read_inflight_unsafe();
632
752
  const normalized_message = this.normalizePersistedMessage(message);
633
753
  const final_message =
@@ -640,6 +760,12 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
640
760
  if (current_inflight) {
641
761
  await this.removeInflightUnsafe();
642
762
  }
763
+ if (final_message) {
764
+ await this.update_history_summary_after_append_unsafe({
765
+ previous_history_bytes,
766
+ appended_records: [final_message],
767
+ });
768
+ }
643
769
  });
644
770
  }
645
771
 
@@ -23,4 +23,10 @@ export type SessionHistoryMetaV1 = {
23
23
  title?: string;
24
24
  /** 当前 session 绑定模型的可读标签。 */
25
25
  modelLabel?: string;
26
+ /** 当前 session 已持久化记录数量,用于列表查询避免扫描完整历史。 */
27
+ messageCount?: number;
28
+ /** 当前 session 最后一条记录的用户可见摘要,用于列表预览。 */
29
+ previewText?: string;
30
+ /** 生成当前摘要时 messages.jsonl 的字节长度,用于校验摘要是否仍然有效。 */
31
+ historyBytes?: number;
26
32
  };