@downcity/agent 1.1.338 → 1.1.400

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 (987) hide show
  1. package/README.md +31 -15
  2. package/bin/agent/Agent.d.ts +42 -91
  3. package/bin/agent/Agent.d.ts.map +1 -1
  4. package/bin/agent/Agent.js +115 -200
  5. package/bin/agent/Agent.js.map +1 -1
  6. package/bin/agent/AgentModel.d.ts +8 -8
  7. package/bin/agent/AgentModel.d.ts.map +1 -1
  8. package/bin/agent/AgentModel.js +5 -5
  9. package/bin/agent/AgentModel.js.map +1 -1
  10. package/bin/agent/AgentSessions.d.ts +42 -28
  11. package/bin/agent/AgentSessions.d.ts.map +1 -1
  12. package/bin/agent/AgentSessions.js +80 -44
  13. package/bin/agent/AgentSessions.js.map +1 -1
  14. package/bin/agent/WorkspaceEntry.d.ts +82 -0
  15. package/bin/agent/WorkspaceEntry.d.ts.map +1 -0
  16. package/bin/agent/WorkspaceEntry.js +246 -0
  17. package/bin/agent/WorkspaceEntry.js.map +1 -0
  18. package/bin/agent/index.d.ts +5 -0
  19. package/bin/agent/index.d.ts.map +1 -0
  20. package/bin/agent/index.js +5 -0
  21. package/bin/agent/index.js.map +1 -0
  22. package/bin/city/host/CityHostRegistry.d.ts +44 -0
  23. package/bin/city/host/CityHostRegistry.d.ts.map +1 -0
  24. package/bin/city/host/CityHostRegistry.js +225 -0
  25. package/bin/city/host/CityHostRegistry.js.map +1 -0
  26. package/bin/city/index.d.ts +22 -0
  27. package/bin/city/index.d.ts.map +1 -0
  28. package/bin/city/index.js +10 -0
  29. package/bin/city/index.js.map +1 -0
  30. package/bin/city/runtime/City.d.ts +99 -0
  31. package/bin/city/runtime/City.d.ts.map +1 -0
  32. package/bin/city/runtime/City.js +376 -0
  33. package/bin/city/runtime/City.js.map +1 -0
  34. package/bin/city/transport/SerializedTransport.d.ts +30 -0
  35. package/bin/city/transport/SerializedTransport.d.ts.map +1 -0
  36. package/bin/city/transport/SerializedTransport.js +57 -0
  37. package/bin/city/transport/SerializedTransport.js.map +1 -0
  38. package/bin/city/transport/http/AgentHTTP.d.ts +66 -0
  39. package/bin/city/transport/http/AgentHTTP.d.ts.map +1 -0
  40. package/bin/city/transport/http/AgentHTTP.js +142 -0
  41. package/bin/city/transport/http/AgentHTTP.js.map +1 -0
  42. package/bin/city/transport/http/CityHTTP.d.ts +44 -0
  43. package/bin/city/transport/http/CityHTTP.d.ts.map +1 -0
  44. package/bin/city/transport/http/CityHTTP.js +171 -0
  45. package/bin/city/transport/http/CityHTTP.js.map +1 -0
  46. package/bin/city/transport/http/NodeHttpAdapter.d.ts +27 -0
  47. package/bin/city/transport/http/NodeHttpAdapter.d.ts.map +1 -0
  48. package/bin/city/transport/http/NodeHttpAdapter.js +57 -0
  49. package/bin/city/transport/http/NodeHttpAdapter.js.map +1 -0
  50. package/bin/city/transport/http/routes/RuntimeRoutes.d.ts +15 -0
  51. package/bin/city/transport/http/routes/RuntimeRoutes.d.ts.map +1 -0
  52. package/bin/city/transport/http/routes/RuntimeRoutes.js +38 -0
  53. package/bin/city/transport/http/routes/RuntimeRoutes.js.map +1 -0
  54. package/bin/city/transport/http/routes/SessionRoutes.d.ts +17 -0
  55. package/bin/city/transport/http/routes/SessionRoutes.d.ts.map +1 -0
  56. package/bin/city/transport/http/routes/SessionRoutes.js +417 -0
  57. package/bin/city/transport/http/routes/SessionRoutes.js.map +1 -0
  58. package/bin/city/transport/rpc/AgentRPC.d.ts +47 -0
  59. package/bin/city/transport/rpc/AgentRPC.d.ts.map +1 -0
  60. package/bin/city/transport/rpc/AgentRPC.js +89 -0
  61. package/bin/city/transport/rpc/AgentRPC.js.map +1 -0
  62. package/bin/city/transport/rpc/CityRPC.d.ts +26 -0
  63. package/bin/city/transport/rpc/CityRPC.d.ts.map +1 -0
  64. package/bin/city/transport/rpc/CityRPC.js +110 -0
  65. package/bin/city/transport/rpc/CityRPC.js.map +1 -0
  66. package/bin/city/transport/rpc/RpcServer.d.ts +30 -0
  67. package/bin/city/transport/rpc/RpcServer.d.ts.map +1 -0
  68. package/bin/city/transport/rpc/RpcServer.js +125 -0
  69. package/bin/city/transport/rpc/RpcServer.js.map +1 -0
  70. package/bin/city/transport/rpc/server/InternalHandlers.d.ts +22 -0
  71. package/bin/city/transport/rpc/server/InternalHandlers.d.ts.map +1 -0
  72. package/bin/city/transport/rpc/server/InternalHandlers.js +196 -0
  73. package/bin/city/transport/rpc/server/InternalHandlers.js.map +1 -0
  74. package/bin/city/transport/rpc/server/RequestDispatcher.d.ts +27 -0
  75. package/bin/city/transport/rpc/server/RequestDispatcher.d.ts.map +1 -0
  76. package/bin/city/transport/rpc/server/RequestDispatcher.js +38 -0
  77. package/bin/city/transport/rpc/server/RequestDispatcher.js.map +1 -0
  78. package/bin/city/transport/rpc/server/SdkSessionHandlers.d.ts +25 -0
  79. package/bin/city/transport/rpc/server/SdkSessionHandlers.d.ts.map +1 -0
  80. package/bin/city/transport/rpc/server/SdkSessionHandlers.js +146 -0
  81. package/bin/city/transport/rpc/server/SdkSessionHandlers.js.map +1 -0
  82. package/bin/city/transport/rpc/server/ServerTypes.d.ts +94 -0
  83. package/bin/city/transport/rpc/server/ServerTypes.d.ts.map +1 -0
  84. package/bin/city/transport/rpc/server/ServerTypes.js +9 -0
  85. package/bin/city/transport/rpc/server/ServerTypes.js.map +1 -0
  86. package/bin/city/transport/session/RemoteSessionConfig.d.ts +15 -0
  87. package/bin/city/transport/session/RemoteSessionConfig.d.ts.map +1 -0
  88. package/bin/city/transport/session/RemoteSessionConfig.js +23 -0
  89. package/bin/city/transport/session/RemoteSessionConfig.js.map +1 -0
  90. package/bin/city/transport/types/AgentHttpBinding.d.ts +24 -0
  91. package/bin/city/transport/types/AgentHttpBinding.d.ts.map +1 -0
  92. package/bin/city/transport/types/AgentHttpBinding.js +5 -0
  93. package/bin/city/transport/types/AgentHttpBinding.js.map +1 -0
  94. package/bin/city/transport/types/AgentHttpRuntime.d.ts +12 -0
  95. package/bin/city/transport/types/AgentHttpRuntime.d.ts.map +1 -0
  96. package/bin/city/transport/types/AgentHttpRuntime.js +7 -0
  97. package/bin/city/transport/types/AgentHttpRuntime.js.map +1 -0
  98. package/bin/city/transport/types/AgentRpcBinding.d.ts +27 -0
  99. package/bin/city/transport/types/AgentRpcBinding.d.ts.map +1 -0
  100. package/bin/city/transport/types/AgentRpcBinding.js +8 -0
  101. package/bin/city/transport/types/AgentRpcBinding.js.map +1 -0
  102. package/bin/city/transport/types/AgentRpcRuntime.d.ts +18 -0
  103. package/bin/city/transport/types/AgentRpcRuntime.d.ts.map +1 -0
  104. package/bin/city/transport/types/AgentRpcRuntime.js +7 -0
  105. package/bin/city/transport/types/AgentRpcRuntime.js.map +1 -0
  106. package/bin/city/transport/types/AgentSessionModelResolver.d.ts +11 -0
  107. package/bin/city/transport/types/AgentSessionModelResolver.d.ts.map +1 -0
  108. package/bin/city/transport/types/AgentSessionModelResolver.js +7 -0
  109. package/bin/city/transport/types/AgentSessionModelResolver.js.map +1 -0
  110. package/bin/city/transport/types/CityHttpRuntime.d.ts +36 -0
  111. package/bin/city/transport/types/CityHttpRuntime.d.ts.map +1 -0
  112. package/bin/city/transport/types/CityHttpRuntime.js +8 -0
  113. package/bin/city/transport/types/CityHttpRuntime.js.map +1 -0
  114. package/bin/city/transport/types/CityRpcRuntime.d.ts +17 -0
  115. package/bin/city/transport/types/CityRpcRuntime.d.ts.map +1 -0
  116. package/bin/city/transport/types/CityRpcRuntime.js +8 -0
  117. package/bin/city/transport/types/CityRpcRuntime.js.map +1 -0
  118. package/bin/city/transport/types/RpcProtocol.d.ts +24 -0
  119. package/bin/city/transport/types/RpcProtocol.d.ts.map +1 -0
  120. package/bin/city/transport/types/RpcProtocol.js +9 -0
  121. package/bin/city/transport/types/RpcProtocol.js.map +1 -0
  122. package/bin/city/types/City.d.ts +84 -0
  123. package/bin/city/types/City.d.ts.map +1 -0
  124. package/bin/city/types/City.js +7 -0
  125. package/bin/city/types/City.js.map +1 -0
  126. package/bin/city/types/PluginHostContext.d.ts +31 -0
  127. package/bin/city/types/PluginHostContext.d.ts.map +1 -0
  128. package/bin/city/types/PluginHostContext.js +11 -0
  129. package/bin/city/types/PluginHostContext.js.map +1 -0
  130. package/bin/city/types/SerializedTransport.d.ts +16 -0
  131. package/bin/city/types/SerializedTransport.d.ts.map +1 -0
  132. package/bin/city/types/SerializedTransport.js +3 -0
  133. package/bin/city/types/SerializedTransport.js.map +1 -0
  134. package/bin/executor/Executor.d.ts +4 -4
  135. package/bin/executor/Executor.d.ts.map +1 -1
  136. package/bin/executor/Executor.js +90 -34
  137. package/bin/executor/Executor.js.map +1 -1
  138. package/bin/executor/composer/system/SessionSystemComposer.d.ts +2 -2
  139. package/bin/executor/composer/system/SessionSystemComposer.d.ts.map +1 -1
  140. package/bin/executor/composer/system/default/DefaultSessionSystemComposer.d.ts +3 -3
  141. package/bin/executor/composer/system/default/DefaultSessionSystemComposer.js +1 -1
  142. package/bin/executor/composer/system/default/PromptRenderer.d.ts +3 -3
  143. package/bin/executor/composer/system/default/PromptRenderer.d.ts.map +1 -1
  144. package/bin/executor/composer/system/default/PromptRenderer.js +1 -1
  145. package/bin/executor/composer/system/default/SystemDomain.d.ts +3 -3
  146. package/bin/executor/composer/system/default/SystemDomain.d.ts.map +1 -1
  147. package/bin/executor/composer/system/default/SystemDomain.js +3 -3
  148. package/bin/executor/composer/system/default/SystemDomain.js.map +1 -1
  149. package/bin/executor/composer/system/default/SystemPromptAssets.js +2 -2
  150. package/bin/executor/composer/system/default/assets/core.prompt.d.ts +1 -1
  151. package/bin/executor/composer/system/default/assets/core.prompt.d.ts.map +1 -1
  152. package/bin/executor/composer/system/default/assets/core.prompt.js +1 -1
  153. package/bin/executor/composer/system/default/assets/core.prompt.js.map +1 -1
  154. package/bin/executor/composer/system/default/variables/GeoContext.d.ts +1 -1
  155. package/bin/executor/composer/system/default/variables/VariableReplacer.js +1 -1
  156. package/bin/executor/core-engine/CoreEngineContextCompaction.d.ts +6 -23
  157. package/bin/executor/core-engine/CoreEngineContextCompaction.d.ts.map +1 -1
  158. package/bin/executor/core-engine/CoreEngineContextCompaction.js +112 -344
  159. package/bin/executor/core-engine/CoreEngineContextCompaction.js.map +1 -1
  160. package/bin/executor/core-engine/CoreEngineError.d.ts +1 -1
  161. package/bin/executor/core-engine/CoreEngineError.js +1 -1
  162. package/bin/executor/core-engine/CoreEngineLoopDecision.d.ts +2 -3
  163. package/bin/executor/core-engine/CoreEngineLoopDecision.d.ts.map +1 -1
  164. package/bin/executor/core-engine/CoreEngineLoopDecision.js +1 -15
  165. package/bin/executor/core-engine/CoreEngineLoopDecision.js.map +1 -1
  166. package/bin/executor/core-engine/CoreEngineMessageState.d.ts +16 -39
  167. package/bin/executor/core-engine/CoreEngineMessageState.d.ts.map +1 -1
  168. package/bin/executor/core-engine/CoreEngineMessageState.js +25 -60
  169. package/bin/executor/core-engine/CoreEngineMessageState.js.map +1 -1
  170. package/bin/executor/core-engine/CoreEngineRunner.d.ts +10 -6
  171. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
  172. package/bin/executor/core-engine/CoreEngineRunner.js +146 -234
  173. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
  174. package/bin/executor/core-engine/CoreEngineSignals.d.ts +20 -58
  175. package/bin/executor/core-engine/CoreEngineSignals.d.ts.map +1 -1
  176. package/bin/executor/core-engine/CoreEngineSignals.js +84 -304
  177. package/bin/executor/core-engine/CoreEngineSignals.js.map +1 -1
  178. package/bin/executor/messages/SessionAttachmentMapper.d.ts +6 -37
  179. package/bin/executor/messages/SessionAttachmentMapper.d.ts.map +1 -1
  180. package/bin/executor/messages/SessionAttachmentMapper.js +12 -243
  181. package/bin/executor/messages/SessionAttachmentMapper.js.map +1 -1
  182. package/bin/executor/messages/SessionMessageLog.d.ts +7 -14
  183. package/bin/executor/messages/SessionMessageLog.d.ts.map +1 -1
  184. package/bin/executor/messages/SessionMessageLog.js +11 -26
  185. package/bin/executor/messages/SessionMessageLog.js.map +1 -1
  186. package/bin/executor/messages/SessionModelMessages.d.ts +14 -0
  187. package/bin/executor/messages/SessionModelMessages.d.ts.map +1 -0
  188. package/bin/executor/messages/SessionModelMessages.js +187 -0
  189. package/bin/executor/messages/SessionModelMessages.js.map +1 -0
  190. package/bin/executor/model/ModelGenerate.d.ts +22 -0
  191. package/bin/executor/model/ModelGenerate.d.ts.map +1 -0
  192. package/bin/executor/model/ModelGenerate.js +54 -0
  193. package/bin/executor/model/ModelGenerate.js.map +1 -0
  194. package/bin/executor/model/ModelStepRunner.d.ts +70 -0
  195. package/bin/executor/model/ModelStepRunner.d.ts.map +1 -0
  196. package/bin/executor/model/ModelStepRunner.js +284 -0
  197. package/bin/executor/model/ModelStepRunner.js.map +1 -0
  198. package/bin/executor/services/ExecutorRecoveryPolicy.d.ts +4 -4
  199. package/bin/executor/services/ExecutorRecoveryPolicy.d.ts.map +1 -1
  200. package/bin/executor/types/SessionHistoryMeta.d.ts +7 -2
  201. package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
  202. package/bin/executor/types/SessionHistoryMeta.js +1 -1
  203. package/bin/executor/types/SessionLoop.d.ts +1 -21
  204. package/bin/executor/types/SessionLoop.d.ts.map +1 -1
  205. package/bin/executor/types/SessionPrompts.d.ts +8 -7
  206. package/bin/executor/types/SessionPrompts.d.ts.map +1 -1
  207. package/bin/executor/types/SessionPrompts.js +1 -2
  208. package/bin/executor/types/SessionPrompts.js.map +1 -1
  209. package/bin/group/Group.d.ts +20 -0
  210. package/bin/group/Group.d.ts.map +1 -0
  211. package/bin/group/Group.js +42 -0
  212. package/bin/group/Group.js.map +1 -0
  213. package/bin/group/GroupSession.d.ts +103 -0
  214. package/bin/group/GroupSession.d.ts.map +1 -0
  215. package/bin/group/GroupSession.js +666 -0
  216. package/bin/group/GroupSession.js.map +1 -0
  217. package/bin/group/GroupSessions.d.ts +21 -0
  218. package/bin/group/GroupSessions.d.ts.map +1 -0
  219. package/bin/group/GroupSessions.js +89 -0
  220. package/bin/group/GroupSessions.js.map +1 -0
  221. package/bin/group/index.d.ts +9 -0
  222. package/bin/group/index.d.ts.map +1 -0
  223. package/bin/group/index.js +6 -0
  224. package/bin/group/index.js.map +1 -0
  225. package/bin/index.d.ts +28 -20
  226. package/bin/index.d.ts.map +1 -1
  227. package/bin/index.js +7 -9
  228. package/bin/index.js.map +1 -1
  229. package/bin/internal/AgentRuntime.d.ts +43 -0
  230. package/bin/internal/AgentRuntime.d.ts.map +1 -0
  231. package/bin/internal/AgentRuntime.js +179 -0
  232. package/bin/internal/AgentRuntime.js.map +1 -0
  233. package/bin/internal/GroupRuntime.d.ts +17 -0
  234. package/bin/internal/GroupRuntime.d.ts.map +1 -0
  235. package/bin/internal/GroupRuntime.js +66 -0
  236. package/bin/internal/GroupRuntime.js.map +1 -0
  237. package/bin/internal/index.d.ts +8 -0
  238. package/bin/internal/index.d.ts.map +1 -0
  239. package/bin/internal/index.js +8 -0
  240. package/bin/internal/index.js.map +1 -0
  241. package/bin/plugin/core/ActionScheduleExecutor.d.ts +3 -2
  242. package/bin/plugin/core/ActionScheduleExecutor.d.ts.map +1 -1
  243. package/bin/plugin/core/ActionScheduleExecutor.js +14 -3
  244. package/bin/plugin/core/ActionScheduleExecutor.js.map +1 -1
  245. package/bin/plugin/core/ActionScheduleRuntime.d.ts +3 -1
  246. package/bin/plugin/core/ActionScheduleRuntime.d.ts.map +1 -1
  247. package/bin/plugin/core/ActionScheduleRuntime.js +9 -8
  248. package/bin/plugin/core/ActionScheduleRuntime.js.map +1 -1
  249. package/bin/plugin/core/ActionScheduleStore.d.ts +7 -3
  250. package/bin/plugin/core/ActionScheduleStore.d.ts.map +1 -1
  251. package/bin/plugin/core/ActionScheduleStore.js +32 -9
  252. package/bin/plugin/core/ActionScheduleStore.js.map +1 -1
  253. package/bin/plugin/core/HookRegistry.d.ts +4 -6
  254. package/bin/plugin/core/HookRegistry.d.ts.map +1 -1
  255. package/bin/plugin/core/HookRegistry.js +8 -14
  256. package/bin/plugin/core/HookRegistry.js.map +1 -1
  257. package/bin/plugin/core/PluginActionExecution.d.ts +33 -0
  258. package/bin/plugin/core/PluginActionExecution.d.ts.map +1 -0
  259. package/bin/plugin/core/PluginActionExecution.js +168 -0
  260. package/bin/plugin/core/PluginActionExecution.js.map +1 -0
  261. package/bin/plugin/core/PluginActionFactory.d.ts +6 -3
  262. package/bin/plugin/core/PluginActionFactory.d.ts.map +1 -1
  263. package/bin/plugin/core/PluginActionFactory.js +3 -0
  264. package/bin/plugin/core/PluginActionFactory.js.map +1 -1
  265. package/bin/plugin/core/PluginContext.d.ts +14 -4
  266. package/bin/plugin/core/PluginContext.d.ts.map +1 -1
  267. package/bin/plugin/core/PluginContext.js +11 -2
  268. package/bin/plugin/core/PluginContext.js.map +1 -1
  269. package/bin/plugin/core/PluginHttpRoutes.d.ts +1 -1
  270. package/bin/plugin/core/PluginHttpRoutes.d.ts.map +1 -1
  271. package/bin/plugin/core/PluginHttpRoutes.js +1 -1
  272. package/bin/plugin/core/PluginHttpRoutes.js.map +1 -1
  273. package/bin/plugin/core/PluginRegistry.d.ts +41 -26
  274. package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
  275. package/bin/plugin/core/PluginRegistry.js +125 -107
  276. package/bin/plugin/core/PluginRegistry.js.map +1 -1
  277. package/bin/plugin/tool/PluginToolRuntime.d.ts.map +1 -1
  278. package/bin/plugin/tool/PluginToolRuntime.js +4 -1
  279. package/bin/plugin/tool/PluginToolRuntime.js.map +1 -1
  280. package/bin/plugin/tool/PluginToolSchemas.d.ts +22 -2
  281. package/bin/plugin/tool/PluginToolSchemas.d.ts.map +1 -1
  282. package/bin/plugin/tool/PluginToolSchemas.js +3 -4
  283. package/bin/plugin/tool/PluginToolSchemas.js.map +1 -1
  284. package/bin/plugin/tool/PluginTools.d.ts +5 -8
  285. package/bin/plugin/tool/PluginTools.d.ts.map +1 -1
  286. package/bin/plugin/tool/PluginTools.js +9 -8
  287. package/bin/plugin/tool/PluginTools.js.map +1 -1
  288. package/bin/plugin/types/ActionSchedule.d.ts +6 -0
  289. package/bin/plugin/types/ActionSchedule.d.ts.map +1 -1
  290. package/bin/remote/RemoteAgent.js +2 -2
  291. package/bin/remote/RemoteSession.d.ts +1 -1
  292. package/bin/remote/TransportFactory.d.ts +1 -1
  293. package/bin/remote/TransportFactory.js +2 -2
  294. package/bin/remote/transports/HttpRemoteAgentTransport.d.ts +1 -1
  295. package/bin/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  296. package/bin/remote/transports/HttpRemoteAgentTransport.js +3 -3
  297. package/bin/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  298. package/bin/remote/transports/RpcClient.d.ts +1 -0
  299. package/bin/remote/transports/RpcClient.d.ts.map +1 -1
  300. package/bin/remote/transports/RpcClient.js +16 -0
  301. package/bin/remote/transports/RpcClient.js.map +1 -1
  302. package/bin/remote/transports/RpcRemoteAgentTransport.d.ts +1 -1
  303. package/bin/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  304. package/bin/remote/transports/RpcRemoteAgentTransport.js +2 -1
  305. package/bin/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  306. package/bin/session/DefaultSessionComposer.js +4 -4
  307. package/bin/session/DefaultSessionComposer.js.map +1 -1
  308. package/bin/session/Session.d.ts +9 -3
  309. package/bin/session/Session.d.ts.map +1 -1
  310. package/bin/session/Session.js +62 -53
  311. package/bin/session/Session.js.map +1 -1
  312. package/bin/session/SessionLoop.d.ts.map +1 -1
  313. package/bin/session/SessionLoop.js +23 -25
  314. package/bin/session/SessionLoop.js.map +1 -1
  315. package/bin/session/SessionMessages.d.ts +9 -11
  316. package/bin/session/SessionMessages.d.ts.map +1 -1
  317. package/bin/session/SessionMessages.js +56 -104
  318. package/bin/session/SessionMessages.js.map +1 -1
  319. package/bin/session/SessionQueue.d.ts +1 -1
  320. package/bin/session/SessionState.d.ts +3 -0
  321. package/bin/session/SessionState.d.ts.map +1 -1
  322. package/bin/session/SessionState.js +42 -12
  323. package/bin/session/SessionState.js.map +1 -1
  324. package/bin/session/SessionTitle.d.ts +6 -6
  325. package/bin/session/SessionTitle.d.ts.map +1 -1
  326. package/bin/session/SessionTitle.js +11 -39
  327. package/bin/session/SessionTitle.js.map +1 -1
  328. package/bin/session/browse/Browse.d.ts +6 -4
  329. package/bin/session/browse/Browse.d.ts.map +1 -1
  330. package/bin/session/browse/Browse.js +25 -43
  331. package/bin/session/browse/Browse.js.map +1 -1
  332. package/bin/session/control/SessionInteractions.d.ts +2 -2
  333. package/bin/session/control/SessionInteractions.d.ts.map +1 -1
  334. package/bin/session/control/SessionInteractions.js +68 -32
  335. package/bin/session/control/SessionInteractions.js.map +1 -1
  336. package/bin/session/execution/SessionAssistantOutputAdapter.d.ts +20 -17
  337. package/bin/session/execution/SessionAssistantOutputAdapter.d.ts.map +1 -1
  338. package/bin/session/execution/SessionAssistantOutputAdapter.js +35 -35
  339. package/bin/session/execution/SessionAssistantOutputAdapter.js.map +1 -1
  340. package/bin/session/execution/tools/SessionShellApprovalAdapter.d.ts +2 -2
  341. package/bin/session/execution/tools/SessionShellApprovalAdapter.d.ts.map +1 -1
  342. package/bin/session/execution/tools/SessionShellApprovalAdapter.js +20 -8
  343. package/bin/session/execution/tools/SessionShellApprovalAdapter.js.map +1 -1
  344. package/bin/session/messages/SessionAssistantMessageWriter.d.ts +42 -48
  345. package/bin/session/messages/SessionAssistantMessageWriter.d.ts.map +1 -1
  346. package/bin/session/messages/SessionAssistantMessageWriter.js +225 -543
  347. package/bin/session/messages/SessionAssistantMessageWriter.js.map +1 -1
  348. package/bin/session/messages/SessionJsonValue.d.ts +0 -10
  349. package/bin/session/messages/SessionJsonValue.d.ts.map +1 -1
  350. package/bin/session/messages/SessionJsonValue.js +0 -23
  351. package/bin/session/messages/SessionJsonValue.js.map +1 -1
  352. package/bin/session/messages/SessionMessageCompaction.d.ts +7 -3
  353. package/bin/session/messages/SessionMessageCompaction.d.ts.map +1 -1
  354. package/bin/session/messages/SessionMessageCompaction.js +69 -91
  355. package/bin/session/messages/SessionMessageCompaction.js.map +1 -1
  356. package/bin/session/messages/SessionMessageInteractionWriter.d.ts.map +1 -1
  357. package/bin/session/messages/SessionMessageInteractionWriter.js +22 -21
  358. package/bin/session/messages/SessionMessageInteractionWriter.js.map +1 -1
  359. package/bin/session/messages/SessionMessageText.d.ts +25 -0
  360. package/bin/session/messages/SessionMessageText.d.ts.map +1 -0
  361. package/bin/session/messages/SessionMessageText.js +52 -0
  362. package/bin/session/messages/SessionMessageText.js.map +1 -0
  363. package/bin/session/preview/SessionMessagePreview.d.ts +3 -3
  364. package/bin/session/preview/SessionMessagePreview.d.ts.map +1 -1
  365. package/bin/session/preview/SessionMessagePreview.js +10 -30
  366. package/bin/session/preview/SessionMessagePreview.js.map +1 -1
  367. package/bin/session/runtime/SessionTurnContext.d.ts.map +1 -1
  368. package/bin/session/runtime/SessionTurnContext.js +4 -2
  369. package/bin/session/runtime/SessionTurnContext.js.map +1 -1
  370. package/bin/session/storage/Metadata.d.ts +5 -3
  371. package/bin/session/storage/Metadata.d.ts.map +1 -1
  372. package/bin/session/storage/Metadata.js +26 -7
  373. package/bin/session/storage/Metadata.js.map +1 -1
  374. package/bin/tools/ask/AskQuestionsTool.d.ts +2 -16
  375. package/bin/tools/ask/AskQuestionsTool.d.ts.map +1 -1
  376. package/bin/tools/ask/AskQuestionsTool.js +21 -17
  377. package/bin/tools/ask/AskQuestionsTool.js.map +1 -1
  378. package/bin/types/action/ActionResult.d.ts +15 -10
  379. package/bin/types/action/ActionResult.d.ts.map +1 -1
  380. package/bin/types/action/ActionResult.js +2 -2
  381. package/bin/types/action/ActionResult.js.map +1 -1
  382. package/bin/types/agent/AgentOptions.d.ts +7 -14
  383. package/bin/types/agent/AgentOptions.d.ts.map +1 -1
  384. package/bin/types/agent/AgentSessionCollection.d.ts +42 -0
  385. package/bin/types/agent/AgentSessionCollection.d.ts.map +1 -0
  386. package/bin/types/agent/AgentSessionCollection.js +7 -0
  387. package/bin/types/agent/AgentSessionCollection.js.map +1 -0
  388. package/bin/types/agent/AgentStorage.d.ts +18 -0
  389. package/bin/types/agent/AgentStorage.d.ts.map +1 -0
  390. package/bin/types/agent/AgentStorage.js +8 -0
  391. package/bin/types/agent/AgentStorage.js.map +1 -0
  392. package/bin/types/agent/RemoteAgentOptions.d.ts +1 -1
  393. package/bin/types/agent/RemoteAgentPluginAction.d.ts +2 -2
  394. package/bin/types/agent/SessionActor.d.ts +15 -8
  395. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  396. package/bin/types/agent/SessionTypes.d.ts +30 -11
  397. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  398. package/bin/types/agent/WorkspaceEntryOptions.d.ts +16 -0
  399. package/bin/types/agent/WorkspaceEntryOptions.d.ts.map +1 -0
  400. package/bin/types/agent/WorkspaceEntryOptions.js +8 -0
  401. package/bin/types/agent/WorkspaceEntryOptions.js.map +1 -0
  402. package/bin/types/executor/SessionAssistantOutput.d.ts +19 -17
  403. package/bin/types/executor/SessionAssistantOutput.d.ts.map +1 -1
  404. package/bin/types/executor/SessionAssistantOutput.js +2 -3
  405. package/bin/types/executor/SessionAssistantOutput.js.map +1 -1
  406. package/bin/types/executor/SessionToolExecutionContext.d.ts +8 -5
  407. package/bin/types/executor/SessionToolExecutionContext.d.ts.map +1 -1
  408. package/bin/types/executor/SessionToolExecutionContext.js +1 -1
  409. package/bin/types/executor/SessionTurnContext.d.ts +16 -16
  410. package/bin/types/executor/SessionTurnContext.d.ts.map +1 -1
  411. package/bin/types/group/DispatchStrategy.d.ts +62 -0
  412. package/bin/types/group/DispatchStrategy.d.ts.map +1 -0
  413. package/bin/types/group/DispatchStrategy.js +93 -0
  414. package/bin/types/group/DispatchStrategy.js.map +1 -0
  415. package/bin/types/group/Group.d.ts +61 -0
  416. package/bin/types/group/Group.d.ts.map +1 -0
  417. package/bin/types/group/Group.js +3 -0
  418. package/bin/types/group/Group.js.map +1 -0
  419. package/bin/types/group/GroupSession.d.ts +149 -0
  420. package/bin/types/group/GroupSession.d.ts.map +1 -0
  421. package/bin/types/group/GroupSession.js +3 -0
  422. package/bin/types/group/GroupSession.js.map +1 -0
  423. package/bin/types/group/GroupSessionStore.d.ts +77 -0
  424. package/bin/types/group/GroupSessionStore.d.ts.map +1 -0
  425. package/bin/types/group/GroupSessionStore.js +3 -0
  426. package/bin/types/group/GroupSessionStore.js.map +1 -0
  427. package/bin/types/plugin/AgentPluginContext.d.ts +25 -0
  428. package/bin/types/plugin/AgentPluginContext.d.ts.map +1 -0
  429. package/bin/types/plugin/AgentPluginContext.js +10 -0
  430. package/bin/types/plugin/AgentPluginContext.js.map +1 -0
  431. package/bin/types/plugin/PluginAction.d.ts +10 -8
  432. package/bin/types/plugin/PluginAction.d.ts.map +1 -1
  433. package/bin/types/plugin/PluginActionExecution.d.ts +31 -0
  434. package/bin/types/plugin/PluginActionExecution.d.ts.map +1 -0
  435. package/bin/types/plugin/PluginActionExecution.js +10 -0
  436. package/bin/types/plugin/PluginActionExecution.js.map +1 -0
  437. package/bin/types/plugin/PluginCommand.d.ts +5 -5
  438. package/bin/types/plugin/PluginCommand.d.ts.map +1 -1
  439. package/bin/types/plugin/PluginContext.d.ts +19 -8
  440. package/bin/types/plugin/PluginContext.d.ts.map +1 -1
  441. package/bin/types/plugin/PluginContext.js +1 -1
  442. package/bin/types/plugin/PluginDefinition.d.ts +6 -6
  443. package/bin/types/plugin/PluginExecutionContext.d.ts +4 -10
  444. package/bin/types/plugin/PluginExecutionContext.d.ts.map +1 -1
  445. package/bin/types/plugin/PluginExecutionContext.js +3 -5
  446. package/bin/types/plugin/PluginExecutionContext.js.map +1 -1
  447. package/bin/types/plugin/PluginHttp.d.ts +1 -1
  448. package/bin/types/plugin/PluginRuntime.d.ts +13 -8
  449. package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
  450. package/bin/types/plugin/PluginServices.d.ts +10 -0
  451. package/bin/types/plugin/PluginServices.d.ts.map +1 -0
  452. package/bin/types/plugin/PluginServices.js +3 -0
  453. package/bin/types/plugin/PluginServices.js.map +1 -0
  454. package/bin/types/plugin/PluginState.d.ts +3 -3
  455. package/bin/types/plugin/PluginState.d.ts.map +1 -1
  456. package/bin/types/plugin/PluginState.js +1 -1
  457. package/bin/types/plugin/PluginTool.d.ts +6 -4
  458. package/bin/types/plugin/PluginTool.d.ts.map +1 -1
  459. package/bin/types/rpc/RpcProtocol.d.ts +30 -8
  460. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  461. package/bin/types/sdk/AgentSessionAction.d.ts +6 -7
  462. package/bin/types/sdk/AgentSessionAction.d.ts.map +1 -1
  463. package/bin/types/sdk/AgentSessionAction.js +2 -3
  464. package/bin/types/sdk/AgentSessionAction.js.map +1 -1
  465. package/bin/types/sdk/AgentSessionPrompt.d.ts +7 -7
  466. package/bin/types/sdk/AgentSessionPrompt.d.ts.map +1 -1
  467. package/bin/types/sdk/AgentSessionPrompt.js +1 -4
  468. package/bin/types/sdk/AgentSessionPrompt.js.map +1 -1
  469. package/bin/types/session/SessionAction.d.ts +39 -0
  470. package/bin/types/session/SessionAction.d.ts.map +1 -0
  471. package/bin/types/session/SessionAction.js +8 -0
  472. package/bin/types/session/SessionAction.js.map +1 -0
  473. package/bin/types/session/SessionComposer.d.ts +10 -11
  474. package/bin/types/session/SessionComposer.d.ts.map +1 -1
  475. package/bin/types/session/SessionContent.d.ts +41 -0
  476. package/bin/types/session/SessionContent.d.ts.map +1 -0
  477. package/bin/types/session/SessionContent.js +8 -0
  478. package/bin/types/session/SessionContent.js.map +1 -0
  479. package/bin/types/session/SessionExecution.d.ts +9 -68
  480. package/bin/types/session/SessionExecution.d.ts.map +1 -1
  481. package/bin/types/session/SessionInteraction.d.ts +92 -58
  482. package/bin/types/session/SessionInteraction.d.ts.map +1 -1
  483. package/bin/types/session/SessionInteraction.js +11 -4
  484. package/bin/types/session/SessionInteraction.js.map +1 -1
  485. package/bin/types/session/SessionInteractions.d.ts +1 -1
  486. package/bin/types/session/SessionLocalState.d.ts +2 -2
  487. package/bin/types/session/SessionLoop.d.ts +5 -5
  488. package/bin/types/session/SessionLoop.d.ts.map +1 -1
  489. package/bin/types/session/SessionMessage.d.ts +27 -87
  490. package/bin/types/session/SessionMessage.d.ts.map +1 -1
  491. package/bin/types/session/SessionMessages.d.ts +14 -14
  492. package/bin/types/session/SessionMessages.d.ts.map +1 -1
  493. package/bin/types/session/SessionMutation.d.ts +2 -2
  494. package/bin/types/session/SessionOptions.d.ts +14 -9
  495. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  496. package/bin/types/session/SessionOrigin.d.ts +19 -0
  497. package/bin/types/session/SessionOrigin.d.ts.map +1 -0
  498. package/bin/types/session/SessionOrigin.js +8 -0
  499. package/bin/types/session/SessionOrigin.js.map +1 -0
  500. package/bin/types/session/SessionPort.d.ts +16 -20
  501. package/bin/types/session/SessionPort.d.ts.map +1 -1
  502. package/bin/types/session/SessionSegment.d.ts +1 -1
  503. package/bin/types/session/SessionState.d.ts +9 -6
  504. package/bin/types/session/SessionState.d.ts.map +1 -1
  505. package/bin/types/session/SessionSystem.d.ts +1 -1
  506. package/bin/types/session/SessionTool.d.ts +12 -1
  507. package/bin/types/session/SessionTool.d.ts.map +1 -1
  508. package/bin/types/store/LocalStore.d.ts +19 -11
  509. package/bin/types/store/LocalStore.d.ts.map +1 -1
  510. package/bin/types/store/LocalStore.js +2 -2
  511. package/bin/types/store/SessionAttachmentStore.d.ts +4 -4
  512. package/bin/types/store/SessionAttachmentStore.js +3 -3
  513. package/bin/types/store/SessionDataStore.d.ts +85 -0
  514. package/bin/types/store/SessionDataStore.d.ts.map +1 -0
  515. package/bin/types/store/SessionDataStore.js +9 -0
  516. package/bin/types/store/SessionDataStore.js.map +1 -0
  517. package/bin/types/store/SessionStore.d.ts +24 -78
  518. package/bin/types/store/SessionStore.d.ts.map +1 -1
  519. package/bin/types/store/SessionStore.js +3 -3
  520. package/bin/types/tools/ToolActionExecutionContext.d.ts +30 -0
  521. package/bin/types/tools/ToolActionExecutionContext.d.ts.map +1 -0
  522. package/bin/types/tools/ToolActionExecutionContext.js +8 -0
  523. package/bin/types/tools/ToolActionExecutionContext.js.map +1 -0
  524. package/bin/types/tools/ask/AskQuestionsTool.d.ts +1 -1
  525. package/bin/utils/logger/Logger.d.ts +21 -9
  526. package/bin/utils/logger/Logger.d.ts.map +1 -1
  527. package/bin/utils/logger/Logger.js +18 -9
  528. package/bin/utils/logger/Logger.js.map +1 -1
  529. package/bin/workspace/WorkspacePaths.d.ts +3 -32
  530. package/bin/workspace/WorkspacePaths.d.ts.map +1 -1
  531. package/bin/workspace/WorkspacePaths.js +7 -46
  532. package/bin/workspace/WorkspacePaths.js.map +1 -1
  533. package/bin/workspace/store/JsonlSessionMessageStore.d.ts +1 -1
  534. package/bin/workspace/store/JsonlSessionMessageStore.d.ts.map +1 -1
  535. package/bin/workspace/store/LocalGroupSessionDataStore.d.ts +46 -0
  536. package/bin/workspace/store/LocalGroupSessionDataStore.d.ts.map +1 -0
  537. package/bin/workspace/store/LocalGroupSessionDataStore.js +197 -0
  538. package/bin/workspace/store/LocalGroupSessionDataStore.js.map +1 -0
  539. package/bin/workspace/store/LocalGroupSessionStore.d.ts +22 -0
  540. package/bin/workspace/store/LocalGroupSessionStore.d.ts.map +1 -0
  541. package/bin/workspace/store/LocalGroupSessionStore.js +75 -0
  542. package/bin/workspace/store/LocalGroupSessionStore.js.map +1 -0
  543. package/bin/workspace/store/LocalSessionAttachmentStore.d.ts +2 -2
  544. package/bin/workspace/store/LocalSessionAttachmentStore.d.ts.map +1 -1
  545. package/bin/workspace/store/LocalSessionAttachmentStore.js +2 -2
  546. package/bin/workspace/store/LocalSessionAttachmentStore.js.map +1 -1
  547. package/bin/workspace/store/LocalSessionDataStore.d.ts +45 -0
  548. package/bin/workspace/store/LocalSessionDataStore.d.ts.map +1 -0
  549. package/bin/workspace/store/LocalSessionDataStore.js +100 -0
  550. package/bin/workspace/store/LocalSessionDataStore.js.map +1 -0
  551. package/bin/workspace/store/LocalSessionStore.d.ts +38 -28
  552. package/bin/workspace/store/LocalSessionStore.d.ts.map +1 -1
  553. package/bin/workspace/store/LocalSessionStore.js +151 -51
  554. package/bin/workspace/store/LocalSessionStore.js.map +1 -1
  555. package/bin/workspace/store/LocalStorePaths.d.ts +31 -83
  556. package/bin/workspace/store/LocalStorePaths.d.ts.map +1 -1
  557. package/bin/workspace/store/LocalStorePaths.js +43 -101
  558. package/bin/workspace/store/LocalStorePaths.js.map +1 -1
  559. package/bin/workspace/store/SessionMessageStoreFactory.d.ts +1 -1
  560. package/bin/workspace/store/SessionMessageStoreFactory.d.ts.map +1 -1
  561. package/bin/workspace/store/SessionMessageStoreFactory.js +1 -1
  562. package/city-test/agent-http.test.mjs +326 -0
  563. package/city-test/city-container.test.mjs +156 -0
  564. package/city-test/city-host-registry.test.mjs +68 -0
  565. package/city-test/city-transport.test.mjs +282 -0
  566. package/city-test/group.test.mjs +739 -0
  567. package/city-test/session-model.test.mjs +169 -0
  568. package/city-test/transport-lifecycle.test.mjs +164 -0
  569. package/package.json +30 -17
  570. package/scripts/ModelClientMock.mjs +165 -0
  571. package/scripts/action-schedule-store.test.mjs +81 -11
  572. package/scripts/agent-compiler.mjs +10 -6
  573. package/scripts/agent-env-shell-sandbox.test.mjs +10 -10
  574. package/scripts/agent-env.test.mjs +6 -8
  575. package/scripts/agent-multi-workspace.test.mjs +213 -0
  576. package/scripts/agent-ready.test.mjs +71 -16
  577. package/scripts/agent-session-maintenance.test.mjs +15 -28
  578. package/scripts/agent-store.test.mjs +121 -22
  579. package/scripts/city-model-tool-loop.test.mjs +66 -195
  580. package/scripts/core-engine-context-compaction.test.mjs +65 -113
  581. package/scripts/core-engine-model-stream.test.mjs +87 -0
  582. package/scripts/executor-failure-result.test.mjs +13 -29
  583. package/scripts/file-tools.test.mjs +6 -6
  584. package/scripts/image-plugin-job.test.mjs +77 -40
  585. package/scripts/logger-isolation.test.mjs +2 -2
  586. package/scripts/multi-agent-plugin-isolation.test.mjs +11 -9
  587. package/scripts/plugin-tools.test.mjs +153 -18
  588. package/scripts/public-api-naming.test.mjs +0 -1
  589. package/scripts/rewrite-build-aliases.mjs +49 -0
  590. package/scripts/rpc-url-routing.test.mjs +29 -0
  591. package/scripts/search-tools.test.mjs +4 -4
  592. package/scripts/session-ask-question.test.mjs +23 -21
  593. package/scripts/session-attachments.test.mjs +11 -9
  594. package/scripts/session-compact-handle.test.mjs +6 -5
  595. package/scripts/session-composer.test.mjs +61 -18
  596. package/scripts/session-config-turn-boundary.test.mjs +123 -92
  597. package/scripts/session-dynamic-interaction.test.mjs +75 -0
  598. package/scripts/session-fork.test.mjs +35 -0
  599. package/scripts/session-list-title.test.mjs +14 -56
  600. package/scripts/session-messages.test.mjs +306 -1681
  601. package/scripts/session-shell-approval.test.mjs +16 -13
  602. package/scripts/session-system-blocks.test.mjs +1 -1
  603. package/scripts/session-title-event.test.mjs +38 -24
  604. package/scripts/session-tool-approval.test.mjs +17 -15
  605. package/scripts/session-turn-context.test.mjs +31 -28
  606. package/scripts/session-turn-failure.test.mjs +112 -110
  607. package/scripts/verify-build-aliases.mjs +32 -0
  608. package/scripts/workspace.test.mjs +146 -32
  609. package/src/agent/Agent.ts +138 -238
  610. package/src/agent/AgentModel.ts +9 -9
  611. package/src/agent/AgentSessions.ts +116 -65
  612. package/src/agent/WorkspaceEntry.ts +309 -0
  613. package/src/agent/index.ts +4 -0
  614. package/src/city/host/CityHostRegistry.ts +260 -0
  615. package/src/city/index.ts +49 -0
  616. package/src/city/runtime/City.ts +425 -0
  617. package/src/city/transport/SerializedTransport.ts +65 -0
  618. package/src/city/transport/http/AgentHTTP.ts +176 -0
  619. package/src/city/transport/http/CityHTTP.ts +196 -0
  620. package/src/city/transport/http/NodeHttpAdapter.ts +75 -0
  621. package/src/city/transport/http/routes/RuntimeRoutes.ts +51 -0
  622. package/src/city/transport/http/routes/SessionRoutes.ts +497 -0
  623. package/src/city/transport/rpc/AgentRPC.ts +111 -0
  624. package/src/city/transport/rpc/CityRPC.ts +138 -0
  625. package/src/city/transport/rpc/RpcServer.ts +158 -0
  626. package/src/city/transport/rpc/server/InternalHandlers.ts +223 -0
  627. package/src/city/transport/rpc/server/RequestDispatcher.ts +67 -0
  628. package/src/city/transport/rpc/server/SdkSessionHandlers.ts +166 -0
  629. package/src/city/transport/rpc/server/ServerTypes.ts +103 -0
  630. package/src/city/transport/session/RemoteSessionConfig.ts +34 -0
  631. package/src/city/transport/types/AgentHttpBinding.ts +25 -0
  632. package/src/city/transport/types/AgentHttpRuntime.ts +13 -0
  633. package/src/city/transport/types/AgentRpcBinding.ts +28 -0
  634. package/src/city/transport/types/AgentRpcRuntime.ts +20 -0
  635. package/src/city/transport/types/AgentSessionModelResolver.ts +13 -0
  636. package/src/city/transport/types/CityHttpRuntime.ts +45 -0
  637. package/src/city/transport/types/CityRpcRuntime.ts +27 -0
  638. package/src/city/transport/types/RpcProtocol.ts +32 -0
  639. package/src/city/types/City.ts +99 -0
  640. package/src/city/types/PluginHostContext.ts +40 -0
  641. package/src/city/types/SerializedTransport.ts +19 -0
  642. package/src/executor/Executor.ts +101 -43
  643. package/src/executor/README.md +11 -8
  644. package/src/executor/composer/system/SessionSystemComposer.ts +2 -2
  645. package/src/executor/composer/system/default/PromptRenderer.ts +2 -2
  646. package/src/executor/composer/system/default/SystemDomain.ts +5 -5
  647. package/src/executor/composer/system/default/assets/core.prompt.ts +1 -1
  648. package/src/executor/composer/system/default/assets/core.prompt.ts.txt +4 -12
  649. package/src/executor/core-engine/CoreEngineContextCompaction.ts +113 -427
  650. package/src/executor/core-engine/CoreEngineError.ts +1 -1
  651. package/src/executor/core-engine/CoreEngineLoopDecision.ts +1 -18
  652. package/src/executor/core-engine/CoreEngineMessageState.ts +32 -101
  653. package/src/executor/core-engine/CoreEngineRunner.ts +179 -281
  654. package/src/executor/core-engine/CoreEngineSignals.ts +98 -346
  655. package/src/executor/messages/SessionAttachmentMapper.ts +15 -273
  656. package/src/executor/messages/SessionMessageLog.ts +12 -25
  657. package/src/executor/messages/SessionModelMessages.ts +223 -0
  658. package/src/executor/model/ModelGenerate.ts +71 -0
  659. package/src/executor/model/ModelStepRunner.ts +381 -0
  660. package/src/executor/services/ExecutorRecoveryPolicy.ts +4 -4
  661. package/src/executor/types/SessionHistoryMeta.ts +8 -2
  662. package/src/executor/types/SessionLoop.ts +0 -26
  663. package/src/executor/types/SessionPrompts.ts +8 -8
  664. package/src/group/Group.ts +45 -0
  665. package/src/group/GroupSession.ts +728 -0
  666. package/src/group/GroupSessions.ts +104 -0
  667. package/src/group/index.ts +29 -0
  668. package/src/index.ts +108 -60
  669. package/src/internal/AgentRuntime.ts +229 -0
  670. package/src/internal/GroupRuntime.ts +89 -0
  671. package/src/internal/index.ts +33 -0
  672. package/src/plugin/core/ActionScheduleExecutor.ts +17 -5
  673. package/src/plugin/core/ActionScheduleRuntime.ts +16 -6
  674. package/src/plugin/core/ActionScheduleStore.ts +43 -12
  675. package/src/plugin/core/HookRegistry.ts +24 -14
  676. package/src/plugin/core/PluginActionExecution.ts +230 -0
  677. package/src/plugin/core/PluginActionFactory.ts +9 -3
  678. package/src/plugin/core/PluginContext.ts +25 -6
  679. package/src/plugin/core/PluginHttpRoutes.ts +2 -2
  680. package/src/plugin/core/PluginRegistry.ts +193 -107
  681. package/src/plugin/tool/PluginToolRuntime.ts +4 -1
  682. package/src/plugin/tool/PluginToolSchemas.ts +3 -4
  683. package/src/plugin/tool/PluginTools.ts +12 -8
  684. package/src/plugin/types/ActionSchedule.ts +6 -0
  685. package/src/remote/transports/HttpRemoteAgentTransport.ts +3 -3
  686. package/src/remote/transports/RpcClient.ts +17 -0
  687. package/src/remote/transports/RpcRemoteAgentTransport.ts +1 -0
  688. package/src/session/DefaultSessionComposer.ts +3 -3
  689. package/src/session/Session.ts +54 -42
  690. package/src/session/SessionLoop.ts +27 -34
  691. package/src/session/SessionMessages.ts +63 -121
  692. package/src/session/SessionState.ts +46 -21
  693. package/src/session/SessionTitle.ts +23 -40
  694. package/src/session/browse/Browse.ts +35 -63
  695. package/src/session/control/SessionInteractions.ts +72 -51
  696. package/src/session/execution/SessionAssistantOutputAdapter.ts +44 -45
  697. package/src/session/execution/tools/SessionShellApprovalAdapter.ts +20 -9
  698. package/src/session/messages/SessionAssistantMessageWriter.ts +259 -637
  699. package/src/session/messages/SessionJsonValue.ts +0 -23
  700. package/src/session/messages/SessionMessageCompaction.ts +78 -103
  701. package/src/session/messages/SessionMessageInteractionWriter.ts +22 -22
  702. package/src/session/messages/SessionMessageText.ts +73 -0
  703. package/src/session/preview/SessionMessagePreview.ts +15 -45
  704. package/src/session/runtime/SessionTurnContext.ts +11 -9
  705. package/src/session/storage/Metadata.ts +38 -9
  706. package/src/tools/ask/AskQuestionsTool.ts +28 -17
  707. package/src/types/action/ActionResult.ts +20 -11
  708. package/src/types/agent/AgentOptions.ts +6 -13
  709. package/src/types/agent/AgentSessionCollection.ts +61 -0
  710. package/src/types/agent/AgentStorage.ts +19 -0
  711. package/src/types/agent/RemoteAgentOptions.ts +1 -1
  712. package/src/types/agent/SessionActor.ts +9 -0
  713. package/src/types/agent/SessionTypes.ts +31 -11
  714. package/src/types/agent/WorkspaceEntryOptions.ts +18 -0
  715. package/src/types/executor/SessionAssistantOutput.ts +22 -17
  716. package/src/types/executor/SessionToolExecutionContext.ts +9 -4
  717. package/src/types/executor/SessionTurnContext.ts +11 -11
  718. package/src/types/group/DispatchStrategy.ts +165 -0
  719. package/src/types/group/Group.ts +64 -0
  720. package/src/types/group/GroupSession.ts +154 -0
  721. package/src/types/group/GroupSessionStore.ts +82 -0
  722. package/src/types/plugin/AgentPluginContext.ts +30 -0
  723. package/src/types/plugin/PluginAction.ts +7 -5
  724. package/src/types/plugin/PluginActionExecution.ts +36 -0
  725. package/src/types/plugin/PluginCommand.ts +5 -5
  726. package/src/types/plugin/PluginContext.ts +23 -7
  727. package/src/types/plugin/PluginExecutionContext.ts +6 -11
  728. package/src/types/plugin/PluginRuntime.ts +5 -0
  729. package/src/types/plugin/PluginServices.ts +11 -0
  730. package/src/types/plugin/PluginState.ts +2 -2
  731. package/src/types/plugin/PluginTool.ts +4 -1
  732. package/src/types/rpc/RpcProtocol.ts +24 -2
  733. package/src/types/sdk/AgentSessionAction.ts +8 -9
  734. package/src/types/sdk/AgentSessionPrompt.ts +8 -11
  735. package/src/types/session/SessionAction.ts +41 -0
  736. package/src/types/session/SessionComposer.ts +8 -13
  737. package/src/types/session/SessionContent.ts +52 -0
  738. package/src/types/session/SessionExecution.ts +9 -88
  739. package/src/types/session/SessionInteraction.ts +106 -73
  740. package/src/types/session/SessionLoop.ts +1 -1
  741. package/src/types/session/SessionMessage.ts +27 -94
  742. package/src/types/session/SessionMessages.ts +12 -9
  743. package/src/types/session/SessionOptions.ts +11 -4
  744. package/src/types/session/SessionOrigin.ts +21 -0
  745. package/src/types/session/SessionPort.ts +12 -20
  746. package/src/types/session/SessionState.ts +7 -4
  747. package/src/types/session/SessionTool.ts +12 -0
  748. package/src/types/store/LocalStore.ts +19 -11
  749. package/src/types/store/SessionAttachmentStore.ts +4 -4
  750. package/src/types/store/SessionDataStore.ts +109 -0
  751. package/src/types/store/SessionStore.ts +37 -94
  752. package/src/types/tools/ToolActionExecutionContext.ts +32 -0
  753. package/src/utils/logger/Logger.ts +33 -12
  754. package/src/workspace/WorkspacePaths.ts +7 -51
  755. package/src/workspace/store/JsonlSessionMessageStore.ts +2 -2
  756. package/src/workspace/store/LocalGroupSessionDataStore.ts +232 -0
  757. package/src/workspace/store/LocalGroupSessionStore.ts +90 -0
  758. package/src/workspace/store/LocalSessionAttachmentStore.ts +3 -3
  759. package/src/workspace/store/LocalSessionDataStore.ts +169 -0
  760. package/src/workspace/store/LocalSessionStore.ts +202 -86
  761. package/src/workspace/store/LocalStorePaths.ts +65 -147
  762. package/src/workspace/store/SessionMessageStoreFactory.ts +1 -1
  763. package/tsconfig.json +2 -1
  764. package/tsconfig.tsbuildinfo +1 -1
  765. package/LICENSE +0 -183
  766. package/bin/agent/AgentState.d.ts +0 -45
  767. package/bin/agent/AgentState.d.ts.map +0 -1
  768. package/bin/agent/AgentState.js +0 -74
  769. package/bin/agent/AgentState.js.map +0 -1
  770. package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts +0 -48
  771. package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts.map +0 -1
  772. package/bin/executor/core-engine/CoreEngineUiStreamCollector.js +0 -92
  773. package/bin/executor/core-engine/CoreEngineUiStreamCollector.js.map +0 -1
  774. package/bin/executor/messages/SessionMessageCodec.d.ts +0 -29
  775. package/bin/executor/messages/SessionMessageCodec.d.ts.map +0 -1
  776. package/bin/executor/messages/SessionMessageCodec.js +0 -75
  777. package/bin/executor/messages/SessionMessageCodec.js.map +0 -1
  778. package/bin/executor/messages/SessionStepEventMapper.d.ts +0 -19
  779. package/bin/executor/messages/SessionStepEventMapper.d.ts.map +0 -1
  780. package/bin/executor/messages/SessionStepEventMapper.js +0 -177
  781. package/bin/executor/messages/SessionStepEventMapper.js.map +0 -1
  782. package/bin/executor/messages/UIMessageTransformer.d.ts +0 -29
  783. package/bin/executor/messages/UIMessageTransformer.d.ts.map +0 -1
  784. package/bin/executor/messages/UIMessageTransformer.js +0 -114
  785. package/bin/executor/messages/UIMessageTransformer.js.map +0 -1
  786. package/bin/executor/messages/UserVisibleText.d.ts +0 -26
  787. package/bin/executor/messages/UserVisibleText.d.ts.map +0 -1
  788. package/bin/executor/messages/UserVisibleText.js +0 -65
  789. package/bin/executor/messages/UserVisibleText.js.map +0 -1
  790. package/bin/executor/types/SessionRecords.d.ts +0 -162
  791. package/bin/executor/types/SessionRecords.d.ts.map +0 -1
  792. package/bin/executor/types/SessionRecords.js +0 -51
  793. package/bin/executor/types/SessionRecords.js.map +0 -1
  794. package/bin/plugin/core/PluginActionRunner.d.ts +0 -24
  795. package/bin/plugin/core/PluginActionRunner.d.ts.map +0 -1
  796. package/bin/plugin/core/PluginActionRunner.js +0 -19
  797. package/bin/plugin/core/PluginActionRunner.js.map +0 -1
  798. package/bin/session/messages/SessionMessageCodec.d.ts +0 -23
  799. package/bin/session/messages/SessionMessageCodec.d.ts.map +0 -1
  800. package/bin/session/messages/SessionMessageCodec.js +0 -455
  801. package/bin/session/messages/SessionMessageCodec.js.map +0 -1
  802. package/bin/types/agent/AgentState.d.ts +0 -19
  803. package/bin/types/agent/AgentState.d.ts.map +0 -1
  804. package/bin/types/agent/AgentState.js +0 -9
  805. package/bin/types/agent/AgentState.js.map +0 -1
  806. package/bin/types/store/AgentStore.d.ts +0 -31
  807. package/bin/types/store/AgentStore.d.ts.map +0 -1
  808. package/bin/types/store/AgentStore.js +0 -9
  809. package/bin/types/store/AgentStore.js.map +0 -1
  810. package/bin/types/workspace/FileSystem.d.ts +0 -55
  811. package/bin/types/workspace/FileSystem.d.ts.map +0 -1
  812. package/bin/types/workspace/FileSystem.js +0 -9
  813. package/bin/types/workspace/FileSystem.js.map +0 -1
  814. package/bin/types/workspace/FileTool.d.ts +0 -181
  815. package/bin/types/workspace/FileTool.d.ts.map +0 -1
  816. package/bin/types/workspace/FileTool.js +0 -10
  817. package/bin/types/workspace/FileTool.js.map +0 -1
  818. package/bin/types/workspace/SearchTool.d.ts +0 -131
  819. package/bin/types/workspace/SearchTool.d.ts.map +0 -1
  820. package/bin/types/workspace/SearchTool.js +0 -10
  821. package/bin/types/workspace/SearchTool.js.map +0 -1
  822. package/bin/types/workspace/Workspace.d.ts +0 -24
  823. package/bin/types/workspace/Workspace.d.ts.map +0 -1
  824. package/bin/types/workspace/Workspace.js +0 -9
  825. package/bin/types/workspace/Workspace.js.map +0 -1
  826. package/bin/types/workspace/WorkspaceEnv.d.ts +0 -16
  827. package/bin/types/workspace/WorkspaceEnv.d.ts.map +0 -1
  828. package/bin/types/workspace/WorkspaceEnv.js +0 -9
  829. package/bin/types/workspace/WorkspaceEnv.js.map +0 -1
  830. package/bin/types/workspace/WorkspaceTools.d.ts +0 -20
  831. package/bin/types/workspace/WorkspaceTools.d.ts.map +0 -1
  832. package/bin/types/workspace/WorkspaceTools.js +0 -9
  833. package/bin/types/workspace/WorkspaceTools.js.map +0 -1
  834. package/bin/utils/cli/CliOutput.d.ts +0 -14
  835. package/bin/utils/cli/CliOutput.d.ts.map +0 -1
  836. package/bin/utils/cli/CliOutput.js +0 -98
  837. package/bin/utils/cli/CliOutput.js.map +0 -1
  838. package/bin/utils/cli/PrettyStatus.d.ts +0 -25
  839. package/bin/utils/cli/PrettyStatus.d.ts.map +0 -1
  840. package/bin/utils/cli/PrettyStatus.js +0 -77
  841. package/bin/utils/cli/PrettyStatus.js.map +0 -1
  842. package/bin/utils/logger/FormatRequest.d.ts +0 -29
  843. package/bin/utils/logger/FormatRequest.d.ts.map +0 -1
  844. package/bin/utils/logger/FormatRequest.js +0 -309
  845. package/bin/utils/logger/FormatRequest.js.map +0 -1
  846. package/bin/utils/logger/FormatResponse.d.ts +0 -46
  847. package/bin/utils/logger/FormatResponse.d.ts.map +0 -1
  848. package/bin/utils/logger/FormatResponse.js +0 -186
  849. package/bin/utils/logger/FormatResponse.js.map +0 -1
  850. package/bin/utils/logger/FormatShared.d.ts +0 -33
  851. package/bin/utils/logger/FormatShared.d.ts.map +0 -1
  852. package/bin/utils/logger/FormatShared.js +0 -215
  853. package/bin/utils/logger/FormatShared.js.map +0 -1
  854. package/bin/utils/object/DeepMerge.d.ts +0 -18
  855. package/bin/utils/object/DeepMerge.d.ts.map +0 -1
  856. package/bin/utils/object/DeepMerge.js +0 -37
  857. package/bin/utils/object/DeepMerge.js.map +0 -1
  858. package/bin/utils/object/ObjectGuards.d.ts +0 -17
  859. package/bin/utils/object/ObjectGuards.d.ts.map +0 -1
  860. package/bin/utils/object/ObjectGuards.js +0 -19
  861. package/bin/utils/object/ObjectGuards.js.map +0 -1
  862. package/bin/utils/path/AncestorFiles.d.ts +0 -17
  863. package/bin/utils/path/AncestorFiles.d.ts.map +0 -1
  864. package/bin/utils/path/AncestorFiles.js +0 -36
  865. package/bin/utils/path/AncestorFiles.js.map +0 -1
  866. package/bin/utils/storage/index.d.ts +0 -4
  867. package/bin/utils/storage/index.d.ts.map +0 -1
  868. package/bin/utils/storage/index.js +0 -17
  869. package/bin/utils/storage/index.js.map +0 -1
  870. package/bin/utils/string/EscapeRegExp.d.ts +0 -17
  871. package/bin/utils/string/EscapeRegExp.d.ts.map +0 -1
  872. package/bin/utils/string/EscapeRegExp.js +0 -19
  873. package/bin/utils/string/EscapeRegExp.js.map +0 -1
  874. package/bin/workspace/LocalFileSystem.d.ts +0 -44
  875. package/bin/workspace/LocalFileSystem.d.ts.map +0 -1
  876. package/bin/workspace/LocalFileSystem.js +0 -153
  877. package/bin/workspace/LocalFileSystem.js.map +0 -1
  878. package/bin/workspace/Workspace.d.ts +0 -53
  879. package/bin/workspace/Workspace.d.ts.map +0 -1
  880. package/bin/workspace/Workspace.js +0 -151
  881. package/bin/workspace/Workspace.js.map +0 -1
  882. package/bin/workspace/WorkspaceBase.d.ts +0 -41
  883. package/bin/workspace/WorkspaceBase.d.ts.map +0 -1
  884. package/bin/workspace/WorkspaceBase.js +0 -19
  885. package/bin/workspace/WorkspaceBase.js.map +0 -1
  886. package/bin/workspace/WorkspaceEnv.d.ts +0 -19
  887. package/bin/workspace/WorkspaceEnv.d.ts.map +0 -1
  888. package/bin/workspace/WorkspaceEnv.js +0 -50
  889. package/bin/workspace/WorkspaceEnv.js.map +0 -1
  890. package/bin/workspace/file/FileActionRuntime.d.ts +0 -14
  891. package/bin/workspace/file/FileActionRuntime.d.ts.map +0 -1
  892. package/bin/workspace/file/FileActionRuntime.js +0 -386
  893. package/bin/workspace/file/FileActionRuntime.js.map +0 -1
  894. package/bin/workspace/file/FileAtomicWriter.d.ts +0 -20
  895. package/bin/workspace/file/FileAtomicWriter.d.ts.map +0 -1
  896. package/bin/workspace/file/FileAtomicWriter.js +0 -50
  897. package/bin/workspace/file/FileAtomicWriter.js.map +0 -1
  898. package/bin/workspace/file/FileEncoding.d.ts +0 -34
  899. package/bin/workspace/file/FileEncoding.d.ts.map +0 -1
  900. package/bin/workspace/file/FileEncoding.js +0 -152
  901. package/bin/workspace/file/FileEncoding.js.map +0 -1
  902. package/bin/workspace/file/FilePathPolicy.d.ts +0 -26
  903. package/bin/workspace/file/FilePathPolicy.d.ts.map +0 -1
  904. package/bin/workspace/file/FilePathPolicy.js +0 -134
  905. package/bin/workspace/file/FilePathPolicy.js.map +0 -1
  906. package/bin/workspace/file/FileToolError.d.ts +0 -30
  907. package/bin/workspace/file/FileToolError.d.ts.map +0 -1
  908. package/bin/workspace/file/FileToolError.js +0 -78
  909. package/bin/workspace/file/FileToolError.js.map +0 -1
  910. package/bin/workspace/search/SearchActionRuntime.d.ts +0 -14
  911. package/bin/workspace/search/SearchActionRuntime.d.ts.map +0 -1
  912. package/bin/workspace/search/SearchActionRuntime.js +0 -359
  913. package/bin/workspace/search/SearchActionRuntime.js.map +0 -1
  914. package/bin/workspace/search/SearchToolError.d.ts +0 -26
  915. package/bin/workspace/search/SearchToolError.d.ts.map +0 -1
  916. package/bin/workspace/search/SearchToolError.js +0 -76
  917. package/bin/workspace/search/SearchToolError.js.map +0 -1
  918. package/bin/workspace/store/LocalAgentStore.d.ts +0 -43
  919. package/bin/workspace/store/LocalAgentStore.d.ts.map +0 -1
  920. package/bin/workspace/store/LocalAgentStore.js +0 -135
  921. package/bin/workspace/store/LocalAgentStore.js.map +0 -1
  922. package/bin/workspace/tool/FileToolSchemas.d.ts +0 -35
  923. package/bin/workspace/tool/FileToolSchemas.d.ts.map +0 -1
  924. package/bin/workspace/tool/FileToolSchemas.js +0 -73
  925. package/bin/workspace/tool/FileToolSchemas.js.map +0 -1
  926. package/bin/workspace/tool/FileTools.d.ts +0 -11
  927. package/bin/workspace/tool/FileTools.d.ts.map +0 -1
  928. package/bin/workspace/tool/FileTools.js +0 -61
  929. package/bin/workspace/tool/FileTools.js.map +0 -1
  930. package/bin/workspace/tool/SearchToolSchemas.d.ts +0 -24
  931. package/bin/workspace/tool/SearchToolSchemas.d.ts.map +0 -1
  932. package/bin/workspace/tool/SearchToolSchemas.js +0 -60
  933. package/bin/workspace/tool/SearchToolSchemas.js.map +0 -1
  934. package/bin/workspace/tool/SearchTools.d.ts +0 -11
  935. package/bin/workspace/tool/SearchTools.d.ts.map +0 -1
  936. package/bin/workspace/tool/SearchTools.js +0 -38
  937. package/bin/workspace/tool/SearchTools.js.map +0 -1
  938. package/bin/workspace/tool/WorkspaceTools.d.ts +0 -11
  939. package/bin/workspace/tool/WorkspaceTools.d.ts.map +0 -1
  940. package/bin/workspace/tool/WorkspaceTools.js +0 -22
  941. package/bin/workspace/tool/WorkspaceTools.js.map +0 -1
  942. package/scripts/core-engine-ui-stream-collector.test.mjs +0 -38
  943. package/scripts/openai-responses-continuation.test.mjs +0 -78
  944. package/src/agent/AgentState.ts +0 -96
  945. package/src/executor/core-engine/CoreEngineUiStreamCollector.ts +0 -136
  946. package/src/executor/messages/SessionMessageCodec.ts +0 -106
  947. package/src/executor/messages/SessionStepEventMapper.ts +0 -191
  948. package/src/executor/messages/UIMessageTransformer.ts +0 -152
  949. package/src/executor/messages/UserVisibleText.ts +0 -75
  950. package/src/executor/types/SessionRecords.ts +0 -217
  951. package/src/plugin/core/PluginActionRunner.ts +0 -36
  952. package/src/session/messages/SessionMessageCodec.ts +0 -495
  953. package/src/types/agent/AgentState.ts +0 -22
  954. package/src/types/store/AgentStore.ts +0 -52
  955. package/src/types/workspace/FileSystem.ts +0 -76
  956. package/src/types/workspace/FileTool.ts +0 -225
  957. package/src/types/workspace/SearchTool.ts +0 -156
  958. package/src/types/workspace/Workspace.ts +0 -27
  959. package/src/types/workspace/WorkspaceEnv.ts +0 -19
  960. package/src/types/workspace/WorkspaceTools.ts +0 -23
  961. package/src/utils/cli/CliOutput.ts +0 -128
  962. package/src/utils/cli/PrettyStatus.ts +0 -94
  963. package/src/utils/logger/FormatRequest.ts +0 -455
  964. package/src/utils/logger/FormatResponse.ts +0 -265
  965. package/src/utils/logger/FormatShared.ts +0 -258
  966. package/src/utils/object/DeepMerge.ts +0 -40
  967. package/src/utils/object/ObjectGuards.ts +0 -19
  968. package/src/utils/path/AncestorFiles.ts +0 -40
  969. package/src/utils/storage/index.ts +0 -19
  970. package/src/utils/string/EscapeRegExp.ts +0 -19
  971. package/src/workspace/LocalFileSystem.ts +0 -209
  972. package/src/workspace/Workspace.ts +0 -178
  973. package/src/workspace/WorkspaceBase.ts +0 -62
  974. package/src/workspace/WorkspaceEnv.ts +0 -54
  975. package/src/workspace/file/FileActionRuntime.ts +0 -433
  976. package/src/workspace/file/FileAtomicWriter.ts +0 -61
  977. package/src/workspace/file/FileEncoding.ts +0 -175
  978. package/src/workspace/file/FilePathPolicy.ts +0 -162
  979. package/src/workspace/file/FileToolError.ts +0 -99
  980. package/src/workspace/search/SearchActionRuntime.ts +0 -438
  981. package/src/workspace/search/SearchToolError.ts +0 -94
  982. package/src/workspace/store/LocalAgentStore.ts +0 -191
  983. package/src/workspace/tool/FileToolSchemas.ts +0 -77
  984. package/src/workspace/tool/FileTools.ts +0 -91
  985. package/src/workspace/tool/SearchToolSchemas.ts +0 -62
  986. package/src/workspace/tool/SearchTools.ts +0 -61
  987. package/src/workspace/tool/WorkspaceTools.ts +0 -31
