@xyit/wellspring 0.0.0-stage → 1.0.0

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 (459) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +829 -2
  3. package/dist/agent/pending-asks.d.ts +20 -0
  4. package/dist/agent/pending-asks.js +70 -0
  5. package/dist/agent/pending-asks.js.map +1 -0
  6. package/dist/agent/task-registry.d.ts +14 -0
  7. package/dist/agent/task-registry.js +80 -0
  8. package/dist/agent/task-registry.js.map +1 -0
  9. package/dist/agent/tool-context.d.ts +30 -0
  10. package/dist/agent/tool-context.js +10 -0
  11. package/dist/agent/tool-context.js.map +1 -0
  12. package/dist/agent/usage.d.ts +40 -0
  13. package/dist/agent/usage.js +122 -0
  14. package/dist/agent/usage.js.map +1 -0
  15. package/dist/api/host/agent.d.ts +165 -0
  16. package/dist/api/host/agent.js +5 -0
  17. package/dist/api/host/agent.js.map +1 -0
  18. package/dist/api/host/events.d.ts +2 -0
  19. package/dist/api/host/events.js +6 -0
  20. package/dist/api/host/events.js.map +1 -0
  21. package/dist/api/host/exec.d.ts +36 -0
  22. package/dist/api/host/exec.js +10 -0
  23. package/dist/api/host/exec.js.map +1 -0
  24. package/dist/api/host/fs.d.ts +24 -0
  25. package/dist/api/host/fs.js +7 -0
  26. package/dist/api/host/fs.js.map +1 -0
  27. package/dist/api/host/hooks.d.ts +210 -0
  28. package/dist/api/host/hooks.js +401 -0
  29. package/dist/api/host/hooks.js.map +1 -0
  30. package/dist/api/host/host.d.ts +36 -0
  31. package/dist/api/host/host.js +53 -0
  32. package/dist/api/host/host.js.map +1 -0
  33. package/dist/api/host/http.d.ts +58 -0
  34. package/dist/api/host/http.js +6 -0
  35. package/dist/api/host/http.js.map +1 -0
  36. package/dist/api/host/index.d.ts +15 -0
  37. package/dist/api/host/index.js +20 -0
  38. package/dist/api/host/index.js.map +1 -0
  39. package/dist/api/host/models.d.ts +65 -0
  40. package/dist/api/host/models.js +8 -0
  41. package/dist/api/host/models.js.map +1 -0
  42. package/dist/api/host/network.d.ts +22 -0
  43. package/dist/api/host/network.js +7 -0
  44. package/dist/api/host/network.js.map +1 -0
  45. package/dist/api/host/plugin.d.ts +138 -0
  46. package/dist/api/host/plugin.js +6 -0
  47. package/dist/api/host/plugin.js.map +1 -0
  48. package/dist/api/host/plugins.d.ts +45 -0
  49. package/dist/api/host/plugins.js +6 -0
  50. package/dist/api/host/plugins.js.map +1 -0
  51. package/dist/api/host/pty.d.ts +55 -0
  52. package/dist/api/host/pty.js +21 -0
  53. package/dist/api/host/pty.js.map +1 -0
  54. package/dist/api/host/session.d.ts +5 -0
  55. package/dist/api/host/session.js +52 -0
  56. package/dist/api/host/session.js.map +1 -0
  57. package/dist/api/host/version.d.ts +2 -0
  58. package/dist/api/host/version.js +3 -0
  59. package/dist/api/host/version.js.map +1 -0
  60. package/dist/api/host/workspace.d.ts +38 -0
  61. package/dist/api/host/workspace.js +9 -0
  62. package/dist/api/host/workspace.js.map +1 -0
  63. package/dist/api/plugin/http-client.d.ts +42 -0
  64. package/dist/api/plugin/http-client.js +364 -0
  65. package/dist/api/plugin/http-client.js.map +1 -0
  66. package/dist/api/plugin/http-protocol.d.ts +152 -0
  67. package/dist/api/plugin/http-protocol.js +49 -0
  68. package/dist/api/plugin/http-protocol.js.map +1 -0
  69. package/dist/api/result/result.d.ts +16 -0
  70. package/dist/api/result/result.js +43 -0
  71. package/dist/api/result/result.js.map +1 -0
  72. package/dist/commands/agent-turn.d.ts +63 -0
  73. package/dist/commands/agent-turn.js +687 -0
  74. package/dist/commands/agent-turn.js.map +1 -0
  75. package/dist/commands/ask-prompt.d.ts +6 -0
  76. package/dist/commands/ask-prompt.js +49 -0
  77. package/dist/commands/ask-prompt.js.map +1 -0
  78. package/dist/commands/config.d.ts +2 -0
  79. package/dist/commands/config.js +283 -0
  80. package/dist/commands/config.js.map +1 -0
  81. package/dist/commands/conversation.d.ts +51 -0
  82. package/dist/commands/conversation.js +630 -0
  83. package/dist/commands/conversation.js.map +1 -0
  84. package/dist/commands/doctor.d.ts +2 -0
  85. package/dist/commands/doctor.js +77 -0
  86. package/dist/commands/doctor.js.map +1 -0
  87. package/dist/commands/exec-approval.d.ts +3 -0
  88. package/dist/commands/exec-approval.js +80 -0
  89. package/dist/commands/exec-approval.js.map +1 -0
  90. package/dist/commands/help.d.ts +2 -0
  91. package/dist/commands/help.js +35 -0
  92. package/dist/commands/help.js.map +1 -0
  93. package/dist/commands/key.d.ts +2 -0
  94. package/dist/commands/key.js +226 -0
  95. package/dist/commands/key.js.map +1 -0
  96. package/dist/commands/plugin.d.ts +2 -0
  97. package/dist/commands/plugin.js +188 -0
  98. package/dist/commands/plugin.js.map +1 -0
  99. package/dist/commands/session.d.ts +2 -0
  100. package/dist/commands/session.js +167 -0
  101. package/dist/commands/session.js.map +1 -0
  102. package/dist/commands/shared.d.ts +35 -0
  103. package/dist/commands/shared.js +66 -0
  104. package/dist/commands/shared.js.map +1 -0
  105. package/dist/commands/start.d.ts +2 -0
  106. package/dist/commands/start.js +75 -0
  107. package/dist/commands/start.js.map +1 -0
  108. package/dist/commands/stop.d.ts +2 -0
  109. package/dist/commands/stop.js +24 -0
  110. package/dist/commands/stop.js.map +1 -0
  111. package/dist/commands/version.d.ts +2 -0
  112. package/dist/commands/version.js +33 -0
  113. package/dist/commands/version.js.map +1 -0
  114. package/dist/config/codes.d.ts +13 -0
  115. package/dist/config/codes.js +15 -0
  116. package/dist/config/codes.js.map +1 -0
  117. package/dist/config/credentials.d.ts +67 -0
  118. package/dist/config/credentials.js +214 -0
  119. package/dist/config/credentials.js.map +1 -0
  120. package/dist/config/defaults.d.ts +5 -0
  121. package/dist/config/defaults.js +31 -0
  122. package/dist/config/defaults.js.map +1 -0
  123. package/dist/config/index.d.ts +5 -0
  124. package/dist/config/index.js +10 -0
  125. package/dist/config/index.js.map +1 -0
  126. package/dist/config/loader.d.ts +10 -0
  127. package/dist/config/loader.js +68 -0
  128. package/dist/config/loader.js.map +1 -0
  129. package/dist/config/providers.d.ts +30 -0
  130. package/dist/config/providers.js +97 -0
  131. package/dist/config/providers.js.map +1 -0
  132. package/dist/config/types.d.ts +75 -0
  133. package/dist/config/types.js +4 -0
  134. package/dist/config/types.js.map +1 -0
  135. package/dist/config/validate.d.ts +4 -0
  136. package/dist/config/validate.js +391 -0
  137. package/dist/config/validate.js.map +1 -0
  138. package/dist/display/markdown.d.ts +7 -0
  139. package/dist/display/markdown.js +116 -0
  140. package/dist/display/markdown.js.map +1 -0
  141. package/dist/display/tool-label.d.ts +6 -0
  142. package/dist/display/tool-label.js +81 -0
  143. package/dist/display/tool-label.js.map +1 -0
  144. package/dist/event/event.d.ts +27 -0
  145. package/dist/event/event.js +152 -0
  146. package/dist/event/event.js.map +1 -0
  147. package/dist/index.d.ts +2 -0
  148. package/dist/index.js +308 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/input/codes.d.ts +15 -0
  151. package/dist/input/codes.js +20 -0
  152. package/dist/input/codes.js.map +1 -0
  153. package/dist/input/commands.d.ts +10 -0
  154. package/dist/input/commands.js +65 -0
  155. package/dist/input/commands.js.map +1 -0
  156. package/dist/input/help.d.ts +8 -0
  157. package/dist/input/help.js +51 -0
  158. package/dist/input/help.js.map +1 -0
  159. package/dist/input/index.d.ts +6 -0
  160. package/dist/input/index.js +10 -0
  161. package/dist/input/index.js.map +1 -0
  162. package/dist/input/parse-options.d.ts +11 -0
  163. package/dist/input/parse-options.js +51 -0
  164. package/dist/input/parse-options.js.map +1 -0
  165. package/dist/input/parse.d.ts +3 -0
  166. package/dist/input/parse.js +119 -0
  167. package/dist/input/parse.js.map +1 -0
  168. package/dist/input/types.d.ts +17 -0
  169. package/dist/input/types.js +2 -0
  170. package/dist/input/types.js.map +1 -0
  171. package/dist/logger/elog4js.d.ts +65 -0
  172. package/dist/logger/elog4js.js +305 -0
  173. package/dist/logger/elog4js.js.map +1 -0
  174. package/dist/logger/logger.d.ts +37 -0
  175. package/dist/logger/logger.js +196 -0
  176. package/dist/logger/logger.js.map +1 -0
  177. package/dist/network/index.d.ts +2 -0
  178. package/dist/network/index.js +6 -0
  179. package/dist/network/index.js.map +1 -0
  180. package/dist/network/openai.d.ts +117 -0
  181. package/dist/network/openai.js +443 -0
  182. package/dist/network/openai.js.map +1 -0
  183. package/dist/network/proxy.d.ts +9 -0
  184. package/dist/network/proxy.js +72 -0
  185. package/dist/network/proxy.js.map +1 -0
  186. package/dist/network/web.d.ts +34 -0
  187. package/dist/network/web.js +152 -0
  188. package/dist/network/web.js.map +1 -0
  189. package/dist/platform/clipboard.d.ts +15 -0
  190. package/dist/platform/clipboard.js +82 -0
  191. package/dist/platform/clipboard.js.map +1 -0
  192. package/dist/platform/exec.d.ts +28 -0
  193. package/dist/platform/exec.js +95 -0
  194. package/dist/platform/exec.js.map +1 -0
  195. package/dist/platform/file.d.ts +38 -0
  196. package/dist/platform/file.js +197 -0
  197. package/dist/platform/file.js.map +1 -0
  198. package/dist/platform/index.d.ts +7 -0
  199. package/dist/platform/index.js +12 -0
  200. package/dist/platform/index.js.map +1 -0
  201. package/dist/platform/notification.d.ts +14 -0
  202. package/dist/platform/notification.js +82 -0
  203. package/dist/platform/notification.js.map +1 -0
  204. package/dist/platform/open-external.d.ts +10 -0
  205. package/dist/platform/open-external.js +105 -0
  206. package/dist/platform/open-external.js.map +1 -0
  207. package/dist/platform/os.d.ts +20 -0
  208. package/dist/platform/os.js +86 -0
  209. package/dist/platform/os.js.map +1 -0
  210. package/dist/platform/path.d.ts +32 -0
  211. package/dist/platform/path.js +154 -0
  212. package/dist/platform/path.js.map +1 -0
  213. package/dist/plugin/access-guard.d.ts +1 -0
  214. package/dist/plugin/access-guard.js +40 -0
  215. package/dist/plugin/access-guard.js.map +1 -0
  216. package/dist/plugin/agent-service.d.ts +25 -0
  217. package/dist/plugin/agent-service.js +809 -0
  218. package/dist/plugin/agent-service.js.map +1 -0
  219. package/dist/plugin/agent-tools.d.ts +12 -0
  220. package/dist/plugin/agent-tools.js +31 -0
  221. package/dist/plugin/agent-tools.js.map +1 -0
  222. package/dist/plugin/capabilities.d.ts +26 -0
  223. package/dist/plugin/capabilities.js +192 -0
  224. package/dist/plugin/capabilities.js.map +1 -0
  225. package/dist/plugin/console-guard.d.ts +2 -0
  226. package/dist/plugin/console-guard.js +52 -0
  227. package/dist/plugin/console-guard.js.map +1 -0
  228. package/dist/plugin/data-access.d.ts +15 -0
  229. package/dist/plugin/data-access.js +57 -0
  230. package/dist/plugin/data-access.js.map +1 -0
  231. package/dist/plugin/fs.d.ts +3 -0
  232. package/dist/plugin/fs.js +93 -0
  233. package/dist/plugin/fs.js.map +1 -0
  234. package/dist/plugin/host-alias.d.ts +2 -0
  235. package/dist/plugin/host-alias.js +57 -0
  236. package/dist/plugin/host-alias.js.map +1 -0
  237. package/dist/plugin/index.d.ts +7 -0
  238. package/dist/plugin/index.js +11 -0
  239. package/dist/plugin/index.js.map +1 -0
  240. package/dist/plugin/install.d.ts +12 -0
  241. package/dist/plugin/install.js +114 -0
  242. package/dist/plugin/install.js.map +1 -0
  243. package/dist/plugin/loader.d.ts +45 -0
  244. package/dist/plugin/loader.js +739 -0
  245. package/dist/plugin/loader.js.map +1 -0
  246. package/dist/plugin/manage.d.ts +31 -0
  247. package/dist/plugin/manage.js +265 -0
  248. package/dist/plugin/manage.js.map +1 -0
  249. package/dist/plugin/models-service.d.ts +15 -0
  250. package/dist/plugin/models-service.js +317 -0
  251. package/dist/plugin/models-service.js.map +1 -0
  252. package/dist/plugin/network-service.d.ts +10 -0
  253. package/dist/plugin/network-service.js +87 -0
  254. package/dist/plugin/network-service.js.map +1 -0
  255. package/dist/plugin/paths.d.ts +6 -0
  256. package/dist/plugin/paths.js +15 -0
  257. package/dist/plugin/paths.js.map +1 -0
  258. package/dist/plugin/registry.d.ts +17 -0
  259. package/dist/plugin/registry.js +82 -0
  260. package/dist/plugin/registry.js.map +1 -0
  261. package/dist/plugin/runtime.d.ts +9 -0
  262. package/dist/plugin/runtime.js +43 -0
  263. package/dist/plugin/runtime.js.map +1 -0
  264. package/dist/plugin/scaffold.d.ts +13 -0
  265. package/dist/plugin/scaffold.js +126 -0
  266. package/dist/plugin/scaffold.js.map +1 -0
  267. package/dist/plugin/state.d.ts +6 -0
  268. package/dist/plugin/state.js +38 -0
  269. package/dist/plugin/state.js.map +1 -0
  270. package/dist/plugin/zip.d.ts +7 -0
  271. package/dist/plugin/zip.js +138 -0
  272. package/dist/plugin/zip.js.map +1 -0
  273. package/dist/sandbox/audit.d.ts +12 -0
  274. package/dist/sandbox/audit.js +20 -0
  275. package/dist/sandbox/audit.js.map +1 -0
  276. package/dist/sandbox/backends/bwrap.d.ts +28 -0
  277. package/dist/sandbox/backends/bwrap.js +197 -0
  278. package/dist/sandbox/backends/bwrap.js.map +1 -0
  279. package/dist/sandbox/backends/direct.d.ts +7 -0
  280. package/dist/sandbox/backends/direct.js +32 -0
  281. package/dist/sandbox/backends/direct.js.map +1 -0
  282. package/dist/sandbox/bridge-state.d.ts +13 -0
  283. package/dist/sandbox/bridge-state.js +12 -0
  284. package/dist/sandbox/bridge-state.js.map +1 -0
  285. package/dist/sandbox/index.d.ts +5 -0
  286. package/dist/sandbox/index.js +6 -0
  287. package/dist/sandbox/index.js.map +1 -0
  288. package/dist/sandbox/manager.d.ts +8 -0
  289. package/dist/sandbox/manager.js +325 -0
  290. package/dist/sandbox/manager.js.map +1 -0
  291. package/dist/sandbox/memory-limit.d.ts +44 -0
  292. package/dist/sandbox/memory-limit.js +193 -0
  293. package/dist/sandbox/memory-limit.js.map +1 -0
  294. package/dist/sandbox/plugin-client.d.ts +80 -0
  295. package/dist/sandbox/plugin-client.js +1601 -0
  296. package/dist/sandbox/plugin-client.js.map +1 -0
  297. package/dist/sandbox/plugin-host.d.ts +1 -0
  298. package/dist/sandbox/plugin-host.js +1001 -0
  299. package/dist/sandbox/plugin-host.js.map +1 -0
  300. package/dist/sandbox/protocol.d.ts +43 -0
  301. package/dist/sandbox/protocol.js +96 -0
  302. package/dist/sandbox/protocol.js.map +1 -0
  303. package/dist/sandbox/rpc-timeouts.d.ts +28 -0
  304. package/dist/sandbox/rpc-timeouts.js +15 -0
  305. package/dist/sandbox/rpc-timeouts.js.map +1 -0
  306. package/dist/sandbox/supervisor.d.ts +31 -0
  307. package/dist/sandbox/supervisor.js +114 -0
  308. package/dist/sandbox/supervisor.js.map +1 -0
  309. package/dist/sandbox/types.d.ts +124 -0
  310. package/dist/sandbox/types.js +17 -0
  311. package/dist/sandbox/types.js.map +1 -0
  312. package/dist/service/control.d.ts +32 -0
  313. package/dist/service/control.js +117 -0
  314. package/dist/service/control.js.map +1 -0
  315. package/dist/session/compress.d.ts +3 -0
  316. package/dist/session/compress.js +42 -0
  317. package/dist/session/compress.js.map +1 -0
  318. package/dist/session/index.d.ts +4 -0
  319. package/dist/session/index.js +8 -0
  320. package/dist/session/index.js.map +1 -0
  321. package/dist/session/registry.d.ts +4 -0
  322. package/dist/session/registry.js +12 -0
  323. package/dist/session/registry.js.map +1 -0
  324. package/dist/session/replay.d.ts +10 -0
  325. package/dist/session/replay.js +109 -0
  326. package/dist/session/replay.js.map +1 -0
  327. package/dist/session/session.d.ts +40 -0
  328. package/dist/session/session.js +108 -0
  329. package/dist/session/session.js.map +1 -0
  330. package/dist/session/store.d.ts +58 -0
  331. package/dist/session/store.js +382 -0
  332. package/dist/session/store.js.map +1 -0
  333. package/dist/session/types.d.ts +56 -0
  334. package/dist/session/types.js +3 -0
  335. package/dist/session/types.js.map +1 -0
  336. package/dist/workspace/index.d.ts +7 -0
  337. package/dist/workspace/index.js +17 -0
  338. package/dist/workspace/index.js.map +1 -0
  339. package/dist/workspace/registry.d.ts +25 -0
  340. package/dist/workspace/registry.js +293 -0
  341. package/dist/workspace/registry.js.map +1 -0
  342. package/package.json +73 -5
  343. package/scripts/build-node-pty.mjs +55 -0
  344. package/scripts/rewrite-dts.mjs +36 -0
  345. package/scripts/verify-sandbox.ts +98 -0
  346. package/src/agent/pending-asks.ts +88 -0
  347. package/src/agent/task-registry.ts +108 -0
  348. package/src/agent/tool-context.ts +47 -0
  349. package/src/agent/usage.ts +170 -0
  350. package/src/api/host/agent.ts +162 -0
  351. package/src/api/host/events.ts +7 -0
  352. package/src/api/host/exec.ts +47 -0
  353. package/src/api/host/fs.ts +33 -0
  354. package/src/api/host/hooks.ts +635 -0
  355. package/src/api/host/host.ts +99 -0
  356. package/src/api/host/http.ts +79 -0
  357. package/src/api/host/index.ts +20 -0
  358. package/src/api/host/models.ts +81 -0
  359. package/src/api/host/network.ts +32 -0
  360. package/src/api/host/plugin.ts +158 -0
  361. package/src/api/host/plugins.ts +55 -0
  362. package/src/api/host/pty.ts +73 -0
  363. package/src/api/host/session.ts +61 -0
  364. package/src/api/host/version.ts +2 -0
  365. package/src/api/host/workspace.ts +51 -0
  366. package/src/api/plugin/http-client.ts +492 -0
  367. package/src/api/plugin/http-protocol.ts +231 -0
  368. package/src/api/result/result.ts +52 -0
  369. package/src/commands/agent-turn.ts +817 -0
  370. package/src/commands/ask-prompt.ts +52 -0
  371. package/src/commands/config.ts +306 -0
  372. package/src/commands/conversation.ts +777 -0
  373. package/src/commands/doctor.ts +92 -0
  374. package/src/commands/exec-approval.ts +91 -0
  375. package/src/commands/help.ts +41 -0
  376. package/src/commands/key.ts +281 -0
  377. package/src/commands/plugin.ts +218 -0
  378. package/src/commands/session.ts +179 -0
  379. package/src/commands/shared.ts +103 -0
  380. package/src/commands/start.ts +88 -0
  381. package/src/commands/stop.ts +24 -0
  382. package/src/commands/version.ts +40 -0
  383. package/src/config/codes.ts +17 -0
  384. package/src/config/credentials.ts +302 -0
  385. package/src/config/defaults.ts +32 -0
  386. package/src/config/index.ts +10 -0
  387. package/src/config/loader.ts +80 -0
  388. package/src/config/providers.ts +133 -0
  389. package/src/config/types.ts +88 -0
  390. package/src/config/validate.ts +458 -0
  391. package/src/display/markdown.ts +137 -0
  392. package/src/display/tool-label.ts +87 -0
  393. package/src/event/event.ts +171 -0
  394. package/src/index.ts +344 -0
  395. package/src/input/codes.ts +21 -0
  396. package/src/input/commands.ts +76 -0
  397. package/src/input/help.ts +60 -0
  398. package/src/input/index.ts +10 -0
  399. package/src/input/parse-options.ts +66 -0
  400. package/src/input/parse.ts +138 -0
  401. package/src/input/types.ts +19 -0
  402. package/src/logger/elog4js.ts +366 -0
  403. package/src/logger/logger.ts +251 -0
  404. package/src/network/index.ts +6 -0
  405. package/src/network/openai.ts +571 -0
  406. package/src/network/proxy.ts +80 -0
  407. package/src/network/web.ts +206 -0
  408. package/src/platform/clipboard.ts +107 -0
  409. package/src/platform/exec.ts +128 -0
  410. package/src/platform/file.ts +235 -0
  411. package/src/platform/index.ts +12 -0
  412. package/src/platform/notification.ts +108 -0
  413. package/src/platform/open-external.ts +132 -0
  414. package/src/platform/os.ts +101 -0
  415. package/src/platform/path.ts +214 -0
  416. package/src/plugin/access-guard.ts +46 -0
  417. package/src/plugin/agent-service.ts +969 -0
  418. package/src/plugin/agent-tools.ts +43 -0
  419. package/src/plugin/capabilities.ts +240 -0
  420. package/src/plugin/console-guard.ts +66 -0
  421. package/src/plugin/data-access.ts +70 -0
  422. package/src/plugin/fs.ts +109 -0
  423. package/src/plugin/host-alias.ts +61 -0
  424. package/src/plugin/index.ts +11 -0
  425. package/src/plugin/install.ts +149 -0
  426. package/src/plugin/loader.ts +952 -0
  427. package/src/plugin/manage.ts +329 -0
  428. package/src/plugin/models-service.ts +383 -0
  429. package/src/plugin/network-service.ts +102 -0
  430. package/src/plugin/paths.ts +19 -0
  431. package/src/plugin/registry.ts +103 -0
  432. package/src/plugin/runtime.ts +57 -0
  433. package/src/plugin/scaffold.ts +152 -0
  434. package/src/plugin/state.ts +52 -0
  435. package/src/plugin/zip.ts +156 -0
  436. package/src/sandbox/audit.ts +38 -0
  437. package/src/sandbox/backends/bwrap.ts +251 -0
  438. package/src/sandbox/backends/direct.ts +43 -0
  439. package/src/sandbox/bridge-state.ts +29 -0
  440. package/src/sandbox/index.ts +5 -0
  441. package/src/sandbox/manager.ts +406 -0
  442. package/src/sandbox/memory-limit.ts +245 -0
  443. package/src/sandbox/plugin-client.ts +1881 -0
  444. package/src/sandbox/plugin-host.ts +1282 -0
  445. package/src/sandbox/protocol.ts +142 -0
  446. package/src/sandbox/pty-bridge.py +154 -0
  447. package/src/sandbox/rpc-timeouts.ts +43 -0
  448. package/src/sandbox/supervisor.ts +172 -0
  449. package/src/sandbox/types.ts +149 -0
  450. package/src/service/control.ts +155 -0
  451. package/src/session/compress.ts +47 -0
  452. package/src/session/index.ts +8 -0
  453. package/src/session/registry.ts +17 -0
  454. package/src/session/replay.ts +132 -0
  455. package/src/session/session.ts +134 -0
  456. package/src/session/store.ts +468 -0
  457. package/src/session/types.ts +61 -0
  458. package/src/workspace/index.ts +21 -0
  459. package/src/workspace/registry.ts +356 -0
