@xyit/wellspring 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +829 -2
  3. package/dist/agent/pending-asks.d.ts +20 -0
  4. package/dist/agent/pending-asks.js +70 -0
  5. package/dist/agent/pending-asks.js.map +1 -0
  6. package/dist/agent/task-registry.d.ts +14 -0
  7. package/dist/agent/task-registry.js +80 -0
  8. package/dist/agent/task-registry.js.map +1 -0
  9. package/dist/agent/tool-context.d.ts +30 -0
  10. package/dist/agent/tool-context.js +10 -0
  11. package/dist/agent/tool-context.js.map +1 -0
  12. package/dist/agent/usage.d.ts +40 -0
  13. package/dist/agent/usage.js +122 -0
  14. package/dist/agent/usage.js.map +1 -0
  15. package/dist/api/host/agent.d.ts +165 -0
  16. package/dist/api/host/agent.js +5 -0
  17. package/dist/api/host/agent.js.map +1 -0
  18. package/dist/api/host/events.d.ts +2 -0
  19. package/dist/api/host/events.js +6 -0
  20. package/dist/api/host/events.js.map +1 -0
  21. package/dist/api/host/exec.d.ts +36 -0
  22. package/dist/api/host/exec.js +10 -0
  23. package/dist/api/host/exec.js.map +1 -0
  24. package/dist/api/host/fs.d.ts +24 -0
  25. package/dist/api/host/fs.js +7 -0
  26. package/dist/api/host/fs.js.map +1 -0
  27. package/dist/api/host/hooks.d.ts +210 -0
  28. package/dist/api/host/hooks.js +401 -0
  29. package/dist/api/host/hooks.js.map +1 -0
  30. package/dist/api/host/host.d.ts +36 -0
  31. package/dist/api/host/host.js +53 -0
  32. package/dist/api/host/host.js.map +1 -0
  33. package/dist/api/host/http.d.ts +58 -0
  34. package/dist/api/host/http.js +6 -0
  35. package/dist/api/host/http.js.map +1 -0
  36. package/dist/api/host/index.d.ts +15 -0
  37. package/dist/api/host/index.js +20 -0
  38. package/dist/api/host/index.js.map +1 -0
  39. package/dist/api/host/models.d.ts +65 -0
  40. package/dist/api/host/models.js +8 -0
  41. package/dist/api/host/models.js.map +1 -0
  42. package/dist/api/host/network.d.ts +22 -0
  43. package/dist/api/host/network.js +7 -0
  44. package/dist/api/host/network.js.map +1 -0
  45. package/dist/api/host/plugin.d.ts +138 -0
  46. package/dist/api/host/plugin.js +6 -0
  47. package/dist/api/host/plugin.js.map +1 -0
  48. package/dist/api/host/plugins.d.ts +45 -0
  49. package/dist/api/host/plugins.js +6 -0
  50. package/dist/api/host/plugins.js.map +1 -0
  51. package/dist/api/host/pty.d.ts +55 -0
  52. package/dist/api/host/pty.js +21 -0
  53. package/dist/api/host/pty.js.map +1 -0
  54. package/dist/api/host/session.d.ts +5 -0
  55. package/dist/api/host/session.js +52 -0
  56. package/dist/api/host/session.js.map +1 -0
  57. package/dist/api/host/version.d.ts +2 -0
  58. package/dist/api/host/version.js +3 -0
  59. package/dist/api/host/version.js.map +1 -0
  60. package/dist/api/host/workspace.d.ts +38 -0
  61. package/dist/api/host/workspace.js +9 -0
  62. package/dist/api/host/workspace.js.map +1 -0
  63. package/dist/api/plugin/http-client.d.ts +42 -0
  64. package/dist/api/plugin/http-client.js +364 -0
  65. package/dist/api/plugin/http-client.js.map +1 -0
  66. package/dist/api/plugin/http-protocol.d.ts +152 -0
  67. package/dist/api/plugin/http-protocol.js +49 -0
  68. package/dist/api/plugin/http-protocol.js.map +1 -0
  69. package/dist/api/result/result.d.ts +16 -0
  70. package/dist/api/result/result.js +43 -0
  71. package/dist/api/result/result.js.map +1 -0
  72. package/dist/commands/agent-turn.d.ts +63 -0
  73. package/dist/commands/agent-turn.js +687 -0
  74. package/dist/commands/agent-turn.js.map +1 -0
  75. package/dist/commands/ask-prompt.d.ts +6 -0
  76. package/dist/commands/ask-prompt.js +49 -0
  77. package/dist/commands/ask-prompt.js.map +1 -0
  78. package/dist/commands/config.d.ts +2 -0
  79. package/dist/commands/config.js +283 -0
  80. package/dist/commands/config.js.map +1 -0
  81. package/dist/commands/conversation.d.ts +51 -0
  82. package/dist/commands/conversation.js +630 -0
  83. package/dist/commands/conversation.js.map +1 -0
  84. package/dist/commands/doctor.d.ts +2 -0
  85. package/dist/commands/doctor.js +77 -0
  86. package/dist/commands/doctor.js.map +1 -0
  87. package/dist/commands/exec-approval.d.ts +3 -0
  88. package/dist/commands/exec-approval.js +80 -0
  89. package/dist/commands/exec-approval.js.map +1 -0
  90. package/dist/commands/help.d.ts +2 -0
  91. package/dist/commands/help.js +35 -0
  92. package/dist/commands/help.js.map +1 -0
  93. package/dist/commands/key.d.ts +2 -0
  94. package/dist/commands/key.js +226 -0
  95. package/dist/commands/key.js.map +1 -0
  96. package/dist/commands/plugin.d.ts +2 -0
  97. package/dist/commands/plugin.js +188 -0
  98. package/dist/commands/plugin.js.map +1 -0
  99. package/dist/commands/session.d.ts +2 -0
  100. package/dist/commands/session.js +167 -0
  101. package/dist/commands/session.js.map +1 -0
  102. package/dist/commands/shared.d.ts +35 -0
  103. package/dist/commands/shared.js +66 -0
  104. package/dist/commands/shared.js.map +1 -0
  105. package/dist/commands/start.d.ts +2 -0
  106. package/dist/commands/start.js +75 -0
  107. package/dist/commands/start.js.map +1 -0
  108. package/dist/commands/stop.d.ts +2 -0
  109. package/dist/commands/stop.js +24 -0
  110. package/dist/commands/stop.js.map +1 -0
  111. package/dist/commands/version.d.ts +2 -0
  112. package/dist/commands/version.js +33 -0
  113. package/dist/commands/version.js.map +1 -0
  114. package/dist/config/codes.d.ts +13 -0
  115. package/dist/config/codes.js +15 -0
  116. package/dist/config/codes.js.map +1 -0
  117. package/dist/config/credentials.d.ts +67 -0
  118. package/dist/config/credentials.js +214 -0
  119. package/dist/config/credentials.js.map +1 -0
  120. package/dist/config/defaults.d.ts +5 -0
  121. package/dist/config/defaults.js +31 -0
  122. package/dist/config/defaults.js.map +1 -0
  123. package/dist/config/index.d.ts +5 -0
  124. package/dist/config/index.js +10 -0
  125. package/dist/config/index.js.map +1 -0
  126. package/dist/config/loader.d.ts +10 -0
  127. package/dist/config/loader.js +68 -0
  128. package/dist/config/loader.js.map +1 -0
  129. package/dist/config/providers.d.ts +30 -0
  130. package/dist/config/providers.js +97 -0
  131. package/dist/config/providers.js.map +1 -0
  132. package/dist/config/types.d.ts +75 -0
  133. package/dist/config/types.js +4 -0
  134. package/dist/config/types.js.map +1 -0
  135. package/dist/config/validate.d.ts +4 -0
  136. package/dist/config/validate.js +391 -0
  137. package/dist/config/validate.js.map +1 -0
  138. package/dist/display/markdown.d.ts +7 -0
  139. package/dist/display/markdown.js +116 -0
  140. package/dist/display/markdown.js.map +1 -0
  141. package/dist/display/tool-label.d.ts +6 -0
  142. package/dist/display/tool-label.js +81 -0
  143. package/dist/display/tool-label.js.map +1 -0
  144. package/dist/event/event.d.ts +27 -0
  145. package/dist/event/event.js +152 -0
  146. package/dist/event/event.js.map +1 -0
  147. package/dist/index.d.ts +2 -0
  148. package/dist/index.js +308 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/input/codes.d.ts +15 -0
  151. package/dist/input/codes.js +20 -0
  152. package/dist/input/codes.js.map +1 -0
  153. package/dist/input/commands.d.ts +10 -0
  154. package/dist/input/commands.js +65 -0
  155. package/dist/input/commands.js.map +1 -0
  156. package/dist/input/help.d.ts +8 -0
  157. package/dist/input/help.js +51 -0
  158. package/dist/input/help.js.map +1 -0
  159. package/dist/input/index.d.ts +6 -0
  160. package/dist/input/index.js +10 -0
  161. package/dist/input/index.js.map +1 -0
  162. package/dist/input/parse-options.d.ts +11 -0
  163. package/dist/input/parse-options.js +51 -0
  164. package/dist/input/parse-options.js.map +1 -0
  165. package/dist/input/parse.d.ts +3 -0
  166. package/dist/input/parse.js +119 -0
  167. package/dist/input/parse.js.map +1 -0
  168. package/dist/input/types.d.ts +17 -0
  169. package/dist/input/types.js +2 -0
  170. package/dist/input/types.js.map +1 -0
  171. package/dist/logger/elog4js.d.ts +65 -0
  172. package/dist/logger/elog4js.js +305 -0
  173. package/dist/logger/elog4js.js.map +1 -0
  174. package/dist/logger/logger.d.ts +37 -0
  175. package/dist/logger/logger.js +196 -0
  176. package/dist/logger/logger.js.map +1 -0
  177. package/dist/network/index.d.ts +2 -0
  178. package/dist/network/index.js +6 -0
  179. package/dist/network/index.js.map +1 -0
  180. package/dist/network/openai.d.ts +117 -0
  181. package/dist/network/openai.js +443 -0
  182. package/dist/network/openai.js.map +1 -0
  183. package/dist/network/proxy.d.ts +9 -0
  184. package/dist/network/proxy.js +72 -0
  185. package/dist/network/proxy.js.map +1 -0
  186. package/dist/network/web.d.ts +34 -0
  187. package/dist/network/web.js +152 -0
  188. package/dist/network/web.js.map +1 -0
  189. package/dist/platform/clipboard.d.ts +15 -0
  190. package/dist/platform/clipboard.js +82 -0
  191. package/dist/platform/clipboard.js.map +1 -0
  192. package/dist/platform/exec.d.ts +28 -0
  193. package/dist/platform/exec.js +95 -0
  194. package/dist/platform/exec.js.map +1 -0
  195. package/dist/platform/file.d.ts +38 -0
  196. package/dist/platform/file.js +197 -0
  197. package/dist/platform/file.js.map +1 -0
  198. package/dist/platform/index.d.ts +7 -0
  199. package/dist/platform/index.js +12 -0
  200. package/dist/platform/index.js.map +1 -0
  201. package/dist/platform/notification.d.ts +14 -0
  202. package/dist/platform/notification.js +82 -0
  203. package/dist/platform/notification.js.map +1 -0
  204. package/dist/platform/open-external.d.ts +10 -0
  205. package/dist/platform/open-external.js +105 -0
  206. package/dist/platform/open-external.js.map +1 -0
  207. package/dist/platform/os.d.ts +20 -0
  208. package/dist/platform/os.js +86 -0
  209. package/dist/platform/os.js.map +1 -0
  210. package/dist/platform/path.d.ts +32 -0
  211. package/dist/platform/path.js +154 -0
  212. package/dist/platform/path.js.map +1 -0
  213. package/dist/plugin/access-guard.d.ts +1 -0
  214. package/dist/plugin/access-guard.js +40 -0
  215. package/dist/plugin/access-guard.js.map +1 -0
  216. package/dist/plugin/agent-service.d.ts +25 -0
  217. package/dist/plugin/agent-service.js +809 -0
  218. package/dist/plugin/agent-service.js.map +1 -0
  219. package/dist/plugin/agent-tools.d.ts +12 -0
  220. package/dist/plugin/agent-tools.js +31 -0
  221. package/dist/plugin/agent-tools.js.map +1 -0
  222. package/dist/plugin/capabilities.d.ts +26 -0
  223. package/dist/plugin/capabilities.js +192 -0
  224. package/dist/plugin/capabilities.js.map +1 -0
  225. package/dist/plugin/console-guard.d.ts +2 -0
  226. package/dist/plugin/console-guard.js +52 -0
  227. package/dist/plugin/console-guard.js.map +1 -0
  228. package/dist/plugin/data-access.d.ts +15 -0
  229. package/dist/plugin/data-access.js +57 -0
  230. package/dist/plugin/data-access.js.map +1 -0
  231. package/dist/plugin/fs.d.ts +3 -0
  232. package/dist/plugin/fs.js +93 -0
  233. package/dist/plugin/fs.js.map +1 -0
  234. package/dist/plugin/host-alias.d.ts +2 -0
  235. package/dist/plugin/host-alias.js +57 -0
  236. package/dist/plugin/host-alias.js.map +1 -0
  237. package/dist/plugin/index.d.ts +7 -0
  238. package/dist/plugin/index.js +11 -0
  239. package/dist/plugin/index.js.map +1 -0
  240. package/dist/plugin/install.d.ts +12 -0
  241. package/dist/plugin/install.js +114 -0
  242. package/dist/plugin/install.js.map +1 -0
  243. package/dist/plugin/loader.d.ts +45 -0
  244. package/dist/plugin/loader.js +739 -0
  245. package/dist/plugin/loader.js.map +1 -0
  246. package/dist/plugin/manage.d.ts +31 -0
  247. package/dist/plugin/manage.js +265 -0
  248. package/dist/plugin/manage.js.map +1 -0
  249. package/dist/plugin/models-service.d.ts +15 -0
  250. package/dist/plugin/models-service.js +317 -0
  251. package/dist/plugin/models-service.js.map +1 -0
  252. package/dist/plugin/network-service.d.ts +10 -0
  253. package/dist/plugin/network-service.js +87 -0
  254. package/dist/plugin/network-service.js.map +1 -0
  255. package/dist/plugin/paths.d.ts +6 -0
  256. package/dist/plugin/paths.js +15 -0
  257. package/dist/plugin/paths.js.map +1 -0
  258. package/dist/plugin/registry.d.ts +17 -0
  259. package/dist/plugin/registry.js +82 -0
  260. package/dist/plugin/registry.js.map +1 -0
  261. package/dist/plugin/runtime.d.ts +9 -0
  262. package/dist/plugin/runtime.js +43 -0
  263. package/dist/plugin/runtime.js.map +1 -0
  264. package/dist/plugin/scaffold.d.ts +13 -0
  265. package/dist/plugin/scaffold.js +126 -0
  266. package/dist/plugin/scaffold.js.map +1 -0
  267. package/dist/plugin/state.d.ts +6 -0
  268. package/dist/plugin/state.js +38 -0
  269. package/dist/plugin/state.js.map +1 -0
  270. package/dist/plugin/zip.d.ts +7 -0
  271. package/dist/plugin/zip.js +138 -0
  272. package/dist/plugin/zip.js.map +1 -0
  273. package/dist/sandbox/audit.d.ts +12 -0
  274. package/dist/sandbox/audit.js +20 -0
  275. package/dist/sandbox/audit.js.map +1 -0
  276. package/dist/sandbox/backends/bwrap.d.ts +28 -0
  277. package/dist/sandbox/backends/bwrap.js +197 -0
  278. package/dist/sandbox/backends/bwrap.js.map +1 -0
  279. package/dist/sandbox/backends/direct.d.ts +7 -0
  280. package/dist/sandbox/backends/direct.js +32 -0
  281. package/dist/sandbox/backends/direct.js.map +1 -0
  282. package/dist/sandbox/bridge-state.d.ts +13 -0
  283. package/dist/sandbox/bridge-state.js +12 -0
  284. package/dist/sandbox/bridge-state.js.map +1 -0
  285. package/dist/sandbox/index.d.ts +5 -0
  286. package/dist/sandbox/index.js +6 -0
  287. package/dist/sandbox/index.js.map +1 -0
  288. package/dist/sandbox/manager.d.ts +8 -0
  289. package/dist/sandbox/manager.js +325 -0
  290. package/dist/sandbox/manager.js.map +1 -0
  291. package/dist/sandbox/memory-limit.d.ts +44 -0
  292. package/dist/sandbox/memory-limit.js +193 -0
  293. package/dist/sandbox/memory-limit.js.map +1 -0
  294. package/dist/sandbox/plugin-client.d.ts +80 -0
  295. package/dist/sandbox/plugin-client.js +1601 -0
  296. package/dist/sandbox/plugin-client.js.map +1 -0
  297. package/dist/sandbox/plugin-host.d.ts +1 -0
  298. package/dist/sandbox/plugin-host.js +1001 -0
  299. package/dist/sandbox/plugin-host.js.map +1 -0
  300. package/dist/sandbox/protocol.d.ts +43 -0
  301. package/dist/sandbox/protocol.js +96 -0
  302. package/dist/sandbox/protocol.js.map +1 -0
  303. package/dist/sandbox/rpc-timeouts.d.ts +28 -0
  304. package/dist/sandbox/rpc-timeouts.js +15 -0
  305. package/dist/sandbox/rpc-timeouts.js.map +1 -0
  306. package/dist/sandbox/supervisor.d.ts +31 -0
  307. package/dist/sandbox/supervisor.js +114 -0
  308. package/dist/sandbox/supervisor.js.map +1 -0
  309. package/dist/sandbox/types.d.ts +124 -0
  310. package/dist/sandbox/types.js +17 -0
  311. package/dist/sandbox/types.js.map +1 -0
  312. package/dist/service/control.d.ts +32 -0
  313. package/dist/service/control.js +117 -0
  314. package/dist/service/control.js.map +1 -0
  315. package/dist/session/compress.d.ts +3 -0
  316. package/dist/session/compress.js +42 -0
  317. package/dist/session/compress.js.map +1 -0
  318. package/dist/session/index.d.ts +4 -0
  319. package/dist/session/index.js +8 -0
  320. package/dist/session/index.js.map +1 -0
  321. package/dist/session/registry.d.ts +4 -0
  322. package/dist/session/registry.js +12 -0
  323. package/dist/session/registry.js.map +1 -0
  324. package/dist/session/replay.d.ts +10 -0
  325. package/dist/session/replay.js +109 -0
  326. package/dist/session/replay.js.map +1 -0
  327. package/dist/session/session.d.ts +40 -0
  328. package/dist/session/session.js +108 -0
  329. package/dist/session/session.js.map +1 -0
  330. package/dist/session/store.d.ts +58 -0
  331. package/dist/session/store.js +382 -0
  332. package/dist/session/store.js.map +1 -0
  333. package/dist/session/types.d.ts +56 -0
  334. package/dist/session/types.js +3 -0
  335. package/dist/session/types.js.map +1 -0
  336. package/dist/workspace/index.d.ts +7 -0
  337. package/dist/workspace/index.js +17 -0
  338. package/dist/workspace/index.js.map +1 -0
  339. package/dist/workspace/registry.d.ts +25 -0
  340. package/dist/workspace/registry.js +293 -0
  341. package/dist/workspace/registry.js.map +1 -0
  342. package/package.json +73 -5
  343. package/scripts/build-node-pty.mjs +55 -0
  344. package/scripts/rewrite-dts.mjs +36 -0
  345. package/scripts/verify-sandbox.ts +98 -0
  346. package/src/agent/pending-asks.ts +88 -0
  347. package/src/agent/task-registry.ts +108 -0
  348. package/src/agent/tool-context.ts +47 -0
  349. package/src/agent/usage.ts +170 -0
  350. package/src/api/host/agent.ts +162 -0
  351. package/src/api/host/events.ts +7 -0
  352. package/src/api/host/exec.ts +47 -0
  353. package/src/api/host/fs.ts +33 -0
  354. package/src/api/host/hooks.ts +635 -0
  355. package/src/api/host/host.ts +99 -0
  356. package/src/api/host/http.ts +79 -0
  357. package/src/api/host/index.ts +20 -0
  358. package/src/api/host/models.ts +81 -0
  359. package/src/api/host/network.ts +32 -0
  360. package/src/api/host/plugin.ts +158 -0
  361. package/src/api/host/plugins.ts +55 -0
  362. package/src/api/host/pty.ts +73 -0
  363. package/src/api/host/session.ts +61 -0
  364. package/src/api/host/version.ts +2 -0
  365. package/src/api/host/workspace.ts +51 -0
  366. package/src/api/plugin/http-client.ts +492 -0
  367. package/src/api/plugin/http-protocol.ts +231 -0
  368. package/src/api/result/result.ts +52 -0
  369. package/src/commands/agent-turn.ts +817 -0
  370. package/src/commands/ask-prompt.ts +52 -0
  371. package/src/commands/config.ts +306 -0
  372. package/src/commands/conversation.ts +777 -0
  373. package/src/commands/doctor.ts +92 -0
  374. package/src/commands/exec-approval.ts +91 -0
  375. package/src/commands/help.ts +41 -0
  376. package/src/commands/key.ts +281 -0
  377. package/src/commands/plugin.ts +218 -0
  378. package/src/commands/session.ts +179 -0
  379. package/src/commands/shared.ts +103 -0
  380. package/src/commands/start.ts +88 -0
  381. package/src/commands/stop.ts +24 -0
  382. package/src/commands/version.ts +40 -0
  383. package/src/config/codes.ts +17 -0
  384. package/src/config/credentials.ts +302 -0
  385. package/src/config/defaults.ts +32 -0
  386. package/src/config/index.ts +10 -0
  387. package/src/config/loader.ts +80 -0
  388. package/src/config/providers.ts +133 -0
  389. package/src/config/types.ts +88 -0
  390. package/src/config/validate.ts +458 -0
  391. package/src/display/markdown.ts +137 -0
  392. package/src/display/tool-label.ts +87 -0
  393. package/src/event/event.ts +171 -0
  394. package/src/index.ts +344 -0
  395. package/src/input/codes.ts +21 -0
  396. package/src/input/commands.ts +76 -0
  397. package/src/input/help.ts +60 -0
  398. package/src/input/index.ts +10 -0
  399. package/src/input/parse-options.ts +66 -0
  400. package/src/input/parse.ts +138 -0
  401. package/src/input/types.ts +19 -0
  402. package/src/logger/elog4js.ts +366 -0
  403. package/src/logger/logger.ts +251 -0
  404. package/src/network/index.ts +6 -0
  405. package/src/network/openai.ts +571 -0
  406. package/src/network/proxy.ts +80 -0
  407. package/src/network/web.ts +206 -0
  408. package/src/platform/clipboard.ts +107 -0
  409. package/src/platform/exec.ts +128 -0
  410. package/src/platform/file.ts +235 -0
  411. package/src/platform/index.ts +12 -0
  412. package/src/platform/notification.ts +108 -0
  413. package/src/platform/open-external.ts +132 -0
  414. package/src/platform/os.ts +101 -0
  415. package/src/platform/path.ts +214 -0
  416. package/src/plugin/access-guard.ts +46 -0
  417. package/src/plugin/agent-service.ts +969 -0
  418. package/src/plugin/agent-tools.ts +43 -0
  419. package/src/plugin/capabilities.ts +240 -0
  420. package/src/plugin/console-guard.ts +66 -0
  421. package/src/plugin/data-access.ts +70 -0
  422. package/src/plugin/fs.ts +109 -0
  423. package/src/plugin/host-alias.ts +61 -0
  424. package/src/plugin/index.ts +11 -0
  425. package/src/plugin/install.ts +149 -0
  426. package/src/plugin/loader.ts +952 -0
  427. package/src/plugin/manage.ts +329 -0
  428. package/src/plugin/models-service.ts +383 -0
  429. package/src/plugin/network-service.ts +102 -0
  430. package/src/plugin/paths.ts +19 -0
  431. package/src/plugin/registry.ts +103 -0
  432. package/src/plugin/runtime.ts +57 -0
  433. package/src/plugin/scaffold.ts +152 -0
  434. package/src/plugin/state.ts +52 -0
  435. package/src/plugin/zip.ts +156 -0
  436. package/src/sandbox/audit.ts +38 -0
  437. package/src/sandbox/backends/bwrap.ts +251 -0
  438. package/src/sandbox/backends/direct.ts +43 -0
  439. package/src/sandbox/bridge-state.ts +29 -0
  440. package/src/sandbox/index.ts +5 -0
  441. package/src/sandbox/manager.ts +406 -0
  442. package/src/sandbox/memory-limit.ts +245 -0
  443. package/src/sandbox/plugin-client.ts +1881 -0
  444. package/src/sandbox/plugin-host.ts +1282 -0
  445. package/src/sandbox/protocol.ts +142 -0
  446. package/src/sandbox/pty-bridge.py +154 -0
  447. package/src/sandbox/rpc-timeouts.ts +43 -0
  448. package/src/sandbox/supervisor.ts +172 -0
  449. package/src/sandbox/types.ts +149 -0
  450. package/src/service/control.ts +155 -0
  451. package/src/session/compress.ts +47 -0
  452. package/src/session/index.ts +8 -0
  453. package/src/session/registry.ts +17 -0
  454. package/src/session/replay.ts +132 -0
  455. package/src/session/session.ts +134 -0
  456. package/src/session/store.ts +468 -0
  457. package/src/session/types.ts +61 -0
  458. package/src/workspace/index.ts +21 -0
  459. package/src/workspace/registry.ts +356 -0
