@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,152 @@
1
+ /*
2
+ * 插件脚手架:生成可加载的最小插件目录。
3
+ */
4
+
5
+ import { join, resolve } from "node:path"
6
+
7
+ import {
8
+ ensureDir,
9
+ removePath,
10
+ statIfExists,
11
+ writeJsonAtomic,
12
+ writeTextAtomic,
13
+ } from "../platform/file.ts"
14
+ import { PLUGIN_NAME } from "./paths.ts"
15
+
16
+ export interface ScaffoldPluginOptions {
17
+ readonly name: string
18
+ /** 生成到哪个父目录,默认当前工作目录 */
19
+ readonly parentDir?: string
20
+ readonly force?: boolean
21
+ }
22
+
23
+ export interface ScaffoldPluginResult {
24
+ readonly dir: string
25
+ readonly commandName: string
26
+ }
27
+
28
+ /** 插件名 → 合法命令名;名字不适用时回落到 hello */
29
+ export function commandNameOf(pluginName: string): string {
30
+ let slug = pluginName
31
+ .toLowerCase()
32
+ .replace(/[^a-z0-9-]+/g, "-")
33
+ .replace(/-+/g, "-")
34
+ .replace(/^-+|-+$/g, "")
35
+ if (!/^[a-z][a-z0-9-]*$/.test(slug) || slug.length < 2) {
36
+ slug = "hello"
37
+ }
38
+ return slug.slice(0, 32).replace(/-+$/, "")
39
+ }
40
+
41
+ function template(pluginName: string, commandName: string): string {
42
+ return [
43
+ 'import { onMessage, onInit, onShutdown } from "wellspring/api/host/index.ts"',
44
+ 'import type { WellspringPlugin } from "wellspring/api/host/index.ts"',
45
+ "",
46
+ "// 钩子可以放在模块顶层;平台在 import 时会把它们归属到当前插件",
47
+ "onMessage(({ phase, prompt }) => {",
48
+ ' if (phase === "before") prompt.push("你是一个运行在 Wellspring 里的助手")',
49
+ "})",
50
+ "",
51
+ "export function setup() {",
52
+ " onInit(() => {",
53
+ " // wellspring start 时触发",
54
+ " })",
55
+ " onShutdown(() => {",
56
+ " // wellspring start 关闭时触发",
57
+ " })",
58
+ "",
59
+ " const plugin = {",
60
+ ` id: ${JSON.stringify(pluginName)},`,
61
+ ` name: ${JSON.stringify(pluginName)},`,
62
+ ' version: "0.1.0",',
63
+ ' status: "ok",',
64
+ " deps: [],",
65
+ " commands: [",
66
+ " {",
67
+ ` name: ${JSON.stringify(commandName)},`,
68
+ ` summary: ${JSON.stringify(`${pluginName} 示例命令`)},`,
69
+ " run: (ctx) => {",
70
+ ' ctx.output.info("argv=" + ctx.argv.join(","))',
71
+ " return 0",
72
+ " },",
73
+ " },",
74
+ " ],",
75
+ " } satisfies WellspringPlugin",
76
+ "",
77
+ " return plugin",
78
+ "}",
79
+ "",
80
+ ].join("\n")
81
+ }
82
+
83
+ function readmeTemplate(pluginName: string, commandName: string): string {
84
+ return [
85
+ `# ${pluginName}`,
86
+ "",
87
+ "## 开发",
88
+ "",
89
+ "编辑器 / tsc 解析 `wellspring/api/host/index.ts` 需要先把 Wellspring 链接进来:",
90
+ "",
91
+ "```bash",
92
+ "pnpm link /path/to/Wellspring",
93
+ "pnpm add -D typescript @types/node # 如果还没有",
94
+ "pnpm exec tsc --noEmit",
95
+ "```",
96
+ "",
97
+ "运行时不需要链接:通过 `wellspring plugin link <this-dir>` 加载时,平台会解析宿主 API。",
98
+ "",
99
+ "## 加载",
100
+ "",
101
+ "```bash",
102
+ `wellspring plugin link \"$PWD\"`,
103
+ "wellspring plugin list",
104
+ `wellspring ${commandName}`,
105
+ "```",
106
+ "",
107
+ ].join("\n")
108
+ }
109
+
110
+ export async function scaffoldPlugin(
111
+ options: ScaffoldPluginOptions,
112
+ ): Promise<ScaffoldPluginResult> {
113
+ const name = options.name.trim()
114
+ if (!PLUGIN_NAME.test(name)) {
115
+ throw new Error(`插件名不合法:${name}`)
116
+ }
117
+ const parentDir = resolve(options.parentDir ?? process.cwd())
118
+ const dir = join(parentDir, name)
119
+ const existing = await statIfExists(dir)
120
+ if (existing !== null) {
121
+ if (options.force !== true) {
122
+ throw new Error(`目录已存在:${dir}`)
123
+ }
124
+ await removePath(dir)
125
+ }
126
+ await ensureDir(dir)
127
+
128
+ const commandName = commandNameOf(name)
129
+ await writeJsonAtomic(join(dir, "package.json"), {
130
+ name,
131
+ version: "0.1.0",
132
+ type: "module",
133
+ main: "index.ts",
134
+ wellspring: { apiVersion: 1 },
135
+ })
136
+ await writeTextAtomic(join(dir, "index.ts"), template(name, commandName))
137
+ await writeJsonAtomic(join(dir, "tsconfig.json"), {
138
+ compilerOptions: {
139
+ target: "ES2024",
140
+ module: "NodeNext",
141
+ moduleResolution: "NodeNext",
142
+ strict: true,
143
+ noEmit: true,
144
+ allowImportingTsExtensions: true,
145
+ skipLibCheck: true,
146
+ types: ["node"],
147
+ },
148
+ include: ["index.ts"],
149
+ })
150
+ await writeTextAtomic(join(dir, "README.md"), readmeTemplate(name, commandName))
151
+ return { dir, commandName }
152
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * 插件启用状态:<dataDir>/plugins-state.json。
3
+ * 以插件目录名为 key,禁用后 loader 不再 import 该插件。
4
+ */
5
+
6
+ import { join } from "node:path"
7
+
8
+ import { readJsonIfExists, writeJsonAtomic } from "../platform/file.ts"
9
+ import { getPaths, type ResolvedPaths } from "../platform/path.ts"
10
+
11
+ interface PluginStateFile {
12
+ readonly version?: number
13
+ readonly disabled?: readonly string[]
14
+ }
15
+
16
+ export function pluginStateFile(paths: ResolvedPaths = getPaths()): string {
17
+ return join(paths.dataDir, "plugins-state.json")
18
+ }
19
+
20
+ /** 读取被禁用的插件目录名集合 */
21
+ export async function readPluginState(
22
+ paths: ResolvedPaths = getPaths(),
23
+ ): Promise<ReadonlySet<string>> {
24
+ const value = await readJsonIfExists<PluginStateFile>(pluginStateFile(paths))
25
+ if (value === null || !Array.isArray(value.disabled)) {
26
+ return new Set()
27
+ }
28
+ return new Set(value.disabled.filter((item): item is string => typeof item === "string"))
29
+ }
30
+
31
+ /** 设置插件启用/禁用,返回是否发生变化 */
32
+ export async function setPluginEnabled(
33
+ name: string,
34
+ enabled: boolean,
35
+ paths: ResolvedPaths = getPaths(),
36
+ ): Promise<boolean> {
37
+ const disabled = new Set(await readPluginState(paths))
38
+ const had = disabled.has(name)
39
+ if (enabled) {
40
+ disabled.delete(name)
41
+ } else {
42
+ disabled.add(name)
43
+ }
44
+ if (had === !enabled) {
45
+ return false
46
+ }
47
+ await writeJsonAtomic(pluginStateFile(paths), {
48
+ version: 1,
49
+ disabled: [...disabled].sort(),
50
+ })
51
+ return true
52
+ }
@@ -0,0 +1,156 @@
1
+ /*
2
+ * 最小 ZIP 读取器,只服务本地插件安装。
3
+ * 支持 store/deflate 两种压缩方式,拒绝加密、ZIP64、符号链接与路径穿越。
4
+ */
5
+
6
+ import { inflateRawSync } from "node:zlib"
7
+
8
+ import { ensureDir, writeBytesAtomic } from "../platform/file.ts"
9
+ import { dirname, resolve, sep } from "node:path"
10
+
11
+ const EOCD_SIGNATURE = 0x06054b50
12
+ const CENTRAL_SIGNATURE = 0x02014b50
13
+ const LOCAL_SIGNATURE = 0x04034b50
14
+ const MAX_ENTRIES = 10_000
15
+ const MAX_ENTRY_BYTES = 100 * 1024 * 1024
16
+ const MAX_TOTAL_BYTES = 200 * 1024 * 1024
17
+
18
+ export interface ZipEntry {
19
+ readonly name: string
20
+ readonly data: Buffer
21
+ }
22
+
23
+ function fail(message: string): never {
24
+ throw new Error(`zip 包非法:${message}`)
25
+ }
26
+
27
+ function validateName(name: string): void {
28
+ if (name === "" || name.startsWith("/") || name.includes("\\") || /^[A-Za-z]:/.test(name)) {
29
+ fail(`非法路径:${name}`)
30
+ }
31
+ for (const segment of name.split("/")) {
32
+ if (segment === ".." || segment === ".") {
33
+ fail(`非法路径:${name}`)
34
+ }
35
+ }
36
+ }
37
+
38
+ function findEocd(buffer: Buffer): number {
39
+ const minimum = Math.max(0, buffer.length - 65_557)
40
+ for (let offset = buffer.length - 22; offset >= minimum; offset -= 1) {
41
+ if (buffer.readUInt32LE(offset) === EOCD_SIGNATURE) {
42
+ return offset
43
+ }
44
+ }
45
+ return fail("找不到中央目录")
46
+ }
47
+
48
+ export function readZip(buffer: Buffer): ZipEntry[] {
49
+ if (buffer.length < 22) {
50
+ fail("文件过短")
51
+ }
52
+ const eocd = findEocd(buffer)
53
+ const entries = buffer.readUInt16LE(eocd + 10)
54
+ const centralOffset = buffer.readUInt32LE(eocd + 16)
55
+ if (entries > MAX_ENTRIES) {
56
+ fail("文件条目过多")
57
+ }
58
+ if (centralOffset === 0xffffffff) {
59
+ fail("不支持 ZIP64")
60
+ }
61
+
62
+ const result: ZipEntry[] = []
63
+ let total = 0
64
+ let offset = centralOffset
65
+
66
+ for (let index = 0; index < entries; index += 1) {
67
+ if (offset + 46 > buffer.length || buffer.readUInt32LE(offset) !== CENTRAL_SIGNATURE) {
68
+ fail("中央目录损坏")
69
+ }
70
+ const flags = buffer.readUInt16LE(offset + 8)
71
+ const method = buffer.readUInt16LE(offset + 10)
72
+ const compressedSize = buffer.readUInt32LE(offset + 20)
73
+ const uncompressedSize = buffer.readUInt32LE(offset + 24)
74
+ const nameLength = buffer.readUInt16LE(offset + 28)
75
+ const extraLength = buffer.readUInt16LE(offset + 30)
76
+ const commentLength = buffer.readUInt16LE(offset + 32)
77
+ const externalAttributes = buffer.readUInt32LE(offset + 38)
78
+ const localOffset = buffer.readUInt32LE(offset + 42)
79
+ const name = buffer.subarray(offset + 46, offset + 46 + nameLength).toString("utf8")
80
+ offset += 46 + nameLength + extraLength + commentLength
81
+
82
+ validateName(name)
83
+ if ((flags & 0x1) !== 0) {
84
+ fail(`不支持加密条目:${name}`)
85
+ }
86
+ if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff) {
87
+ fail("不支持 ZIP64 条目")
88
+ }
89
+ if (uncompressedSize > MAX_ENTRY_BYTES) {
90
+ fail(`单文件过大:${name}`)
91
+ }
92
+ total += uncompressedSize
93
+ if (total > MAX_TOTAL_BYTES) {
94
+ fail("解压后总大小超限")
95
+ }
96
+
97
+ if (name.endsWith("/")) {
98
+ result.push({ name, data: Buffer.alloc(0) })
99
+ continue
100
+ }
101
+ if (((externalAttributes >>> 16) & 0o170000) === 0o120000) {
102
+ fail(`不支持符号链接:${name}`)
103
+ }
104
+ if (
105
+ localOffset + 30 > buffer.length ||
106
+ buffer.readUInt32LE(localOffset) !== LOCAL_SIGNATURE
107
+ ) {
108
+ fail(`本地文件头损坏:${name}`)
109
+ }
110
+ const localNameLength = buffer.readUInt16LE(localOffset + 26)
111
+ const localExtraLength = buffer.readUInt16LE(localOffset + 28)
112
+ const dataStart = localOffset + 30 + localNameLength + localExtraLength
113
+ const end = dataStart + compressedSize
114
+ if (end > buffer.length) {
115
+ fail(`数据越界:${name}`)
116
+ }
117
+ const compressed = buffer.subarray(dataStart, end)
118
+
119
+ let data: Buffer
120
+ if (method === 0) {
121
+ data = Buffer.from(compressed)
122
+ } else if (method === 8) {
123
+ try {
124
+ data = inflateRawSync(compressed)
125
+ } catch (error) {
126
+ fail(
127
+ `解压失败:${name}(${error instanceof Error ? error.message : String(error)})`,
128
+ )
129
+ }
130
+ } else {
131
+ fail(`不支持的压缩方式 ${method}:${name}`)
132
+ }
133
+ if (data.length !== uncompressedSize) {
134
+ fail(`解压大小不符:${name}`)
135
+ }
136
+ result.push({ name, data })
137
+ }
138
+ return result
139
+ }
140
+
141
+ /** 解压到目标目录;调用方负责临时目录与清理 */
142
+ export async function extractZip(buffer: Buffer, destination: string): Promise<void> {
143
+ const root = resolve(destination)
144
+ for (const entry of readZip(buffer)) {
145
+ const target = resolve(root, entry.name)
146
+ if (target !== root && !target.startsWith(`${root}${sep}`)) {
147
+ fail(`路径穿越:${entry.name}`)
148
+ }
149
+ if (entry.name.endsWith("/")) {
150
+ await ensureDir(target)
151
+ continue
152
+ }
153
+ await ensureDir(dirname(target))
154
+ await writeBytesAtomic(target, entry.data)
155
+ }
156
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * sandbox 审计日志:<logDir>/sandbox-audit.log,JSONL。
3
+ */
4
+
5
+ import { appendFile } from "node:fs/promises"
6
+ import { dirname, join } from "node:path"
7
+
8
+ import { ensureDir } from "../platform/file.ts"
9
+ import type { ResolvedPaths } from "../platform/path.ts"
10
+
11
+ export interface SandboxAuditEvent {
12
+ readonly type: string
13
+ readonly sandboxId: string
14
+ readonly pluginId?: string
15
+ readonly backend?: string
16
+ readonly decision?: "allowed" | "denied"
17
+ readonly reason?: string
18
+ readonly [key: string]: unknown
19
+ }
20
+
21
+ let queue: Promise<void> = Promise.resolve()
22
+
23
+ export function auditSandbox(paths: ResolvedPaths, event: SandboxAuditEvent): void {
24
+ const file = join(paths.logDir, "sandbox-audit.log")
25
+ queue = queue
26
+ .then(async () => {
27
+ await ensureDir(dirname(file))
28
+ await appendFile(
29
+ file,
30
+ `${JSON.stringify({ time: new Date().toISOString(), ...event })}\n`,
31
+ )
32
+ })
33
+ .catch(() => undefined)
34
+ }
35
+
36
+ export async function flushSandboxAudit(): Promise<void> {
37
+ await queue
38
+ }
@@ -0,0 +1,251 @@
1
+ /*
2
+ * Linux bwrap 后端。
3
+ * 由 bwrap 提供 mount / PID / network namespace;Node permission model 作为第二层。
4
+ */
5
+
6
+ import { spawnSync } from "node:child_process"
7
+ import { existsSync } from "node:fs"
8
+ import { dirname, join, relative, resolve, sep } from "node:path"
9
+
10
+ import type {
11
+ AgentLaunchInput,
12
+ LaunchSpec,
13
+ PluginLaunchInput,
14
+ SandboxBackend,
15
+ SandboxBackendStatus,
16
+ } from "../types.ts"
17
+
18
+ const SYSTEM_DIRS = ["/usr", "/bin", "/sbin", "/lib", "/lib64", "/lib32"]
19
+
20
+ /** 沙箱内挂载 Node 运行时的目录 */
21
+ export const NODE_ROOT = "/opt/wellspring"
22
+
23
+ function existingSystemDirs(): string[] {
24
+ return SYSTEM_DIRS.filter((dir) => existsSync(dir))
25
+ }
26
+
27
+ export interface NodeRuntimeLayout {
28
+ /** 可整体挂载的 Node 前缀(含 bin/npm 与 lib/node_modules);只有裸二进制时为 null */
29
+ readonly prefix: string | null
30
+ /** 沙箱内的 node 路径 */
31
+ readonly nodePath: string
32
+ /** PATH 里的目录 */
33
+ readonly pathEntry: string
34
+ }
35
+
36
+ /**
37
+ * 找到当前 node 的安装前缀。
38
+ *
39
+ * 只 bind 单个 node 二进制会丢掉同目录的 npm/npx(npm 在 lib/node_modules/npm 里),
40
+ * 所以优先把整个前缀挂到 /opt/wellspring;找不到 npm 的发行版才退回裸二进制。
41
+ */
42
+ export function resolveNodeRuntime(
43
+ execPath: string = process.execPath,
44
+ exists: (path: string) => boolean = existsSync,
45
+ ): NodeRuntimeLayout {
46
+ const prefix = resolve(dirname(execPath), "..")
47
+ const hasNpm =
48
+ exists(join(prefix, "bin", "npm")) ||
49
+ exists(join(prefix, "lib", "node_modules", "npm")) ||
50
+ exists(join(prefix, "share", "nodejs", "npm"))
51
+ if (hasNpm) {
52
+ return {
53
+ prefix,
54
+ nodePath: `${NODE_ROOT}/bin/node`,
55
+ pathEntry: `${NODE_ROOT}/bin`,
56
+ }
57
+ }
58
+ return {
59
+ prefix: null,
60
+ nodePath: `${NODE_ROOT}/node`,
61
+ pathEntry: NODE_ROOT,
62
+ }
63
+ }
64
+
65
+ function nodeRuntimeMount(runtime: NodeRuntimeLayout, execPath: string): string[] {
66
+ if (runtime.prefix !== null) {
67
+ return ["--dir", NODE_ROOT, "--ro-bind", runtime.prefix, NODE_ROOT]
68
+ }
69
+ return ["--dir", NODE_ROOT, "--ro-bind", execPath, `${NODE_ROOT}/node`]
70
+ }
71
+
72
+ function insideWorkspace(workspaceDir: string, cwd: string): string {
73
+ const root = resolve(workspaceDir)
74
+ const target = resolve(cwd)
75
+ if (target !== root && !target.startsWith(`${root}${sep}`)) {
76
+ throw new Error(`cwd 不在 workspace 内:${cwd}`)
77
+ }
78
+ const rel = relative(root, target)
79
+ return rel === "" ? "/workspace" : `/workspace/${rel.split(sep).join("/")}`
80
+ }
81
+
82
+ /** 纯函数,方便单测 */
83
+ export function buildBwrapLaunch(input: AgentLaunchInput): LaunchSpec {
84
+ const args: string[] = [
85
+ "--unshare-all",
86
+ "--die-with-parent",
87
+ "--new-session",
88
+ "--clearenv",
89
+ "--cap-drop",
90
+ "ALL",
91
+ "--proc",
92
+ "/proc",
93
+ "--dev",
94
+ "/dev",
95
+ "--tmpfs",
96
+ "/tmp",
97
+ ]
98
+
99
+ if (input.network === true) {
100
+ args.push("--share-net")
101
+ }
102
+ for (const dir of existingSystemDirs()) {
103
+ args.push("--ro-bind-try", dir, dir)
104
+ }
105
+ // DNS、CA 证书与用户解析
106
+ args.push("--ro-bind-try", "/etc", "/etc")
107
+ // systemd-resolved 的 resolv.conf 指向 /run;先建目标目录再尝试绑定
108
+ if (existsSync("/run/systemd/resolve")) {
109
+ args.push("--dir", "/run", "--dir", "/run/systemd", "--dir", "/run/systemd/resolve")
110
+ args.push("--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve")
111
+ }
112
+ const nodeRuntime = resolveNodeRuntime()
113
+ args.push(...nodeRuntimeMount(nodeRuntime, process.execPath))
114
+ const workspaceBind = input.access === "workspace-write" ? "--bind" : "--ro-bind"
115
+ args.push(workspaceBind, resolve(input.workspaceDir), "/workspace")
116
+ args.push("--bind", resolve(input.scratchDir), "/scratch")
117
+ args.push("--chdir", insideWorkspace(input.workspaceDir, input.cwd))
118
+
119
+ const env: Record<string, string> = { ...input.env }
120
+ env["PATH"] = `${nodeRuntime.pathEntry}:${env["PATH"] ?? "/usr/bin:/bin"}`
121
+ /*
122
+ * 不再限制 RLIMIT_AS(虚拟地址空间会直接打死 V8 / JVM / Go)。
123
+ * 物理内存由 SandboxManager 挂 cgroup v2 memory.max;这里只留 V8 堆上限,
124
+ * 且对所有命令生效,npx / npm 派生的 node 子进程也能继承。
125
+ */
126
+ env["NODE_OPTIONS"] =
127
+ `${env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${input.limits.memoryMB}`.trim()
128
+ for (const [key, value] of Object.entries(env)) {
129
+ args.push("--setenv", key, value)
130
+ }
131
+
132
+ args.push("--")
133
+
134
+ // prlimit 自身在 /usr/bin;只留文件描述符与进程数上限,超时交给 supervisor 的 wall timeout
135
+ const prlimitArgs = ["--nofile=256", `--nproc=${input.limits.pids}`]
136
+ args.push("/usr/bin/prlimit", ...prlimitArgs, "--", input.command, ...input.argv)
137
+
138
+ return {
139
+ command: "bwrap",
140
+ args,
141
+ cwd: resolve(input.workspaceDir),
142
+ env: { ...input.env },
143
+ }
144
+ }
145
+
146
+ /** 纯函数:插件 profile 的 bwrap launch spec */
147
+ export function buildBwrapPluginLaunch(input: PluginLaunchInput): LaunchSpec {
148
+ const args: string[] = [
149
+ "--unshare-all",
150
+ "--die-with-parent",
151
+ "--new-session",
152
+ "--clearenv",
153
+ "--cap-drop",
154
+ "ALL",
155
+ "--proc",
156
+ "/proc",
157
+ "--dev",
158
+ "/dev",
159
+ "--tmpfs",
160
+ "/tmp",
161
+ ]
162
+ for (const dir of existingSystemDirs()) {
163
+ args.push("--ro-bind-try", dir, dir)
164
+ }
165
+ const nodeRuntime = resolveNodeRuntime()
166
+ args.push(...nodeRuntimeMount(nodeRuntime, process.execPath))
167
+ args.push("--ro-bind", resolve(input.pluginDir), "/plugin")
168
+ args.push("--ro-bind", resolve(input.hostSdkDir), "/host")
169
+ args.push("--chdir", "/plugin")
170
+
171
+ const env: Record<string, string> = { ...input.env }
172
+ env["PATH"] = `${nodeRuntime.pathEntry}:${env["PATH"] ?? "/usr/bin:/bin"}`
173
+ env["NODE_OPTIONS"] =
174
+ `${env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${input.limits.memoryMB}`.trim()
175
+ for (const [key, value] of Object.entries(env)) {
176
+ args.push("--setenv", key, value)
177
+ }
178
+ args.push("--")
179
+ args.push(
180
+ "/usr/bin/prlimit",
181
+ "--nofile=256",
182
+ `--nproc=${input.limits.pids}`,
183
+ "--",
184
+ nodeRuntime.nodePath,
185
+ "--permission",
186
+ "--allow-fs-read=/plugin",
187
+ "--allow-fs-read=/host",
188
+ "/host/src/sandbox/plugin-host.ts",
189
+ )
190
+
191
+ return {
192
+ command: "bwrap",
193
+ args,
194
+ cwd: resolve(input.pluginDir),
195
+ env: { ...input.env },
196
+ }
197
+ }
198
+
199
+ export class BwrapBackend implements SandboxBackend {
200
+ readonly name = "bwrap"
201
+
202
+ async probe(): Promise<SandboxBackendStatus> {
203
+ if (!existsSync("/usr/bin/bwrap")) {
204
+ return {
205
+ available: false,
206
+ backend: this.name,
207
+ reason: "找不到 bwrap",
208
+ version: null,
209
+ }
210
+ }
211
+ const result = spawnSync(
212
+ "bwrap",
213
+ [
214
+ "--unshare-all",
215
+ "--die-with-parent",
216
+ "--ro-bind",
217
+ "/",
218
+ "/",
219
+ "--dev",
220
+ "/dev",
221
+ "--proc",
222
+ "/proc",
223
+ "--",
224
+ "/bin/true",
225
+ ],
226
+ { timeout: 3_000, encoding: "utf8" },
227
+ )
228
+ if (result.status !== 0) {
229
+ return {
230
+ available: false,
231
+ backend: this.name,
232
+ reason: (result.stderr ?? "").trim() || "bwrap 无法创建 namespace",
233
+ version: null,
234
+ }
235
+ }
236
+ return {
237
+ available: true,
238
+ backend: this.name,
239
+ reason: null,
240
+ version: null,
241
+ }
242
+ }
243
+
244
+ buildAgentLaunch(input: AgentLaunchInput): LaunchSpec {
245
+ return buildBwrapLaunch(input)
246
+ }
247
+
248
+ buildPluginLaunch(input: PluginLaunchInput): LaunchSpec {
249
+ return buildBwrapPluginLaunch(input)
250
+ }
251
+ }
@@ -0,0 +1,43 @@
1
+ /*
2
+ * 无隔离 fallback:仅用于 best-effort / 测试。
3
+ * 不提供 fs、网络、进程隔离;严格模式禁止使用。
4
+ */
5
+
6
+ import type {
7
+ AgentLaunchInput,
8
+ LaunchSpec,
9
+ PluginLaunchInput,
10
+ SandboxBackend,
11
+ SandboxBackendStatus,
12
+ } from "../types.ts"
13
+
14
+ export class DirectBackend implements SandboxBackend {
15
+ readonly name = "direct"
16
+
17
+ async probe(): Promise<SandboxBackendStatus> {
18
+ return {
19
+ available: true,
20
+ backend: this.name,
21
+ reason: null,
22
+ version: null,
23
+ }
24
+ }
25
+
26
+ buildAgentLaunch(input: AgentLaunchInput): LaunchSpec {
27
+ return {
28
+ command: input.command,
29
+ args: [...input.argv],
30
+ cwd: input.cwd,
31
+ env: input.env,
32
+ }
33
+ }
34
+
35
+ buildPluginLaunch(input: PluginLaunchInput): LaunchSpec {
36
+ return {
37
+ command: process.execPath,
38
+ args: [input.runnerPath],
39
+ cwd: input.pluginDir,
40
+ env: input.env,
41
+ }
42
+ }
43
+ }