@@ -1,57 +1,26 @@
1
1
  /**
2
- * @file 验证 SessionMessages Message 快照、Assistant 草稿与实时事件一致性。
2
+ * @file 验证 canonical SessionMessage 的流式写入、交互、恢复与分段压缩。
3
+ *
4
+ * 测试只使用 Downcity Model Protocol 与 canonical SessionMessage,不经过 UI Message
5
+ * 或 Executor Record 投影。
3
6
  */
4
7
 
5
- import test from "node:test";
6
8
  import assert from "node:assert/strict";
7
9
  import fs from "node:fs/promises";
8
10
  import os from "node:os";
9
11
  import path from "node:path";
12
+ import test from "node:test";
10
13
 
11
- import { JsonlSessionMessageStore } from "../bin/workspace/store/JsonlSessionMessageStore.js";
12
- import { LocalFileSystem } from "../bin/workspace/LocalFileSystem.js";
13
- import { SessionMessages } from "../bin/session/SessionMessages.js";
14
+ import { LocalFileSystem } from "@downcity/workspace";
14
15
  import { SessionInteractions } from "../bin/session/control/SessionInteractions.js";
15
16
  import { SessionShellApprovalAdapter } from "../bin/session/execution/tools/SessionShellApprovalAdapter.js";
17
+ import { SessionMessages } from "../bin/session/SessionMessages.js";
16
18
  import { compose_session_compaction } from "../bin/session/messages/SessionMessageCompaction.js";
