@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,383 @@
1
+ /*
2
+ * 宿主模型服务:给 sandbox 插件提供自定义供应商与模型切换。
3
+ *
4
+ * 配置写在 app-setting.json 的 models 节(每次调用实时读写,改完下一轮对话生效),
5
+ * 每个供应商的 Key 写在 credentials.json;状态只返回掩码。
6
+ */
7
+
8
+ import type { Logger } from "pino"
9
+
10
+ import type {
11
+ HostModelProvider,
12
+ HostModelProviderInput,
13
+ HostModelsApi,
14
+ HostModelsFetchResult,
15
+ HostModelsState,
16
+ HostModelsStateResult,
17
+ } from "../api/host/models.ts"
18
+ import { CONFIG_CODES } from "../config/codes.ts"
19
+ import {
20
+ clearProviderApiKey,
21
+ credentialsFile,
22
+ normalizeApiKey,
23
+ providerKeyStatusSync,
24
+ saveProviderApiKey,
25
+ } from "../config/credentials.ts"
26
+ import { DEFAULT_SETTINGS } from "../config/defaults.ts"
27
+ import { REASONING_LEVELS, type ReasoningLevel } from "../config/types.ts"
28
+ import { loadSettings, saveSettings, SETTING_FILE_NAME } from "../config/loader.ts"
29
+ import {
30
+ BUILTIN_PROVIDER_ID,
31
+ listModelProviders,
32
+ modelsSetting,
33
+ providerApiKey,
34
+ } from "../config/providers.ts"
35
+ import type { AppSetting, ModelProviderSetting } from "../config/types.ts"
36
+ import { OpenAIClient } from "../network/openai.ts"
37
+ import { createProxyFetch } from "../network/proxy.ts"
38
+
39
+ export interface HostModelsServiceOptions {
40
+ readonly settings: AppSetting
41
+ /** app-setting.json 的绝对路径 */
42
+ readonly settingsFile: string
43
+ /** 凭据文件所在 config 目录 */
44
+ readonly configDir: string
45
+ readonly logger: Logger
46
+ }
47
+
48
+ export interface HostModelsService extends HostModelsApi {
49
+ state(): Promise<HostModelsState>
50
+ }
51
+
52
+ const PROVIDER_ID = /^[a-z0-9][a-z0-9._-]{0,31}$/
53
+ const MAX_MODEL_CHARS = 120
54
+
55
+ function normalizeModels(value: readonly string[] | undefined): string[] {
56
+ const models: string[] = []
57
+ for (const item of value ?? []) {
58
+ if (typeof item !== "string") {
59
+ continue
60
+ }
61
+ const model = item.trim()
62
+ if (model === "" || model.length > MAX_MODEL_CHARS || models.includes(model)) {
63
+ continue
64
+ }
65
+ models.push(model)
66
+ }
67
+ return models
68
+ }
69
+
70
+ function slugOf(name: string): string {
71
+ const slug = name
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9]+/g, "-")
74
+ .replace(/^-+|-+$/g, "")
75
+ .slice(0, 32)
76
+ return PROVIDER_ID.test(slug) ? slug : "provider"
77
+ }
78
+
79
+ function uniqueId(base: string, taken: ReadonlySet<string>): string {
80
+ if (!taken.has(base) && base !== BUILTIN_PROVIDER_ID) {
81
+ return base
82
+ }
83
+ for (let index = 2; index < 100; index += 1) {
84
+ const candidate = `${base}-${index}`
85
+ if (
86
+ !taken.has(candidate) &&
87
+ candidate !== BUILTIN_PROVIDER_ID &&
88
+ PROVIDER_ID.test(candidate)
89
+ ) {
90
+ return candidate
91
+ }
92
+ }
93
+ return `provider-${Date.now().toString(36)}`
94
+ }
95
+
96
+ export function createHostModelsService(options: HostModelsServiceOptions): HostModelsService {
97
+ const logger = options.logger.child({ module: "models" })
98
+ const settingsFile = options.settingsFile
99
+ const credentialsPath = credentialsFile(options.configDir)
100
+
101
+ const currentSettings = async (): Promise<AppSetting> => {
102
+ const loaded = await loadSettings(settingsFile)
103
+ return loaded.getData() ?? options.settings ?? DEFAULT_SETTINGS
104
+ }
105
+
106
+ const stateOf = (settings: AppSetting): HostModelsState => {
107
+ const models = modelsSetting(settings)
108
+ const providers: HostModelProvider[] = listModelProviders(settings).map((provider) => {
109
+ const key = providerKeyStatusSync(
110
+ { id: provider.id, apiKeyEnv: provider.apiKeyEnv },
111
+ credentialsPath,
112
+ )
113
+ return {
114
+ id: provider.id,
115
+ name: provider.name,
116
+ baseUrl: provider.baseUrl,
117
+ models: provider.models,
118
+ defaultModel: provider.defaultModel,
119
+ apiKeyEnv: provider.apiKeyEnv,
120
+ builtin: provider.builtin,
121
+ keyConfigured: key.configured,
122
+ keySource: key.source,
123
+ keyMasked: key.masked,
124
+ envVar: key.envVar,
125
+ keyStored: key.stored,
126
+ }
127
+ })
128
+ const activeProvider = providers.some((provider) => provider.id === models.activeProvider)
129
+ ? models.activeProvider
130
+ : BUILTIN_PROVIDER_ID
131
+ return {
132
+ providers,
133
+ activeProvider,
134
+ activeModel: models.activeModel,
135
+ activeReasoning: models.activeReasoning,
136
+ reasoningLevels: REASONING_LEVELS,
137
+ }
138
+ }
139
+
140
+ const saveProviderConfig = async (
141
+ settings: AppSetting,
142
+ provider: ModelProviderSetting,
143
+ ): Promise<HostModelsStateResult> => {
144
+ const models = modelsSetting(settings)
145
+ const providers = [...models.providers]
146
+ const index = providers.findIndex((item) => item.id === provider.id)
147
+ if (index === -1) {
148
+ if (providers.length >= 20) {
149
+ return { ok: false, code: "models.too-many-providers", state: stateOf(settings) }
150
+ }
151
+ providers.push(provider)
152
+ } else {
153
+ providers[index] = provider
154
+ }
155
+ const next: AppSetting = {
156
+ ...settings,
157
+ models: { ...models, providers },
158
+ }
159
+ const saved = await saveSettings(next, settingsFile)
160
+ if (!saved.isSuccess()) {
161
+ return {
162
+ ok: false,
163
+ code: saved.getCode() ?? CONFIG_CODES.invalid,
164
+ state: stateOf(settings),
165
+ }
166
+ }
167
+ return { ok: true, state: stateOf(next) }
168
+ }
169
+
170
+ const fail = async (code: string): Promise<HostModelsStateResult> => ({
171
+ ok: false,
172
+ code,
173
+ state: stateOf(await currentSettings()),
174
+ })
175
+
176
+ return {
177
+ async state(): Promise<HostModelsState> {
178
+ return stateOf(await currentSettings())
179
+ },
180
+
181
+ async setActive(
182
+ providerId: string,
183
+ model: string | null,
184
+ reasoning?: string | null,
185
+ ): Promise<HostModelsState> {
186
+ const settings = await currentSettings()
187
+ const models = modelsSetting(settings)
188
+ const id = providerId.trim()
189
+ const exists = listModelProviders(settings).some((provider) => provider.id === id)
190
+ if (!exists) {
191
+ throw new Error("models.provider-not-found")
192
+ }
193
+ const nextModel = typeof model === "string" && model.trim() !== "" ? model.trim() : null
194
+ if (nextModel !== null && nextModel.length > MAX_MODEL_CHARS) {
195
+ throw new Error("models.invalid-model")
196
+ }
197
+ const nextReasoning =
198
+ typeof reasoning === "string" && reasoning.trim() !== ""
199
+ ? reasoning.trim()
200
+ : models.activeReasoning
201
+ if (!(REASONING_LEVELS as readonly string[]).includes(nextReasoning)) {
202
+ throw new Error("models.invalid-reasoning")
203
+ }
204
+ const next: AppSetting = {
205
+ ...settings,
206
+ models: {
207
+ ...models,
208
+ activeProvider: id,
209
+ activeModel: nextModel,
210
+ activeReasoning: nextReasoning as ReasoningLevel,
211
+ },
212
+ }
213
+ const saved = await saveSettings(next, settingsFile)
214
+ if (!saved.isSuccess()) {
215
+ throw new Error(saved.getCode() ?? CONFIG_CODES.invalid)
216
+ }
217
+ logger.info({ provider: id, model: nextModel, reasoning: nextReasoning }, "切换模型")
218
+ return stateOf(next)
219
+ },
220
+
221
+ async saveProvider(input: HostModelProviderInput): Promise<HostModelsStateResult> {
222
+ const settings = await currentSettings()
223
+ const models = modelsSetting(settings)
224
+ const name = typeof input.name === "string" ? input.name.trim() : ""
225
+ if (name === "" || name.length > 60) {
226
+ return await fail(CONFIG_CODES.invalid)
227
+ }
228
+ const explicitId =
229
+ typeof input.id === "string" && input.id.trim() !== "" ? input.id.trim() : ""
230
+ const taken = new Set(models.providers.map((provider) => provider.id))
231
+ let id = explicitId
232
+ if (id === "") {
233
+ id = uniqueId(slugOf(name), taken)
234
+ } else if (!PROVIDER_ID.test(id) || id === BUILTIN_PROVIDER_ID) {
235
+ return await fail(CONFIG_CODES.invalid)
236
+ }
237
+ const apiKey =
238
+ typeof input.apiKey === "string" && input.apiKey.trim() !== ""
239
+ ? normalizeApiKey(input.apiKey)
240
+ : null
241
+ if (typeof input.apiKey === "string" && input.apiKey.trim() !== "" && apiKey === null) {
242
+ return { ok: false, code: CONFIG_CODES.invalidApiKey, state: stateOf(settings) }
243
+ }
244
+ const rawBaseUrl = input.baseUrl
245
+ const baseUrl =
246
+ typeof rawBaseUrl === "string" && rawBaseUrl.trim() !== ""
247
+ ? rawBaseUrl.trim()
248
+ : null
249
+ const provider: ModelProviderSetting = {
250
+ id,
251
+ name,
252
+ baseUrl,
253
+ models: normalizeModels(input.models),
254
+ defaultModel:
255
+ typeof input.defaultModel === "string" && input.defaultModel.trim() !== ""
256
+ ? input.defaultModel.trim()
257
+ : null,
258
+ apiKeyEnv:
259
+ typeof input.apiKeyEnv === "string" && input.apiKeyEnv.trim() !== ""
260
+ ? input.apiKeyEnv.trim()
261
+ : null,
262
+ }
263
+ const result = await saveProviderConfig(settings, provider)
264
+ if (!result.ok) {
265
+ return result
266
+ }
267
+ if (apiKey !== null) {
268
+ const keyed = await saveProviderApiKey(id, apiKey, credentialsPath)
269
+ if (!keyed.isSuccess()) {
270
+ return await fail(keyed.getCode() ?? CONFIG_CODES.credentialsWriteFailed)
271
+ }
272
+ }
273
+ return { ok: true, state: stateOf(await currentSettings()) }
274
+ },
275
+
276
+ async removeProvider(id: string): Promise<HostModelsStateResult> {
277
+ const settings = await currentSettings()
278
+ const models = modelsSetting(settings)
279
+ if (id === BUILTIN_PROVIDER_ID) {
280
+ return await fail("models.builtin-provider")
281
+ }
282
+ const providers = models.providers.filter((provider) => provider.id !== id)
283
+ if (providers.length === models.providers.length) {
284
+ return await fail("models.provider-not-found")
285
+ }
286
+ const next: AppSetting = {
287
+ ...settings,
288
+ models: {
289
+ ...models,
290
+ providers,
291
+ activeProvider:
292
+ models.activeProvider === id ? BUILTIN_PROVIDER_ID : models.activeProvider,
293
+ activeModel: models.activeProvider === id ? null : models.activeModel,
294
+ },
295
+ }
296
+ const saved = await saveSettings(next, settingsFile)
297
+ if (!saved.isSuccess()) {
298
+ return await fail(saved.getCode() ?? CONFIG_CODES.invalid)
299
+ }
300
+ await clearProviderApiKey(id, credentialsPath)
301
+ logger.info({ provider: id }, "删除自定义供应商")
302
+ return { ok: true, state: stateOf(next) }
303
+ },
304
+
305
+ async fetchModels(id: string, candidate?: string): Promise<HostModelsFetchResult> {
306
+ const settings = await currentSettings()
307
+ const provider = listModelProviders(settings).find((item) => item.id === id)
308
+ if (provider === undefined) {
309
+ return { ok: false, code: "models.provider-not-found" }
310
+ }
311
+ let apiKey: string | undefined
312
+ if (typeof candidate === "string" && candidate.trim() !== "") {
313
+ const normalized = normalizeApiKey(candidate)
314
+ if (normalized === null) {
315
+ return { ok: false, code: CONFIG_CODES.invalidApiKey }
316
+ }
317
+ apiKey = normalized
318
+ } else {
319
+ apiKey = providerApiKey(provider, options.configDir) ?? undefined
320
+ }
321
+ if (apiKey === undefined) {
322
+ return { ok: false, code: "openai.missing-api-key" }
323
+ }
324
+ let proxyFetch: ReturnType<typeof createProxyFetch> | undefined
325
+ try {
326
+ proxyFetch = createProxyFetch(settings.network.proxy)
327
+ } catch {
328
+ proxyFetch = undefined
329
+ }
330
+ const created = OpenAIClient.create({
331
+ settings: {
332
+ ...settings.openai,
333
+ baseUrl: provider.baseUrl,
334
+ model: provider.defaultModel ?? provider.models[0] ?? settings.openai.model,
335
+ },
336
+ apiKey,
337
+ credentialsFile: credentialsPath,
338
+ logger,
339
+ fetch: proxyFetch,
340
+ })
341
+ const client = created.getData()
342
+ if (!created.isSuccess() || client === null) {
343
+ return { ok: false, code: created.getCode() ?? "openai.missing-api-key" }
344
+ }
345
+ const listed = await client.listModels()
346
+ const data = listed.getData()
347
+ if (!listed.isSuccess() || data === null) {
348
+ return { ok: false, code: listed.getCode() ?? "openai.request-failed" }
349
+ }
350
+ return { ok: true, models: data }
351
+ },
352
+
353
+ async saveKey(id: string, apiKey: string): Promise<HostModelsStateResult> {
354
+ const settings = await currentSettings()
355
+ const exists = listModelProviders(settings).some((provider) => provider.id === id)
356
+ if (!exists) {
357
+ return await fail("models.provider-not-found")
358
+ }
359
+ const saved = await saveProviderApiKey(id, apiKey, credentialsPath)
360
+ if (!saved.isSuccess()) {
361
+ return {
362
+ ok: false,
363
+ code: saved.getCode() ?? CONFIG_CODES.invalidApiKey,
364
+ state: stateOf(settings),
365
+ }
366
+ }
367
+ return { ok: true, state: stateOf(settings) }
368
+ },
369
+
370
+ async clearKey(id: string): Promise<HostModelsState> {
371
+ const settings = await currentSettings()
372
+ const exists = listModelProviders(settings).some((provider) => provider.id === id)
373
+ if (!exists) {
374
+ throw new Error("models.provider-not-found")
375
+ }
376
+ const cleared = await clearProviderApiKey(id, credentialsPath)
377
+ if (!cleared.isSuccess()) {
378
+ throw new Error(cleared.getCode() ?? CONFIG_CODES.credentialsWriteFailed)
379
+ }
380
+ return stateOf(settings)
381
+ },
382
+ }
383
+ }
@@ -0,0 +1,102 @@
1
+ /*
2
+ * 宿主网络配置服务:读写 app-setting.json 的 network.proxy。
3
+ *
4
+ * 只写这一个字段,其余配置原样保留;写入前经过 validateSettings,
5
+ * 非法 URL / noProxy 不会落盘。模型调用、拉模型、网页搜索每轮都会重新读配置,
6
+ * 所以保存后不需要重启服务,下一轮请求生效。
7
+ */
8
+
9
+ import type { Logger } from "pino"
10
+
11
+ import type { HostNetworkApi, HostNetworkResult, HostNetworkState } from "../api/host/network.ts"
12
+ import { CONFIG_CODES, invalidCode } from "../config/codes.ts"
13
+ import { loadSettings, saveSettings } from "../config/loader.ts"
14
+ import type { AppSetting, ProxySetting } from "../config/types.ts"
15
+
16
+ const MAX_NO_PROXY = 100
17
+ const MAX_NO_PROXY_LENGTH = 256
18
+
19
+ export interface HostNetworkServiceOptions {
20
+ /** 启动时的配置;配置文件不存在或读取失败时回退到它 */
21
+ readonly settings: AppSetting
22
+ readonly settingsFile: string
23
+ readonly logger?: Logger
24
+ }
25
+
26
+ function isHttpUrl(value: string): boolean {
27
+ try {
28
+ const url = new URL(value)
29
+ return url.protocol === "http:" || url.protocol === "https:"
30
+ } catch {
31
+ return false
32
+ }
33
+ }
34
+
35
+ /** 去空、去重、限长,避免把超大/空白的 noProxy 写进配置 */
36
+ function normalizeNoProxy(values: readonly string[] | undefined): string[] {
37
+ const result: string[] = []
38
+ for (const raw of values ?? []) {
39
+ if (typeof raw !== "string") continue
40
+ const value = raw.trim()
41
+ if (value === "" || value.length > MAX_NO_PROXY_LENGTH) continue
42
+ if (result.includes(value)) continue
43
+ if (result.length >= MAX_NO_PROXY) break
44
+ result.push(value)
45
+ }
46
+ return result
47
+ }
48
+
49
+ export function createHostNetworkService(options: HostNetworkServiceOptions): HostNetworkApi {
50
+ const currentSettings = async (): Promise<AppSetting> => {
51
+ try {
52
+ const loaded = await loadSettings(options.settingsFile)
53
+ return loaded.getData() ?? options.settings
54
+ } catch {
55
+ return options.settings
56
+ }
57
+ }
58
+
59
+ const stateOf = (settings: AppSetting): HostNetworkState => ({
60
+ proxy: settings.network.proxy,
61
+ })
62
+
63
+ return {
64
+ async state(): Promise<HostNetworkState> {
65
+ return stateOf(await currentSettings())
66
+ },
67
+
68
+ async saveProxy(input): Promise<HostNetworkResult> {
69
+ const settings = await currentSettings()
70
+ const rawUrl = typeof input.url === "string" ? input.url.trim() : ""
71
+ if (rawUrl !== "" && !isHttpUrl(rawUrl)) {
72
+ return {
73
+ ok: false,
74
+ code: invalidCode("network.proxy.url"),
75
+ state: stateOf(settings),
76
+ }
77
+ }
78
+ let proxy: ProxySetting | null = null
79
+ if (rawUrl !== "") {
80
+ proxy = {
81
+ type: "http",
82
+ url: rawUrl,
83
+ noProxy: normalizeNoProxy(input.noProxy),
84
+ }
85
+ }
86
+ const next: AppSetting = { ...settings, network: { proxy } }
87
+ const saved = await saveSettings(next, options.settingsFile)
88
+ if (!saved.isSuccess()) {
89
+ return {
90
+ ok: false,
91
+ code: saved.getCode() ?? CONFIG_CODES.invalid,
92
+ state: stateOf(settings),
93
+ }
94
+ }
95
+ options.logger?.info(
96
+ { proxy: proxy?.url ?? null, noProxy: proxy?.noProxy ?? [] },
97
+ proxy === null ? "已关闭 HTTP 代理" : "已更新 HTTP 代理",
98
+ )
99
+ return { ok: true, state: stateOf(next) }
100
+ },
101
+ }
102
+ }
@@ -0,0 +1,214 @@
1
+ /*
2
+ * 插件依赖安装:插件自己的 package.json 用 packageManager 字段声明包管理器。
3
+ *
4
+ * 只认白名单命令(npm / pnpm / yarn / bun),字段不会变成任意命令执行;
5
+ * 版本号只作声明,实际使用 PATH 里的同名命令(不安装新版本,也不走 corepack)。
6
+ */
7
+
8
+ import { spawn } from "node:child_process"
9
+ import { readdir } from "node:fs/promises"
10
+
11
+ export type PluginPackageManagerName = "npm" | "pnpm" | "yarn" | "bun"
12
+
13
+ export interface PluginPackageManager {
14
+ readonly name: PluginPackageManagerName
15
+ readonly version: string | null
16
+ }
17
+
18
+ export interface PluginPackageJsonLike {
19
+ readonly packageManager?: unknown
20
+ readonly dependencies?: Record<string, unknown> | null
21
+ readonly optionalDependencies?: Record<string, unknown> | null
22
+ }
23
+
24
+ export interface PluginDependencyInstallResult {
25
+ readonly manager: PluginPackageManager | null
26
+ readonly installed: boolean
27
+ readonly reason: string | null
28
+ }
29
+
30
+ interface ManagerCommand {
31
+ readonly binary: string
32
+ readonly args: readonly string[]
33
+ }
34
+
35
+ const MANAGERS: ReadonlyMap<PluginPackageManagerName, ManagerCommand> = new Map([
36
+ ["npm", { binary: "npm", args: ["install", "--no-audit", "--no-fund", "--omit=dev"] }],
37
+ ["pnpm", { binary: "pnpm", args: ["install", "--prod"] }],
38
+ ["yarn", { binary: "yarn", args: ["install", "--production"] }],
39
+ ["bun", { binary: "bun", args: ["install", "--production"] }],
40
+ ])
41
+
42
+ const INSTALL_TIMEOUT_MS = 5 * 60 * 1000
43
+ const MAX_OUTPUT_CHARS = 4000
44
+
45
+ function isManagerName(value: string): value is PluginPackageManagerName {
46
+ return MANAGERS.has(value as PluginPackageManagerName)
47
+ }
48
+
49
+ /** 解析 "npm@11.20.0" / "pnpm" 这类声明;不合法返回 null */
50
+ export function parsePackageManager(value: unknown): PluginPackageManager | null {
51
+ if (typeof value !== "string") {
52
+ return null
53
+ }
54
+ const trimmed = value.trim()
55
+ const match = /^([A-Za-z][A-Za-z0-9-]*)(?:@(.+))?$/.exec(trimmed)
56
+ if (match === null) {
57
+ return null
58
+ }
59
+ const name = (match[1] ?? "").toLowerCase()
60
+ if (!isManagerName(name)) {
61
+ return null
62
+ }
63
+ const version = match[2] ?? null
64
+ // 允许 corepack 常见的 "pnpm@10.0.0+sha512..." 形式
65
+ if (version !== null && !/^[0-9A-Za-z][0-9A-Za-z.+-]*$/.test(version)) {
66
+ return null
67
+ }
68
+ return { name, version }
69
+ }
70
+
71
+ /** 没声明 packageManager 时按 lockfile 推断,兼容旧插件 */
72
+ export function lockfilePackageManager(files: readonly string[]): PluginPackageManager | null {
73
+ if (files.includes("pnpm-lock.yaml")) {
74
+ return { name: "pnpm", version: null }
75
+ }
76
+ if (files.includes("yarn.lock")) {
77
+ return { name: "yarn", version: null }
78
+ }
79
+ if (files.includes("bun.lockb") || files.includes("bun.lock")) {
80
+ return { name: "bun", version: null }
81
+ }
82
+ if (files.includes("package-lock.json")) {
83
+ return { name: "npm", version: null }
84
+ }
85
+ return null
86
+ }
87
+
88
+ export function hasPluginDependencies(pkg: PluginPackageJsonLike): boolean {
89
+ return (
90
+ Object.keys(pkg.dependencies ?? {}).length > 0 ||
91
+ Object.keys(pkg.optionalDependencies ?? {}).length > 0
92
+ )
93
+ }
94
+
95
+ /** 实际执行的命令;Windows 下 .cmd 需要 shell */
96
+ export function pluginInstallCommand(manager: PluginPackageManager): {
97
+ readonly command: string
98
+ readonly args: readonly string[]
99
+ } {
100
+ const spec = MANAGERS.get(manager.name)
101
+ if (spec === undefined) {
102
+ throw new Error(`不支持的包管理器:${manager.name}`)
103
+ }
104
+ const command = process.platform === "win32" ? `${spec.binary}.cmd` : spec.binary
105
+ return { command, args: spec.args }
106
+ }
107
+
108
+ export interface RunPackageInstallOptions {
109
+ readonly timeoutMs?: number
110
+ }
111
+
112
+ /** 在插件目录里执行包管理器安装;失败时抛出带输出尾部的错误 */
113
+ export async function runPackageInstall(
114
+ directory: string,
115
+ manager: PluginPackageManager,
116
+ options: RunPackageInstallOptions = {},
117
+ ): Promise<void> {
118
+ const { command, args } = pluginInstallCommand(manager)
119
+ const timeoutMs = options.timeoutMs ?? INSTALL_TIMEOUT_MS
120
+ await new Promise<void>((resolvePromise, rejectPromise) => {
121
+ let output = ""
122
+ let settled = false
123
+ const append = (chunk: Buffer): void => {
124
+ if (output.length >= MAX_OUTPUT_CHARS) {
125
+ return
126
+ }
127
+ output += chunk.toString("utf8")
128
+ }
129
+ const child = spawn(command, [...args], {
130
+ cwd: directory,
131
+ env: process.env,
132
+ stdio: ["ignore", "pipe", "pipe"],
133
+ // Windows 的 npm.cmd / pnpm.cmd 必须经 shell 才能启动;args 都是固定白名单
134
+ shell: process.platform === "win32",
135
+ })
136
+ const timer = setTimeout(() => {
137
+ if (settled) return
138
+ settled = true
139
+ child.kill("SIGKILL")
140
+ rejectPromise(
141
+ new Error(`${manager.name} 安装依赖超时(${Math.round(timeoutMs / 1000)}s)`),
142
+ )
143
+ }, timeoutMs)
144
+ child.stdout?.on("data", append)
145
+ child.stderr?.on("data", append)
146
+ child.once("error", (error) => {
147
+ if (settled) return
148
+ settled = true
149
+ clearTimeout(timer)
150
+ rejectPromise(new Error(`无法运行 ${manager.name}:${error.message}`))
151
+ })
152
+ child.once("close", (code) => {
153
+ if (settled) return
154
+ settled = true
155
+ clearTimeout(timer)
156
+ if (code === 0) {
157
+ resolvePromise()
158
+ return
159
+ }
160
+ const tail = output.trim().slice(-MAX_OUTPUT_CHARS)
161
+ rejectPromise(
162
+ new Error(
163
+ `${manager.name} 安装依赖失败(退出码 ${String(code)})` +
164
+ (tail === "" ? "" : `:${tail}`),
165
+ ),
166
+ )
167
+ })
168
+ })
169
+ }
170
+
171
+ export interface InstallPluginDependenciesOptions {
172
+ /** 插件目录里的文件名;默认读目录,用于 lockfile 推断 */
173
+ readonly files?: readonly string[]
174
+ /** 测试注入;默认 runPackageInstall */
175
+ readonly run?: typeof runPackageInstall
176
+ readonly timeoutMs?: number
177
+ }
178
+
179
+ /**
180
+ * 安装插件依赖。
181
+ *
182
+ * - 没有 dependencies / optionalDependencies:直接跳过
183
+ * - 有依赖但没有 packageManager:先用 lockfile 推断;仍没有则报错
184
+ * (不允许猜 npm,避免在错误的目录里跑错包管理器)
185
+ */
186
+ export async function installPluginDependencies(
187
+ directory: string,
188
+ pkg: PluginPackageJsonLike,
189
+ options: InstallPluginDependenciesOptions = {},
190
+ ): Promise<PluginDependencyInstallResult> {
191
+ if (!hasPluginDependencies(pkg)) {
192
+ return { manager: null, installed: false, reason: null }
193
+ }
194
+ const declared = parsePackageManager(pkg.packageManager)
195
+ if (declared === null && pkg.packageManager !== undefined) {
196
+ throw new Error(
197
+ 'package.json 的 packageManager 不合法,支持 "npm@11"、"pnpm@10"、"yarn@1"、"bun@1"',
198
+ )
199
+ }
200
+ const files = options.files ?? (await readdir(directory).catch(() => []))
201
+ const manager = declared ?? lockfilePackageManager(files)
202
+ if (manager === null) {
203
+ throw new Error(
204
+ '插件声明了 dependencies,但 package.json 缺少 packageManager(例如 "npm@11.20.0")',
205
+ )
206
+ }
207
+ const run = options.run ?? runPackageInstall
208
+ await run(directory, manager, { timeoutMs: options.timeoutMs ?? INSTALL_TIMEOUT_MS })
209
+ return { manager, installed: true, reason: null }
210
+ }
211
+
212
+ export function packageManagerLabel(manager: PluginPackageManager): string {
213
+ return manager.version === null ? manager.name : `${manager.name}@${manager.version}`
214
+ }