@@ -0,0 +1,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,19 @@
1
+ /*
2
+ * 插件目录与命名规则。
3
+ */
4
+
5
+ import { join } from "node:path"
6
+
7
+ import { getPaths, type ResolvedPaths } from "../platform/path.ts"
8
+
9
+ /** 插件目录名/包名:字母数字开头,允许 . _ -,最长 64 */
10
+ export const PLUGIN_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/
11
+
12
+ /** 插件根目录:<dataDir>/plugins */
13
+ export function pluginsRoot(paths: ResolvedPaths = getPaths()): string {
14
+ return join(paths.dataDir, "plugins")
15
+ }
16
+
17
+ export function isValidPluginName(name: string): boolean {
18
+ return PLUGIN_NAME.test(name)
19
+ }
@@ -0,0 +1,103 @@
1
+ /*
2
+ * 插件命令注册表。
3
+ * 内置命令保留,插件命令挂在顶级;重名拒绝,别名同样参与冲突检查。
4
+ */
5
+
6
+ import type { PluginCommand } from "../api/host/plugin.ts"
7
+
8
+ /** 内置命令名,插件不可占用 */
9
+ export const RESERVED_COMMANDS: ReadonlySet<string> = new Set([
10
+ "chat",
11
+ "run",
12
+ "session",
13
+ "config",
14
+ "plugin",
15
+ "doctor",
16
+ "version",
17
+ "help",
18
+ ])
19
+
20
+ const COMMAND_NAME = /^[a-z][a-z0-9-]*$/
21
+
22
+ export interface RegisteredPluginCommand {
23
+ readonly pluginName: string
24
+ readonly command: PluginCommand
25
+ }
26
+
27
+ export class PluginRegistry {
28
+ readonly #reserved: ReadonlySet<string>
29
+ readonly #commands = new Map<string, RegisteredPluginCommand>()
30
+ readonly #aliases = new Map<string, RegisteredPluginCommand>()
31
+ readonly #pluginNames = new Set<string>()
32
+
33
+ constructor(reserved: ReadonlySet<string> = RESERVED_COMMANDS) {
34
+ this.#reserved = reserved
35
+ }
36
+
37
+ /** 注册插件命令;非法、保留名或重名时抛错 */
38
+ register(pluginName: string, command: PluginCommand): void {
39
+ if (
40
+ !COMMAND_NAME.test(command.name) ||
41
+ command.name.length < 2 ||
42
+ command.name.length > 32
43
+ ) {
44
+ throw new Error(`插件命令名不合法:${command.name}`)
45
+ }
46
+ if (this.#reserved.has(command.name)) {
47
+ throw new Error(`插件不能覆盖内置命令:${command.name}`)
48
+ }
49
+ if (this.#commands.has(command.name) || this.#aliases.has(command.name)) {
50
+ throw new Error(`插件命令重名:${command.name}`)
51
+ }
52
+ if (typeof command.summary !== "string" || command.summary.trim() === "") {
53
+ throw new Error(`插件命令缺少 summary:${command.name}`)
54
+ }
55
+ if (typeof command.run !== "function") {
56
+ throw new Error(`插件命令缺少 run:${command.name}`)
57
+ }
58
+
59
+ const record: RegisteredPluginCommand = { pluginName, command }
60
+ this.#commands.set(command.name, record)
61
+ this.#pluginNames.add(pluginName)
62
+
63
+ for (const alias of command.aliases ?? []) {
64
+ if (!COMMAND_NAME.test(alias) || alias.length < 2 || alias.length > 32) {
65
+ throw new Error(`插件命令别名不合法:${alias}`)
66
+ }
67
+ if (
68
+ this.#reserved.has(alias) ||
69
+ this.#commands.has(alias) ||
70
+ this.#aliases.has(alias)
71
+ ) {
72
+ throw new Error(`插件命令别名冲突:${alias}`)
73
+ }
74
+ this.#aliases.set(alias, record)
75
+ }
76
+ }
77
+
78
+ resolve(name: string): RegisteredPluginCommand | undefined {
79
+ return this.#commands.get(name) ?? this.#aliases.get(name)
80
+ }
81
+
82
+ list(): readonly RegisteredPluginCommand[] {
83
+ return [...this.#commands.values()]
84
+ }
85
+
86
+ removePlugin(pluginName: string): void {
87
+ for (const [name, record] of this.#commands) {
88
+ if (record.pluginName === pluginName) {
89
+ this.#commands.delete(name)
90
+ }
91
+ }
92
+ for (const [alias, record] of this.#aliases) {
93
+ if (record.pluginName === pluginName) {
94
+ this.#aliases.delete(alias)
95
+ }
96
+ }
97
+ this.#pluginNames.delete(pluginName)
98
+ }
99
+
100
+ pluginNames(): readonly string[] {
101
+ return [...this.#pluginNames]
102
+ }
103
+ }
@@ -0,0 +1,57 @@
1
+ /*
2
+ * 插件运行时目录:
3
+ * - 数据:<dataDir>/plugin-data/<id>/
4
+ * - 配置:<configDir>/plugins/<id>.json(插件自己的 JSON 配置)
5
+ */
6
+
7
+ import { join } from "node:path"
8
+
9
+ import type { PluginConfig } from "../api/host/plugin.ts"
10
+ import { ensureDir, readJsonIfExists } from "../platform/file.ts"
11
+ import { getPaths, type ResolvedPaths } from "../platform/path.ts"
12
+ import { PLUGIN_NAME } from "./paths.ts"
13
+
14
+ // eslint-disable-next-line no-control-regex
15
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f]/
16
+
17
+ function assertPluginId(id: string): void {
18
+ if (!PLUGIN_NAME.test(id) || CONTROL_CHARS.test(id)) {
19
+ throw new Error(`插件 id 不合法:${id}`)
20
+ }
21
+ }
22
+
23
+ /** 插件专属数据目录,平台会确保存在 */
24
+ export function pluginDataDir(id: string, paths: ResolvedPaths = getPaths()): string {
25
+ assertPluginId(id)
26
+ return join(paths.dataDir, "plugin-data", id)
27
+ }
28
+
29
+ /** 插件专属配置文件 */
30
+ export function pluginConfigFile(id: string, paths: ResolvedPaths = getPaths()): string {
31
+ assertPluginId(id)
32
+ return join(paths.configDir, "plugins", `${id}.json`)
33
+ }
34
+
35
+ export async function ensurePluginDataDir(
36
+ id: string,
37
+ paths: ResolvedPaths = getPaths(),
38
+ ): Promise<string> {
39
+ const dir = pluginDataDir(id, paths)
40
+ await ensureDir(dir)
41
+ return dir
42
+ }
43
+
44
+ /** 读取插件配置;文件不存在时返回空对象 */
45
+ export async function loadPluginConfig(
46
+ id: string,
47
+ paths: ResolvedPaths = getPaths(),
48
+ ): Promise<PluginConfig> {
49
+ const value = await readJsonIfExists<unknown>(pluginConfigFile(id, paths))
50
+ if (value === null) {
51
+ return {}
52
+ }
53
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
54
+ throw new Error(`插件配置必须是 JSON 对象:${pluginConfigFile(id, paths)}`)
55
+ }
56
+ return value as Record<string, unknown>
57
+ }