@xyit/wellspring 0.0.0-stage → 1.1.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 (464) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +853 -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 +49 -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 +190 -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 +17 -0
  241. package/dist/plugin/install.js +163 -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 +271 -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/package-manager.d.ts +46 -0
  256. package/dist/plugin/package-manager.js +148 -0
  257. package/dist/plugin/package-manager.js.map +1 -0
  258. package/dist/plugin/paths.d.ts +6 -0
  259. package/dist/plugin/paths.js +15 -0
  260. package/dist/plugin/paths.js.map +1 -0
  261. package/dist/plugin/registry.d.ts +17 -0
  262. package/dist/plugin/registry.js +82 -0
  263. package/dist/plugin/registry.js.map +1 -0
  264. package/dist/plugin/runtime.d.ts +9 -0
  265. package/dist/plugin/runtime.js +43 -0
  266. package/dist/plugin/runtime.js.map +1 -0
  267. package/dist/plugin/scaffold.d.ts +13 -0
  268. package/dist/plugin/scaffold.js +132 -0
  269. package/dist/plugin/scaffold.js.map +1 -0
  270. package/dist/plugin/state.d.ts +6 -0
  271. package/dist/plugin/state.js +38 -0
  272. package/dist/plugin/state.js.map +1 -0
  273. package/dist/plugin/zip.d.ts +7 -0
  274. package/dist/plugin/zip.js +138 -0
  275. package/dist/plugin/zip.js.map +1 -0
  276. package/dist/sandbox/audit.d.ts +12 -0
  277. package/dist/sandbox/audit.js +20 -0
  278. package/dist/sandbox/audit.js.map +1 -0
  279. package/dist/sandbox/backends/bwrap.d.ts +28 -0
  280. package/dist/sandbox/backends/bwrap.js +197 -0
  281. package/dist/sandbox/backends/bwrap.js.map +1 -0
  282. package/dist/sandbox/backends/direct.d.ts +7 -0
  283. package/dist/sandbox/backends/direct.js +32 -0
  284. package/dist/sandbox/backends/direct.js.map +1 -0
  285. package/dist/sandbox/bridge-state.d.ts +13 -0
  286. package/dist/sandbox/bridge-state.js +12 -0
  287. package/dist/sandbox/bridge-state.js.map +1 -0
  288. package/dist/sandbox/index.d.ts +5 -0
  289. package/dist/sandbox/index.js +6 -0
  290. package/dist/sandbox/index.js.map +1 -0
  291. package/dist/sandbox/manager.d.ts +8 -0
  292. package/dist/sandbox/manager.js +325 -0
  293. package/dist/sandbox/manager.js.map +1 -0
  294. package/dist/sandbox/memory-limit.d.ts +44 -0
  295. package/dist/sandbox/memory-limit.js +193 -0
  296. package/dist/sandbox/memory-limit.js.map +1 -0
  297. package/dist/sandbox/plugin-client.d.ts +80 -0
  298. package/dist/sandbox/plugin-client.js +1601 -0
  299. package/dist/sandbox/plugin-client.js.map +1 -0
  300. package/dist/sandbox/plugin-host.d.ts +1 -0
  301. package/dist/sandbox/plugin-host.js +1001 -0
  302. package/dist/sandbox/plugin-host.js.map +1 -0
  303. package/dist/sandbox/protocol.d.ts +43 -0
  304. package/dist/sandbox/protocol.js +96 -0
  305. package/dist/sandbox/protocol.js.map +1 -0
  306. package/dist/sandbox/rpc-timeouts.d.ts +28 -0
  307. package/dist/sandbox/rpc-timeouts.js +15 -0
  308. package/dist/sandbox/rpc-timeouts.js.map +1 -0
  309. package/dist/sandbox/supervisor.d.ts +31 -0
  310. package/dist/sandbox/supervisor.js +114 -0
  311. package/dist/sandbox/supervisor.js.map +1 -0
  312. package/dist/sandbox/types.d.ts +124 -0
  313. package/dist/sandbox/types.js +17 -0
  314. package/dist/sandbox/types.js.map +1 -0
  315. package/dist/service/control.d.ts +32 -0
  316. package/dist/service/control.js +117 -0
  317. package/dist/service/control.js.map +1 -0
  318. package/dist/session/compress.d.ts +3 -0
  319. package/dist/session/compress.js +42 -0
  320. package/dist/session/compress.js.map +1 -0
  321. package/dist/session/index.d.ts +4 -0
  322. package/dist/session/index.js +8 -0
  323. package/dist/session/index.js.map +1 -0
  324. package/dist/session/registry.d.ts +4 -0
  325. package/dist/session/registry.js +12 -0
  326. package/dist/session/registry.js.map +1 -0
  327. package/dist/session/replay.d.ts +10 -0
  328. package/dist/session/replay.js +109 -0
  329. package/dist/session/replay.js.map +1 -0
  330. package/dist/session/session.d.ts +40 -0
  331. package/dist/session/session.js +108 -0
  332. package/dist/session/session.js.map +1 -0
  333. package/dist/session/store.d.ts +58 -0
  334. package/dist/session/store.js +382 -0
  335. package/dist/session/store.js.map +1 -0
  336. package/dist/session/types.d.ts +56 -0
  337. package/dist/session/types.js +3 -0
  338. package/dist/session/types.js.map +1 -0
  339. package/dist/workspace/index.d.ts +7 -0
  340. package/dist/workspace/index.js +17 -0
  341. package/dist/workspace/index.js.map +1 -0
  342. package/dist/workspace/registry.d.ts +25 -0
  343. package/dist/workspace/registry.js +293 -0
  344. package/dist/workspace/registry.js.map +1 -0
  345. package/package.json +73 -5
  346. package/scripts/build-node-pty.mjs +55 -0
  347. package/scripts/rewrite-dts.mjs +36 -0
  348. package/scripts/run-tests.mjs +41 -0
  349. package/scripts/verify-sandbox.ts +98 -0
  350. package/src/agent/pending-asks.ts +88 -0
  351. package/src/agent/task-registry.ts +108 -0
  352. package/src/agent/tool-context.ts +47 -0
  353. package/src/agent/usage.ts +170 -0
  354. package/src/api/host/agent.ts +162 -0
  355. package/src/api/host/events.ts +7 -0
  356. package/src/api/host/exec.ts +47 -0
  357. package/src/api/host/fs.ts +33 -0
  358. package/src/api/host/hooks.ts +635 -0
  359. package/src/api/host/host.ts +99 -0
  360. package/src/api/host/http.ts +79 -0
  361. package/src/api/host/index.ts +20 -0
  362. package/src/api/host/models.ts +81 -0
  363. package/src/api/host/network.ts +32 -0
  364. package/src/api/host/plugin.ts +158 -0
  365. package/src/api/host/plugins.ts +59 -0
  366. package/src/api/host/pty.ts +73 -0
  367. package/src/api/host/session.ts +61 -0
  368. package/src/api/host/version.ts +2 -0
  369. package/src/api/host/workspace.ts +51 -0
  370. package/src/api/plugin/http-client.ts +492 -0
  371. package/src/api/plugin/http-protocol.ts +231 -0
  372. package/src/api/result/result.ts +52 -0
  373. package/src/commands/agent-turn.ts +817 -0
  374. package/src/commands/ask-prompt.ts +52 -0
  375. package/src/commands/config.ts +306 -0
  376. package/src/commands/conversation.ts +777 -0
  377. package/src/commands/doctor.ts +92 -0
  378. package/src/commands/exec-approval.ts +91 -0
  379. package/src/commands/help.ts +41 -0
  380. package/src/commands/key.ts +281 -0
  381. package/src/commands/plugin.ts +222 -0
  382. package/src/commands/session.ts +179 -0
  383. package/src/commands/shared.ts +103 -0
  384. package/src/commands/start.ts +88 -0
  385. package/src/commands/stop.ts +24 -0
  386. package/src/commands/version.ts +40 -0
  387. package/src/config/codes.ts +17 -0
  388. package/src/config/credentials.ts +302 -0
  389. package/src/config/defaults.ts +32 -0
  390. package/src/config/index.ts +10 -0
  391. package/src/config/loader.ts +80 -0
  392. package/src/config/providers.ts +133 -0
  393. package/src/config/types.ts +88 -0
  394. package/src/config/validate.ts +458 -0
  395. package/src/display/markdown.ts +137 -0
  396. package/src/display/tool-label.ts +87 -0
  397. package/src/event/event.ts +171 -0
  398. package/src/index.ts +344 -0
  399. package/src/input/codes.ts +21 -0
  400. package/src/input/commands.ts +76 -0
  401. package/src/input/help.ts +60 -0
  402. package/src/input/index.ts +10 -0
  403. package/src/input/parse-options.ts +66 -0
  404. package/src/input/parse.ts +138 -0
  405. package/src/input/types.ts +19 -0
  406. package/src/logger/elog4js.ts +366 -0
  407. package/src/logger/logger.ts +251 -0
  408. package/src/network/index.ts +6 -0
  409. package/src/network/openai.ts +571 -0
  410. package/src/network/proxy.ts +80 -0
  411. package/src/network/web.ts +206 -0
  412. package/src/platform/clipboard.ts +107 -0
  413. package/src/platform/exec.ts +128 -0
  414. package/src/platform/file.ts +235 -0
  415. package/src/platform/index.ts +12 -0
  416. package/src/platform/notification.ts +108 -0
  417. package/src/platform/open-external.ts +132 -0
  418. package/src/platform/os.ts +101 -0
  419. package/src/platform/path.ts +214 -0
  420. package/src/plugin/access-guard.ts +46 -0
  421. package/src/plugin/agent-service.ts +969 -0
  422. package/src/plugin/agent-tools.ts +43 -0
  423. package/src/plugin/capabilities.ts +240 -0
  424. package/src/plugin/console-guard.ts +66 -0
  425. package/src/plugin/data-access.ts +70 -0
  426. package/src/plugin/fs.ts +109 -0
  427. package/src/plugin/host-alias.ts +61 -0
  428. package/src/plugin/index.ts +11 -0
  429. package/src/plugin/install.ts +213 -0
  430. package/src/plugin/loader.ts +952 -0
  431. package/src/plugin/manage.ts +338 -0
  432. package/src/plugin/models-service.ts +383 -0
  433. package/src/plugin/network-service.ts +102 -0
  434. package/src/plugin/package-manager.ts +214 -0
  435. package/src/plugin/paths.ts +19 -0
  436. package/src/plugin/registry.ts +103 -0
  437. package/src/plugin/runtime.ts +57 -0
  438. package/src/plugin/scaffold.ts +158 -0
  439. package/src/plugin/state.ts +52 -0
  440. package/src/plugin/zip.ts +156 -0
  441. package/src/sandbox/audit.ts +38 -0
  442. package/src/sandbox/backends/bwrap.ts +251 -0
  443. package/src/sandbox/backends/direct.ts +43 -0
  444. package/src/sandbox/bridge-state.ts +29 -0
  445. package/src/sandbox/index.ts +5 -0
  446. package/src/sandbox/manager.ts +406 -0
  447. package/src/sandbox/memory-limit.ts +245 -0
  448. package/src/sandbox/plugin-client.ts +1881 -0
  449. package/src/sandbox/plugin-host.ts +1282 -0
  450. package/src/sandbox/protocol.ts +142 -0
  451. package/src/sandbox/pty-bridge.py +154 -0
  452. package/src/sandbox/rpc-timeouts.ts +43 -0
  453. package/src/sandbox/supervisor.ts +172 -0
  454. package/src/sandbox/types.ts +149 -0
  455. package/src/service/control.ts +155 -0
  456. package/src/session/compress.ts +47 -0
  457. package/src/session/index.ts +8 -0
  458. package/src/session/registry.ts +17 -0
  459. package/src/session/replay.ts +132 -0
  460. package/src/session/session.ts +134 -0
  461. package/src/session/store.ts +468 -0
  462. package/src/session/types.ts +61 -0
  463. package/src/workspace/index.ts +21 -0
  464. package/src/workspace/registry.ts +356 -0
