@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,222 @@
1
+ /*
2
+ * plugin 命令:list / install / link / remove。
3
+ */
4
+
5
+ import { readFile, readdir } from "node:fs/promises"
6
+ import { basename, join } from "node:path"
7
+
8
+ import { Result } from "../api/result/result.ts"
9
+ import { USAGE_CODES } from "../input/codes.ts"
10
+ import { booleanValue, parseCommandArgs } from "../input/parse-options.ts"
11
+ import type { ResolvedPaths } from "../platform/path.ts"
12
+ import { statIfExists } from "../platform/file.ts"
13
+ import { installPlugin } from "../plugin/install.ts"
14
+ import { linkPlugin, removePlugin } from "../plugin/loader.ts"
15
+ import { PLUGIN_NAME, pluginsRoot } from "../plugin/paths.ts"
16
+ import { scaffoldPlugin } from "../plugin/scaffold.ts"
17
+ import { readPluginState, setPluginEnabled } from "../plugin/state.ts"
18
+ import { failure, success, type CommandContext, type CommandOutcome } from "./shared.ts"
19
+
20
+ interface ScannedPlugin {
21
+ readonly dirName: string
22
+ readonly name: string
23
+ readonly version: string
24
+ readonly enabled: boolean
25
+ readonly deps: readonly string[]
26
+ }
27
+
28
+ /** 只扫插件目录的文件,不启动插件进程(plugin list / enable / disable 用) */
29
+ async function scanPluginDirs(paths: ResolvedPaths): Promise<ScannedPlugin[]> {
30
+ const root = pluginsRoot(paths)
31
+ const disabled = await readPluginState(paths)
32
+ let names: string[]
33
+ try {
34
+ names = (await readdir(root)).sort()
35
+ } catch {
36
+ return []
37
+ }
38
+ const plugins: ScannedPlugin[] = []
39
+ for (const dirName of names) {
40
+ if (!PLUGIN_NAME.test(dirName) || dirName.startsWith(".")) continue
41
+ const dir = join(root, dirName)
42
+ const info = await statIfExists(dir)
43
+ if (info === null || !info.isDirectory()) continue
44
+ let name = dirName
45
+ let version = "0.0.0"
46
+ try {
47
+ const pkg = JSON.parse(await readFile(join(dir, "package.json"), "utf8")) as {
48
+ name?: unknown
49
+ version?: unknown
50
+ }
51
+ if (typeof pkg.name === "string" && pkg.name !== "") name = pkg.name
52
+ if (typeof pkg.version === "string" && pkg.version !== "") version = pkg.version
53
+ } catch {
54
+ /* 没有 package.json 时用目录名 */
55
+ }
56
+ let deps: string[] = []
57
+ try {
58
+ const source = await readFile(join(dir, "index.ts"), "utf8")
59
+ const match = /deps\s*:\s*\[([^\]]*)\]/.exec(source)
60
+ if (match !== null) {
61
+ deps = [...(match[1] ?? "").matchAll(/["']([^"']+)["']/g)].map(
62
+ (item) => item[1] ?? "",
63
+ )
64
+ }
65
+ } catch {
66
+ /* 读不到入口时按没有依赖处理 */
67
+ }
68
+ plugins.push({
69
+ dirName,
70
+ name,
71
+ version,
72
+ enabled: !disabled.has(dirName),
73
+ deps: deps.filter((dep) => dep !== ""),
74
+ })
75
+ }
76
+ return plugins
77
+ }
78
+
79
+ /** 依赖目标插件的其它插件目录名 */
80
+ function dependentsOf(
81
+ plugins: readonly ScannedPlugin[],
82
+ dirName: string,
83
+ pluginName: string,
84
+ ): string[] {
85
+ return plugins
86
+ .filter(
87
+ (plugin) =>
88
+ plugin.dirName !== dirName &&
89
+ (plugin.deps.includes(dirName) || plugin.deps.includes(pluginName)),
90
+ )
91
+ .map((plugin) => plugin.dirName)
92
+ }
93
+
94
+ export async function runPluginCommand(
95
+ ctx: CommandContext,
96
+ argv: readonly string[],
97
+ ): Promise<CommandOutcome> {
98
+ const [subcommand, ...rest] = argv
99
+ const parsed = parseCommandArgs(rest, {
100
+ force: { type: "boolean" },
101
+ ignore: { type: "boolean" },
102
+ dir: { type: "string" },
103
+ })
104
+ if (!parsed.isSuccess()) {
105
+ return { result: Result.error<unknown>(parsed.getCode() ?? USAGE_CODES.invalidValue) }
106
+ }
107
+ const line = parsed.getData()
108
+ if (line === null) {
109
+ return failure(USAGE_CODES.invalidValue)
110
+ }
111
+ const force = booleanValue(line.values, "force")
112
+ const ignore = booleanValue(line.values, "ignore")
113
+
114
+ try {
115
+ switch (subcommand) {
116
+ case "list": {
117
+ const plugins = await scanPluginDirs(ctx.paths)
118
+ for (const plugin of plugins) {
119
+ const deps = plugin.deps.length === 0 ? "-" : plugin.deps.join(",")
120
+ const status = plugin.enabled ? "enabled" : "disabled"
121
+ ctx.elog.print(
122
+ `${plugin.dirName} ${plugin.name}@${plugin.version} ${status} deps=${deps}`,
123
+ )
124
+ }
125
+ return success(plugins)
126
+ }
127
+ case "new": {
128
+ const name = line.positionals[0]
129
+ if (name === undefined) {
130
+ return failure(`${USAGE_CODES.missingValue}:name`)
131
+ }
132
+ const created = await scaffoldPlugin({
133
+ name,
134
+ parentDir:
135
+ typeof line.values["dir"] === "string" ? line.values["dir"] : undefined,
136
+ force,
137
+ })
138
+ ctx.elog.info(`已创建插件 ${name}:${created.dir}`)
139
+ ctx.elog.print("下一步:")
140
+ ctx.elog.print(` wellspring plugin link ${created.dir}`)
141
+ ctx.elog.print(` wellspring plugin list`)
142
+ ctx.elog.print(` wellspring ${created.commandName}`)
143
+ ctx.elog.print("类型检查:先把 Wellspring 链接进插件(见插件 README)")
144
+ return success(created)
145
+ }
146
+ case "enable":
147
+ case "disable": {
148
+ const name = line.positionals[0]
149
+ if (name === undefined) {
150
+ return failure(`${USAGE_CODES.missingValue}:name`)
151
+ }
152
+ if (!PLUGIN_NAME.test(name)) {
153
+ return failure(`${USAGE_CODES.invalidValue}:name`)
154
+ }
155
+ const plugins = await scanPluginDirs(ctx.paths)
156
+ const match =
157
+ plugins.find((plugin) => plugin.dirName === name) ??
158
+ plugins.find((plugin) => plugin.name === name)
159
+ const dirName = match === undefined ? name : match.dirName
160
+ const dir = join(pluginsRoot(ctx.paths), dirName)
161
+ if ((await statIfExists(dir)) === null) {
162
+ return failure(`plugin.not-found:${name}`)
163
+ }
164
+ const enabled = subcommand === "enable"
165
+ await setPluginEnabled(dirName, enabled, ctx.paths)
166
+ ctx.elog.info(`${enabled ? "已启用" : "已禁用"}插件 ${dirName}(下次运行生效)`)
167
+ return success({ id: dirName, enabled })
168
+ }
169
+ case "install": {
170
+ const source = line.positionals[0]
171
+ if (source === undefined) {
172
+ return failure(`${USAGE_CODES.missingValue}:path`)
173
+ }
174
+ const installed = await installPlugin(source, ctx.paths, { force })
175
+ ctx.elog.info(
176
+ installed.dependenciesInstalled
177
+ ? `已安装插件 ${installed.id},已用 ${installed.packageManager ?? "包管理器"} 安装依赖`
178
+ : `已安装插件 ${installed.id}`,
179
+ )
180
+ return success(installed)
181
+ }
182
+ case "link": {
183
+ const source = line.positionals[0]
184
+ if (source === undefined) {
185
+ return failure(`${USAGE_CODES.missingValue}:path`)
186
+ }
187
+ const name = await linkPlugin(source, ctx.paths)
188
+ ctx.elog.info(`已链接插件 ${name}`)
189
+ return success({ id: name })
190
+ }
191
+ case "remove": {
192
+ const name = line.positionals[0]
193
+ if (name === undefined) {
194
+ return failure(`${USAGE_CODES.missingValue}:name`)
195
+ }
196
+ const plugins = await scanPluginDirs(ctx.paths)
197
+ const target =
198
+ plugins.find((plugin) => plugin.dirName === name) ??
199
+ plugins.find((plugin) => plugin.name === name)
200
+ const dirName = target === undefined ? name : target.dirName
201
+ const dependents = dependentsOf(plugins, dirName, target?.name ?? dirName)
202
+ if (dependents.length > 0 && !ignore) {
203
+ ctx.elog.error(
204
+ `插件 ${dirName} 被这些插件依赖:${dependents.join(", ")};` +
205
+ "确认要强制卸载请加 --ignore",
206
+ )
207
+ return failure(`plugin.has-dependents:${dependents.join(",")}`)
208
+ }
209
+ await removePlugin(dirName, ctx.paths)
210
+ ctx.elog.info(
211
+ `已删除插件 ${dirName}${ignore && dependents.length > 0 ? "(忽略依赖)" : ""}`,
212
+ )
213
+ return success({ id: dirName })
214
+ }
215
+ default:
216
+ return failure(`${USAGE_CODES.unknownSubcommand}:plugin ${subcommand ?? ""}`)
217
+ }
218
+ } catch (error) {
219
+ const message = error instanceof Error ? error.message : String(error)
220
+ return failure(`plugin.install-failed:${message}`)
221
+ }
222
+ }
@@ -0,0 +1,179 @@
1
+ /*
2
+ * session 命令:list / show / delete。
3
+ */
4
+
5
+ import { Result } from "../api/result/result.ts"
6
+ import { USAGE_CODES } from "../input/codes.ts"
7
+ import { booleanValue, parseCommandArgs, stringValue } from "../input/parse-options.ts"
8
+ import { contentText } from "../session/replay.ts"
9
+ import { SessionStore, type SessionData } from "../session/store.ts"
10
+ import type { SessionEvent } from "../session/types.ts"
11
+ import { failure, success, type CommandContext, type CommandOutcome } from "./shared.ts"
12
+
13
+ function truncate(text: string, full: boolean): string {
14
+ if (full || text.length <= 200) {
15
+ return text
16
+ }
17
+ return `${text.slice(0, 200)}…`
18
+ }
19
+
20
+ function eventText(event: SessionEvent): string {
21
+ const message = event.data["message"]
22
+ if (typeof message !== "object" || message === null) {
23
+ return ""
24
+ }
25
+ const content = (message as { content?: unknown }).content
26
+ if (!Array.isArray(content)) {
27
+ return ""
28
+ }
29
+ return contentText(content as never)
30
+ }
31
+
32
+ function printSession(session: SessionData, full: boolean, ctx: CommandContext): void {
33
+ ctx.elog.print(`${session.meta.id} ${session.meta.cwd}`)
34
+ for (const turn of session.turns) {
35
+ const mode = turn.mode ?? "chat"
36
+ ctx.elog.print(`#${turn.turn} [${mode}]`)
37
+ for (const event of turn.events) {
38
+ if (event.type === "user/message") {
39
+ ctx.elog.print(` > ${truncate(eventText(event), full)}`)
40
+ } else if (event.type === "assistant/message") {
41
+ ctx.elog.print(` ${truncate(eventText(event), full)}`)
42
+ } else if (event.type === "tool/call") {
43
+ const name = event.data["name"]
44
+ ctx.elog.print(` [tool] ${String(name ?? "")}`)
45
+ } else if (event.type === "turn/end") {
46
+ const reason = event.data["reason"]
47
+ const kind =
48
+ typeof reason === "object" && reason !== null
49
+ ? ((reason as { kind?: unknown }).kind ?? "")
50
+ : ""
51
+ ctx.elog.print(` -- ${String(kind)}`)
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ async function handleList(ctx: CommandContext, args: readonly string[]): Promise<CommandOutcome> {
58
+ const parsed = parseCommandArgs(args, {
59
+ all: { type: "boolean" },
60
+ limit: { type: "string" },
61
+ })
62
+ if (!parsed.isSuccess()) {
63
+ return { result: Result.error<unknown>(parsed.getCode() ?? USAGE_CODES.invalidValue) }
64
+ }
65
+ const line = parsed.getData()
66
+ if (line === null) {
67
+ return failure(USAGE_CODES.invalidValue)
68
+ }
69
+ const rawLimit = stringValue(line.values, "limit")
70
+ let limit = 20
71
+ if (rawLimit !== undefined) {
72
+ const value = Number(rawLimit)
73
+ if (!Number.isInteger(value) || value <= 0) {
74
+ return failure(`${USAGE_CODES.invalidValue}:--limit`)
75
+ }
76
+ limit = value
77
+ }
78
+
79
+ const list = await new SessionStore().list({
80
+ cwd: process.cwd(),
81
+ all: booleanValue(line.values, "all"),
82
+ limit,
83
+ })
84
+ for (const item of list) {
85
+ ctx.elog.print(`${item.id}${item.last ? " *" : ""} turns=${item.turns} ${item.cwd}`)
86
+ }
87
+ return success(list.map((item) => ({ ...item, dir: undefined })))
88
+ }
89
+
90
+ async function handleShow(ctx: CommandContext, args: readonly string[]): Promise<CommandOutcome> {
91
+ const parsed = parseCommandArgs(args, {
92
+ full: { type: "boolean" },
93
+ })
94
+ if (!parsed.isSuccess()) {
95
+ return { result: Result.error<unknown>(parsed.getCode() ?? USAGE_CODES.invalidValue) }
96
+ }
97
+ const line = parsed.getData()
98
+ const id = line?.positionals[0]
99
+ if (id === undefined) {
100
+ return failure(`${USAGE_CODES.missingValue}:session-id`)
101
+ }
102
+ const session = await new SessionStore().open(id, process.cwd())
103
+ printSession(session, line !== null && booleanValue(line.values, "full"), ctx)
104
+ return success({ meta: session.meta, turns: session.turns })
105
+ }
106
+
107
+ async function handleDelete(ctx: CommandContext, args: readonly string[]): Promise<CommandOutcome> {
108
+ const parsed = parseCommandArgs(args, {
109
+ all: { type: "boolean" },
110
+ yes: { type: "boolean" },
111
+ })
112
+ if (!parsed.isSuccess()) {
113
+ return { result: Result.error<unknown>(parsed.getCode() ?? USAGE_CODES.invalidValue) }
114
+ }
115
+ const line = parsed.getData()
116
+ if (line === null) {
117
+ return failure(USAGE_CODES.invalidValue)
118
+ }
119
+ const all = booleanValue(line.values, "all")
120
+ const yes = booleanValue(line.values, "yes")
121
+ const store = new SessionStore()
122
+
123
+ if (all) {
124
+ if (!yes) {
125
+ return failure(USAGE_CODES.missingConfirmation)
126
+ }
127
+ const removed = await store.removeAll(process.cwd())
128
+ ctx.elog.info(`已删除 ${removed} 个会话`)
129
+ return success({ removed })
130
+ }
131
+
132
+ if (line.positionals.length === 0) {
133
+ return failure(`${USAGE_CODES.missingValue}:session-id`)
134
+ }
135
+ for (const id of line.positionals) {
136
+ await store.remove(id)
137
+ ctx.elog.info(`已删除 ${id}`)
138
+ }
139
+ return success({ removed: line.positionals })
140
+ }
141
+
142
+ export async function runSessionCommand(
143
+ ctx: CommandContext,
144
+ argv: readonly string[],
145
+ ): Promise<CommandOutcome> {
146
+ const [subcommand, ...rest] = argv
147
+ try {
148
+ switch (subcommand) {
149
+ case "list":
150
+ return await handleList(ctx, rest)
151
+ case "show":
152
+ return await handleShow(ctx, rest)
153
+ case "delete":
154
+ return await handleDelete(ctx, rest)
155
+ default:
156
+ return failure(`${USAGE_CODES.unknownSubcommand}:session ${subcommand ?? ""}`)
157
+ }
158
+ } catch (error) {
159
+ return failure(sessionErrorCode(error))
160
+ }
161
+ }
162
+
163
+ function sessionErrorCode(error: unknown): string {
164
+ if (error instanceof Error) {
165
+ if (error.name === "SessionNotFoundError") {
166
+ return "session.not-found"
167
+ }
168
+ if (error.name === "SessionAmbiguousError") {
169
+ return "session.ambiguous-id"
170
+ }
171
+ if (error.name === "SessionLockedError") {
172
+ return "session.locked"
173
+ }
174
+ if (error.message.includes("会话 id 不合法")) {
175
+ return USAGE_CODES.invalidSessionId
176
+ }
177
+ }
178
+ return "session.corrupted"
179
+ }
@@ -0,0 +1,103 @@
1
+ /*
2
+ * 命令层公共类型与错误文案。
3
+ */
4
+
5
+ import type { Logger } from "pino"
6
+
7
+ import { Result } from "../api/result/result.ts"
8
+ import type { AppSetting } from "../config/types.ts"
9
+ import { isUsageCode } from "../input/codes.ts"
10
+ import type { GlobalOptions } from "../input/types.ts"
11
+ import type { Elog } from "../logger/elog4js.ts"
12
+ import type { OpenAIClient } from "../network/openai.ts"
13
+ import type { ResolvedPaths } from "../platform/path.ts"
14
+ import type { LoadedPlugin } from "../plugin/loader.ts"
15
+ import type { AgentToolRegistry } from "../plugin/agent-tools.ts"
16
+ import type { PluginRegistry } from "../plugin/registry.ts"
17
+
18
+ export interface CommandContext {
19
+ readonly global: GlobalOptions
20
+ readonly settings: AppSetting
21
+ readonly settingsFile: string
22
+ readonly configCode: string | null
23
+ readonly paths: ResolvedPaths
24
+ readonly logger: Logger
25
+ readonly elog: Elog
26
+ readonly registry: PluginRegistry
27
+ readonly agentTools: AgentToolRegistry
28
+ readonly openai: OpenAIClient | null
29
+ /** 当前生效的供应商 id 与模型(已按 models.* 解析);测试上下文可省略 */
30
+ readonly activeProvider?: string
31
+ readonly activeModel?: string
32
+ readonly activeReasoning?: string
33
+ loadPlugins(): Promise<readonly LoadedPlugin[]>
34
+ }
35
+
36
+ export interface CommandOutcome {
37
+ readonly result: Result<unknown>
38
+ }
39
+
40
+ export function success(data?: unknown): CommandOutcome {
41
+ return { result: Result.successWithData<unknown>(data ?? null) }
42
+ }
43
+
44
+ export function failure(code: string): CommandOutcome {
45
+ return { result: Result.error<unknown>(code) }
46
+ }
47
+
48
+ const MESSAGES: Readonly<Record<string, string>> = {
49
+ "usage.unknown-command": "未知命令",
50
+ "usage.unknown-option": "未知参数",
51
+ "usage.unknown-subcommand": "未知子命令",
52
+ "usage.missing-value": "缺少参数值",
53
+ "usage.invalid-value": "参数值非法",
54
+ "usage.missing-context": "缺少对话内容",
55
+ "usage.invalid-session-id": "会话 id 不合法",
56
+ "usage.unknown-config-key": "配置项不存在",
57
+ "usage.conflicting-options": "参数互相冲突",
58
+ "usage.missing-confirmation": "缺少 --yes 确认",
59
+ "config.read-failed": "配置文件读取失败",
60
+ "config.parse-failed": "配置文件解析失败",
61
+ "config.invalid": "配置项非法",
62
+ "config.version-unsupported": "配置版本不受支持",
63
+ "config.write-failed": "配置文件写入失败",
64
+ "config.invalid-api-key": "API Key 格式不合法",
65
+ "config.credentials-write-failed": "凭据文件写入失败",
66
+ "openai.missing-api-key": "缺少 API Key(wellspring key set 或环境变量 OPENAI_API_KEY)",
67
+ "openai.empty-output": "模型返回空内容",
68
+ "openai.timeout": "模型请求超时",
69
+ "openai.connection-failed": "无法连接模型服务",
70
+ "openai.unauthorized": "模型服务鉴权失败",
71
+ "openai.forbidden": "模型服务拒绝访问",
72
+ "openai.rate-limited": "模型服务限流",
73
+ "openai.bad-request": "模型请求参数非法",
74
+ "openai.server-error": "模型服务内部错误",
75
+ "openai.request-failed": "模型请求失败",
76
+ "session.not-found": "会话不存在",
77
+ "session.parse-failed": "会话事件解析失败",
78
+ "session.corrupted": "会话段损坏",
79
+ "session.ambiguous-id": "会话 id 在多处重复",
80
+ "session.locked": "会话正在被其他进程写入",
81
+ "plugin.command-failed": "插件命令执行失败",
82
+ "plugin.load-failed": "插件加载失败",
83
+ "plugin.install-failed": "插件安装失败",
84
+ "plugin.has-dependents": "插件被其它插件依赖,使用 --ignore 强制卸载",
85
+ "plugin.not-found": "插件不存在",
86
+ "service.already-running": "服务已在运行",
87
+ "service.not-running": "没有正在运行的服务",
88
+ "service.stop-timeout": "等待服务退出超时",
89
+ "service.stop-failed": "停止服务失败",
90
+ "service.record-failed": "服务状态记录失败",
91
+ "runtime.unknown-status": "未知运行状态",
92
+ }
93
+
94
+ /** 错误码 → 人类可读文案,字段级细节保留在冒号后 */
95
+ export function errorMessage(code: string): string {
96
+ const [base, detail] = code.split(":", 2)
97
+ const message = MESSAGES[base ?? ""] ?? base ?? code
98
+ return detail === undefined || detail === "" ? message : `${message}:${detail}`
99
+ }
100
+
101
+ export function exitCodeFor(code: string | null): number {
102
+ return isUsageCode(code) ? 2 : 1
103
+ }
@@ -0,0 +1,88 @@
1
+ /*
2
+ * start:以服务进程启动。
3
+ * 加载插件后触发 onInit,保持进程存活;只有 Ctrl+C / SIGTERM 或插件调用 shutdown() 才退出。
4
+ */
5
+
6
+ import { emitInitHooks, emitShutdownHooks, setShutdownHandler } from "../api/host/hooks.ts"
7
+ import { setHostStatus } from "../api/host/host.ts"
8
+ import {
9
+ ServiceAlreadyRunningError,
10
+ assertServiceStartable,
11
+ clearServiceRecord,
12
+ writeServiceRecord,
13
+ } from "../service/control.ts"
14
+ import { failure, success, type CommandContext, type CommandOutcome } from "./shared.ts"
15
+
16
+ export async function runStartCommand(ctx: CommandContext): Promise<CommandOutcome> {
17
+ await ctx.loadPlugins()
18
+
19
+ try {
20
+ await assertServiceStartable(ctx.paths)
21
+ } catch (error) {
22
+ if (error instanceof ServiceAlreadyRunningError) {
23
+ return failure(`service.already-running:${error.pid}`)
24
+ }
25
+ const message = error instanceof Error ? error.message : String(error)
26
+ return failure(`service.record-failed:${message}`)
27
+ }
28
+
29
+ await writeServiceRecord(
30
+ { pid: process.pid, startedAt: new Date().toISOString(), cwd: process.cwd() },
31
+ ctx.paths,
32
+ )
33
+
34
+ const state: { resolve: (() => void) | null } = { resolve: null }
35
+ const shutdownPromise = new Promise<void>((resolve) => {
36
+ state.resolve = resolve
37
+ })
38
+ const requestShutdown = (): void => {
39
+ state.resolve?.()
40
+ }
41
+
42
+ setShutdownHandler(requestShutdown)
43
+ let signals = 0
44
+ const onSignal = (): void => {
45
+ signals += 1
46
+ // 第二次 Ctrl+C 直接退出:某个插件的 onShutdown 卡住时仍然能停下来
47
+ if (signals > 1) {
48
+ ctx.elog.notice("强制退出")
49
+ process.exit(130)
50
+ }
51
+ requestShutdown()
52
+ }
53
+ process.on("SIGINT", onSignal)
54
+ process.on("SIGTERM", onSignal)
55
+
56
+ // 没有常驻 handle 时 Node 会直接退出,这里显式保持事件循环
57
+ const keepAlive = setInterval(() => undefined, 60_000)
58
+
59
+ ctx.logger.info({ pid: process.pid }, "service started")
60
+ ctx.elog.info("Wellspring 服务已启动,Ctrl+C 退出")
61
+
62
+ try {
63
+ await emitInitHooks()
64
+ await shutdownPromise
65
+ setHostStatus("stopping")
66
+ ctx.elog.notice("正在关闭…")
67
+ // 兜底:插件 onShutdown 若卡住(例如有长连接没断开),超时后强制退出
68
+ const forceExit = setTimeout(() => {
69
+ ctx.elog.notice("关闭超时,强制退出")
70
+ process.exit(130)
71
+ }, 10_000)
72
+ forceExit.unref()
73
+ try {
74
+ await emitShutdownHooks()
75
+ } finally {
76
+ clearTimeout(forceExit)
77
+ }
78
+ ctx.logger.info("service stopped")
79
+ ctx.elog.info("Wellspring 服务已关闭")
80
+ return success({ pid: process.pid })
81
+ } finally {
82
+ clearInterval(keepAlive)
83
+ process.off("SIGINT", onSignal)
84
+ process.off("SIGTERM", onSignal)
85
+ setShutdownHandler(null)
86
+ await clearServiceRecord(process.pid, ctx.paths)
87
+ }
88
+ }
@@ -0,0 +1,24 @@
1
+ /*
2
+ * stop:停止正在运行的 wellspring start 服务。
3
+ * 读取 <dataDir>/service.json,发送 SIGTERM,等待进程退出。
4
+ */
5
+
6
+ import { ServiceNotRunningError, ServiceStopTimeoutError, stopService } from "../service/control.ts"
7
+ import { failure, success, type CommandContext, type CommandOutcome } from "./shared.ts"
8
+
9
+ export async function runStopCommand(ctx: CommandContext): Promise<CommandOutcome> {
10
+ try {
11
+ const pid = await stopService(ctx.paths)
12
+ ctx.elog.info(`已停止 Wellspring 服务(pid ${pid})`)
13
+ return success({ pid })
14
+ } catch (error) {
15
+ if (error instanceof ServiceNotRunningError) {
16
+ return failure("service.not-running")
17
+ }
18
+ if (error instanceof ServiceStopTimeoutError) {
19
+ return failure(`service.stop-timeout:${error.pid}`)
20
+ }
21
+ const message = error instanceof Error ? error.message : String(error)
22
+ return failure(`service.stop-failed:${message}`)
23
+ }
24
+ }
@@ -0,0 +1,40 @@
1
+ /*
2
+ * version:读取 package.json 的版本号并输出运行环境。
3
+ */
4
+
5
+ import { join } from "node:path"
6
+
7
+ import { readTextIfExists } from "../platform/file.ts"
8
+ import { platform } from "../platform/os.ts"
9
+ import { success, type CommandContext, type CommandOutcome } from "./shared.ts"
10
+
11
+ interface PackageJson {
12
+ readonly name?: string
13
+ readonly version?: string
14
+ }
15
+
16
+ async function readPackage(): Promise<PackageJson> {
17
+ const file = join(import.meta.dirname, "../../package.json")
18
+ const text = await readTextIfExists(file)
19
+ if (text === null) {
20
+ return {}
21
+ }
22
+ try {
23
+ return JSON.parse(text) as PackageJson
24
+ } catch {
25
+ return {}
26
+ }
27
+ }
28
+
29
+ export async function runVersionCommand(ctx: CommandContext): Promise<CommandOutcome> {
30
+ const pkg = await readPackage()
31
+ const data = {
32
+ name: pkg.name ?? "wellspring",
33
+ version: pkg.version ?? "0.0.0",
34
+ node: process.versions.node,
35
+ platform,
36
+ }
37
+ ctx.elog.print(`${data.name} ${data.version}`)
38
+ ctx.elog.print(`node ${data.node} ${data.platform}`)
39
+ return success(data)
40
+ }
@@ -0,0 +1,17 @@
1
+ /** 配置错误码。字段级错误格式为 `${invalid}:<path>`,path 为英文点分路径 */
2
+ export const CONFIG_CODES = {
3
+ readFailed: "config.read-failed",
4
+ parseFailed: "config.parse-failed",
5
+ invalid: "config.invalid",
6
+ versionUnsupported: "config.version-unsupported",
7
+ writeFailed: "config.write-failed",
8
+ invalidApiKey: "config.invalid-api-key",
9
+ credentialsWriteFailed: "config.credentials-write-failed",
10
+ } as const
11
+
12
+ export type ConfigCode = (typeof CONFIG_CODES)[keyof typeof CONFIG_CODES]
13
+
14
+ /** 字段级错误码,例如 config.invalid:logger.level */
15
+ export function invalidCode(path: string): string {
16
+ return `${CONFIG_CODES.invalid}:${path}`
17
+ }