17
- import {
18
- from_ui_assistant_parts,
19
- from_ui_user_parts,
20
- to_executor_history,
21
- to_executor_ui_message,
22
- } from "../bin/session/messages/SessionMessageCodec.js";
23
- import { convertToModelMessages } from "ai";
24
- import { MockLanguageModelV3 } from "ai/test";
25
-
26
- /** 可暂停一次 Assistant 草稿写入,用于稳定复现并发写入顺序。 */
27
- class PausingAssistantMessageStore extends JsonlSessionMessageStore {
28
- next_assistant_write = null;
29
-
30
- pause_next_assistant_write() {
31
- let mark_started;
32
- let release;
33
- const started = new Promise((resolve) => {
34
- mark_started = resolve;
35
- });
36
- const wait = new Promise((resolve) => {
37
- release = resolve;
38
- });
39
- this.next_assistant_write = { mark_started, wait };
40
- return { started, release };
41
- }
42
-
43
- async write_assistant_message(message) {
44
- const pending = this.next_assistant_write;
45
- if (pending) {
46
- this.next_assistant_write = null;
47
- pending.mark_started();
48
- await pending.wait;
49
- }
50
- await super.write_assistant_message(message);
51
- }
52
- }
19
+ import { session_context_to_model_messages } from "../bin/executor/messages/SessionModelMessages.js";
20
+ import { JsonlSessionMessageStore } from "../bin/workspace/store/JsonlSessionMessageStore.js";
21
+ import { MockModelClient } from "./ModelClientMock.mjs";
53
22
 