@@ -0,0 +1,952 @@
1
+ /*
2
+ * 插件加载。
3
+ * 先 import + setup() 收集元数据,再按 deps 拓扑排序,最后注册命令并执行 init(api)。
4
+ * 支持 plugins-state.json 禁用插件;坏插件只记录失败,不影响其他插件。
5
+ */
6
+
7
+ import { cp, readdir, rm, stat, symlink } from "node:fs/promises"
8
+ import { basename, join, resolve } from "node:path"
9
+ import { pathToFileURL } from "node:url"
10
+
11
+ import type { Logger } from "pino"
12
+
13
+ import {
14
+ configureHost,
15
+ setHostPlugins,
16
+ type HostPlugin,
17
+ type HostPluginStatus,
18
+ } from "../api/host/host.ts"
19
+ import {
20
+ emitInitHooksForOwner,
21
+ emitShutdownHooksForOwner,
22
+ removeHooksByOwner,
23
+ withHookOwner,
24
+ } from "../api/host/hooks.ts"
25
+ import type {
26
+ AgentTool,
27
+ PluginCommand,
28
+ PluginConfig,
29
+ PluginStatus,
30
+ WellspringPlugin,
31
+ } from "../api/host/plugin.ts"
32
+ import { HOST_API_VERSION } from "../api/host/version.ts"
33
+ import { DEFAULT_SETTINGS } from "../config/defaults.ts"
34
+ import { SETTING_FILE_NAME } from "../config/loader.ts"
35
+ import type { AppSetting, SandboxSetting } from "../config/types.ts"
36
+ import { getElog } from "../logger/elog4js.ts"
37
+ import { ensureDir, readJsonIfExists } from "../platform/file.ts"
38
+ import { getPaths, type ResolvedPaths } from "../platform/path.ts"
39
+ import { BwrapBackend } from "../sandbox/backends/bwrap.ts"
40
+ import { PluginSandboxClient, type PluginSandboxPermissions } from "../sandbox/plugin-client.ts"
41
+ import type { SandboxBackend } from "../sandbox/types.ts"
42
+ import { AgentToolRegistry } from "./agent-tools.ts"
43
+ import { createHostCapabilities } from "./capabilities.ts"
44
+ import { PluginDataService } from "./data-access.ts"
45
+ import { ensureAccessGuard } from "./access-guard.ts"
46
+ import { ensureHostAlias } from "./host-alias.ts"
47
+ import { PLUGIN_NAME, pluginsRoot } from "./paths.ts"
48
+ import { PluginRegistry } from "./registry.ts"
49
+ import { createHostAgentService, type HostAgentService } from "./agent-service.ts"
50
+ import { createHostModelsService } from "./models-service.ts"
51
+ import { createHostNetworkService } from "./network-service.ts"
52
+ import { loadPluginConfig } from "./runtime.ts"
53
+ import { readPluginState, setPluginEnabled } from "./state.ts"
54
+ import {
55
+ createHostPluginManager,
56
+ type HostPluginManager,
57
+ type PluginHotController,
58
+ type PluginHotOutcome,
59
+ } from "./manage.ts"
60
+
61
+ interface PluginPackageJson {
62
+ readonly name?: string
63
+ readonly version?: string
64
+ readonly main?: string
65
+ readonly wellspring?: {
66
+ readonly apiVersion?: number
67
+ readonly entry?: string
68
+ readonly sandbox?: unknown
69
+ }
70
+ }
71
+
72
+ export type PluginLoadStatus = PluginStatus | "disabled"
73
+ export type { PluginStatus } from "../api/host/plugin.ts"
74
+
75
+ export interface LoadedPlugin {
76
+ readonly id: string
77
+ readonly name: string
78
+ readonly version: string
79
+ readonly deps: readonly string[]
80
+ readonly dir: string
81
+ readonly status: PluginLoadStatus
82
+ readonly commands: readonly string[]
83
+ readonly error: string | null
84
+ }
85
+
86
+ export interface LoadPluginsOptions {
87
+ readonly paths?: ResolvedPaths
88
+ readonly config: AppSetting
89
+ readonly logger: Logger
90
+ readonly registry: PluginRegistry
91
+ readonly sandbox?: SandboxSetting
92
+ readonly agentTools?: AgentToolRegistry
93
+ /** 测试注入 */
94
+ readonly dataAccess?: PluginDataService
95
+ /** 测试注入;生产根据 sandbox.mode 探测 */
96
+ readonly sandboxBackend?: SandboxBackend
97
+ /** 测试注入;默认按 settings 构造 */
98
+ readonly agent?: HostAgentService
99
+ }
100
+
101
+ /** 当前进程内的插件运行时;热启用/禁用用 */
102
+ interface RuntimeState {
103
+ readonly options: LoadPluginsOptions
104
+ readonly paths: ResolvedPaths
105
+ readonly root: string
106
+ readonly sandboxSetting: SandboxSetting
107
+ readonly sandboxBackend: SandboxBackend | null
108
+ readonly dataAccess: PluginDataService
109
+ readonly agentService: HostAgentService
110
+ readonly modelsService: ReturnType<typeof createHostModelsService>
111
+ readonly networkService: ReturnType<typeof createHostNetworkService>
112
+ pluginManager: HostPluginManager
113
+ readonly queueHost: { current: Promise<void> }
114
+ entries: PluginEntry[]
115
+ }
116
+
117
+ const runtimes = new Map<string, RuntimeState>()
118
+
119
+ const ENTRY_CANDIDATES = ["index.ts", "index.js", "index.mjs", "index.mts"]
120
+
121
+ function errorText(error: unknown): string {
122
+ if (error instanceof Error) {
123
+ const cause = error.cause
124
+ return cause instanceof Error ? `${error.message}(${cause.message})` : error.message
125
+ }
126
+ return String(error)
127
+ }
128
+
129
+ function isObject(value: unknown): value is Record<string, unknown> {
130
+ return typeof value === "object" && value !== null && !Array.isArray(value)
131
+ }
132
+
133
+ async function resolveEntry(dir: string, pkg: PluginPackageJson | null): Promise<string | null> {
134
+ const candidates: string[] = []
135
+ if (pkg?.wellspring?.entry !== undefined) {
136
+ candidates.push(pkg.wellspring.entry)
137
+ }
138
+ if (pkg?.main !== undefined) {
139
+ candidates.push(pkg.main)
140
+ }
141
+ candidates.push(...ENTRY_CANDIDATES)
142
+
143
+ for (const candidate of candidates) {
144
+ const file = resolve(dir, candidate)
145
+ try {
146
+ const info = await stat(file)
147
+ if (info.isFile()) {
148
+ return file
149
+ }
150
+ } catch {
151
+ // 尝试下一个候选
152
+ }
153
+ }
154
+ return null
155
+ }
156
+
157
+ function validatePlugin(value: unknown): WellspringPlugin {
158
+ if (!isObject(value)) {
159
+ throw new Error("setup() 必须返回 WellspringPlugin 对象")
160
+ }
161
+ const id = value["id"]
162
+ const name = value["name"]
163
+ const version = value["version"]
164
+ if (typeof id !== "string" || !PLUGIN_NAME.test(id)) {
165
+ throw new Error(`插件 id 不合法:${String(id)}`)
166
+ }
167
+ if (typeof name !== "string" || name.trim() === "") {
168
+ throw new Error(`插件 name 不合法:${String(name)}`)
169
+ }
170
+ if (typeof version !== "string" || version.trim() === "") {
171
+ throw new Error(`插件 version 不合法:${String(version)}`)
172
+ }
173
+ const status = value["status"]
174
+ if (status !== "ok" && status !== "failed") {
175
+ throw new Error(`插件 status 必须是 ok / failed:${String(status)}`)
176
+ }
177
+ const deps = value["deps"]
178
+ if (deps !== undefined) {
179
+ if (!Array.isArray(deps) || deps.some((item) => typeof item !== "string")) {
180
+ throw new Error("插件 deps 必须是字符串数组")
181
+ }
182
+ }
183
+ const commands = value["commands"]
184
+ if (commands !== undefined && !Array.isArray(commands)) {
185
+ throw new Error("插件 commands 必须是数组")
186
+ }
187
+ const agentTools = value["agentTools"]
188
+ if (agentTools !== undefined) {
189
+ if (!Array.isArray(agentTools)) {
190
+ throw new Error("插件 agentTools 必须是数组")
191
+ }
192
+ for (const tool of agentTools) {
193
+ if (!isObject(tool) || typeof tool["name"] !== "string") {
194
+ throw new Error("Agent 工具缺少 name")
195
+ }
196
+ if (!isObject(tool["introduction"]) || typeof tool["call"] !== "function") {
197
+ throw new Error(`Agent 工具缺少 introduction / call:${tool["name"]}`)
198
+ }
199
+ }
200
+ }
201
+ const init = value["init"]
202
+ if (init !== undefined && typeof init !== "function") {
203
+ throw new Error("插件 init 必须是函数")
204
+ }
205
+ return value as unknown as WellspringPlugin
206
+ }
207
+
208
+ interface PluginEntry {
209
+ readonly dirName: string
210
+ readonly dir: string
211
+ readonly owner: string
212
+ readonly pkg: PluginPackageJson | null
213
+ readonly index: number
214
+ plugin: WellspringPlugin | null
215
+ error: string | null
216
+ disabled: boolean
217
+ sandboxClient: PluginSandboxClient | null
218
+ }
219
+
220
+ function ownerOf(dirName: string, pkg: PluginPackageJson | null): string {
221
+ if (typeof pkg?.name === "string" && PLUGIN_NAME.test(pkg.name)) {
222
+ return pkg.name
223
+ }
224
+ return dirName
225
+ }
226
+
227
+ function sandboxPermissions(pkg: PluginPackageJson | null): PluginSandboxPermissions {
228
+ const raw = pkg?.wellspring?.sandbox
229
+ const manifest =
230
+ typeof raw === "object" && raw !== null && !Array.isArray(raw)
231
+ ? (raw as Record<string, unknown>)
232
+ : {}
233
+ const rawHostApi = manifest["hostApi"]
234
+ const hostApi = Array.isArray(rawHostApi)
235
+ ? rawHostApi.filter((item): item is string => typeof item === "string")
236
+ : ["logger", "fs", "event", "getHost", "shutdown", "agent"]
237
+ const rawFs = manifest["fs"]
238
+ const fs =
239
+ typeof rawFs === "object" && rawFs !== null && !Array.isArray(rawFs)
240
+ ? (rawFs as Record<string, unknown>)
241
+ : {}
242
+ const read = Array.isArray(fs["read"])
243
+ ? fs["read"].filter((item): item is string => typeof item === "string")
244
+ : []
245
+ const write = Array.isArray(fs["write"])
246
+ ? fs["write"].filter((item): item is string => typeof item === "string")
247
+ : []
248
+ return {
249
+ hostApi,
250
+ workspaceRead: read.includes("${workspace}") || write.includes("${workspace}"),
251
+ workspaceWrite: write.includes("${workspace}"),
252
+ }
253
+ }
254
+
255
+ async function loadEntry(
256
+ dirName: string,
257
+ dir: string,
258
+ index: number,
259
+ options: LoadPluginsOptions,
260
+ disabled: ReadonlySet<string>,
261
+ sandboxBackend: SandboxBackend | null,
262
+ dataAccess: PluginDataService,
263
+ sandboxSetting: SandboxSetting,
264
+ agentService: HostAgentService,
265
+ modelsService: ReturnType<typeof createHostModelsService>,
266
+ networkService: ReturnType<typeof createHostNetworkService>,
267
+ pluginManager: HostPluginManager,
268
+ ): Promise<PluginEntry> {
269
+ let pkg: PluginPackageJson | null = null
270
+ try {
271
+ pkg = await readJsonIfExists<PluginPackageJson>(join(dir, "package.json"))
272
+ } catch (error) {
273
+ return {
274
+ dirName,
275
+ dir,
276
+ owner: dirName,
277
+ pkg: null,
278
+ index,
279
+ plugin: null,
280
+ error: `package.json 无法解析:${errorText(error)}`,
281
+ disabled: false,
282
+ sandboxClient: null,
283
+ }
284
+ }
285
+
286
+ const owner = ownerOf(dirName, pkg)
287
+ const entry: PluginEntry = {
288
+ dirName,
289
+ dir,
290
+ owner,
291
+ pkg,
292
+ index,
293
+ plugin: null,
294
+ error: null,
295
+ disabled: disabled.has(dirName),
296
+ sandboxClient: null,
297
+ }
298
+ if (entry.disabled) {
299
+ return entry
300
+ }
301
+
302
+ const apiVersion = pkg?.wellspring?.apiVersion ?? HOST_API_VERSION
303
+ if (apiVersion !== HOST_API_VERSION) {
304
+ entry.error = `不支持的 apiVersion:${apiVersion}`
305
+ return entry
306
+ }
307
+
308
+ const entryFile = await resolveEntry(dir, pkg)
309
+ if (entryFile === null) {
310
+ entry.error = "找不到插件入口"
311
+ return entry
312
+ }
313
+
314
+ if (sandboxSetting.mode === "off") {
315
+ entry.error = "sandbox.mode = off,插件禁止在宿主进程内运行"
316
+ return entry
317
+ }
318
+ if (sandboxBackend === null) {
319
+ entry.error = "sandbox backend 不可用(插件必须运行在 sandbox 内)"
320
+ return entry
321
+ }
322
+ {
323
+ let client: PluginSandboxClient | null = null
324
+ try {
325
+ const paths = options.paths ?? getPaths()
326
+ const pluginConfig = await loadPluginConfig(owner, paths)
327
+ client = new PluginSandboxClient({
328
+ pluginId: owner,
329
+ pluginName: typeof pkg?.name === "string" ? pkg.name : owner,
330
+ pluginVersion: typeof pkg?.version === "string" ? pkg.version : "0.0.0",
331
+ pluginDir: dir,
332
+ pluginEntry: entryFile,
333
+ paths,
334
+ config: options.config,
335
+ pluginConfig,
336
+ backend: sandboxBackend,
337
+ limits: {
338
+ timeoutMs: sandboxSetting.defaultTimeoutMs,
339
+ memoryMB: sandboxSetting.defaultMemoryMB,
340
+ pids: 64,
341
+ outputLimitBytes: sandboxSetting.outputLimitBytes,
342
+ },
343
+ logger: options.logger,
344
+ permissions: sandboxPermissions(pkg),
345
+ dataAccess,
346
+ capabilities: createHostCapabilities({
347
+ config: options.config,
348
+ paths,
349
+ logger: options.logger,
350
+ settingsFile: join(paths.configDir, SETTING_FILE_NAME),
351
+ }),
352
+ agent: agentService,
353
+ models: modelsService,
354
+ network: networkService,
355
+ plugins: pluginManager,
356
+ })
357
+ const ready = await client.start({ deferInit: true })
358
+ entry.sandboxClient = client
359
+ entry.plugin = {
360
+ id: ready.plugin.id,
361
+ name: ready.plugin.name,
362
+ version: ready.plugin.version,
363
+ status: ready.plugin.status,
364
+ deps: ready.plugin.deps,
365
+ commands: [],
366
+ }
367
+ } catch (error) {
368
+ await client?.close().catch(() => undefined)
369
+ entry.error = errorText(error)
370
+ entry.plugin = null
371
+ removeHooksByOwner(owner)
372
+ }
373
+ return entry
374
+ }
375
+ }
376
+
377
+ async function cleanupEntry(entry: PluginEntry, options: LoadPluginsOptions): Promise<void> {
378
+ if (entry.sandboxClient !== null) {
379
+ await entry.sandboxClient.close().catch(() => undefined)
380
+ }
381
+ if (entry.plugin !== null) {
382
+ options.registry.removePlugin(entry.plugin.id)
383
+ }
384
+ options.agentTools?.removePlugin(entry.plugin?.id ?? entry.owner)
385
+ removeHooksByOwner(entry.owner)
386
+ }
387
+
388
+ /** 按 deps 做稳定拓扑排序;循环依赖 / 缺失依赖的节点会写入 error */
389
+ function sortByDeps(entries: readonly PluginEntry[]): PluginEntry[] {
390
+ const byId = new Map<string, PluginEntry>()
391
+ for (const entry of entries) {
392
+ const id = entry.plugin?.id ?? entry.owner
393
+ if (!byId.has(id)) {
394
+ byId.set(id, entry)
395
+ }
396
+ }
397
+
398
+ const active = entries.filter((entry) => entry.plugin !== null && entry.error === null)
399
+ const indegree = new Map<PluginEntry, number>()
400
+ const dependents = new Map<PluginEntry, PluginEntry[]>()
401
+
402
+ for (const entry of active) {
403
+ const deps = entry.plugin?.deps ?? []
404
+ let count = 0
405
+ for (const dep of deps) {
406
+ const target = byId.get(dep)
407
+ if (target === undefined) {
408
+ entry.error = `依赖不存在:${dep}`
409
+ break
410
+ }
411
+ if (target === entry) {
412
+ entry.error = `循环依赖:${dep}`
413
+ break
414
+ }
415
+ if (target.disabled) {
416
+ entry.error = `依赖被禁用:${dep}`
417
+ break
418
+ }
419
+ if (target.error !== null || target.plugin === null) {
420
+ entry.error = `依赖不可用:${dep}`
421
+ break
422
+ }
423
+ count += 1
424
+ const list = dependents.get(target) ?? []
425
+ list.push(entry)
426
+ dependents.set(target, list)
427
+ }
428
+ if (entry.error === null) {
429
+ indegree.set(entry, count)
430
+ }
431
+ }
432
+
433
+ const ready = active
434
+ .filter((entry) => entry.error === null && (indegree.get(entry) ?? 0) === 0)
435
+ .sort((left, right) => left.index - right.index)
436
+ const sorted: PluginEntry[] = []
437
+ while (ready.length > 0) {
438
+ const current = ready.shift()
439
+ if (current === undefined) {
440
+ break
441
+ }
442
+ sorted.push(current)
443
+ for (const dependent of dependents.get(current) ?? []) {
444
+ const remaining = (indegree.get(dependent) ?? 0) - 1
445
+ indegree.set(dependent, remaining)
446
+ if (remaining === 0) {
447
+ ready.push(dependent)
448
+ ready.sort((left, right) => left.index - right.index)
449
+ }
450
+ }
451
+ }
452
+
453
+ for (const entry of active) {
454
+ if (entry.error === null && !sorted.includes(entry)) {
455
+ entry.error = "循环依赖"
456
+ }
457
+ }
458
+ return sorted
459
+ }
460
+
461
+ /** 加载全部插件。坏插件记录失败状态,不影响其他插件 */
462
+ function pluginIdOf(entry: PluginEntry): string {
463
+ return entry.plugin?.id ?? entry.owner
464
+ }
465
+
466
+ /** 把当前 entries 写回宿主快照;热变更后管理页与 /api/host 立即反映 */
467
+ function publishHostPlugins(entries: readonly PluginEntry[], registry: PluginRegistry): void {
468
+ setHostPlugins(
469
+ entries.map((entry): HostPlugin => {
470
+ const plugin = entry.plugin
471
+ const id = pluginIdOf(entry)
472
+ const commands =
473
+ plugin === null || entry.error !== null || entry.disabled
474
+ ? []
475
+ : registry
476
+ .list()
477
+ .filter((record) => record.pluginName === id)
478
+ .map((record) => record.command.name)
479
+ return {
480
+ id,
481
+ name:
482
+ plugin?.name ??
483
+ (typeof entry.pkg?.name === "string" ? entry.pkg.name : entry.owner),
484
+ version:
485
+ plugin?.version ??
486
+ (typeof entry.pkg?.version === "string" ? entry.pkg.version : "0.0.0"),
487
+ deps: plugin?.deps ?? [],
488
+ commands,
489
+ getStatus: (): HostPluginStatus =>
490
+ entry.disabled ? "disabled" : entry.error ? "failed" : "ok",
491
+ }
492
+ }),
493
+ )
494
+ }
495
+
496
+ /** 依赖数据访问按运行中的插件刷新 */
497
+ function refreshDataAccess(state: RuntimeState): void {
498
+ state.dataAccess.setPlugins(
499
+ state.entries.map((entry) => ({
500
+ id: pluginIdOf(entry),
501
+ deps: entry.plugin?.deps ?? [],
502
+ })),
503
+ )
504
+ }
505
+
506
+ function runExclusive<T>(state: RuntimeState, task: () => Promise<T>): Promise<T> {
507
+ const next = state.queueHost.current.then(task, task)
508
+ state.queueHost.current = next.then(
509
+ () => undefined,
510
+ () => undefined,
511
+ )
512
+ return next
513
+ }
514
+
515
+ async function hotEnableEntry(state: RuntimeState, idOrDir: string): Promise<PluginHotOutcome> {
516
+ const existing = state.entries.find(
517
+ (entry) =>
518
+ entry.dirName === idOrDir || pluginIdOf(entry) === idOrDir || entry.owner === idOrDir,
519
+ )
520
+ if (
521
+ existing !== undefined &&
522
+ existing.plugin !== null &&
523
+ existing.error === null &&
524
+ existing.sandboxClient !== null
525
+ ) {
526
+ return { ok: true, id: existing.plugin.id, changed: false }
527
+ }
528
+ const dirName = existing?.dirName ?? idOrDir
529
+ if (!PLUGIN_NAME.test(dirName)) {
530
+ return { ok: false, id: dirName, error: "插件名不合法" }
531
+ }
532
+ const dir = join(state.root, dirName)
533
+ const info = await stat(dir).catch(() => null)
534
+ if (info === null || !info.isDirectory()) {
535
+ return { ok: false, id: dirName, error: "plugin.not-found" }
536
+ }
537
+ if (existing !== undefined) {
538
+ state.entries = state.entries.filter((entry) => entry !== existing)
539
+ }
540
+ const disabled = new Set(await readPluginState(state.paths))
541
+ disabled.delete(dirName)
542
+ const entry = await loadEntry(
543
+ dirName,
544
+ dir,
545
+ state.entries.length,
546
+ state.options,
547
+ disabled,
548
+ state.sandboxBackend,
549
+ state.dataAccess,
550
+ state.sandboxSetting,
551
+ state.agentService,
552
+ state.modelsService,
553
+ state.networkService,
554
+ state.pluginManager,
555
+ )
556
+ if (entry.plugin === null || entry.error !== null) {
557
+ state.entries.push(entry)
558
+ publishHostPlugins(state.entries, state.options.registry)
559
+ return { ok: false, id: pluginIdOf(entry), error: entry.error ?? "插件加载失败" }
560
+ }
561
+ const plugin = entry.plugin
562
+ for (const dep of plugin.deps ?? []) {
563
+ const target = state.entries.find(
564
+ (candidate) =>
565
+ pluginIdOf(candidate) === dep &&
566
+ candidate.plugin !== null &&
567
+ candidate.error === null &&
568
+ candidate.sandboxClient !== null,
569
+ )
570
+ if (target === undefined) {
571
+ await cleanupEntry(entry, state.options)
572
+ entry.error = `依赖未启动:${dep}`
573
+ state.entries.push(entry)
574
+ publishHostPlugins(state.entries, state.options.registry)
575
+ return { ok: false, id: plugin.id, error: entry.error }
576
+ }
577
+ }
578
+ if (entry.sandboxClient === null) {
579
+ entry.error = "插件未运行在 sandbox 内"
580
+ state.entries.push(entry)
581
+ publishHostPlugins(state.entries, state.options.registry)
582
+ return { ok: false, id: plugin.id, error: entry.error }
583
+ }
584
+ // 先恢复依赖图再执行 init:插件 init 里可能通过 api.pluginData 读取依赖插件的数据
585
+ state.entries.push(entry)
586
+ refreshDataAccess(state)
587
+ publishHostPlugins(state.entries, state.options.registry)
588
+ try {
589
+ const ready = await entry.sandboxClient.initialize()
590
+ for (const command of entry.sandboxClient.commandStubs()) {
591
+ state.options.registry.register(ready.plugin.id, command)
592
+ }
593
+ for (const tool of entry.sandboxClient.agentToolStubs()) {
594
+ state.options.agentTools?.register(ready.plugin.id, tool)
595
+ }
596
+ await setPluginEnabled(dirName, true, state.paths)
597
+ await emitInitHooksForOwner(ready.plugin.id)
598
+ refreshDataAccess(state)
599
+ publishHostPlugins(state.entries, state.options.registry)
600
+ return { ok: true, id: ready.plugin.id, changed: true }
601
+ } catch (error) {
602
+ entry.error = errorText(error)
603
+ await cleanupEntry(entry, state.options)
604
+ refreshDataAccess(state)
605
+ publishHostPlugins(state.entries, state.options.registry)
606
+ return { ok: false, id: plugin.id, error: entry.error }
607
+ }
608
+ }
609
+
610
+ async function hotDisableEntry(state: RuntimeState, idOrDir: string): Promise<PluginHotOutcome> {
611
+ const entry = state.entries.find(
612
+ (candidate) =>
613
+ candidate.dirName === idOrDir ||
614
+ pluginIdOf(candidate) === idOrDir ||
615
+ candidate.owner === idOrDir,
616
+ )
617
+ if (entry === undefined) {
618
+ return { ok: false, id: idOrDir, error: "plugin.not-found" }
619
+ }
620
+ const id = pluginIdOf(entry)
621
+ if (id === "wellspring-web" || entry.dirName === "wellspring-web") {
622
+ return {
623
+ ok: false,
624
+ id,
625
+ error: "wellspring-web 不支持热禁用,请走重启流程",
626
+ }
627
+ }
628
+ const dependents = state.entries
629
+ .filter((candidate) => {
630
+ if (candidate === entry) return false
631
+ if (candidate.plugin === null || candidate.error !== null) return false
632
+ return (candidate.plugin.deps ?? []).includes(id)
633
+ })
634
+ .map(pluginIdOf)
635
+ if (dependents.length > 0) {
636
+ return {
637
+ ok: false,
638
+ id,
639
+ dependents,
640
+ error: `插件被依赖:${dependents.join(", ")}`,
641
+ }
642
+ }
643
+ try {
644
+ await emitShutdownHooksForOwner(entry.owner)
645
+ } catch {
646
+ // 关闭钩子的错误不阻止停用
647
+ }
648
+ await cleanupEntry(entry, state.options)
649
+ // 留一个禁用占位,宿主快照与管理页仍能看到它
650
+ entry.disabled = true
651
+ entry.plugin = null
652
+ entry.error = null
653
+ entry.sandboxClient = null
654
+ await setPluginEnabled(entry.dirName, false, state.paths)
655
+ refreshDataAccess(state)
656
+ publishHostPlugins(state.entries, state.options.registry)
657
+ return { ok: true, id, changed: true }
658
+ }
659
+
660
+ async function hotForgetEntry(state: RuntimeState, idOrDir: string): Promise<void> {
661
+ const entry = state.entries.find(
662
+ (candidate) =>
663
+ candidate.dirName === idOrDir ||
664
+ pluginIdOf(candidate) === idOrDir ||
665
+ candidate.owner === idOrDir,
666
+ )
667
+ if (entry === undefined) {
668
+ return
669
+ }
670
+ state.entries = state.entries.filter((candidate) => candidate !== entry)
671
+ refreshDataAccess(state)
672
+ publishHostPlugins(state.entries, state.options.registry)
673
+ }
674
+
675
+ function controllerOf(state: RuntimeState): PluginHotController {
676
+ return {
677
+ hotEnable: (id: string) => runExclusive(state, () => hotEnableEntry(state, id)),
678
+ hotDisable: (id: string) => runExclusive(state, () => hotDisableEntry(state, id)),
679
+ hotForget: (id: string) => runExclusive(state, () => hotForgetEntry(state, id)),
680
+ }
681
+ }
682
+
683
+ /** 取当前进程的插件热加载控制器;未经过 loadPlugins 时返回 null */
684
+ export function getPluginRuntime(paths: ResolvedPaths = getPaths()): PluginHotController | null {
685
+ const state = runtimes.get(paths.dataDir)
686
+ if (state === undefined) {
687
+ return null
688
+ }
689
+ return controllerOf(state)
690
+ }
691
+
692
+ export async function hotEnablePlugin(
693
+ id: string,
694
+ paths: ResolvedPaths = getPaths(),
695
+ ): Promise<PluginHotOutcome> {
696
+ const runtime = getPluginRuntime(paths)
697
+ if (runtime === null) {
698
+ return { ok: false, id, error: "插件运行时未初始化" }
699
+ }
700
+ return await runtime.hotEnable(id)
701
+ }
702
+
703
+ export async function hotDisablePlugin(
704
+ id: string,
705
+ paths: ResolvedPaths = getPaths(),
706
+ ): Promise<PluginHotOutcome> {
707
+ const runtime = getPluginRuntime(paths)
708
+ if (runtime === null) {
709
+ return { ok: false, id, error: "插件运行时未初始化" }
710
+ }
711
+ return await runtime.hotDisable(id)
712
+ }
713
+
714
+ export async function loadPlugins(options: LoadPluginsOptions): Promise<readonly LoadedPlugin[]> {
715
+ ensureHostAlias()
716
+ ensureAccessGuard()
717
+ const resolvedPaths = options.paths ?? getPaths()
718
+ configureHost({ paths: resolvedPaths })
719
+ const dataAccess = options.dataAccess ?? new PluginDataService(resolvedPaths)
720
+ const root = pluginsRoot(options.paths)
721
+ await ensureDir(root)
722
+
723
+ let names: string[]
724
+ try {
725
+ names = (await readdir(root)).sort()
726
+ } catch {
727
+ return []
728
+ }
729
+
730
+ const sandboxSetting: SandboxSetting = options.sandbox ?? {
731
+ ...DEFAULT_SETTINGS.sandbox,
732
+ mode: "best-effort",
733
+ }
734
+ let sandboxBackend: SandboxBackend | null = null
735
+ if (sandboxSetting.mode !== "off") {
736
+ if (options.sandboxBackend !== undefined) {
737
+ sandboxBackend = options.sandboxBackend
738
+ } else if (sandboxSetting.backend === "auto" || sandboxSetting.backend === "bwrap") {
739
+ const bwrap = new BwrapBackend()
740
+ const status = await bwrap.probe()
741
+ // 插件不做 direct 降级:没有 OS 后端就拒绝加载
742
+ sandboxBackend = status.available ? bwrap : null
743
+ }
744
+ }
745
+
746
+ const agentService =
747
+ options.agent ??
748
+ createHostAgentService({
749
+ settings: options.config,
750
+ paths: resolvedPaths,
751
+ logger: options.logger,
752
+ registry: options.registry,
753
+ agentTools: options.agentTools ?? new AgentToolRegistry(),
754
+ })
755
+ // 自定义供应商与模型切换:直接读写 app-setting.json,改完下一轮对话生效
756
+ const modelsService = createHostModelsService({
757
+ settings: options.config,
758
+ settingsFile: join(resolvedPaths.configDir, SETTING_FILE_NAME),
759
+ configDir: resolvedPaths.configDir,
760
+ logger: options.logger,
761
+ })
762
+ const networkService = createHostNetworkService({
763
+ settings: options.config,
764
+ settingsFile: join(resolvedPaths.configDir, SETTING_FILE_NAME),
765
+ logger: options.logger,
766
+ })
767
+ const state: RuntimeState = {
768
+ options,
769
+ paths: resolvedPaths,
770
+ root,
771
+ sandboxSetting,
772
+ sandboxBackend,
773
+ dataAccess,
774
+ agentService,
775
+ modelsService,
776
+ networkService,
777
+ pluginManager: null as unknown as HostPluginManager,
778
+ queueHost: { current: Promise.resolve() },
779
+ entries: [],
780
+ }
781
+ const pluginManager = createHostPluginManager({
782
+ paths: resolvedPaths,
783
+ logger: options.logger,
784
+ runtime: controllerOf(state),
785
+ })
786
+ state.pluginManager = pluginManager
787
+ runtimes.set(resolvedPaths.dataDir, state)
788
+ const disabled = await readPluginState(options.paths ?? getPaths())
789
+ const entries: PluginEntry[] = []
790
+ let index = 0
791
+ for (const dirName of names) {
792
+ if (!PLUGIN_NAME.test(dirName) || dirName.startsWith(".")) {
793
+ continue
794
+ }
795
+ const dir = join(root, dirName)
796
+ try {
797
+ const info = await stat(dir)
798
+ if (!info.isDirectory()) {
799
+ continue
800
+ }
801
+ } catch {
802
+ continue
803
+ }
804
+ entries.push(
805
+ await loadEntry(
806
+ dirName,
807
+ dir,
808
+ index,
809
+ options,
810
+ disabled,
811
+ sandboxBackend,
812
+ dataAccess,
813
+ sandboxSetting,
814
+ agentService,
815
+ modelsService,
816
+ networkService,
817
+ pluginManager,
818
+ ),
819
+ )
820
+ index += 1
821
+ }
822
+
823
+ state.entries = entries
824
+ refreshDataAccess(state)
825
+ const sorted = sortByDeps(entries)
826
+
827
+ // 让 init/hook 期间也能看到宿主插件列表(命令列表在 finalize 后补全)
828
+ publishHostPlugins(entries, options.registry)
829
+
830
+ const finished = new Set<PluginEntry>()
831
+
832
+ for (const entry of sorted) {
833
+ const plugin = entry.plugin
834
+ if (plugin === null || entry.error !== null) {
835
+ continue
836
+ }
837
+ const deps = plugin.deps ?? []
838
+ let blocked = false
839
+ for (const dep of deps) {
840
+ const target = entries.find(
841
+ (candidate) => (candidate.plugin?.id ?? candidate.owner) === dep,
842
+ )
843
+ if (target === undefined || !finished.has(target)) {
844
+ entry.error = `依赖未就绪:${dep}`
845
+ blocked = true
846
+ break
847
+ }
848
+ }
849
+ if (blocked) {
850
+ await cleanupEntry(entry, options)
851
+ continue
852
+ }
853
+ if (entry.sandboxClient !== null) {
854
+ try {
855
+ const ready = await entry.sandboxClient.initialize()
856
+ for (const command of entry.sandboxClient.commandStubs()) {
857
+ options.registry.register(ready.plugin.id, command)
858
+ }
859
+ for (const tool of entry.sandboxClient.agentToolStubs()) {
860
+ options.agentTools?.register(ready.plugin.id, tool)
861
+ }
862
+ finished.add(entry)
863
+ } catch (error) {
864
+ entry.error = errorText(error)
865
+ await cleanupEntry(entry, options)
866
+ }
867
+ continue
868
+ }
869
+ entry.error = "插件未运行在 sandbox 内"
870
+ await cleanupEntry(entry, options)
871
+ }
872
+
873
+ for (const entry of entries) {
874
+ if (
875
+ (entry.plugin !== null || entry.sandboxClient !== null) &&
876
+ entry.error !== null &&
877
+ !finished.has(entry)
878
+ ) {
879
+ await cleanupEntry(entry, options)
880
+ }
881
+ }
882
+
883
+ const loaded: LoadedPlugin[] = entries.map((entry) => {
884
+ const plugin = entry.plugin
885
+ const base = {
886
+ id: plugin?.id ?? entry.owner,
887
+ name:
888
+ plugin?.name ??
889
+ (typeof entry.pkg?.name === "string" ? entry.pkg.name : entry.owner),
890
+ version:
891
+ plugin?.version ??
892
+ (typeof entry.pkg?.version === "string" ? entry.pkg.version : "0.0.0"),
893
+ deps: plugin?.deps ?? [],
894
+ dir: entry.dir,
895
+ }
896
+ if (entry.disabled) {
897
+ return { ...base, status: "disabled", commands: [], error: null }
898
+ }
899
+ if (plugin === null || entry.error !== null) {
900
+ return {
901
+ ...base,
902
+ status: "failed",
903
+ commands: [],
904
+ error: entry.error ?? "插件加载失败",
905
+ }
906
+ }
907
+ const commands = options.registry
908
+ .list()
909
+ .filter((record) => record.pluginName === plugin.id)
910
+ .map((record) => record.command.name)
911
+ return { ...base, status: "ok", commands, error: null }
912
+ })
913
+
914
+ state.entries = entries
915
+ publishHostPlugins(entries, options.registry)
916
+ return loaded
917
+ }
918
+
919
+ /** 把本地插件目录链接进插件根目录,开发用;没有符号链接权限时退化为复制 */
920
+ export async function linkPlugin(
921
+ sourcePath: string,
922
+ paths: ResolvedPaths = getPaths(),
923
+ ): Promise<string> {
924
+ const source = resolve(sourcePath)
925
+ const name = basename(source)
926
+ if (!PLUGIN_NAME.test(name)) {
927
+ throw new Error(`插件目录名不合法:${name}`)
928
+ }
929
+ const root = pluginsRoot(paths)
930
+ await ensureDir(root)
931
+ const target = join(root, name)
932
+
933
+ const existing = await stat(target).catch(() => null)
934
+ if (existing !== null) {
935
+ throw new Error(`插件已存在:${name}`)
936
+ }
937
+
938
+ try {
939
+ await symlink(source, target, "dir")
940
+ } catch {
941
+ await cp(source, target, { recursive: true })
942
+ }
943
+ return name
944
+ }
945
+
946
+ /** 删除插件目录 */
947
+ export async function removePlugin(name: string, paths: ResolvedPaths = getPaths()): Promise<void> {
948
+ if (!PLUGIN_NAME.test(name)) {
949
+ throw new Error(`插件名不合法:${name}`)
950
+ }
951
+ await rm(join(pluginsRoot(paths), name), { recursive: true, force: true })
952
+ }