@@ -0,0 +1,817 @@
1
+ /*
2
+ * Agent 轮次:run 模式的工具循环
3
+ */
4
+
5
+ import { randomUUID } from "node:crypto"
6
+
7
+ import type {
8
+ ChatCompletionMessageParam,
9
+ ChatCompletionTool,
10
+ } from "openai/resources/chat/completions"
11
+
12
+ import {
13
+ emitAfterStep,
14
+ emitAfterTurn,
15
+ emitAgentAsk,
16
+ emitAgentCallTool,
17
+ emitAgentRunSync,
18
+ emitAgentSubmit,
19
+ emitBeforeStep,
20
+ emitBeforeTurn,
21
+ emitOnMessage,
22
+ emitSessionStart,
23
+ emitUserAnswer,
24
+ } from "../api/host/hooks.ts"
25
+ import type { HostAgentAskRequest, HostAgentToolEvent } from "../api/host/agent.ts"
26
+ import type { AgentTool } from "../api/host/plugin.ts"
27
+ import type { SandboxAccess } from "../config/types.ts"
28
+ import { createAskId, cancelAsk, waitForAsk } from "../agent/pending-asks.ts"
29
+ import { compactUsage, usageOf } from "../agent/usage.ts"
30
+ import { toolLabel, toolSummary } from "../display/tool-label.ts"
31
+ import { withAgentToolContext } from "../agent/tool-context.ts"
32
+ import { Result } from "../api/result/result.ts"
33
+ import type { OpenAIClient, OpenAIReply, ToolCall } from "../network/openai.ts"
34
+ import { createMarkdownStream, renderMarkdown, type MarkdownStream } from "../display/markdown.ts"
35
+ import { lastSeq, lastTurnNumber, toChatMessages } from "../session/replay.ts"
36
+ import type { SessionStore, SessionData } from "../session/store.ts"
37
+ import type { ContentPart, SessionEvent, SessionMessageRecord } from "../session/types.ts"
38
+ import { getSession } from "../session/registry.ts"
39
+ import type { TurnData } from "./conversation.ts"
40
+ import type { CommandContext } from "./shared.ts"
41
+
42
+ export interface ExecApprovalRequest {
43
+ readonly sessionId: string
44
+ readonly turn: number
45
+ readonly step: number
46
+ readonly command: string
47
+ readonly argv: readonly string[]
48
+ readonly cwd: string
49
+ readonly currentPermission?: SandboxAccess
50
+ readonly requestedPermission?: SandboxAccess
51
+ readonly justification?: string
52
+ /** 请求联网 */
53
+ readonly network?: boolean
54
+ /** 请求后台执行 */
55
+ readonly background?: boolean
56
+ /** 标准输入(界面截断显示) */
57
+ readonly stdin?: string
58
+ /** 请求的超时毫秒 */
59
+ readonly timeoutMs?: number
60
+ }
61
+
62
+ export type ExecApproval = (request: ExecApprovalRequest) => Promise<boolean>
63
+
64
+ export interface AgentTurnParams {
65
+ readonly ctx: CommandContext
66
+ readonly client: OpenAIClient
67
+ readonly store: SessionStore
68
+ readonly session: SessionData
69
+ readonly input: string
70
+ readonly model: string
71
+ readonly reasoning?: string
72
+ readonly system?: string
73
+ readonly stream: boolean
74
+ readonly display: boolean
75
+ readonly allowExec?: boolean
76
+ readonly permission?: SandboxAccess
77
+ readonly maxSteps?: number
78
+ readonly approveExec?: ExecApproval
79
+ readonly onDelta?: (text: string) => void
80
+ /** 模型思考内容增量 */
81
+ readonly onReasoning?: (text: string) => void
82
+ /** 工具调用开始/结束;界面用来显示工具提示 */
83
+ readonly onTool?: (event: HostAgentToolEvent) => void
84
+ /** ask 工具提问;界面收到后把问题展示给用户 */
85
+ readonly onAsk?: (request: HostAgentAskRequest) => void
86
+ /** 直接问当前终端用户(CLI);缺省时等待 HostAgentApi.respondAsk */
87
+ readonly askUser?: (request: HostAgentAskRequest) => Promise<string | null>
88
+ readonly onStep?: (event: { readonly turn: number; readonly step: number }) => void
89
+ /**
90
+ * 中断信号:用户停止时打断正在进行的模型调用
91
+ *
92
+ * 已经收到的正文照常保留(按「已停止」收尾),不会再发起新的步骤。
93
+ */
94
+ readonly signal?: AbortSignal
95
+ }
96
+
97
+ /** 平台内置提问工具:模型用它向用户要信息,宿主等回答后再继续 */
98
+ const ASK_TOOL_NAME = "ask"
99
+ const ASK_QUESTION_LIMIT = 2000
100
+ const ASK_OPTION_LIMIT = 10
101
+ const ASK_ANSWER_LIMIT = 8000
102
+
103
+ const ASK_TOOL_DESCRIPTION =
104
+ "向用户提问并等待回答。用于缺少必要信息、需要在几个方案里选择或确认时。问题要简短、自包含,用用户看得懂的话;不要用它代替执行任务。如果你推荐某个选项,把它放第一个,并在标签后加「(推荐)」。"
105
+
106
+ const ASK_TOOL_SCHEMA: ChatCompletionTool = {
107
+ type: "function",
108
+ function: {
109
+ name: ASK_TOOL_NAME,
110
+ description: ASK_TOOL_DESCRIPTION,
111
+ parameters: {
112
+ type: "object",
113
+ additionalProperties: false,
114
+ required: ["question"],
115
+ properties: {
116
+ question: { type: "string", description: "要问用户的问题" },
117
+ options: {
118
+ type: "array",
119
+ items: { type: "string" },
120
+ description:
121
+ "可选答案;推荐的选项放第一个并在标签后加「(推荐)」,用户也可以自由输入",
122
+ },
123
+ },
124
+ },
125
+ },
126
+ }
127
+
128
+ /** 推给界面的工具输出上限;完整内容仍写进会话 */
129
+ const TOOL_EVENT_RESULT_LIMIT = 4000
130
+
131
+ function toolEventResult(text: string): { readonly result: string; readonly truncated: boolean } {
132
+ if (text.length <= TOOL_EVENT_RESULT_LIMIT) {
133
+ return { result: text, truncated: false }
134
+ }
135
+ return { result: text.slice(0, TOOL_EVENT_RESULT_LIMIT), truncated: true }
136
+ }
137
+
138
+ interface ToolArgs {
139
+ readonly [key: string]: unknown
140
+ }
141
+
142
+ function parseToolArguments(raw: string): ToolArgs {
143
+ try {
144
+ const value: unknown = JSON.parse(raw)
145
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
146
+ return value as ToolArgs
147
+ }
148
+ return {}
149
+ } catch {
150
+ return {}
151
+ }
152
+ }
153
+
154
+ function toolCallParts(toolCalls: readonly ToolCall[]): SessionMessageRecord["content"] {
155
+ return toolCalls.map((call) => ({
156
+ type: "tool-call" as const,
157
+ id: call.id,
158
+ name: call.name,
159
+ arguments: call.arguments,
160
+ }))
161
+ }
162
+
163
+ // 插件工具 → 模型 schema
164
+ function pluginToolSchema(tool: AgentTool): ChatCompletionTool {
165
+ const entries = Object.entries(tool.introduction.params)
166
+ const required: string[] = []
167
+ const properties: Record<string, Record<string, unknown>> = {}
168
+ for (const [name, spec] of entries) {
169
+ if (typeof spec === "string") {
170
+ required.push(name)
171
+ properties[name] = { type: "string", description: spec }
172
+ continue
173
+ }
174
+ if (spec.optional !== true) {
175
+ required.push(name)
176
+ }
177
+ properties[name] = {
178
+ type: spec.type ?? "string",
179
+ ...(spec.description === undefined ? {} : { description: spec.description }),
180
+ ...(spec.enum === undefined ? {} : { enum: [...spec.enum] }),
181
+ ...(spec.items === undefined ? {} : { items: { ...spec.items } }),
182
+ }
183
+ }
184
+ return {
185
+ type: "function",
186
+ function: {
187
+ name: tool.name,
188
+ description: tool.introduction.content,
189
+ parameters: {
190
+ type: "object",
191
+ additionalProperties: false,
192
+ required,
193
+ properties,
194
+ },
195
+ },
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 单步 usage:缓存/推理字段只在供应商报告时出现,inputTokens 同时是本步的上下文占用
201
+ */
202
+ function stepUsage(reply: OpenAIReply): Record<string, unknown> {
203
+ return { ...usageOf(reply) }
204
+ }
205
+
206
+ export async function executeAgentTurn(params: AgentTurnParams): Promise<Result<TurnData>> {
207
+ const { ctx, client, store, session, input, model, system, stream, display } = params
208
+ const turn = lastTurnNumber(session.events) + 1
209
+ let seq = lastSeq(session.events) + 1
210
+ const startedAt = Date.now()
211
+
212
+ await emitBeforeTurn({
213
+ sessionId: session.meta.id,
214
+ mode: "run",
215
+ turn,
216
+ input,
217
+ cwd: session.meta.workspace ?? session.meta.cwd,
218
+ })
219
+ await emitAgentRunSync({
220
+ sessionId: session.meta.id,
221
+ turn,
222
+ input,
223
+ cwd: session.meta.workspace ?? session.meta.cwd,
224
+ })
225
+
226
+ const events: SessionEvent[] = [
227
+ { type: "turn/start", seq: seq++, time: startedAt, data: { turn, mode: "run" } },
228
+ {
229
+ type: "user/message",
230
+ seq: seq++,
231
+ time: Date.now(),
232
+ data: {
233
+ message: {
234
+ role: "user",
235
+ content: [{ type: "text", text: input }],
236
+ source: { kind: "cli" },
237
+ id: randomUUID(),
238
+ } satisfies SessionMessageRecord,
239
+ surfaceOp: "append",
240
+ },
241
+ },
242
+ ]
243
+
244
+ /*
245
+ * 本轮事件边产生边落盘:进程在流式过程中被停掉/重启时,至少留下已经开始的
246
+ * 这一轮(尤其是用户消息),下一轮不会突然「失忆」。
247
+ * persisted 记录已写入的条数,避免重复追加。
248
+ */
249
+ let persisted = 0
250
+ const persistPending = async (): Promise<void> => {
251
+ if (events.length <= persisted) {
252
+ return
253
+ }
254
+ await store.appendTurn(session, events.slice(persisted))
255
+ persisted = events.length
256
+ }
257
+
258
+ const pluginTools = ctx.agentTools.list()
259
+ // 插件可以自己提供同名工具覆盖内置 ask
260
+ const askToolBuiltin = ctx.agentTools.get(ASK_TOOL_NAME) === undefined
261
+ const availableTools = [
262
+ ...pluginTools.map((record) => ({
263
+ name: record.tool.name,
264
+ description: record.tool.introduction.content,
265
+ })),
266
+ ...(askToolBuiltin ? [{ name: ASK_TOOL_NAME, description: ASK_TOOL_DESCRIPTION }] : []),
267
+ ]
268
+
269
+ const prompt: string[] = []
270
+ if (system !== undefined && system.trim() !== "") {
271
+ prompt.push(system)
272
+ }
273
+ // 会话的第一轮:只触发一次,适合注入一次性的 system prompt
274
+ if (turn === 1) {
275
+ await emitSessionStart({
276
+ sessionId: session.meta.id,
277
+ mode: "run",
278
+ turn,
279
+ input,
280
+ cwd: session.meta.workspace ?? session.meta.cwd,
281
+ time: startedAt,
282
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
283
+ model,
284
+ permission: params.permission ?? ctx.settings.sandbox.access,
285
+ tools: availableTools,
286
+ prompt,
287
+ })
288
+ }
289
+ await emitOnMessage({
290
+ sessionId: session.meta.id,
291
+ mode: "run",
292
+ turn,
293
+ role: "user",
294
+ phase: "before",
295
+ content: input,
296
+ prompt,
297
+ })
298
+
299
+ const messages: ChatCompletionMessageParam[] = []
300
+ const systemText = prompt
301
+ .map((item) => item.trim())
302
+ .filter((item) => item !== "")
303
+ .join("\n\n")
304
+ if (systemText !== "") {
305
+ messages.push({ role: "system", content: systemText })
306
+ }
307
+
308
+ messages.push(...toChatMessages(session.events), { role: "user", content: input })
309
+
310
+ // 模型入参已经拼好(不能再把本轮用户消息重复算进历史),此时把开头事件落盘
311
+ await persistPending()
312
+
313
+ const hostSession = getSession(session.meta.id)
314
+ hostSession?.begin()
315
+ const maxSteps = params.maxSteps ?? Number.POSITIVE_INFINITY
316
+ let stepsExecuted = 0
317
+ const toolDefinitions = pluginTools.map((record) => pluginToolSchema(record.tool))
318
+ if (askToolBuiltin) {
319
+ toolDefinitions.push(ASK_TOOL_SCHEMA)
320
+ }
321
+ let finalText = ""
322
+ let printedStepText = ""
323
+ let finalModel = model
324
+ let inputTokens: number | null = null
325
+ let outputTokens: number | null = null
326
+ let cacheReadTokens: number | null = null
327
+ let cacheWriteTokens: number | null = null
328
+ let reasoningTokens: number | null = null
329
+ /* 最后一次请求的 prompt tokens:本轮收尾时上下文里实际占用了多少 */
330
+ let contextTokens: number | null = null
331
+ const streamed = display && stream
332
+ const markdownEnabled = display && process.stdout.isTTY === true
333
+
334
+ const fail = async (code: string): Promise<Result<TurnData>> => {
335
+ events.push({
336
+ type: "turn/end",
337
+ seq: seq++,
338
+ time: Date.now(),
339
+ data: { turn, reason: { kind: "failed", code } },
340
+ })
341
+ await persistPending()
342
+ hostSession?.end()
343
+ await emitAfterTurn({
344
+ sessionId: session.meta.id,
345
+ mode: "run",
346
+ turn,
347
+ reason: "failed",
348
+ code,
349
+ })
350
+ if (streamed) {
351
+ ctx.elog.line()
352
+ }
353
+ return Result.error<TurnData>(code)
354
+ }
355
+
356
+ const runAskTool = async (
357
+ rawArguments: string,
358
+ askStep: number,
359
+ ): Promise<{ content: string; ok: boolean }> => {
360
+ const args = parseToolArguments(rawArguments)
361
+ const rawQuestion = args["question"]
362
+ const question =
363
+ typeof rawQuestion === "string" ? rawQuestion.trim().slice(0, ASK_QUESTION_LIMIT) : ""
364
+ if (question === "") {
365
+ return { content: "ask 工具缺少 question 参数", ok: false }
366
+ }
367
+ const options: string[] = []
368
+ if (Array.isArray(args["options"])) {
369
+ for (const item of args["options"]) {
370
+ if (typeof item !== "string") {
371
+ continue
372
+ }
373
+ const value = item.trim()
374
+ if (value === "") {
375
+ continue
376
+ }
377
+ options.push(value.slice(0, 200))
378
+ if (options.length >= ASK_OPTION_LIMIT) {
379
+ break
380
+ }
381
+ }
382
+ }
383
+ const askId = createAskId()
384
+ const request: HostAgentAskRequest = {
385
+ askId,
386
+ question,
387
+ ...(options.length === 0 ? {} : { options }),
388
+ }
389
+ const waiting =
390
+ params.askUser === undefined ? waitForAsk({ askId, sessionId: session.meta.id }) : null
391
+ try {
392
+ await emitAgentAsk({
393
+ sessionId: session.meta.id,
394
+ askId,
395
+ turn,
396
+ step: askStep,
397
+ question,
398
+ ...(options.length === 0 ? {} : { options }),
399
+ })
400
+ try {
401
+ params.onAsk?.(request)
402
+ } catch {
403
+ return { content: "提问没有送达用户,已跳过。", ok: false }
404
+ }
405
+ const answer =
406
+ params.askUser === undefined ? await waiting! : await params.askUser(request)
407
+ if (answer !== null) {
408
+ await emitUserAnswer({
409
+ sessionId: session.meta.id,
410
+ askId,
411
+ answer: answer.slice(0, ASK_ANSWER_LIMIT),
412
+ })
413
+ }
414
+ if (answer === null) {
415
+ return {
416
+ content: "用户没有回答问题。请根据已有信息继续,或说明还需要哪些信息。",
417
+ ok: false,
418
+ }
419
+ }
420
+ const trimmed = answer.trim().slice(0, ASK_ANSWER_LIMIT)
421
+ if (trimmed === "") {
422
+ return { content: "用户跳过了这个问题。", ok: true }
423
+ }
424
+ return { content: `用户回答:${trimmed}`, ok: true }
425
+ } finally {
426
+ cancelAsk(askId)
427
+ }
428
+ }
429
+
430
+ // 用户点了停止:在步与步之间收尾,不再发起新的模型调用或工具执行
431
+ let stopped = false
432
+ // 用函数读,避免第一处判断把类型收窄成 false,后面的比较被判成恒假
433
+ const stopRequested = (): boolean => hostSession?.stopRequested === true
434
+
435
+ for (let step = 1; step <= maxSteps; step += 1) {
436
+ if (stopRequested()) {
437
+ stopped = true
438
+ break
439
+ }
440
+ stepsExecuted = step
441
+ params.onStep?.({ turn, step })
442
+ events.push({ type: "step/start", seq: seq++, time: Date.now(), data: { turn, step } })
443
+ await emitBeforeStep({
444
+ sessionId: session.meta.id,
445
+ mode: "run",
446
+ turn,
447
+ step,
448
+ input,
449
+ })
450
+ if (display && process.stdout.isTTY === true) {
451
+ ctx.elog.step(step, Number.isFinite(maxSteps) ? maxSteps : undefined)
452
+ }
453
+
454
+ const injected = hostSession?.take() ?? []
455
+ for (const message of injected) {
456
+ const text = message.content.join("")
457
+ messages.push({ role: "user", content: text })
458
+ events.push({
459
+ type: "user/message",
460
+ seq: seq++,
461
+ time: Date.now(),
462
+ data: {
463
+ turn,
464
+ message: {
465
+ role: "user",
466
+ content: [{ type: "text", text }],
467
+ source: { kind: "plugin", type: message.type },
468
+ id: randomUUID(),
469
+ },
470
+ },
471
+ })
472
+ }
473
+
474
+ let agentStarted = false
475
+ let markdownWrote = false
476
+ let markdown: MarkdownStream | null = null
477
+ const writeMarkdown = (output: string): void => {
478
+ if (output === "") {
479
+ return
480
+ }
481
+ const text = markdownWrote ? output : output.replace(/^\n+/, "")
482
+ if (text === "") {
483
+ return
484
+ }
485
+ markdownWrote = true
486
+ ctx.elog.stream(text)
487
+ }
488
+ const endAgent = (): void => {
489
+ if (!streamed || !agentStarted) {
490
+ return
491
+ }
492
+ if (markdown !== null) {
493
+ writeMarkdown(markdown.finish().replace(/\n+$/, ""))
494
+ }
495
+ ctx.elog.line()
496
+ }
497
+ const streamResult = await client.chatStream(messages, {
498
+ model,
499
+ reasoning: params.reasoning,
500
+ ...(params.signal === undefined ? {} : { signal: params.signal }),
501
+ tools: toolDefinitions,
502
+ toolChoice: "auto",
503
+ onReasoning: (text) => {
504
+ params.onReasoning?.(text)
505
+ },
506
+ onText: (text) => {
507
+ params.onDelta?.(text)
508
+ if (!streamed) {
509
+ return
510
+ }
511
+ if (!agentStarted) {
512
+ ctx.elog.agentStart()
513
+ agentStarted = true
514
+ if (markdownEnabled) {
515
+ markdown = createMarkdownStream()
516
+ }
517
+ }
518
+ if (markdown !== null) {
519
+ writeMarkdown(markdown.push(text))
520
+ } else {
521
+ ctx.elog.stream(text)
522
+ }
523
+ },
524
+ })
525
+ if (!streamResult.isSuccess()) {
526
+ return await fail(streamResult.getCode() ?? "openai.request-failed")
527
+ }
528
+
529
+ const data = streamResult.getData()
530
+ if (data === null) {
531
+ return await fail("openai.request-failed")
532
+ }
533
+ finalModel = data.reply.model
534
+ if (data.reply.text !== "") {
535
+ finalText = data.reply.text
536
+ }
537
+ if (data.interrupted === true) {
538
+ /*
539
+ * 用户停止:这一次模型调用被信号打断,收到的正文已经记进 finalText。
540
+ * 本轮就地收尾——不执行这次返回的工具调用,也不再进入下一步。
541
+ */
542
+ stopped = true
543
+ }
544
+ if (data.reply.inputTokens !== null) {
545
+ inputTokens = (inputTokens ?? 0) + data.reply.inputTokens
546
+ // 最后一次请求的 prompt tokens 就是当时占用的上下文
547
+ contextTokens = data.reply.inputTokens
548
+ }
549
+ if (data.reply.outputTokens !== null) {
550
+ outputTokens = (outputTokens ?? 0) + data.reply.outputTokens
551
+ }
552
+ if (data.reply.cacheReadTokens !== undefined && data.reply.cacheReadTokens !== null) {
553
+ cacheReadTokens = (cacheReadTokens ?? 0) + data.reply.cacheReadTokens
554
+ }
555
+ if (data.reply.cacheWriteTokens !== undefined && data.reply.cacheWriteTokens !== null) {
556
+ cacheWriteTokens = (cacheWriteTokens ?? 0) + data.reply.cacheWriteTokens
557
+ }
558
+ if (data.reply.reasoningTokens !== undefined && data.reply.reasoningTokens !== null) {
559
+ reasoningTokens = (reasoningTokens ?? 0) + data.reply.reasoningTokens
560
+ }
561
+
562
+ const parts: ContentPart[] = []
563
+ if (data.reasoning !== "") {
564
+ parts.push({ type: "reasoning", text: data.reasoning })
565
+ }
566
+ if (data.reply.text !== "") {
567
+ parts.push({ type: "text", text: data.reply.text })
568
+ }
569
+ parts.push(...toolCallParts(data.toolCalls))
570
+ const assistant: SessionMessageRecord = {
571
+ role: "assistant",
572
+ content: parts,
573
+ source: { kind: "model", provider: "openai", model: data.reply.model },
574
+ id: data.reply.id,
575
+ }
576
+ events.push({
577
+ type: "assistant/message",
578
+ seq: seq++,
579
+ time: Date.now(),
580
+ data: {
581
+ turn,
582
+ step,
583
+ message: assistant,
584
+ usage: stepUsage(data.reply),
585
+ stream: data.chunks,
586
+ },
587
+ })
588
+
589
+ if (data.reply.text !== "") {
590
+ await emitOnMessage({
591
+ sessionId: session.meta.id,
592
+ mode: "run",
593
+ turn,
594
+ role: "assistant",
595
+ phase: "after",
596
+ content: data.reply.text,
597
+ prompt,
598
+ })
599
+ }
600
+ endAgent()
601
+ // 非流式(管道/--no-stream)也按步打印模型输出,让工具调用和消息交替出现(参考 Harness)
602
+ if (display && !streamed && data.reply.text !== "") {
603
+ ctx.elog.agent(markdownEnabled ? renderMarkdown(data.reply.text) : data.reply.text)
604
+ printedStepText = data.reply.text
605
+ }
606
+
607
+ if (data.toolCalls.length === 0) {
608
+ await emitAfterStep({
609
+ sessionId: session.meta.id,
610
+ mode: "run",
611
+ turn,
612
+ step,
613
+ reply: data.reply.text,
614
+ })
615
+ // 最终回答已经生成:先落盘,进程在收尾前退出也不会丢
616
+ await persistPending()
617
+ break
618
+ }
619
+
620
+ const assistantForApi: ChatCompletionMessageParam = {
621
+ role: "assistant",
622
+ content: data.reply.text === "" ? null : data.reply.text,
623
+ tool_calls: data.toolCalls.map((call) => ({
624
+ id: call.id,
625
+ type: "function" as const,
626
+ function: { name: call.name, arguments: call.arguments },
627
+ })),
628
+ }
629
+ messages.push(assistantForApi)
630
+
631
+ for (const call of data.toolCalls) {
632
+ events.push({
633
+ type: "tool/call",
634
+ seq: seq++,
635
+ time: Date.now(),
636
+ data: {
637
+ turn,
638
+ step,
639
+ callId: call.id,
640
+ name: call.name,
641
+ arguments: call.arguments,
642
+ },
643
+ })
644
+ await emitAgentCallTool({
645
+ sessionId: session.meta.id,
646
+ turn,
647
+ step,
648
+ callId: call.id,
649
+ name: call.name,
650
+ arguments: call.arguments,
651
+ })
652
+ params.onTool?.({
653
+ id: call.id,
654
+ name: call.name,
655
+ phase: "start",
656
+ arguments: call.arguments,
657
+ })
658
+
659
+ const args = parseToolArguments(call.arguments)
660
+ ctx.elog.toolCall(toolLabel(call.name), toolSummary(call.name, args))
661
+ let toolContent = ""
662
+ let toolOk = false
663
+ if (askToolBuiltin && call.name === ASK_TOOL_NAME) {
664
+ const outcome = await runAskTool(call.arguments, step)
665
+ toolContent = outcome.content
666
+ toolOk = outcome.ok
667
+ } else if (ctx.agentTools.get(call.name) !== undefined) {
668
+ const record = ctx.agentTools.get(call.name)
669
+ if (record === undefined) {
670
+ toolContent = `未知工具:${call.name}`
671
+ } else {
672
+ try {
673
+ const result = await withAgentToolContext(
674
+ {
675
+ sessionId: session.meta.id,
676
+ turn,
677
+ step,
678
+ workspaceDir: session.meta.workspace ?? session.meta.cwd,
679
+ permission: params.permission ?? ctx.settings.sandbox.access,
680
+ allowExec: params.allowExec === true,
681
+ approveExec:
682
+ params.approveExec === undefined
683
+ ? undefined
684
+ : async (request) =>
685
+ await params.approveExec!({
686
+ ...request,
687
+ sessionId: session.meta.id,
688
+ turn,
689
+ step,
690
+ }),
691
+ elog: ctx.elog,
692
+ },
693
+ async () => await record.tool.call(args),
694
+ )
695
+ toolContent =
696
+ typeof result.data["content"] === "string"
697
+ ? result.data["content"]
698
+ : result.success
699
+ ? JSON.stringify(result.data)
700
+ : `工具失败:${JSON.stringify(result.data)}`
701
+ toolOk = result.success
702
+ } catch (error) {
703
+ const message = error instanceof Error ? error.message : String(error)
704
+ toolContent = `工具执行失败:${message}`
705
+ }
706
+ }
707
+ } else {
708
+ toolContent = `未知工具:${call.name}`
709
+ }
710
+
711
+ if (!toolOk) {
712
+ const firstLine = toolContent.split("\n")[0] ?? ""
713
+ ctx.elog.toolResult(`失败:${firstLine.slice(0, 160)}`)
714
+ }
715
+ const toolEvent = toolEventResult(toolContent)
716
+ params.onTool?.({
717
+ id: call.id,
718
+ name: call.name,
719
+ phase: "end",
720
+ ok: toolOk,
721
+ result: toolEvent.result,
722
+ ...(toolEvent.truncated ? { truncated: true } : {}),
723
+ })
724
+ events.push({
725
+ type: "tool/result",
726
+ seq: seq++,
727
+ time: Date.now(),
728
+ data: {
729
+ turn,
730
+ step,
731
+ callId: call.id,
732
+ name: call.name,
733
+ content: toolContent,
734
+ },
735
+ })
736
+ messages.push({
737
+ role: "tool",
738
+ tool_call_id: call.id,
739
+ content: toolContent,
740
+ })
741
+ }
742
+
743
+ await emitAfterStep({
744
+ sessionId: session.meta.id,
745
+ mode: "run",
746
+ turn,
747
+ step,
748
+ reply: data.reply.text,
749
+ })
750
+ events.push({
751
+ type: "step/end",
752
+ seq: seq++,
753
+ time: Date.now(),
754
+ data: { turn, step, reason: "completed" },
755
+ })
756
+ // 一步的工具调用与结果都齐了,checkpoint 一次
757
+ await persistPending()
758
+ if (stopRequested()) {
759
+ stopped = true
760
+ break
761
+ }
762
+ }
763
+
764
+ if (finalText === "") {
765
+ finalText = stopped ? "(已停止)" : "(达到最大步数,未生成最终回答)"
766
+ }
767
+
768
+ hostSession?.end()
769
+ await emitAgentSubmit({
770
+ sessionId: session.meta.id,
771
+ result: finalText,
772
+ })
773
+ events.push({
774
+ type: "turn/end",
775
+ seq: seq++,
776
+ time: Date.now(),
777
+ data: { turn, reason: { kind: stopped ? "stopped" : "completed" } },
778
+ })
779
+ await persistPending()
780
+ await emitAfterTurn({
781
+ sessionId: session.meta.id,
782
+ mode: "run",
783
+ turn,
784
+ reason: stopped ? "cancelled" : "completed",
785
+ code: null,
786
+ })
787
+
788
+ if (!streamed && display && finalText !== printedStepText) {
789
+ // 只有兜底文案(例如达到最大步数)或最后一步没有正文时才需要补打
790
+ ctx.elog.agent(markdownEnabled ? renderMarkdown(finalText) : finalText)
791
+ }
792
+
793
+ const usage =
794
+ inputTokens === null &&
795
+ outputTokens === null &&
796
+ cacheReadTokens === null &&
797
+ cacheWriteTokens === null
798
+ ? null
799
+ : compactUsage({
800
+ inputTokens,
801
+ outputTokens,
802
+ cacheReadTokens,
803
+ cacheWriteTokens,
804
+ reasoningTokens,
805
+ contextTokens,
806
+ })
807
+
808
+ return Result.successWithData<TurnData>({
809
+ sessionId: session.meta.id,
810
+ mode: "run",
811
+ turn,
812
+ steps: stepsExecuted,
813
+ reply: finalText,
814
+ model: finalModel,
815
+ usage,
816
+ })
817
+ }