54
- /** 可让下一次 Assistant 草稿更新失败,用于验证持久化失败边界。 */
23
+ /** 可让下一次 Assistant 草稿更新失败的测试 Store。 */
55
24
  class FailingAssistantMessageStore extends JsonlSessionMessageStore {
56
25
  next_assistant_error = null;
57
26
 
@@ -69,66 +38,76 @@ class FailingAssistantMessageStore extends JsonlSessionMessageStore {
69
38
  }
70
39
  }
71
40
 
41
+ /** 创建隔离的 canonical SessionMessages 测试环境。 */
72
42
  async function create_recorder(
73
- session_id = "session-recorder-test",
43
+ session_id = "session-messages-test",
74
44
  create_store = (options) => new JsonlSessionMessageStore(options),
75
45
  ) {
76
- const root_path = await fs.mkdtemp(path.join(os.tmpdir(), "downcity-session-recorder-"));
46
+ const root_path = await fs.mkdtemp(path.join(os.tmpdir(), "downcity-session-messages-"));
77
47
  const file_path = path.join(root_path, "active.jsonl");
78
48
  const assistant_message_file_path = path.join(root_path, "assistant_message.json");
79
- const events = [];
80
49
  const files = new LocalFileSystem(root_path);
81
- const store = create_store({ files, session_id, file_path });
50
+ const events = [];
51
+ const store = create_store({
52
+ files,
53
+ session_id,
54
+ file_path,
55
+ assistant_message_file_path,
56
+ });
82
57
  const recorder = new SessionMessages({
83
58
  session_id,
84
59
  store,
85
60
  publish: (mutation) => events.push(mutation),
86
61
  });
87
62
  await recorder.initialize();
88
- return { recorder, store, events, files, file_path, assistant_message_file_path };
63
+ return {
64
+ recorder,
65
+ store,
66
+ events,
67
+ files,
68
+ root_path,
69
+ file_path,
70
+ assistant_message_file_path,
71
+ };
89
72
  }
90
73
 
74
+ /** 读取 active JSONL。 */
91
75
  async function read_jsonl(file_path) {
92
76
  const raw = await fs.readFile(file_path, "utf8");
93
77
  return raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
94
78
  }
95
79
 
96
- async function create_seeded_recorder(session_id, messages) {
97
- const root_path = await fs.mkdtemp(path.join(os.tmpdir(), "downcity-session-seeded-"));
98
- const file_path = path.join(root_path, "active.jsonl");
99
- await fs.writeFile(
100
- file_path,
101
- `${messages.map((message) => JSON.stringify(message)).join("\n")}\n`,
102
- "utf8",
103
- );
104
- const recorder = new SessionMessages({
105
- session_id,
106
- store: new JsonlSessionMessageStore({
107
- files: new LocalFileSystem(root_path),
108
- session_id,
109
- file_path,
110
- }),
111
- publish: () => {},
112
- });
113
- await recorder.initialize();
114
- return { recorder, file_path };
80
+ /** 写入一个完整的标准模型文本事件序列。 */
81
+ async function write_text(writer, content_id, text) {
82
+ await writer.apply_model_event({ type: "text_start", content_id });
83
+ await writer.apply_model_event({ type: "text_delta", content_id, delta: text });
84
+ await writer.apply_model_event({ type: "text_finish", content_id });
115
85
  }
116
86
 
117
- async function compact_messages(recorder, session_id, model) {
118
- const plan = await compose_session_compaction({
119
- session_id,
120
- snapshot: await recorder.context_snapshot(),
121
- model,
122
- });
123
- if (!plan) return false;
124
- await recorder.compact_active({
125
- through_sequence: plan.through_sequence,
126
- summary: plan.summary,
87
+ /** 写入一个完整的标准模型工具调用事件序列。 */
88
+ async function write_tool_call(writer, input) {
89
+ await writer.apply_model_event({
90
+ type: "tool_call_start",
91
+ content_id: input.content_id,
92
+ tool_call_id: input.tool_call_id,
93
+ tool_name: input.tool_name,
94
+ });
95
+ if (input.input_delta) {
96
+ await writer.apply_model_event({
97
+ type: "tool_call_delta",
98
+ content_id: input.content_id,
99
+ input_delta: input.input_delta,
100
+ });
101
+ }
102
+ await writer.apply_model_event({
103
+ type: "tool_call_finish",
104
+ content_id: input.content_id,
105
+ input: input.tool_input,
127
106
  });
128
- return true;
129
107
  }
130
108
 
131
- function create_seeded_user_message(session_id, sequence) {
109
+ /** 创建一条测试 User Message。 */
110
+ function create_user_message(session_id, sequence) {
132
111
  return {
133
112
  message_id: `user-${String(sequence)}`,
134
113
  session_id,
@@ -141,7 +120,7 @@ function create_seeded_user_message(session_id, sequence) {
141
120
  type: "user",
142
121
  input_type: "prompt",
143
122
  parts: [{
144
- part_id: `user-text-${String(sequence)}`,
123
+ part_id: `text-${String(sequence)}`,
145
124
  type: "text",
146
125
  text: `message ${String(sequence)}`,
147
126
  state: "done",
@@ -149,47 +128,45 @@ function create_seeded_user_message(session_id, sequence) {
149
128
  };
150
129
  }
151
130
 
152
- test("delta 只更新 Assistant 草稿,完成后才写入 active.jsonl", async () => {
153
- const { recorder, events, file_path, assistant_message_file_path } = await create_recorder();
131
+ test("模型文本增量只更新草稿,完成后写入 active JSONL", async () => {
132
+ const {
133
+ recorder,
134
+ events,
135
+ file_path,
136
+ assistant_message_file_path,
137
+ } = await create_recorder();
154
138
  await recorder.append_user_message({
155
139
  turn_id: "turn-1",
156
140
  input_type: "prompt",
157
141
  parts: [{ part_id: "user-text-1", type: "text", text: "你好", state: "done" }],
158
142
  });
159
143
  const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
160
- await writer.apply_chunk({ type: "text-start", id: "text-1" });
161
- await writer.apply_chunk({ type: "text-delta", id: "text-1", delta: "你" });
162
- await writer.apply_chunk({ type: "text-delta", id: "text-1", delta: "好" });
144
+ await writer.apply_model_event({ type: "text_start", content_id: "text-1" });
145
+ await writer.apply_model_event({ type: "text_delta", content_id: "text-1", delta: "你" });
146
+ await writer.apply_model_event({ type: "text_delta", content_id: "text-1", delta: "好" });
163
147
 
164
- const history_during_stream = await read_jsonl(file_path);
148
+ const active_during_stream = await read_jsonl(file_path);
165
149
  const draft = JSON.parse(await fs.readFile(assistant_message_file_path, "utf8"));
166
- assert.equal(history_during_stream.length, 1);
167
- assert.equal(history_during_stream[0].type, "user");
168
- assert.equal(draft.type, "assistant");
150
+ assert.deepEqual(active_during_stream.map((message) => message.type), ["user"]);
169
151
  assert.equal(draft.parts[0].text, "你好");
170
- assert.equal(draft.parts[0].sequence, 1);
152
+ assert.equal(draft.parts[0].state, "streaming");
171
153
 
172
- await writer.apply_chunk({ type: "text-end", id: "text-1" });
154
+ await writer.apply_model_event({ type: "text_finish", content_id: "text-1" });
173
155
  await writer.complete();
174
156
 
175
- const history = await read_jsonl(file_path);
176
- assert.deepEqual(history.map((message) => message.type), ["user", "assistant"]);
177
- assert.equal(history[1].parts[0].text, "你好");
178
- assert.equal(await fs.stat(assistant_message_file_path).then(() => true).catch(() => false), false);
179
- assert.deepEqual(events.map((event) => `${event.variant}:${event.type}`), [
180
- "message:user",
181
- "message:assistant",
182
- "part:text",
183
- "delta:text",
184
- "delta:text",
185
- "part:text",
186
- "message:assistant",
187
- ]);
188
- assert.equal(history.some((value) => "variant" in value), false);
157
+ const active = await read_jsonl(file_path);
158
+ assert.deepEqual(active.map((message) => message.type), ["user", "assistant"]);
159
+ assert.equal(active[1].parts[0].text, "你好");
160
+ assert.equal(active[1].parts[0].state, "done");
161
+ assert.equal(
162
+ await fs.stat(assistant_message_file_path).then(() => true).catch(() => false),
163
+ false,
164
+ );
165
+ assert.equal(events.some((event) => event.variant === "delta"), true);
189
166
  });
190
167
 
191
- test("Tool Runtime 在文本中间保持输入、审批和输出的确定顺序", async () => {
192
- const { recorder, events, file_path } = await create_recorder("tool-order-test");
168
+ test("工具调用、审批、结果和后续文本保持 canonical 顺序", async () => {
169
+ const { recorder, file_path } = await create_recorder("tool-order-test");
193
170
  const interactions = new SessionInteractions({
194
171
  session_id: "tool-order-test",
195
172
  messages: recorder,
@@ -199,28 +176,17 @@ test("Tool Runtime 在文本中间保持输入、审批和输出的确定顺序"
199
176
  interactions,
200
177
  });
201
178
  const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
202
- await writer.apply_chunk({ type: "text-start", id: "text-1" });
203
- await writer.apply_chunk({ type: "text-delta", id: "text-1", delta: "before" });
204
- await writer.apply_chunk({ type: "text-end", id: "text-1" });
205
- await writer.apply_chunk({ type: "tool-input-start", toolCallId: "call-1", toolName: "shell_exec" });
206
- await writer.apply_chunk({
207
- type: "tool-input-delta",
208
- toolCallId: "call-1",
209
- inputTextDelta: '{"cmd":"',
210
- });
211
- await writer.apply_chunk({
212
- type: "tool-input-delta",
213
- toolCallId: "call-1",
214
- inputTextDelta: 'pwd"}',
215
- });
216
- await writer.apply_chunk({
217
- type: "tool-input-available",
218
- toolCallId: "call-1",
219
- toolName: "shell_exec",
220
- input: { cmd: "pwd", sandbox: "unrestricted", reason: "Inspect directory" },
179
+ await writer.begin_step();
180
+ await write_text(writer, "text-1", "before");
181
+ await write_tool_call(writer, {
182
+ content_id: "tool-1",
183
+ tool_call_id: "call-1",
184
+ tool_name: "shell_exec",
185
+ input_delta: '{"cmd":"pwd"}',
186
+ tool_input: { cmd: "pwd" },
221
187
  });
222
- assert.equal(recorder.get_message(writer.message_id).parts[1].state, "ready");
223
- const approval_handle = await approval_adapter.request({
188
+
189
+ const approval = await approval_adapter.request({
224
190
  shell_id: "shell-1",
225
191
  tool_call_id: "call-1",
226
192
  tool_name: "shell_exec",
@@ -232,1634 +198,293 @@ test("Tool Runtime 在文本中间保持输入、审批和输出的确定顺序"
232
198
  operation: "exec",
233
199
  timeout_ms: 60_000,
234
200
  });
235
- assert.equal(recorder.get_message(writer.message_id).parts[1].state, "waiting-user");
236
201
  await interactions.respond({
237
- interaction_id: approval_handle.approval_id,
238
- response: { kind: "approval", decision: "approved" },
202
+ interaction_id: approval.approval_id,
203
+ response: {
204
+ type: "approval",
205
+ outcome: "resolved",
206
+ payload: { decision: "approved" },
207
+ },
208
+ });
209
+ assert.equal(await approval.decision, "approved");
210
+
211
+ await writer.apply_tool_result({
212
+ tool_call_id: "call-1",
213
+ tool_name: "shell_exec",
214
+ succeeded: true,
215
+ output: { count: 1 },
239
216
  });
240
- assert.equal(await approval_handle.decision, "approved");
241
- await writer.apply_chunk({ type: "text-start", id: "text-2" });
242
- await writer.apply_chunk({ type: "text-delta", id: "text-2", delta: "after" });
243
- await writer.apply_chunk({ type: "text-end", id: "text-2" });
244
- await writer.apply_chunk({ type: "tool-output-available", toolCallId: "call-1", output: { count: 1 } });
217
+ await write_text(writer, "text-2", "after");
218
+ await writer.finish_step([
219
+ { part_id: "text-1", sequence: 1, type: "text", text: "before", state: "done" },
220
+ {
221
+ part_id: "tool-1",
222
+ sequence: 2,
223
+ type: "tool",
224
+ tool_call_id: "call-1",
225
+ tool_name: "shell_exec",
226
+ state: "completed",
227
+ input: { cmd: "pwd" },
228
+ output: { count: 1 },
229
+ },
230
+ { part_id: "text-2", sequence: 3, type: "text", text: "after", state: "done" },
231
+ ]);
245
232
  await writer.complete();
246
233
 
247
234
  const assistant = (await read_jsonl(file_path))[0];
248
- assert.deepEqual(assistant.parts.map((part) => part.type), ["text", "tool", "interaction", "text"]);
235
+ assert.deepEqual(
236
+ assistant.parts.map((part) => part.type),
237
+ ["text", "tool", "interaction", "text"],
238
+ );
249
239
  assert.deepEqual(assistant.parts.map((part) => part.sequence), [1, 2, 3, 4]);
250
240
  assert.equal(assistant.parts[1].state, "completed");
251
- assert.equal(assistant.parts[1].input_text, '{"cmd":"pwd"}');
252
241
  assert.deepEqual(assistant.parts[1].output, { count: 1 });
253
- assert.deepEqual(
254
- events
255
- .filter((event) => event.variant === "delta" && event.type === "tool_input")
256
- .map((event) => [event.tool_call_id, event.delta]),
257
- [
258
- ["call-1", '{"cmd":"'],
259
- ["call-1", 'pwd"}'],
260
- ],
261
- );
262
- assert.deepEqual(
263
- events
264
- .filter((event) => event.variant === "part" && event.type === "tool")
265
- .map((event) => [event.part.sequence, event.part.state]),
266
- [
267
- [2, "input-streaming"],
268
- [2, "ready"],
269
- [2, "waiting-user"],
270
- [2, "running"],
271
- [2, "completed"],
272
- ],
273
- );
274
242
  });
275
243
 
276
- test("Streaming Tool Provider metadata 在输入和输出状态间完整保留", async () => {
277
- const { recorder, file_path } = await create_recorder("tool-provider-metadata-test");
278
- const writer = await recorder.open_assistant_message({
279
- turn_id: "turn-provider-metadata",
280
- });
244
+ test("reasoning signature canonical Message 保留到模型历史", async () => {
245
+ const { recorder } = await create_recorder("reasoning-signature-test");
246
+ const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
281
247
  await writer.begin_step();
282
- await writer.apply_chunk({
283
- type: "tool-input-start",
284
- toolCallId: "call_1",
285
- toolName: "lookup",
286
- providerMetadata: { openai: { itemId: "fc_start" } },
248
+ await writer.apply_model_event({ type: "reasoning_start", content_id: "reasoning-1" });
249
+ await writer.apply_model_event({
250
+ type: "reasoning_delta",
251
+ content_id: "reasoning-1",
252
+ delta: "分析过程",
287
253
  });
288
- await writer.prepare_tool_input({
289
- tool_call_id: "call_1",
290
- tool_name: "lookup",
291
- input: { q: "x" },
292
- });
293
- await writer.apply_chunk({
294
- type: "tool-input-available",
295
- toolCallId: "call_1",
296
- toolName: "lookup",
297
- input: { q: "x" },
298
- providerExecuted: true,
299
- providerMetadata: { openai: { itemId: "fc_1" } },
300
- });
301
- await writer.apply_chunk({
302
- type: "tool-output-available",
303
- toolCallId: "call_1",
304
- output: "ok",
305
- providerExecuted: true,
306
- providerMetadata: { openai: { resultId: "result_1" } },
254
+ await writer.apply_model_event({
255
+ type: "reasoning_finish",
256
+ content_id: "reasoning-1",
257
+ signature: "opaque-signature",
307
258
  });
308
259
  await writer.finish_step([{
309
- part_id: "call_1",
260
+ part_id: "reasoning-1",
310
261
  sequence: 1,
311
- type: "tool",
312
- tool_call_id: "call_1",
313
- tool_name: "lookup",
314
- state: "completed",
315
- input: { q: "x" },
316
- output: "ok",
317
- provider_executed: true,
318
- call_provider_metadata: { openai: { itemId: "fc_final" } },
262
+ type: "reasoning",
263
+ text: "分析过程",
264
+ state: "done",
265
+ reasoning_signature: "opaque-signature",
319
266
  }]);
320
267
  await writer.complete();
321
268
 
322
- const assistant = (await read_jsonl(file_path))[0];
323
- const tool_part = assistant.parts.find((part) => part.type === "tool");
324
- assert.deepEqual(tool_part.call_provider_metadata, {
325
- openai: { itemId: "fc_final" },
326
- });
327
- assert.deepEqual(tool_part.result_provider_metadata, {
328
- openai: { resultId: "result_1" },
329
- });
330
- assert.equal(tool_part.provider_executed, true);
331
- assert.equal(tool_part.state, "completed");
269
+ const snapshot = await recorder.context_snapshot();
270
+ const model_messages = await session_context_to_model_messages(snapshot);
271
+ assert.equal(snapshot.messages[0].parts[0].reasoning_signature, "opaque-signature");
272
+ assert.equal(model_messages[0].content[0].signature, "opaque-signature");
332
273
  });
333
274
 
334
- test("AI SDK 流式 metadata、source、data、step 和审批状态完整持久化", async () => {
335
- const { recorder, file_path } = await create_recorder("ui-chunk-semantics-test");
336
- const writer = await recorder.open_assistant_message({
337
- turn_id: "turn-ui-chunks",
338
- });
339
- await writer.apply_chunk({
340
- type: "text-start",
341
- id: "text_1",
342
- providerMetadata: { openai: { itemId: "text_start" } },
343
- });
344
- await writer.apply_chunk({ type: "text-delta", id: "text_1", delta: "hello" });
345
- await writer.apply_chunk({
346
- type: "text-end",
347
- id: "text_1",
348
- providerMetadata: { openai: { itemId: "text_end" } },
349
- });
350
- await writer.apply_chunk({ type: "reasoning-start", id: "reasoning_1" });
351
- await writer.apply_chunk({
352
- type: "reasoning-delta",
353
- id: "reasoning_1",
354
- delta: "think",
355
- providerMetadata: { openai: { itemId: "reasoning_delta" } },
356
- });
357
- await writer.apply_chunk({ type: "reasoning-end", id: "reasoning_1" });
358
- await writer.apply_chunk({
359
- type: "source-url",
360
- sourceId: "source_1",
361
- url: "https://example.com/old",
362
- });
363
- await writer.apply_chunk({
364
- type: "source-url",
365
- sourceId: "source_1",
366
- url: "https://example.com/new",
367
- title: "Updated source",
368
- providerMetadata: { openai: { itemId: "source_item" } },
369
- });
370
- await writer.apply_chunk({
371
- type: "data-progress",
372
- id: "progress_1",
373
- data: { percent: 10 },
374
- });
375
- await writer.apply_chunk({
376
- type: "data-progress",
377
- id: "progress_1",
378
- data: { percent: 90 },
379
- });
380
- await writer.apply_chunk({
381
- type: "data-progress",
382
- id: "transient_1",
383
- data: { percent: 100 },
384
- transient: true,
385
- });
386
- await writer.apply_chunk({
387
- type: "tool-input-start",
388
- toolCallId: "call_approval",
389
- toolName: "lookup",
390
- title: "Lookup",
391
- dynamic: true,
392
- providerMetadata: { openai: { itemId: "fc_approval" } },
393
- });
394
- await writer.apply_chunk({
395
- type: "tool-input-available",
396
- toolCallId: "call_approval",
397
- toolName: "lookup",
398
- input: { q: "downcity" },
399
- toolMetadata: { category: "search" },
400
- });
401
- await writer.apply_chunk({
402
- type: "tool-approval-request",
403
- approvalId: "approval_stream_1",
404
- toolCallId: "call_approval",
405
- });
406
- await writer.apply_chunk({
407
- type: "tool-output-denied",
408
- toolCallId: "call_approval",
409
- });
410
- await writer.apply_chunk({
411
- type: "file",
412
- mediaType: "image/png",
413
- url: "https://example.com/output.png",
414
- providerMetadata: { openai: { fileId: "output_file" } },
415
- });
416
- await writer.apply_chunk({ type: "start-step" });
417
- await writer.complete();
418
-
419
- const assistant = (await read_jsonl(file_path))[0];
420
- assert.deepEqual(assistant.parts.map((part) => part.type), [
421
- "text",
422
- "reasoning",
423
- "source",
424
- "data",
425
- "tool",
426
- "file",
427
- "step-start",
428
- ]);
429
- assert.deepEqual(assistant.parts[0].provider_metadata, {
430
- openai: { itemId: "text_end" },
431
- });
432
- assert.deepEqual(assistant.parts[1].provider_metadata, {
433
- openai: { itemId: "reasoning_delta" },
434
- });
435
- assert.equal(assistant.parts[2].url, "https://example.com/new");
436
- assert.equal(assistant.parts[2].title, "Updated source");
437
- assert.deepEqual(assistant.parts[3].data, { percent: 90 });
438
- const tool_part = assistant.parts[4];
439
- assert.equal(tool_part.state, "failed");
440
- assert.equal(tool_part.title, "Lookup");
441
- assert.equal(tool_part.dynamic, true);
442
- assert.deepEqual(tool_part.tool_metadata, { category: "search" });
443
- assert.equal("approval" in tool_part, false);
444
- assert.equal(
445
- assistant.parts.some((part) => part.type === "data" && part.data_id === "transient_1"),
446
- false,
447
- );
448
- });
275
+ test("多个模型 Step 可复用 content_id 且追加到同一 Assistant Message", async () => {
276
+ const { recorder } = await create_recorder("reused-content-id-test");
277
+ const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
449
278
 
450
- test("UI Tool Provider metadata 经 Session roundtrip 后恢复为 ModelMessage providerOptions", async () => {
451
- const parts = from_ui_assistant_parts([{
452
- type: "dynamic-tool",
453
- toolName: "lookup",
454
- toolCallId: "call_1",
455
- state: "output-available",
456
- input: { q: "x" },
457
- output: "ok",
458
- providerExecuted: false,
459
- callProviderMetadata: { openai: { itemId: "fc_1" } },
460
- resultProviderMetadata: { openai: { resultId: "result_1" } },
279
+ await writer.begin_step();
280
+ await write_text(writer, "text-1", "first");
281
+ await writer.finish_step([{
282
+ part_id: "first",
283
+ sequence: 1,
284
+ type: "text",
285
+ text: "first",
286
+ state: "done",
461
287
  }]);
462
- assert.deepEqual(parts[0].call_provider_metadata, {
463
- openai: { itemId: "fc_1" },
464
- });
465
- assert.deepEqual(parts[0].result_provider_metadata, {
466
- openai: { resultId: "result_1" },
467
- });
468
- assert.equal(parts[0].provider_executed, false);
469
288
 
470
- const restored = to_executor_ui_message({
471
- message_id: "assistant_1",
472
- session_id: "session_1",
473
- turn_id: "turn_1",
289
+ await writer.begin_step();
290
+ await write_text(writer, "text-1", "second");
291
+ await writer.finish_step([{
292
+ part_id: "second",
474
293
  sequence: 1,
475
- revision: 1,
476
- visibility: "visible",
477
- created_at: 1,
478
- updated_at: 1,
479
- type: "assistant",
480
- kind: "normal",
481
- status: "completed",
482
- parts,
483
- });
484
- assert.deepEqual(restored.parts[0].callProviderMetadata, {
485
- openai: { itemId: "fc_1" },
486
- });
487
- assert.deepEqual(restored.parts[0].resultProviderMetadata, {
488
- openai: { resultId: "result_1" },
489
- });
490
- assert.equal(restored.parts[0].providerExecuted, false);
294
+ type: "text",
295
+ text: "second",
296
+ state: "done",
297
+ }]);
298
+ await writer.complete();
491
299
 
492
- const model_messages = await convertToModelMessages([restored]);
493
- const model_parts = model_messages.flatMap((message) =>
494
- Array.isArray(message.content) ? message.content : []
495
- );
496
- const tool_call = model_parts.find((part) => part.type === "tool-call");
497
- const tool_result = model_parts.find((part) => part.type === "tool-result");
498
- assert.equal(tool_call.toolCallId, "call_1");
499
- assert.deepEqual(tool_call.providerOptions, {
500
- openai: { itemId: "fc_1" },
501
- });
502
- assert.deepEqual(tool_result.providerOptions, {
503
- openai: { itemId: "fc_1" },
504
- });
300
+ const assistant = recorder.get_message(writer.message_id);
301
+ assert.deepEqual(assistant.parts.map((part) => part.text), ["first", "second"]);
302
+ assert.deepEqual(assistant.parts.map((part) => part.sequence), [1, 2]);
303
+ });
505
304
 
506
- const typed_parts = from_ui_assistant_parts([{
507
- type: "tool-lookup",
508
- toolCallId: "call_2",
509
- state: "input-available",
510
- input: { q: "typed" },
511
- providerExecuted: true,
512
- callProviderMetadata: { openai: { itemId: "fc_2" } },
513
- }]);
514
- assert.equal(typed_parts[0].tool_name, "lookup");
515
- assert.equal(typed_parts[0].provider_executed, true);
516
- assert.deepEqual(typed_parts[0].call_provider_metadata, {
517
- openai: { itemId: "fc_2" },
305
+ test("工具执行等待对应标准流事件创建 canonical Part", async () => {
306
+ const { recorder } = await create_recorder("tool-gate-test");
307
+ const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
308
+ await writer.begin_step();
309
+ let prepared = false;
310
+ const preparation = writer.prepare_tool_input({
311
+ tool_call_id: "call-1",
312
+ tool_name: "lookup",
313
+ input: { query: "downcity" },
314
+ }).then(() => {
315
+ prepared = true;
518
316
  });
317
+ await Promise.resolve();
318
+ assert.equal(prepared, false);
519
319
 
520
- const provider_executed = to_executor_ui_message({
521
- message_id: "assistant_2",
522
- session_id: "session_1",
523
- turn_id: "turn_1",
524
- sequence: 2,
525
- revision: 1,
526
- visibility: "visible",
527
- created_at: 2,
528
- updated_at: 2,
529
- type: "assistant",
530
- kind: "normal",
531
- status: "completed",
532
- parts: from_ui_assistant_parts([{
533
- type: "dynamic-tool",
534
- toolName: "provider_lookup",
535
- toolCallId: "call_3",
536
- state: "output-available",
537
- input: { q: "provider" },
538
- output: "provider result",
539
- providerExecuted: true,
540
- callProviderMetadata: { openai: { itemId: "fc_3" } },
541
- resultProviderMetadata: { openai: { resultId: "result_3" } },
542
- }]),
543
- });
544
- const provider_model_messages = await convertToModelMessages([
545
- provider_executed,
546
- ]);
547
- const provider_result = provider_model_messages
548
- .flatMap((message) => Array.isArray(message.content) ? message.content : [])
549
- .find((part) => part.type === "tool-result");
550
- assert.deepEqual(provider_result.providerOptions, {
551
- openai: { resultId: "result_3" },
320
+ await writer.apply_model_event({
321
+ type: "tool_call_start",
322
+ content_id: "tool-1",
323
+ tool_call_id: "call-1",
324
+ tool_name: "lookup",
552
325
  });
326
+ await preparation;
327
+ assert.equal(prepared, true);
328
+ assert.equal(recorder.get_message(writer.message_id).parts[0].state, "ready");
329
+ await writer.abort_step();
330
+ await writer.fail("stopped");
553
331
  });
554
332
 
555
- test("SessionMessage 保留稳定语义并归一化 AI SDK 原生审批字段", () => {
556
- const user_parts = [
557
- {
558
- type: "text",
559
- text: "分析附件",
560
- state: "done",
561
- providerMetadata: { openai: { itemId: "user_text_1" } },
562
- },
563
- {
564
- type: "file",
565
- mediaType: "image/png",
566
- url: "https://example.com/input.png",
567
- filename: "input.png",
568
- providerMetadata: { openai: { fileId: "file_1" } },
569
- },
570
- {
571
- type: "data-preferences",
572
- id: "preferences_1",
573
- data: { locale: "zh-CN" },
574
- },
575
- ];
576
- const restored_user = to_executor_ui_message({
577
- message_id: "user_roundtrip",
578
- session_id: "session_roundtrip",
579
- turn_id: "turn_roundtrip",
580
- sequence: 1,
581
- revision: 1,
582
- visibility: "visible",
583
- created_at: 1,
584
- updated_at: 1,
585
- type: "user",
586
- input_type: "prompt",
587
- parts: from_ui_user_parts(user_parts),
588
- });
589
- assert.deepEqual(restored_user.parts, user_parts);
590
-
591
- const assistant_parts = [
592
- {
593
- type: "text",
594
- text: "正在处理",
595
- state: "streaming",
596
- providerMetadata: { openai: { itemId: "msg_1" } },
597
- },
598
- {
599
- type: "reasoning",
600
- text: "先读取来源",
601
- state: "done",
602
- providerMetadata: { openai: { itemId: "reasoning_1" } },
603
- },
604
- {
605
- type: "file",
606
- mediaType: "application/pdf",
607
- url: "https://example.com/report.pdf",
608
- filename: "report.pdf",
609
- providerMetadata: { openai: { fileId: "file_2" } },
610
- },
611
- {
612
- type: "source-url",
613
- sourceId: "source_url_1",
614
- url: "https://example.com/source",
615
- title: "Example source",
616
- providerMetadata: { openai: { itemId: "source_1" } },
617
- },
618
- {
619
- type: "source-document",
620
- sourceId: "source_document_1",
621
- mediaType: "application/pdf",
622
- title: "Source document",
623
- filename: "source.pdf",
624
- providerMetadata: { openai: { itemId: "source_2" } },
625
- },
626
- { type: "step-start" },
627
- {
628
- type: "data-progress",
629
- id: "progress_1",
630
- data: { percent: 80 },
631
- },
632
- {
633
- type: "dynamic-tool",
634
- toolName: "lookup",
635
- toolCallId: "call_streaming",
636
- state: "input-streaming",
637
- title: "Lookup",
638
- toolMetadata: { category: "search" },
639
- providerExecuted: false,
640
- callProviderMetadata: { openai: { itemId: "fc_streaming" } },
641
- },
642
- {
643
- type: "tool-fetch",
644
- toolCallId: "call_ready",
645
- state: "input-available",
646
- input: { url: "https://example.com" },
647
- title: "Fetch",
648
- toolMetadata: { category: "network" },
649
- providerExecuted: true,
650
- callProviderMetadata: { openai: { itemId: "fc_ready" } },
651
- },
652
- {
653
- type: "dynamic-tool",
654
- toolName: "shell_exec",
655
- toolCallId: "call_approval",
656
- state: "approval-requested",
657
- input: { cmd: "pwd" },
658
- approval: { id: "approval_1" },
659
- },
660
- {
661
- type: "tool-shell_exec",
662
- toolCallId: "call_responded",
663
- state: "approval-responded",
664
- input: { cmd: "ls" },
665
- approval: { id: "approval_2", approved: true, reason: "Allowed" },
666
- },
667
- {
668
- type: "dynamic-tool",
669
- toolName: "lookup",
670
- toolCallId: "call_completed",
671
- state: "output-available",
672
- input: { q: "downcity" },
673
- output: { count: 1 },
674
- preliminary: true,
675
- approval: { id: "approval_3", approved: true, reason: "Allowed" },
676
- callProviderMetadata: { openai: { itemId: "fc_completed" } },
677
- resultProviderMetadata: { openai: { itemId: "result_completed" } },
678
- },
679
- {
680
- type: "dynamic-tool",
681
- toolName: "parse",
682
- toolCallId: "call_error",
683
- state: "output-error",
684
- input: undefined,
685
- rawInput: "{invalid",
686
- errorText: "Invalid input",
687
- approval: { id: "approval_4", approved: true },
688
- resultProviderMetadata: { openai: { itemId: "result_error" } },
689
- },
333
+ test("Step 最终快照不能补造未经过模型事件的 Part", async () => {
334
+ const { recorder } = await create_recorder("snapshot-mismatch-test");
335
+ const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
336
+ await writer.begin_step();
337
+ await write_text(writer, "text-1", "final");
338
+ await assert.rejects(writer.finish_step([
690
339
  {
691
- type: "dynamic-tool",
692
- toolName: "delete",
693
- toolCallId: "call_denied",
694
- state: "output-denied",
695
- input: { path: "/tmp/a" },
696
- approval: { id: "approval_5", approved: false, reason: "Denied" },
340
+ part_id: "tool-1",
341
+ sequence: 1,
342
+ type: "tool",
343
+ tool_call_id: "call-1",
344
+ tool_name: "lookup",
345
+ state: "completed",
346
+ input: {},
347
+ output: "ok",
697
348
  },
698
- ];
699
- const canonical_parts = from_ui_assistant_parts(assistant_parts);
700
- const restored_assistant = to_executor_ui_message({
701
- message_id: "assistant_roundtrip",
702
- session_id: "session_roundtrip",
703
- turn_id: "turn_roundtrip",
704
- sequence: 2,
705
- revision: 1,
706
- visibility: "visible",
707
- created_at: 2,
708
- updated_at: 2,
709
- type: "assistant",
710
- kind: "normal",
711
- status: "streaming",
712
- parts: canonical_parts,
713
- });
714
- assert.deepEqual(restored_assistant.parts.slice(0, 9), assistant_parts.slice(0, 9));
715
- const restored_tools = restored_assistant.parts.slice(9);
716
- assert.deepEqual(
717
- restored_tools.map((part) => part.state),
718
- ["input-available", "input-available", "output-available", "output-error", "output-error"],
719
- );
720
- assert.equal(restored_tools.every((part) => !("approval" in part)), true);
721
- assert.equal(canonical_parts.find((part) => part.tool_call_id === "call_ready").dynamic, false);
722
- assert.equal(canonical_parts.find((part) => part.tool_call_id === "call_completed").dynamic, true);
349
+ { part_id: "text-1", sequence: 2, type: "text", text: "final", state: "done" },
350
+ ]), /snapshot mismatch/);
351
+ await writer.abort_step();
352
+ await writer.fail("invalid stream");
723
353
  });
724
354
 
725
- test("Session Interaction 只接受已经准备完整输入的 Tool", async () => {
726
- const { recorder, events } = await create_recorder("tool-ready-barrier-test");
727
- const interactions = new SessionInteractions({
728
- session_id: "tool-ready-barrier-test",
729
- messages: recorder,
730
- });
731
- const approval_adapter = new SessionShellApprovalAdapter({
732
- session_id: "tool-ready-barrier-test",
733
- interactions,
734
- });
735
- const writer = await recorder.open_assistant_message({
736
- turn_id: "turn-ready-barrier",
737
- });
738
- const approval_input = {
739
- shell_id: "shell-ready-barrier",
740
- tool_call_id: "call-ready-barrier",
741
- tool_name: "shell_exec",
742
- session_id: "tool-ready-barrier-test",
743
- turn_id: "turn-ready-barrier",
744
- command: "ls -la /Users/example/Desktop",
745
- cwd: "/workspace",
746
- reason: "Inspect requested desktop files",
747
- operation: "exec",
748
- timeout_ms: 60_000,
749
- };
750
- await assert.rejects(
751
- approval_adapter.request(approval_input),
752
- /Streaming Tool Part not found/,
355
+ test("Tool Part 持久化失败时不释放工具执行等待", async () => {
356
+ const { recorder, store } = await create_recorder(
357
+ "tool-write-failure-test",
358
+ (options) => new FailingAssistantMessageStore(options),
753
359
  );
754
- assert.equal(interactions.list().length, 0);
755
-
756
- const tool_input_ready = writer.prepare_tool_input({
757
- tool_call_id: "call-ready-barrier",
758
- tool_name: "shell_exec",
759
- input: {
760
- cmd: "ls -la /Users/example/Desktop",
761
- sandbox: "unrestricted",
762
- reason: "Inspect requested desktop files",
763
- },
764
- });
765
- await writer.flush();
766
- assert.deepEqual(recorder.get_message(writer.message_id).parts, []);
767
- await writer.apply_chunk({
768
- type: "tool-input-start",
769
- toolCallId: "call-ready-barrier",
770
- toolName: "shell_exec",
771
- });
772
- await tool_input_ready;
773
- const approval_handle = await approval_adapter.request(approval_input);
360
+ const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
361
+ await writer.begin_step();
362
+ store.fail_next_assistant_write("tool write failed");
363
+ await assert.rejects(writer.apply_model_event({
364
+ type: "tool_call_start",
365
+ content_id: "tool-1",
366
+ tool_call_id: "call-1",
367
+ tool_name: "lookup",
368
+ }), /tool write failed/);
774
369
 
775
- const tool = recorder.get_message(writer.message_id).parts[0];
776
- const interaction = recorder.get_message(writer.message_id).parts[1];
777
- assert.equal(tool.state, "waiting-user");
778
- assert.equal(interaction.interaction_id, approval_handle.approval_id);
779
- assert.equal(interaction.request.command, approval_input.command);
780
- assert.equal(tool.input.cmd, "ls -la /Users/example/Desktop");
781
- await interactions.respond({
782
- interaction_id: approval_handle.approval_id,
783
- response: { kind: "approval", decision: "denied" },
784
- });
785
- assert.equal(await approval_handle.decision, "denied");
786
- await writer.apply_chunk({
787
- type: "tool-output-available",
788
- toolCallId: "call-ready-barrier",
789
- output: { success: false, approval_status: "denied" },
370
+ const pending = writer.prepare_tool_input({
371
+ tool_call_id: "call-1",
372
+ tool_name: "lookup",
373
+ input: {},
790
374
  });
791
- assert.equal(recorder.get_message(writer.message_id).parts[0].state, "failed");
792
- assert.deepEqual(
793
- events
794
- .filter((event) => event.variant === "part" && event.type === "tool")
795
- .map((event) => event.part.state),
796
- ["input-streaming", "ready", "waiting-user", "failed"],
797
- );
375
+ await writer.abort_step();
376
+ await assert.rejects(pending, /aborted/);
377
+ await writer.fail("write failed");
798
378
  });
799
379
 
800
- test("Question Interaction 校验文本、单选与多选回答后恢复执行", async () => {
801
- const { recorder } = await create_recorder("question-interaction-test");
802
- const interactions = new SessionInteractions({
803
- session_id: "question-interaction-test",
804
- messages: recorder,
805
- });
806
- const writer = await recorder.open_assistant_message({
807
- turn_id: "turn-question",
808
- });
809
- const handle = await interactions.request({
810
- interaction_id: "interaction-question",
811
- turn_id: "turn-question",
812
- kind: "question",
813
- source: { type: "execution" },
814
- title: "补充信息",
815
- questions: [
816
- { question_id: "name", prompt: "项目名称?", response_type: "text" },
817
- {
818
- question_id: "region",
819
- prompt: "部署区域?",
820
- response_type: "single_select",
821
- options: [{ value: "cn", label: "中国" }],
822
- },
823
- {
824
- question_id: "features",
825
- prompt: "启用能力?",
826
- response_type: "multi_select",
827
- options: [{ value: "search", label: "搜索" }],
828
- },
829
- ],
830
- created_at: Date.now(),
380
+ test("重启时收口流式 Assistant 和运行中 Action", async () => {
381
+ const session_id = "restart-recovery-test";
382
+ const harness = await create_recorder(session_id);
383
+ const writer = await harness.recorder.open_assistant_message({ turn_id: "turn-1" });
384
+ await writer.begin_step();
385
+ await writer.apply_model_event({ type: "text_start", content_id: "text-1" });
386
+ await writer.apply_model_event({ type: "text_delta", content_id: "text-1", delta: "partial" });
387
+ await harness.recorder.open_action_message({
388
+ message_id: "action-1",
389
+ turn_id: "turn-1",
390
+ action_type: "test",
391
+ title: "Running action",
831
392
  });
832
393
 
833
- await assert.rejects(
834
- interactions.respond({
835
- interaction_id: handle.interaction_id,
836
- response: {
837
- kind: "question",
838
- answers: [{ question_id: "name", value: "Downcity" }],
839
- },
394
+ const restarted = new SessionMessages({
395
+ session_id,
396
+ store: new JsonlSessionMessageStore({
397
+ files: harness.files,
398
+ session_id,
399
+ file_path: harness.file_path,
400
+ assistant_message_file_path: harness.assistant_message_file_path,
840
401
  }),
841
- /answer count mismatch/,
842
- );
843
- const result = await interactions.respond({
844
- interaction_id: handle.interaction_id,
845
- response: {
846
- kind: "question",
847
- answers: [
848
- { question_id: "name", value: "Downcity" },
849
- { question_id: "region", value: "cn" },
850
- { question_id: "features", value: ["search"] },
851
- ],
852
- },
402
+ publish: () => {},
853
403
  });
854
-
855
- assert.deepEqual(await handle.result, result);
856
- const interaction = recorder.get_message(writer.message_id).parts[0];
857
- assert.equal(interaction.type, "interaction");
858
- assert.equal(interaction.status, "resolved");
859
- assert.deepEqual(interaction.response, result.response);
404
+ await restarted.initialize();
405
+ const page = await restarted.list_messages();
406
+ const assistant = page.items.find((message) => message.type === "assistant");
407
+ const action = page.items.find((message) => message.type === "action");
408
+ assert.equal(assistant.status, "stopped");
409
+ assert.equal(assistant.parts[0].text, "partial");
410
+ assert.equal(action.status, "failed");
860
411
  });
861
412
 
862
- test("取消 Interaction 先原子关闭 Tool 与 Interaction,再兑现等待结果", async () => {
863
- const { recorder } = await create_recorder("cancel-interaction-test");
864
- const interactions = new SessionInteractions({
865
- session_id: "cancel-interaction-test",
866
- messages: recorder,
867
- });
868
- const writer = await recorder.open_assistant_message({
869
- turn_id: "turn-cancel",
870
- });
871
- await writer.apply_chunk({
872
- type: "tool-input-start",
873
- toolCallId: "call-cancel",
874
- toolName: "ask_user",
875
- });
876
- await writer.prepare_tool_input({
877
- tool_call_id: "call-cancel",
878
- tool_name: "ask_user",
879
- input: { prompt: "继续吗?" },
880
- });
881
- const handle = await interactions.request({
882
- interaction_id: "interaction-cancel",
883
- turn_id: "turn-cancel",
884
- kind: "question",
885
- source: {
886
- type: "tool",
887
- tool_call_id: "call-cancel",
888
- tool_name: "ask_user",
889
- },
890
- title: "确认",
891
- questions: [{ question_id: "continue", prompt: "继续吗?", response_type: "text" }],
892
- created_at: Date.now(),
893
- });
894
-
895
- await interactions.cancel_all("turn_stopped");
896
- assert.deepEqual(await handle.result, {
897
- status: "cancelled",
898
- interaction_id: "interaction-cancel",
899
- reason: "turn_stopped",
900
- });
901
- const parts = recorder.get_message(writer.message_id).parts;
902
- assert.equal(parts.find((part) => part.type === "tool").state, "failed");
903
- assert.equal(parts.find((part) => part.type === "interaction").status, "cancelled");
904
- });
905
-
906
- test("Interaction 超时后持久化 expired 终态", async () => {
907
- const { recorder } = await create_recorder("expire-interaction-test");
908
- const interactions = new SessionInteractions({
909
- session_id: "expire-interaction-test",
910
- messages: recorder,
911
- });
912
- const writer = await recorder.open_assistant_message({
913
- turn_id: "turn-expire",
914
- });
915
- const handle = await interactions.request({
916
- interaction_id: "interaction-expire",
917
- turn_id: "turn-expire",
918
- kind: "question",
919
- source: { type: "execution" },
920
- title: "即将超时",
921
- questions: [{ question_id: "value", prompt: "请输入", response_type: "text" }],
922
- created_at: Date.now(),
923
- expires_at: Date.now() + 10,
924
- });
925
-
926
- await new Promise((resolve) => setTimeout(resolve, 20));
927
- assert.deepEqual(await handle.result, {
928
- status: "expired",
929
- interaction_id: "interaction-expire",
930
- });
931
- assert.equal(recorder.get_message(writer.message_id).parts[0].status, "expired");
932
- });
933
-
934
- test("流式更新与 Interaction 共享 Assistant revision 写队列", async () => {
935
- const session_id = "approval-revision-queue-test";
936
- const { recorder, store, events } = await create_recorder(
937
- session_id,
938
- (options) => new PausingAssistantMessageStore(options),
939
- );
940
- const interactions = new SessionInteractions({ session_id, messages: recorder });
941
- const approval_adapter = new SessionShellApprovalAdapter({ session_id, interactions });
942
- const writer = await recorder.open_assistant_message({
943
- turn_id: "turn-approval-race",
944
- });
945
- await writer.apply_chunk({
946
- type: "tool-input-start",
947
- toolCallId: "call-approval-race",
948
- toolName: "shell_exec",
949
- });
950
- await writer.prepare_tool_input({
951
- tool_call_id: "call-approval-race",
952
- tool_name: "shell_exec",
953
- input: {
954
- cmd: "ls -la ~",
955
- sandbox: "unrestricted",
956
- reason: "验证审批写入顺序",
957
- },
958
- });
959
- await writer.apply_chunk({ type: "text-start", id: "text-race" });
960
- await writer.apply_chunk({ type: "text-delta", id: "text-race", delta: "准备执行" });
961
-
962
- const gate = store.pause_next_assistant_write();
963
- const stream_write = writer.apply_chunk({
964
- type: "text-delta",
965
- id: "text-race",
966
- delta: "命令",
967
- });
968
- await gate.started;
969
- const approval_write = approval_adapter.request({
970
- shell_id: "shell-approval-race",
971
- tool_call_id: "call-approval-race",
972
- tool_name: "shell_exec",
973
- session_id,
974
- turn_id: "turn-approval-race",
975
- command: "ls -la ~",
976
- cwd: "/workspace",
977
- reason: "验证审批写入顺序",
978
- operation: "exec",
979
- timeout_ms: 60_000,
980
- });
981
-
982
- // 让 Approval 路径有机会进入写入;旧实现会在这里基于同一 revision 提交。
983
- await new Promise((resolve) => setImmediate(resolve));
984
- gate.release();
985
- const [, approval_handle] = await Promise.all([stream_write, approval_write]);
986
-
987
- const assistant = recorder.get_message(writer.message_id);
988
- const tool = assistant.parts.find((part) => part.type === "tool");
989
- const text_part = assistant.parts.find((part) => part.type === "text");
990
- assert.equal(assistant.revision, 7);
991
- const interaction = assistant.parts.find((part) => part.type === "interaction");
992
- assert.equal(tool.state, "waiting-user");
993
- assert.equal(interaction.interaction_id, approval_handle.approval_id);
994
- assert.equal(tool.input.cmd, "ls -la ~");
995
- assert.equal(text_part.text, "准备执行命令");
996
- assert.deepEqual(
997
- events
998
- .filter((event) => event.message_id === writer.message_id)
999
- .map((event) => event.revision),
1000
- [1, 2, 3, 4, 5, 6, 7, 7],
1001
- );
1002
- assert.equal(
1003
- events.some(
1004
- (event) =>
1005
- event.variant === "part" &&
1006
- event.type === "interaction" &&
1007
- event.part.status === "pending",
1008
- ),
1009
- true,
1010
- );
1011
-
1012
- await interactions.respond({
1013
- interaction_id: approval_handle.approval_id,
1014
- response: { kind: "approval", decision: "denied" },
1015
- });
1016
- assert.equal(await approval_handle.decision, "denied");
1017
- });
1018
-
1019
- test("不同模型 step 重复使用 Text chunk ID 时仍保持真实 Part 顺序", async () => {
1020
- const { recorder, file_path } = await create_recorder("reused-text-id-order-test");
1021
- const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
1022
-
1023
- await writer.begin_step();
1024
- await writer.apply_chunk({ type: "text-start", id: "txt-0" });
1025
- await writer.apply_chunk({ type: "text-delta", id: "txt-0", delta: "first" });
1026
- await writer.apply_chunk({ type: "text-end", id: "txt-0" });
1027
- await writer.apply_chunk({ type: "tool-input-start", toolCallId: "call-1", toolName: "search" });
1028
- await writer.apply_chunk({
1029
- type: "tool-input-available",
1030
- toolCallId: "call-1",
1031
- toolName: "search",
1032
- input: { query: "first" },
1033
- });
1034
- await writer.apply_chunk({ type: "tool-output-available", toolCallId: "call-1", output: "one" });
1035
- await writer.finish_step([
1036
- {
1037
- part_id: "text-1",
1038
- sequence: 1,
1039
- type: "text",
1040
- text: "first",
1041
- state: "done",
1042
- },
1043
- {
1044
- part_id: "call-1",
1045
- sequence: 2,
1046
- type: "tool",
1047
- tool_call_id: "call-1",
1048
- tool_name: "search",
1049
- state: "completed",
1050
- input: { query: "first" },
1051
- output: "one",
1052
- },
1053
- ]);
1054
-
1055
- await writer.begin_step();
1056
- await writer.apply_chunk({ type: "text-start", id: "txt-0" });
1057
- await writer.apply_chunk({ type: "text-delta", id: "txt-0", delta: "second" });
1058
- await writer.apply_chunk({ type: "text-end", id: "txt-0" });
1059
- await writer.apply_chunk({ type: "tool-input-start", toolCallId: "call-2", toolName: "search" });
1060
- await writer.apply_chunk({
1061
- type: "tool-input-available",
1062
- toolCallId: "call-2",
1063
- toolName: "search",
1064
- input: { query: "second" },
1065
- });
1066
- await writer.apply_chunk({ type: "tool-output-available", toolCallId: "call-2", output: "two" });
1067
- await writer.finish_step([
1068
- {
1069
- part_id: "text-1",
1070
- sequence: 1,
1071
- type: "text",
1072
- text: "second",
1073
- state: "done",
1074
- },
1075
- {
1076
- part_id: "call-2",
1077
- sequence: 2,
1078
- type: "tool",
1079
- tool_call_id: "call-2",
1080
- tool_name: "search",
1081
- state: "completed",
1082
- input: { query: "second" },
1083
- output: "two",
1084
- },
1085
- ]);
1086
- await writer.complete();
1087
-
1088
- const assistant = (await read_jsonl(file_path))[0];
1089
- assert.deepEqual(assistant.parts.map((part) => part.type), ["text", "tool", "text", "tool"]);
1090
- assert.deepEqual(assistant.parts.map((part) => part.sequence), [1, 2, 3, 4]);
1091
- assert.deepEqual(
1092
- assistant.parts.filter((part) => part.type === "text").map((part) => part.text),
1093
- ["first", "second"],
1094
- );
1095
- assert.notEqual(assistant.parts[0].part_id, assistant.parts[2].part_id);
1096
- });
1097
-
1098
- test("不同模型 step 重复 Source 与 Data ID 时创建独立 canonical Parts", async () => {
1099
- const { recorder, file_path } = await create_recorder("reused-structured-id-order-test");
1100
- const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
1101
-
1102
- for (const step of [1, 2]) {
1103
- await writer.begin_step();
1104
- await writer.apply_chunk({
1105
- type: "source-url",
1106
- sourceId: "source-1",
1107
- url: `https://example.com/${step}`,
1108
- });
1109
- await writer.apply_chunk({
1110
- type: "data-status",
1111
- id: "status-1",
1112
- data: { step },
1113
- });
1114
- await writer.finish_step([
1115
- {
1116
- part_id: "source-1",
1117
- sequence: 1,
1118
- type: "source",
1119
- source_type: "url",
1120
- source_id: "source-1",
1121
- url: `https://example.com/${step}`,
1122
- },
1123
- {
1124
- part_id: "data-1",
1125
- sequence: 2,
1126
- type: "data",
1127
- data_type: "data-status",
1128
- data: { step },
1129
- data_id: "status-1",
1130
- },
1131
- ]);
1132
- }
1133
- await writer.complete();
1134
-
1135
- const assistant = (await read_jsonl(file_path))[0];
1136
- assert.deepEqual(
1137
- assistant.parts.map((part) => part.type),
1138
- ["source", "data", "source", "data"],
1139
- );
1140
- assert.deepEqual(assistant.parts.map((part) => part.sequence), [1, 2, 3, 4]);
1141
- assert.notEqual(assistant.parts[0].part_id, assistant.parts[2].part_id);
1142
- assert.notEqual(assistant.parts[1].part_id, assistant.parts[3].part_id);
1143
- });
1144
-
1145
- test("step 最终快照忽略没有 delta 的空 Text 与 Reasoning Part", async () => {
1146
- const { recorder, file_path } = await create_recorder("empty-final-text-part-test");
1147
- const writer = await recorder.open_assistant_message({
1148
- turn_id: "turn-empty-final-text",
1149
- });
1150
-
1151
- await writer.begin_step();
1152
- await writer.apply_chunk({ type: "text-start", id: "text-empty" });
1153
- await writer.apply_chunk({ type: "text-delta", id: "text-empty", delta: "" });
1154
- await writer.apply_chunk({ type: "text-end", id: "text-empty" });
1155
- await writer.apply_chunk({
1156
- type: "tool-input-start",
1157
- toolCallId: "call-1",
1158
- toolName: "find",
1159
- });
1160
- await writer.apply_chunk({
1161
- type: "tool-input-available",
1162
- toolCallId: "call-1",
1163
- toolName: "find",
1164
- input: { glob: "**/*.mdx" },
1165
- });
1166
- await writer.apply_chunk({
1167
- type: "tool-output-available",
1168
- toolCallId: "call-1",
1169
- output: ["document.mdx"],
1170
- });
1171
- await writer.apply_chunk({ type: "reasoning-start", id: "reasoning-empty" });
1172
- await writer.apply_chunk({ type: "reasoning-end", id: "reasoning-empty" });
1173
- const final_parts = from_ui_assistant_parts([
1174
- {
1175
- type: "text",
1176
- text: "",
1177
- state: "done",
1178
- },
1179
- {
1180
- type: "tool-find",
1181
- toolCallId: "call-1",
1182
- state: "output-available",
1183
- input: { glob: "**/*.mdx" },
1184
- output: ["document.mdx"],
1185
- },
1186
- {
1187
- type: "reasoning",
1188
- text: "",
1189
- state: "done",
1190
- },
1191
- ]);
1192
- assert.deepEqual(final_parts.map((part) => part.type), ["tool"]);
1193
- await writer.finish_step(final_parts);
1194
- await writer.complete();
1195
-
1196
- const assistant = (await read_jsonl(file_path))[0];
1197
- assert.equal(assistant.status, "completed");
1198
- assert.deepEqual(assistant.parts.map((part) => part.type), ["tool"]);
1199
- assert.equal(assistant.parts[0].tool_call_id, "call-1");
1200
- });
1201
-
1202
- test("空 Reasoning 携带 Provider metadata 时作为跨轮重放协议 Part 保存", async () => {
1203
- const { recorder, file_path } = await create_recorder("empty-provider-reasoning-test");
1204
- const writer = await recorder.open_assistant_message({
1205
- turn_id: "turn-empty-provider-reasoning",
1206
- });
1207
- const reasoning_metadata = {
1208
- openai: { itemId: "rs_required" },
1209
- };
1210
- const message_metadata = {
1211
- openai: { itemId: "msg_required", phase: "final_answer" },
1212
- };
1213
-
1214
- await writer.begin_step();
1215
- await writer.apply_chunk({
1216
- type: "reasoning-start",
1217
- id: "reasoning-empty-provider",
1218
- providerMetadata: reasoning_metadata,
1219
- });
1220
- await writer.apply_chunk({
1221
- type: "reasoning-end",
1222
- id: "reasoning-empty-provider",
1223
- providerMetadata: reasoning_metadata,
1224
- });
1225
- await writer.apply_chunk({ type: "text-start", id: "text-provider" });
1226
- await writer.apply_chunk({
1227
- type: "text-delta",
1228
- id: "text-provider",
1229
- delta: "完成",
1230
- });
1231
- await writer.apply_chunk({
1232
- type: "text-end",
1233
- id: "text-provider",
1234
- providerMetadata: message_metadata,
1235
- });
1236
- const final_parts = from_ui_assistant_parts([
1237
- {
1238
- type: "reasoning",
1239
- text: "",
1240
- state: "done",
1241
- providerMetadata: reasoning_metadata,
1242
- },
1243
- {
1244
- type: "text",
1245
- text: "完成",
1246
- state: "done",
1247
- providerMetadata: message_metadata,
1248
- },
1249
- ]);
1250
- assert.deepEqual(final_parts.map((part) => part.type), ["reasoning", "text"]);
1251
- await writer.finish_step(final_parts);
1252
- await writer.complete();
1253
-
1254
- const assistant = (await read_jsonl(file_path))[0];
1255
- assert.deepEqual(assistant.parts.map((part) => part.type), ["reasoning", "text"]);
1256
- assert.equal(assistant.parts[0].text, "");
1257
- assert.deepEqual(assistant.parts[0].provider_metadata, reasoning_metadata);
1258
- assert.deepEqual(assistant.parts[1].provider_metadata, message_metadata);
1259
-
1260
- });
1261
-
1262
- test("空 Text Start 不会抢占后续 Tool 的真实顺序", async () => {
1263
- const { recorder, events, file_path } = await create_recorder("deferred-text-order-test");
1264
- const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
1265
-
1266
- await writer.begin_step();
1267
- await writer.apply_chunk({ type: "reasoning-start", id: "reasoning-0" });
1268
- await writer.apply_chunk({ type: "reasoning-delta", id: "reasoning-0", delta: "先执行命令" });
1269
- await writer.apply_chunk({ type: "reasoning-end", id: "reasoning-0" });
1270
- await writer.apply_chunk({ type: "text-start", id: "text-0" });
1271
- await writer.apply_chunk({
1272
- type: "tool-input-start",
1273
- toolCallId: "call-1",
1274
- toolName: "shell_exec",
1275
- });
1276
- await writer.apply_chunk({
1277
- type: "tool-input-available",
1278
- toolCallId: "call-1",
1279
- toolName: "shell_exec",
1280
- input: { cmd: "pwd" },
1281
- });
1282
- await writer.apply_chunk({
1283
- type: "tool-output-available",
1284
- toolCallId: "call-1",
1285
- output: { success: true },
1286
- });
1287
- await writer.apply_chunk({ type: "text-delta", id: "text-0", delta: "命令执行完成" });
1288
- await writer.apply_chunk({ type: "text-end", id: "text-0" });
1289
- await writer.finish_step([
1290
- {
1291
- part_id: "reasoning-1",
1292
- sequence: 1,
1293
- type: "reasoning",
1294
- text: "先执行命令",
1295
- state: "done",
1296
- },
1297
- {
1298
- part_id: "call-1",
1299
- sequence: 2,
1300
- type: "tool",
1301
- tool_call_id: "call-1",
1302
- tool_name: "shell_exec",
1303
- state: "completed",
1304
- input: { cmd: "pwd" },
1305
- output: { success: true },
1306
- },
1307
- {
1308
- part_id: "text-1",
1309
- sequence: 3,
1310
- type: "text",
1311
- text: "命令执行完成",
1312
- state: "done",
1313
- },
1314
- ]);
1315
- await writer.complete();
1316
-
1317
- const assistant = (await read_jsonl(file_path))[0];
1318
- assert.deepEqual(assistant.parts.map((part) => part.type), ["reasoning", "tool", "text"]);
1319
- assert.deepEqual(assistant.parts.map((part) => part.sequence), [1, 2, 3]);
1320
- assert.deepEqual(
1321
- events
1322
- .filter((event) => event.variant === "part")
1323
- .map((event) => [event.type, event.part.sequence]),
1324
- [
1325
- ["reasoning", 1],
1326
- ["reasoning", 1],
1327
- ["tool", 2],
1328
- ["tool", 2],
1329
- ["tool", 2],
1330
- ["text", 3],
1331
- ["text", 3],
1332
- ],
1333
- );
1334
- });
1335
-
1336
- test("Tool 执行先到时等待 stream 固定 canonical Part 顺序", async () => {
1337
- const { recorder, file_path } = await create_recorder("pending-tool-input-order-test");
1338
- const writer = await recorder.open_assistant_message({
1339
- turn_id: "turn-pending-tool-input",
1340
- });
1341
-
1342
- await writer.begin_step();
1343
- const tool_input_ready = writer.prepare_tool_input({
1344
- tool_call_id: "call-pending",
1345
- tool_name: "search",
1346
- input: { query: "downcity" },
1347
- });
1348
- await writer.flush();
1349
- assert.deepEqual(recorder.get_message(writer.message_id).parts, []);
1350
-
1351
- await writer.apply_chunk({ type: "text-start", id: "text-0" });
1352
- await writer.apply_chunk({ type: "text-delta", id: "text-0", delta: "第一段" });
1353
- await writer.apply_chunk({ type: "text-end", id: "text-0" });
1354
- await writer.apply_chunk({ type: "text-start", id: "text-1" });
1355
- await writer.apply_chunk({ type: "text-delta", id: "text-1", delta: "第二段" });
1356
- await writer.apply_chunk({ type: "text-end", id: "text-1" });
1357
- await writer.apply_chunk({
1358
- type: "tool-input-start",
1359
- toolCallId: "call-pending",
1360
- toolName: "search",
1361
- });
1362
- await tool_input_ready;
1363
- await writer.apply_chunk({
1364
- type: "tool-input-available",
1365
- toolCallId: "call-pending",
1366
- toolName: "search",
1367
- input: { query: "downcity" },
1368
- });
1369
- await writer.apply_chunk({
1370
- type: "tool-output-available",
1371
- toolCallId: "call-pending",
1372
- output: "result",
1373
- });
1374
- await writer.finish_step([
1375
- {
1376
- part_id: "text-final-0",
1377
- sequence: 1,
1378
- type: "text",
1379
- text: "第一段",
1380
- state: "done",
1381
- },
1382
- {
1383
- part_id: "text-final-1",
1384
- sequence: 2,
1385
- type: "text",
1386
- text: "第二段",
1387
- state: "done",
1388
- },
1389
- {
1390
- part_id: "call-pending",
1391
- sequence: 3,
1392
- type: "tool",
1393
- tool_call_id: "call-pending",
1394
- tool_name: "search",
1395
- state: "completed",
1396
- input: { query: "downcity" },
1397
- output: "result",
1398
- },
1399
- ]);
1400
- await writer.complete();
1401
-
1402
- const assistant = (await read_jsonl(file_path))[0];
1403
- assert.deepEqual(assistant.parts.map((part) => part.type), ["text", "text", "tool"]);
1404
- assert.deepEqual(assistant.parts.map((part) => part.sequence), [1, 2, 3]);
1405
- assert.deepEqual(assistant.parts[2].input, { query: "downcity" });
1406
- });
1407
-
1408
- test("step abort 与 writer close 会拒绝未释放的 Tool 输入屏障", async () => {
1409
- const { recorder } = await create_recorder("pending-tool-input-cleanup-test");
1410
- const aborted_writer = await recorder.open_assistant_message({
1411
- turn_id: "turn-aborted-tool-input",
1412
- });
1413
- await aborted_writer.begin_step();
1414
- const aborted_input = assert.rejects(
1415
- aborted_writer.prepare_tool_input({
1416
- tool_call_id: "call-aborted",
1417
- tool_name: "search",
1418
- input: { query: "aborted" },
1419
- }),
1420
- /canonical step was aborted: call-aborted/,
1421
- );
1422
- await aborted_writer.flush();
1423
- await aborted_writer.abort_step();
1424
- await aborted_input;
1425
- await aborted_writer.fail("aborted");
1426
-
1427
- const closed_writer = await recorder.open_assistant_message({
1428
- turn_id: "turn-closed-tool-input",
1429
- });
1430
- const closed_input = assert.rejects(
1431
- closed_writer.prepare_tool_input({
1432
- tool_call_id: "call-closed",
1433
- tool_name: "search",
1434
- input: { query: "closed" },
1435
- }),
1436
- /writer closed with status stopped: call-closed/,
1437
- );
1438
- await closed_writer.flush();
1439
- await closed_writer.stop();
1440
- await closed_input;
1441
- });
1442
-
1443
- test("Tool Part 持久化失败时不会释放对应执行等待", async () => {
1444
- const { recorder, store } = await create_recorder(
1445
- "tool-part-persistence-gate-test",
1446
- (options) => new FailingAssistantMessageStore(options),
1447
- );
1448
- const writer = await recorder.open_assistant_message({
1449
- turn_id: "turn-tool-part-persistence",
1450
- });
1451
- await writer.begin_step();
1452
- const tool_input = assert.rejects(
1453
- writer.prepare_tool_input({
1454
- tool_call_id: "call-persistence-failed",
1455
- tool_name: "search",
1456
- input: { query: "downcity" },
1457
- }),
1458
- /canonical step was aborted: call-persistence-failed/,
1459
- );
1460
-
1461
- store.fail_next_assistant_write("disk full");
1462
- await assert.rejects(
1463
- writer.apply_chunk({
1464
- type: "tool-input-start",
1465
- toolCallId: "call-persistence-failed",
1466
- toolName: "search",
1467
- }),
1468
- /disk full/,
1469
- );
1470
- assert.deepEqual(recorder.get_message(writer.message_id).parts, []);
1471
-
1472
- await writer.abort_step();
1473
- await tool_input;
1474
- await writer.fail("disk full");
1475
- });
1476
-
1477
- test("step 最终快照缺少 canonical Tool chunk 时拒绝猜测顺序", async () => {
1478
- const { recorder, events, file_path } = await create_recorder("final-reconcile-order-test");
1479
- const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
1480
-
1481
- await writer.begin_step();
1482
- await writer.apply_chunk({ type: "text-start", id: "text-0" });
1483
- await writer.apply_chunk({ type: "text-delta", id: "text-0", delta: "最终结论" });
1484
- await writer.apply_chunk({ type: "text-end", id: "text-0" });
1485
- const streamed_text_part_id = events.find(
1486
- (event) => event.variant === "part" && event.type === "text",
1487
- ).part.part_id;
1488
-
1489
- await assert.rejects(
1490
- writer.finish_step([
1491
- {
1492
- part_id: "call-1",
1493
- sequence: 1,
1494
- type: "tool",
1495
- tool_call_id: "call-1",
1496
- tool_name: "shell_exec",
1497
- state: "completed",
1498
- input: { cmd: "pwd" },
1499
- output: { success: true },
1500
- },
1501
- {
1502
- part_id: "text-1",
1503
- sequence: 2,
1504
- type: "text",
1505
- text: "最终结论",
1506
- state: "done",
1507
- },
1508
- ]),
1509
- /snapshot mismatch: part count 1 != 2/,
1510
- );
1511
- await writer.abort_step();
1512
- await writer.fail("canonical snapshot mismatch");
1513
-
1514
- const assistant = (await read_jsonl(file_path))[0];
1515
- assert.equal(assistant.status, "failed");
1516
- assert.deepEqual(assistant.parts.map((part) => part.type), ["text"]);
1517
- assert.deepEqual(assistant.parts.map((part) => part.sequence), [1]);
1518
- assert.equal(assistant.parts[0].part_id, streamed_text_part_id);
1519
- });
1520
-
1521
- test("重启时收口 Assistant、取消 Interaction 并标记运行中 Action 失败", async () => {
1522
- const { recorder, files, file_path, assistant_message_file_path } = await create_recorder("recovery-test");
1523
- await recorder.append_user_message({
1524
- turn_id: "turn-1",
1525
- input_type: "prompt",
1526
- parts: [{ part_id: "u1", type: "text", text: "one", state: "done" }],
1527
- });
1528
- const writer = await recorder.open_assistant_message({ turn_id: "turn-1" });
1529
- await writer.apply_chunk({ type: "text-start", id: "text-1" });
1530
- await writer.apply_chunk({ type: "text-delta", id: "text-1", delta: "partial" });
1531
- await writer.apply_chunk({
1532
- type: "tool-input-start",
1533
- toolCallId: "call-recovery",
1534
- toolName: "shell_exec",
1535
- });
1536
- await writer.prepare_tool_input({
1537
- tool_call_id: "call-recovery",
1538
- tool_name: "shell_exec",
1539
- input: { cmd: "pwd" },
1540
- });
1541
- const interactions = new SessionInteractions({
1542
- session_id: "recovery-test",
1543
- messages: recorder,
1544
- });
1545
- await interactions.request({
1546
- interaction_id: "interaction-recovery",
1547
- turn_id: "turn-1",
1548
- kind: "approval",
1549
- source: {
1550
- type: "tool",
1551
- tool_call_id: "call-recovery",
1552
- tool_name: "shell_exec",
1553
- },
1554
- title: "Approve shell_exec",
1555
- command: "pwd",
1556
- cwd: "/workspace",
1557
- reason: "test recovery",
1558
- operation: "exec",
1559
- created_at: Date.now(),
1560
- });
1561
- await recorder.open_action_message({ message_id: "running-action", action_type: "fork", title: "Forking" });
1562
-
1563
- const recovered_events = [];
1564
- const recovered = new SessionMessages({
1565
- session_id: "recovery-test",
1566
- store: new JsonlSessionMessageStore({ files, session_id: "recovery-test", file_path }),
1567
- publish: (mutation) => recovered_events.push(mutation),
1568
- });
1569
- await recovered.initialize();
1570
-
1571
- const page = await recovered.list_messages();
1572
- assert.deepEqual(page.items.map((message) => message.type), ["user", "assistant", "action"]);
1573
- assert.equal(page.items[1].status, "stopped");
1574
- assert.equal(page.items[1].parts[0].text, "partial");
1575
- assert.equal(page.items[1].parts.find((part) => part.type === "tool").state, "failed");
1576
- const recovered_interaction = page.items[1].parts.find(
1577
- (part) => part.type === "interaction",
1578
- );
1579
- assert.equal(recovered_interaction.status, "cancelled");
1580
- assert.equal(recovered_interaction.cancel_reason, "runtime_interrupted");
1581
- assert.equal(page.items[2].status, "failed");
1582
- assert.deepEqual(recovered_events.map((event) => `${event.variant}:${event.type}`), [
1583
- "message:assistant",
1584
- "message:action",
1585
- ]);
1586
- assert.equal(await fs.stat(assistant_message_file_path).then(() => true).catch(() => false), false);
1587
- });
1588
-
1589
- test("Action revision 追加完整快照,读取时只返回最新版本", async () => {
1590
- const { recorder, file_path } = await create_recorder("action-revision-test");
1591
- const action = await recorder.open_action_message({
413
+ test("Action 更新保留 identity 并只读取最新 revision", async () => {
414
+ const { recorder } = await create_recorder("action-revision-test");
415
+ const writer = await recorder.open_action_message({
1592
416
  message_id: "action-1",
1593
- action_type: "compact",
1594
- title: "Compacting",
417
+ turn_id: "turn-1",
418
+ action_type: "deploy",
419
+ title: "Deploying",
1595
420
  });
1596
- await action.complete({ title: "Compacted" });
1597
- const history = await read_jsonl(file_path);
1598
- assert.equal(history.length, 2);
1599
- assert.deepEqual(history.map((message) => message.revision), [1, 2]);
421
+ await writer.complete();
1600
422
  const page = await recorder.list_messages();
1601
423
  assert.equal(page.items.length, 1);
1602
- assert.equal(page.items[0].title, "Compacted");
424
+ assert.equal(page.items[0].message_id, "action-1");
425
+ assert.equal(page.items[0].revision, 2);
426
+ assert.equal(page.items[0].status, "completed");
1603
427
  });
1604
428
 
1605
- test("compact Active 前缀关闭为带累计 Summary Segment", async () => {
1606
- const { recorder, file_path } = await create_recorder("compact-test");
1607
- for (let index = 1; index <= 6; index += 1) {
429
+ test("Compact 生成累计 Summary 并让模型只读取 Summary Active", async () => {
430
+ const session_id = "compact-model-history-test";
431
+ const { recorder } = await create_recorder(session_id);
432
+ for (let sequence = 1; sequence <= 4; sequence += 1) {
1608
433
  await recorder.append_user_message({
1609
- turn_id: `turn-${String(index)}`,
434
+ message_id: `user-${String(sequence)}`,
435
+ turn_id: `turn-${String(sequence)}`,
1610
436
  input_type: "prompt",
1611
- parts: [{ part_id: `user-${String(index)}`, type: "text", text: `message ${String(index)}`, state: "done" }],
437
+ parts: create_user_message(session_id, sequence).parts,
1612
438
  });
1613
439
  }
1614
- const model = new MockLanguageModelV3({
1615
- modelId: "compact-model",
440
+ const model = new MockModelClient({
441
+ modelId: "summary-model",
1616
442
  doGenerate: async () => ({
1617
- content: [{ type: "text", text: "Compact summary" }],
443
+ content: [{ type: "text", text: "summary checkpoint" }],
1618
444
  finishReason: { unified: "stop", raw: "stop" },
1619
445
  usage: {
1620
- inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
1621
- outputTokens: { total: 0, text: 0, reasoning: 0 },
446
+ inputTokens: { total: 1 },
447
+ outputTokens: { total: 1 },
1622
448
  },
1623
- warnings: [],
1624
449
  }),
1625
450
  });
1626
- assert.equal(await compact_messages(recorder, "compact-test", model), true);
1627
- assert.equal((await fs.readFile(file_path, "utf8")).includes("message 1"), false);
1628
- const active = await recorder.list_messages({ include_internal: true });
1629
- assert.deepEqual(active.items.map((message) => message.sequence), []);
1630
- assert.equal(active.source, "active");
1631
- assert.equal(active.has_more, true);
1632
- const segment = await recorder.list_messages({
1633
- before_sequence: 7,
1634
- include_internal: true,
1635
- });
1636
- assert.deepEqual(segment.items.map((message) => message.sequence), [1, 2, 3, 4, 5, 6]);
1637
- assert.equal(segment.source, "segment");
1638
- assert.equal(
1639
- to_executor_history("compact-test", await recorder.context_snapshot()).length,
1640
- 1,
1641
- );
1642
- await assert.rejects(
1643
- recorder.list_messages({ before_sequence: 0 }),
1644
- /before_sequence must be a positive integer/,
1645
- );
1646
- });
1647
-
1648
- test("重启后从最新 Segment Summary 与 Active 恢复模型上下文", async () => {
1649
- const session_id = "compact-restart-test";
1650
- const { recorder, files, file_path } = await create_recorder(session_id);
1651
- for (let index = 1; index <= 6; index += 1) {
1652
- await recorder.append_user_message({
1653
- turn_id: `turn-${String(index)}`,
1654
- input_type: "prompt",
1655
- parts: [{
1656
- part_id: `user-${String(index)}`,
1657
- type: "text",
1658
- text: `message ${String(index)}`,
1659
- state: "done",
1660
- }],
1661
- });
1662
- }
1663
- await recorder.compact_active({
1664
- through_sequence: 4,
1665
- summary: {
1666
- record_type: "summary",
1667
- session_id,
1668
- summary_id: "summary-through-4",
1669
- through_sequence: 4,
1670
- text: "summary through 4",
1671
- created_at: 7,
1672
- },
1673
- });
1674
-
1675
- const restarted = new SessionMessages({
451
+ const plan = await compose_session_compaction({
1676
452
  session_id,
1677
- store: new JsonlSessionMessageStore({ files, session_id, file_path }),
1678
- publish: () => {},
453
+ snapshot: await recorder.context_snapshot(),
454
+ model,
1679
455
  });
1680
- await restarted.initialize();
1681
- const context = to_executor_history(session_id, await restarted.context_snapshot());
1682
- assert.deepEqual(
1683
- context.map((message) => message.parts[0]?.text),
1684
- ["summary through 4", "message 5", "message 6"],
1685
- );
1686
- const segment = await restarted.list_messages({ before_sequence: 5 });
1687
- assert.deepEqual(segment.items.map((message) => message.sequence), [1, 2, 3, 4]);
1688
- });
1689
-
1690
- test("Compact 两步提交中断后会清理 Active 与 Segment 的重叠前缀", async () => {
1691
- const session_id = "compact-overlap-recovery-test";
1692
- const { recorder, files, file_path } = await create_recorder(session_id);
1693
- for (let index = 1; index <= 6; index += 1) {
1694
- await recorder.append_user_message({
1695
- turn_id: `turn-${String(index)}`,
1696
- input_type: "prompt",
1697
- parts: [{
1698
- part_id: `user-${String(index)}`,
1699
- type: "text",
1700
- text: `message ${String(index)}`,
1701
- state: "done",
1702
- }],
1703
- });
1704
- }
456
+ assert.equal(plan.through_sequence, 2);
1705
457
  await recorder.compact_active({
1706
- through_sequence: 4,
1707
- summary: {
1708
- record_type: "summary",
1709
- session_id,
1710
- summary_id: "summary-through-4",
1711
- through_sequence: 4,
1712
- text: "summary through 4",
1713
- created_at: 7,
1714
- },
458
+ through_sequence: plan.through_sequence,
459
+ summary: plan.summary,
1715
460
  });
1716
461
 
1717
- const segment_path = path.join(
1718
- path.dirname(file_path),
1719
- "segments",
1720
- "000000000001-000000000004.jsonl",
1721
- );
1722
- const segment_rows = await read_jsonl(segment_path);
1723
- const active_rows = await read_jsonl(file_path);
1724
- await fs.writeFile(
1725
- file_path,
1726
- `${[...segment_rows.slice(0, -1), ...active_rows]
1727
- .map((message) => JSON.stringify(message))
1728
- .join("\n")}\n`,
1729
- "utf8",
1730
- );
1731
-
1732
- const restarted = new SessionMessages({
1733
- session_id,
1734
- store: new JsonlSessionMessageStore({ files, session_id, file_path }),
1735
- publish: () => {},
1736
- });
1737
- await restarted.initialize();
1738
- const active = await restarted.list_messages();
1739
- assert.deepEqual(active.items.map((message) => message.sequence), [5, 6]);
462
+ const snapshot = await recorder.context_snapshot();
463
+ const model_messages = await session_context_to_model_messages(snapshot);
464
+ assert.equal(snapshot.summary.text, "summary checkpoint");
465
+ assert.deepEqual(snapshot.messages.map((message) => message.sequence), [3, 4]);
466
+ assert.equal(model_messages[0].role, "assistant");
467
+ assert.equal(model_messages[0].content[0].text, "summary checkpoint");
1740
468
  assert.deepEqual(
1741
- (await read_jsonl(file_path)).map((message) => message.sequence),
1742
- [5, 6],
469
+ model_messages.slice(1).map((message) => message.content[0].text),
470
+ ["message 3", "message 4"],
1743
471
  );
1744
472
  });
1745
473
 
1746
- test("连续 Compact 生成按 sequence 连续的 Segment 与累计 Summary", async () => {
1747
- const session_id = "cumulative-compact-test";
474
+ test("内部上下文读取不会被 500 UI 分页边界截断", async () => {
475
+ const session_id = "large-context-test";
1748
476
  const { recorder } = await create_recorder(session_id);
1749
- const prompts = [];
1750
- let generation_count = 0;
1751
- const model = new MockLanguageModelV3({
1752
- modelId: "cumulative-compact-model",
1753
- doGenerate: async (options) => {
1754
- prompts.push(JSON.stringify(options.prompt));
1755
- generation_count += 1;
1756
- return {
1757
- content: [{ type: "text", text: `Summary ${String(generation_count)}` }],
1758
- finishReason: { unified: "stop", raw: "stop" },
1759
- usage: {
1760
- inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
1761
- outputTokens: { total: 0, text: 0, reasoning: 0 },
1762
- },
1763
- warnings: [],
1764
- };
1765
- },
1766
- });
1767
- const append_messages = async (start, end) => {
1768
- for (let index = start; index <= end; index += 1) {
1769
- await recorder.append_user_message({
1770
- turn_id: `turn-${String(index)}`,
1771
- input_type: "prompt",
1772
- parts: [{
1773
- part_id: `user-${String(index)}`,
1774
- type: "text",
1775
- text: `message ${String(index)}`,
1776
- state: "done",
1777
- }],
1778
- });
1779
- }
1780
- };
1781
- const compact = async () => await compact_messages(recorder, session_id, model);
1782
-
1783
- await append_messages(1, 8);
1784
- assert.equal(await compact(), true);
1785
- await append_messages(9, 12);
1786
- assert.equal(await compact(), true);
1787
-
1788
- assert.equal(prompts.length, 2);
1789
- assert.equal(prompts[1].includes("<previous-summary>"), true);
1790
- assert.equal(prompts[1].includes("Summary 1"), true);
1791
- const active = await recorder.list_messages();
1792
- assert.deepEqual(active.items.map((message) => message.sequence), []);
1793
- const latest_segment = await recorder.list_messages({ before_sequence: 13 });
1794
- assert.deepEqual(latest_segment.items.map((message) => message.sequence), [9, 10, 11, 12]);
1795
- assert.equal(latest_segment.has_more, true);
1796
- const earliest_segment = await recorder.list_messages({
1797
- before_sequence: latest_segment.start_sequence,
1798
- });
1799
- assert.deepEqual(earliest_segment.items.map((message) => message.sequence), [1, 2, 3, 4, 5, 6, 7, 8]);
1800
- const context = to_executor_history(session_id, await recorder.context_snapshot());
1801
- assert.equal(context[0].parts[0]?.text, "Summary 2");
1802
- });
1803
-
1804
- test("Summary 生成失败时使用确定性 Summary 完成归档", async () => {
1805
- const session_id = "compact-summary-failure-test";
1806
- const { recorder, file_path } = await create_recorder(session_id);
1807
- for (let index = 1; index <= 6; index += 1) {
477
+ for (let sequence = 1; sequence <= 505; sequence += 1) {
1808
478
  await recorder.append_user_message({
1809
- turn_id: `turn-${String(index)}`,
479
+ message_id: `user-${String(sequence)}`,
480
+ turn_id: `turn-${String(sequence)}`,
1810
481
  input_type: "prompt",
1811
- parts: [{
1812
- part_id: `user-${String(index)}`,
1813
- type: "text",
1814
- text: `message ${String(index)}`,
1815
- state: "done",
1816
- }],
482
+ parts: create_user_message(session_id, sequence).parts,
1817
483
  });
1818
484
  }
1819
- const model = new MockLanguageModelV3({
1820
- modelId: "failed-compact-model",
1821
- doGenerate: async () => {
1822
- throw new Error("summary unavailable");
1823
- },
1824
- });
1825
- assert.equal(await compact_messages(recorder, session_id, model), true);
1826
- assert.deepEqual((await recorder.list_messages()).items, []);
1827
- const segment_entries = await fs.readdir(path.join(path.dirname(file_path), "segments"));
1828
- assert.equal(segment_entries.length, 1);
1829
- });
1830
-
1831
- test("内部上下文读取完整快照并保留第 500 条之后的最新消息", async () => {
1832
- const session_id = "complete-context-snapshot-test";
1833
- const messages = Array.from(
1834
- { length: 501 },
1835
- (_, index) => create_seeded_user_message(session_id, index + 1),
1836
- );
1837
- const { recorder } = await create_seeded_recorder(session_id, messages);
1838
- await recorder.compact_active({
1839
- through_sequence: 499,
1840
- summary: {
1841
- record_type: "summary",
1842
- session_id,
1843
- summary_id: "summary-through-499",
1844
- through_sequence: 499,
1845
- text: "summary through 499",
1846
- created_at: 502,
1847
- },
1848
- });
1849
-
1850
- const active = await recorder.list_messages();
1851
- assert.deepEqual(active.items.map((message) => message.sequence), [500, 501]);
1852
- assert.equal(active.source, "active");
1853
- assert.equal(active.next_before_sequence, 500);
1854
- const previous = await recorder.list_messages({ before_sequence: 500 });
1855
- assert.equal(previous.source, "segment");
1856
- assert.equal(previous.items.length, 499);
1857
- assert.equal(previous.start_sequence, 1);
1858
- assert.equal(previous.end_sequence, 499);
1859
-
1860
- const context = to_executor_history(session_id, await recorder.context_snapshot());
1861
- assert.deepEqual(
1862
- context.map((message) => message.parts[0]?.text),
1863
- ["summary through 499", "message 500", "message 501"],
1864
- );
485
+ const snapshot = await recorder.context_snapshot();
486
+ const model_messages = await session_context_to_model_messages(snapshot);
487
+ assert.equal(snapshot.messages.length, 505);
488
+ assert.equal(model_messages.length, 505);
489
+ assert.equal(model_messages.at(-1).content[0].text, "message 505");
1865
490
  });