@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,206 @@
1
+ /*
2
+ * DeepSeek 网页搜索与抓取 API
3
+ * Search POST /v1/tools/search
4
+ * Fetch POST /v1/tools/fetch
5
+ */
6
+
7
+ import { Result } from "../api/result/result.ts"
8
+ import { readEnv } from "../platform/os.ts"
9
+
10
+ export const WEB_CODES = {
11
+ missingApiKey: "web.missing-api-key",
12
+ invalidUrl: "web.invalid-url",
13
+ requestFailed: "web.request-failed",
14
+ searchFailed: "web.search-failed",
15
+ fetchFailed: "web.fetch-failed",
16
+ unsupported: "web.unsupported-provider",
17
+ } as const
18
+
19
+ export interface WebSearchResult {
20
+ readonly title: string
21
+ readonly url: string
22
+ readonly snippet: string
23
+ readonly summary: string
24
+ readonly content: string
25
+ readonly siteName: string | null
26
+ readonly date: string | null
27
+ readonly authority: number
28
+ }
29
+
30
+ export interface WebFetchResult {
31
+ readonly title: string
32
+ readonly content: string
33
+ }
34
+
35
+ export interface WebClient {
36
+ search(query: string, count?: number): Promise<Result<readonly WebSearchResult[]>>
37
+ fetch(url: string): Promise<Result<WebFetchResult>>
38
+ }
39
+
40
+ export interface WebClientOptions {
41
+ readonly baseUrl?: string | null
42
+ readonly apiKey?: string
43
+ readonly fetch?: typeof fetch
44
+ readonly timeoutMs?: number
45
+ }
46
+
47
+ function isObject(value: unknown): value is Record<string, unknown> {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value)
49
+ }
50
+
51
+ function endpoint(baseUrl: string | null | undefined, path: string): string | null {
52
+ const base = (baseUrl ?? "https://api.deepseek.com").trim().replace(/\/+$/, "")
53
+ if (base === "") {
54
+ return null
55
+ }
56
+ const normalized = base.endsWith("/v1") ? base.slice(0, -3) : base
57
+ try {
58
+ return new URL(`${normalized}/v1/tools/${path}`).toString()
59
+ } catch {
60
+ return null
61
+ }
62
+ }
63
+
64
+ const RETRY_STATUSES = new Set([408, 429, 500, 502, 503, 504])
65
+
66
+ function sleep(ms: number): Promise<void> {
67
+ return new Promise((resolve) => setTimeout(resolve, ms))
68
+ }
69
+
70
+ async function postJson(
71
+ url: string,
72
+ apiKey: string,
73
+ body: unknown,
74
+ fetchImpl: typeof fetch,
75
+ timeoutMs: number,
76
+ ): Promise<Result<unknown>> {
77
+ for (let attempt = 0; attempt < 2; attempt += 1) {
78
+ try {
79
+ const response = await fetchImpl(url, {
80
+ method: "POST",
81
+ headers: {
82
+ Authorization: `Bearer ${apiKey}`,
83
+ "Content-Type": "application/json",
84
+ },
85
+ body: JSON.stringify(body),
86
+ signal: AbortSignal.timeout(timeoutMs),
87
+ })
88
+ if (!response.ok) {
89
+ const code = `web.http-${response.status}`
90
+ if (RETRY_STATUSES.has(response.status) && attempt === 0) {
91
+ await sleep(300)
92
+ continue
93
+ }
94
+ return Result.error<unknown>(code)
95
+ }
96
+ return Result.successWithData<unknown>(await response.json())
97
+ } catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error)
99
+ const code = `${WEB_CODES.requestFailed}:${message}`
100
+ if (attempt === 0) {
101
+ await sleep(300)
102
+ continue
103
+ }
104
+ return Result.error<unknown>(code)
105
+ }
106
+ }
107
+ return Result.error<unknown>(WEB_CODES.requestFailed)
108
+ }
109
+
110
+ // Markdown 转义清理:DeepSeek Fetch 会把所有 ASCII 标点转义成 `\.` / `\(` 之类
111
+ const MARKDOWN_ESCAPES = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g
112
+
113
+ function unescapeMarkdown(text: string): string {
114
+ return text.replace(MARKDOWN_ESCAPES, "$1")
115
+ }
116
+
117
+ function parseSearchResult(value: unknown): WebSearchResult | null {
118
+ if (!isObject(value) || typeof value["url"] !== "string") {
119
+ return null
120
+ }
121
+ return {
122
+ title: unescapeMarkdown(typeof value["title"] === "string" ? value["title"] : ""),
123
+ url: value["url"],
124
+ snippet: unescapeMarkdown(typeof value["snippet"] === "string" ? value["snippet"] : ""),
125
+ summary: unescapeMarkdown(typeof value["summary"] === "string" ? value["summary"] : ""),
126
+ content: unescapeMarkdown(typeof value["content"] === "string" ? value["content"] : ""),
127
+ siteName: typeof value["site_name"] === "string" ? value["site_name"] : null,
128
+ date: typeof value["date"] === "string" ? value["date"] : null,
129
+ authority: typeof value["authority"] === "number" ? value["authority"] : -1,
130
+ }
131
+ }
132
+
133
+ // HTTP/HTTPS 校验
134
+ function validUrl(value: string): boolean {
135
+ try {
136
+ const url = new URL(value)
137
+ return (url.protocol === "http:" || url.protocol === "https:") && url.host !== ""
138
+ } catch {
139
+ return false
140
+ }
141
+ }
142
+
143
+ export function createDeepSeekWebClient(options: WebClientOptions = {}): WebClient {
144
+ const apiKey = options.apiKey ?? readEnv("OPENAI_API_KEY") ?? ""
145
+ const fetchImpl = options.fetch ?? fetch
146
+ const timeoutMs = options.timeoutMs ?? 60_000
147
+
148
+ return {
149
+ async search(query, count = 5) {
150
+ if (apiKey === "") {
151
+ return Result.error<readonly WebSearchResult[]>(WEB_CODES.missingApiKey)
152
+ }
153
+ const url = endpoint(options.baseUrl, "search")
154
+ if (url === null) {
155
+ return Result.error<readonly WebSearchResult[]>(WEB_CODES.unsupported)
156
+ }
157
+ const response = await postJson(
158
+ url,
159
+ apiKey,
160
+ { query, count: Math.max(1, Math.min(50, count)), need_content: false },
161
+ fetchImpl,
162
+ timeoutMs,
163
+ )
164
+ if (!response.isSuccess()) {
165
+ return Result.error<readonly WebSearchResult[]>(
166
+ response.getCode() ?? WEB_CODES.searchFailed,
167
+ )
168
+ }
169
+ const data = response.getData()
170
+ const results = isObject(data) ? data["results"] : undefined
171
+ if (!Array.isArray(results)) {
172
+ return Result.error<readonly WebSearchResult[]>(WEB_CODES.searchFailed)
173
+ }
174
+ return Result.successWithData<readonly WebSearchResult[]>(
175
+ results
176
+ .map(parseSearchResult)
177
+ .filter((item): item is WebSearchResult => item !== null),
178
+ )
179
+ },
180
+
181
+ async fetch(targetUrl) {
182
+ if (apiKey === "") {
183
+ return Result.error<WebFetchResult>(WEB_CODES.missingApiKey)
184
+ }
185
+ if (!validUrl(targetUrl)) {
186
+ return Result.error<WebFetchResult>(WEB_CODES.invalidUrl)
187
+ }
188
+ const url = endpoint(options.baseUrl, "fetch")
189
+ if (url === null) {
190
+ return Result.error<WebFetchResult>(WEB_CODES.unsupported)
191
+ }
192
+ const response = await postJson(url, apiKey, { url: targetUrl }, fetchImpl, timeoutMs)
193
+ if (!response.isSuccess()) {
194
+ return Result.error<WebFetchResult>(response.getCode() ?? WEB_CODES.fetchFailed)
195
+ }
196
+ const data = response.getData()
197
+ if (!isObject(data) || typeof data["content"] !== "string") {
198
+ return Result.error<WebFetchResult>(WEB_CODES.fetchFailed)
199
+ }
200
+ return Result.successWithData<WebFetchResult>({
201
+ title: unescapeMarkdown(typeof data["title"] === "string" ? data["title"] : ""),
202
+ content: unescapeMarkdown(data["content"]),
203
+ })
204
+ },
205
+ }
206
+ }
@@ -0,0 +1,107 @@
1
+ /*
2
+ * 剪贴板读写。
3
+ * 各平台通过系统命令实现;命令缺失或不可用时按候选顺序回退。
4
+ * Windows 的文本按 UTF-8 字节经 stdin/stdout 收发,避开控制台代码页。
5
+ */
6
+
7
+ import { describeError, powershellArgs, runCommand, type RunCommandResult } from "./exec.ts"
8
+ import { isTermux, platform, type PlatformName } from "./os.ts"
9
+
10
+ export interface ClipboardCommand {
11
+ readonly command: string
12
+ readonly args: readonly string[]
13
+ }
14
+
15
+ export interface ClipboardCommands {
16
+ readonly write: readonly ClipboardCommand[]
17
+ readonly read: readonly ClipboardCommand[]
18
+ }
19
+
20
+ const POWERSHELL_WRITE = powershellArgs(
21
+ [
22
+ "$input = [Console]::OpenStandardInput()",
23
+ "$buffer = New-Object byte[] 8192",
24
+ "$memory = New-Object System.IO.MemoryStream",
25
+ "while (($read = $input.Read($buffer, 0, $buffer.Length)) -gt 0) { $memory.Write($buffer, 0, $read) }",
26
+ "Set-Clipboard -Value ([System.Text.Encoding]::UTF8.GetString($memory.ToArray()))",
27
+ ].join("\n"),
28
+ )
29
+
30
+ const POWERSHELL_READ = powershellArgs(
31
+ [
32
+ "$text = Get-Clipboard -Raw",
33
+ "if ($null -eq $text) { $text = '' }",
34
+ "$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)",
35
+ "$output = [Console]::OpenStandardOutput()",
36
+ "$output.Write($bytes, 0, $bytes.Length)",
37
+ "$output.Flush()",
38
+ ].join("\n"),
39
+ )
40
+
41
+ /** 生成候选命令,按优先级排列。单独导出以便在任意平台测试 */
42
+ export function buildClipboardCommands(
43
+ targetPlatform: PlatformName = platform,
44
+ inTermux: boolean = isTermux,
45
+ ): ClipboardCommands {
46
+ switch (targetPlatform) {
47
+ case "windows":
48
+ return {
49
+ write: [{ command: "powershell", args: POWERSHELL_WRITE }],
50
+ read: [{ command: "powershell", args: POWERSHELL_READ }],
51
+ }
52
+ case "macos":
53
+ return {
54
+ write: [{ command: "pbcopy", args: [] }],
55
+ read: [{ command: "pbpaste", args: [] }],
56
+ }
57
+ case "android":
58
+ // Android 应用沙箱内只有 Termux:API 提供的命令
59
+ if (!inTermux) {
60
+ return { write: [], read: [] }
61
+ }
62
+ return {
63
+ write: [{ command: "termux-clipboard-set", args: [] }],
64
+ read: [{ command: "termux-clipboard-get", args: [] }],
65
+ }
66
+ default:
67
+ return {
68
+ write: [
69
+ { command: "wl-copy", args: [] },
70
+ { command: "xclip", args: ["-selection", "clipboard"] },
71
+ { command: "xsel", args: ["--clipboard", "--input"] },
72
+ ],
73
+ read: [
74
+ { command: "wl-paste", args: [] },
75
+ { command: "xclip", args: ["-selection", "clipboard", "-o"] },
76
+ { command: "xsel", args: ["--clipboard", "--output"] },
77
+ ],
78
+ }
79
+ }
80
+ }
81
+
82
+ async function runCandidates(
83
+ commands: readonly ClipboardCommand[],
84
+ input?: string,
85
+ ): Promise<RunCommandResult> {
86
+ const failures: string[] = []
87
+ for (const candidate of commands) {
88
+ try {
89
+ return await runCommand(candidate.command, candidate.args, { input })
90
+ } catch (error) {
91
+ failures.push(`${candidate.command}: ${describeError(error)}`)
92
+ }
93
+ }
94
+ const detail = failures.length === 0 ? "当前平台没有可用命令" : failures.join(";")
95
+ throw new Error(`剪贴板不可用:${detail}`)
96
+ }
97
+
98
+ /** 写入剪贴板 */
99
+ export async function writeClipboard(text: string): Promise<void> {
100
+ await runCandidates(buildClipboardCommands().write, text)
101
+ }
102
+
103
+ /** 读取剪贴板。返回命令的原始输出,不做换行裁剪 */
104
+ export async function readClipboard(): Promise<string> {
105
+ const result = await runCandidates(buildClipboardCommands().read)
106
+ return result.stdout
107
+ }
@@ -0,0 +1,128 @@
1
+ /*
2
+ * 命令执行。
3
+ * 命令缺失统一抛 CommandNotFoundError,供候选命令回退;stderr 有内容但退出码为 0 不算失败。
4
+ */
5
+
6
+ import { spawn } from "node:child_process"
7
+
8
+ const DEFAULT_TIMEOUT_MS = 15_000
9
+
10
+ /** 命令不存在(ENOENT) */
11
+ export class CommandNotFoundError extends Error {
12
+ readonly command: string
13
+
14
+ constructor(command: string) {
15
+ super(`命令不存在:${command}`)
16
+ this.name = "CommandNotFoundError"
17
+ this.command = command
18
+ }
19
+ }
20
+
21
+ /** 命令存在但退出码非 0 */
22
+ export class CommandFailedError extends Error {
23
+ readonly command: string
24
+ readonly exitCode: number | null
25
+ readonly stderr: string
26
+
27
+ constructor(command: string, exitCode: number | null, stderr: string) {
28
+ const detail = stderr.trim()
29
+ super(
30
+ `命令执行失败:${command}(退出码 ${exitCode ?? "signal"})${detail === "" ? "" : `:${detail}`}`,
31
+ )
32
+ this.name = "CommandFailedError"
33
+ this.command = command
34
+ this.exitCode = exitCode
35
+ this.stderr = stderr
36
+ }
37
+ }
38
+
39
+ export interface RunCommandOptions {
40
+ /** 写入 stdin 的文本 */
41
+ readonly input?: string
42
+ readonly env?: NodeJS.ProcessEnv
43
+ readonly timeoutMs?: number
44
+ }
45
+
46
+ export interface RunCommandResult {
47
+ readonly stdout: string
48
+ readonly stderr: string
49
+ }
50
+
51
+ /** 运行命令并收集输出 */
52
+ export function runCommand(
53
+ command: string,
54
+ args: readonly string[] = [],
55
+ options: RunCommandOptions = {},
56
+ ): Promise<RunCommandResult> {
57
+ return new Promise((resolve, reject) => {
58
+ let settled = false
59
+ let timer: NodeJS.Timeout | undefined
60
+
61
+ const finish = (action: () => void): void => {
62
+ if (settled) {
63
+ return
64
+ }
65
+ settled = true
66
+ if (timer !== undefined) {
67
+ clearTimeout(timer)
68
+ }
69
+ action()
70
+ }
71
+
72
+ const child = spawn(command, [...args], {
73
+ env: options.env,
74
+ windowsHide: true,
75
+ })
76
+
77
+ let stdout = ""
78
+ let stderr = ""
79
+ child.stdout.setEncoding("utf8")
80
+ child.stderr.setEncoding("utf8")
81
+ child.stdout.on("data", (chunk: string) => {
82
+ stdout += chunk
83
+ })
84
+ child.stderr.on("data", (chunk: string) => {
85
+ stderr += chunk
86
+ })
87
+ // 命令未读取 stdin 就退出时写入会触发 EPIPE
88
+ child.stdin.on("error", () => undefined)
89
+
90
+ timer = setTimeout(() => {
91
+ child.kill("SIGKILL")
92
+ finish(() => reject(new Error(`命令执行超时:${command}`)))
93
+ }, options.timeoutMs ?? DEFAULT_TIMEOUT_MS)
94
+
95
+ child.once("error", (error) => {
96
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
97
+ finish(() => reject(new CommandNotFoundError(command)))
98
+ return
99
+ }
100
+ finish(() => reject(error))
101
+ })
102
+
103
+ child.once("close", (code) => {
104
+ if (code === 0) {
105
+ finish(() => resolve({ stdout, stderr }))
106
+ return
107
+ }
108
+ finish(() => reject(new CommandFailedError(command, code, stderr)))
109
+ })
110
+
111
+ child.stdin.end(options.input)
112
+ })
113
+ }
114
+
115
+ /** PowerShell 脚本编码为 -EncodedCommand 参数,绕开 cmd 的引号与编码问题 */
116
+ export function powershellArgs(script: string): string[] {
117
+ const encoded = Buffer.from(script, "utf16le").toString("base64")
118
+ return ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded]
119
+ }
120
+
121
+ /** 合并 message 与 cause 链,用于候选命令全失败时的报错 */
122
+ export function describeError(error: unknown): string {
123
+ if (!(error instanceof Error)) {
124
+ return String(error)
125
+ }
126
+ const cause = error.cause
127
+ return cause === undefined ? error.message : `${error.message}(${describeError(cause)})`
128
+ }
@@ -0,0 +1,235 @@
1
+ /*
2
+ * 文件落盘。
3
+ * 流程:临时文件 -> fsync -> rename -> chmod -> fsync 目录。
4
+ * Windows 不执行 chmod 与目录 fsync;rename 遇文件占用时短暂重试。
5
+ */
6
+
7
+ import { randomUUID } from "node:crypto"
8
+ import { closeSync, mkdirSync, openSync, type Stats } from "node:fs"
9
+ import { chmod, mkdir, open, readFile, rename, rm, stat, unlink } from "node:fs/promises"
10
+ import { basename, dirname, join } from "node:path"
11
+ import { setTimeout as sleep } from "node:timers/promises"
12
+
13
+ import { isWindows } from "./os.ts"
14
+ import type { PathRoots } from "./path.ts"
15
+
16
+ const FILE_MODE = 0o600
17
+ const DIR_MODE = 0o700
18
+
19
+ /** 是否为文件不存在(ENOENT) */
20
+ export function isNotFound(error: unknown): boolean {
21
+ return (
22
+ typeof error === "object" &&
23
+ error !== null &&
24
+ (error as { code?: unknown }).code === "ENOENT"
25
+ )
26
+ }
27
+
28
+ /** 创建目录,默认权限 0700 */
29
+ export async function ensureDir(dir: string, mode = DIR_MODE): Promise<void> {
30
+ await mkdir(dir, { recursive: true, mode })
31
+ }
32
+
33
+ /** 同步验证文件可写:创建父目录并打开目标,失败时抛出。用于需要提前确认的调用方 */
34
+ export function assertWritableFile(file: string, mode = FILE_MODE): void {
35
+ mkdirSync(dirname(file), { recursive: true, mode: DIR_MODE })
36
+ const fd = openSync(file, "a", mode)
37
+ closeSync(fd)
38
+ }
39
+
40
+ /** 读取文本文件,不存在时返回 null */
41
+ export async function readTextIfExists(file: string): Promise<string | null> {
42
+ try {
43
+ return await readFile(file, "utf8")
44
+ } catch (error) {
45
+ if (isNotFound(error)) {
46
+ return null
47
+ }
48
+ throw error
49
+ }
50
+ }
51
+
52
+ /** 读取二进制文件,不存在时返回 null */
53
+ export async function readBytesIfExists(file: string): Promise<Buffer | null> {
54
+ try {
55
+ return await readFile(file)
56
+ } catch (error) {
57
+ if (isNotFound(error)) {
58
+ return null
59
+ }
60
+ throw error
61
+ }
62
+ }
63
+
64
+ /** stat,文件不存在时返回 null */
65
+ export async function statIfExists(file: string): Promise<Stats | null> {
66
+ try {
67
+ return await stat(file)
68
+ } catch (error) {
69
+ if (isNotFound(error)) {
70
+ return null
71
+ }
72
+ throw error
73
+ }
74
+ }
75
+
76
+ /** 删除文件或目录,不存在时忽略 */
77
+ export async function removePath(file: string): Promise<void> {
78
+ await rm(file, { recursive: true, force: true })
79
+ }
80
+
81
+ /** 读取 JSON 文件,不存在时返回 null。解析失败向上抛出 */
82
+ export async function readJsonIfExists<T>(file: string): Promise<T | null> {
83
+ const text = await readTextIfExists(file)
84
+ return text === null ? null : (JSON.parse(text) as T)
85
+ }
86
+
87
+ export interface WriteFileOptions {
88
+ /** 文件权限,默认 0600 */
89
+ readonly mode?: number
90
+ }
91
+
92
+ /**
93
+ * 原子写。临时文件必须与目标文件同目录,跨分区 rename 不再具有原子性。
94
+ */
95
+ export async function writeTextAtomic(
96
+ file: string,
97
+ text: string,
98
+ options: WriteFileOptions = {},
99
+ ): Promise<void> {
100
+ await writeBytesAtomic(file, Buffer.from(text, "utf8"), options)
101
+ }
102
+
103
+ /** 原子写二进制(会话的 zstd 段用) */
104
+ export async function writeBytesAtomic(
105
+ file: string,
106
+ data: Uint8Array,
107
+ options: WriteFileOptions = {},
108
+ ): Promise<void> {
109
+ const dir = dirname(file)
110
+ const mode = options.mode ?? FILE_MODE
111
+ await ensureDir(dir)
112
+
113
+ const tmp = join(dir, `.${basename(file)}.tmp-${process.pid}-${randomUUID().slice(0, 8)}`)
114
+ const handle = await open(tmp, "w", mode)
115
+ try {
116
+ await handle.writeFile(data)
117
+ // 内容先落盘
118
+ await handle.sync()
119
+ } catch (error) {
120
+ await handle.close().catch(() => undefined)
121
+ await unlink(tmp).catch(() => undefined)
122
+ throw error
123
+ }
124
+ await handle.close()
125
+
126
+ try {
127
+ await renameWithRetry(tmp, file)
128
+ } catch (error) {
129
+ await unlink(tmp).catch(() => undefined)
130
+ throw error
131
+ }
132
+
133
+ if (!isWindows) {
134
+ // 部分文件系统 rename 后权限跟随临时文件,这里显式重设
135
+ await chmodQuiet(file, mode)
136
+ }
137
+ await syncDir(dir)
138
+ }
139
+
140
+ /** 原子写 JSON,固定 4 空格缩进并保留末尾换行 */
141
+ export async function writeJsonAtomic(
142
+ file: string,
143
+ value: unknown,
144
+ options: WriteFileOptions & { readonly indent?: number } = {},
145
+ ): Promise<void> {
146
+ const text = `${JSON.stringify(value, null, options.indent ?? 4)}\n`
147
+ await writeTextAtomic(file, text, options)
148
+ }
149
+
150
+ /** 将损坏文件移到一边,返回移动后的位置 */
151
+ export async function moveAside(file: string, suffix: string): Promise<string> {
152
+ const target = `${file}.${suffix}`
153
+ await rename(file, target)
154
+ return target
155
+ }
156
+
157
+ /** 备份与损坏文件用的时间戳,形如 20260919-013045 */
158
+ export function timeStamp(date = new Date()): string {
159
+ const pad = (n: number): string => String(n).padStart(2, "0")
160
+ return (
161
+ `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}` +
162
+ `-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`
163
+ )
164
+ }
165
+
166
+ /** 创建四个根目录。POSIX 下将已存在目录权限收紧至 0700 */
167
+ export async function ensureDirs(roots: PathRoots, mode = DIR_MODE): Promise<void> {
168
+ for (const dir of [roots.configDir, roots.dataDir, roots.cacheDir, roots.logDir]) {
169
+ await ensureDir(dir, mode)
170
+ if (isWindows) {
171
+ continue
172
+ }
173
+ const info = await stat(dir)
174
+ if ((info.mode & 0o777) !== mode) {
175
+ await chmodQuiet(dir, mode)
176
+ }
177
+ }
178
+ }
179
+
180
+ /** Android 外部存储(FAT/exFAT)与部分挂载点不支持 chmod,权限收紧失败时继续 */
181
+ const CHMOD_IGNORED_CODES = new Set(["EPERM", "EACCES", "EINVAL", "ENOTSUP", "ENOSYS"])
182
+
183
+ const RENAME_RETRY_CODES = new Set(["EPERM", "EACCES", "EBUSY"])
184
+ const RENAME_ATTEMPTS = 3
185
+
186
+ function isRetryableRenameError(error: unknown): boolean {
187
+ if (typeof error !== "object" || error === null) {
188
+ return false
189
+ }
190
+ const code = (error as { code?: unknown }).code
191
+ return typeof code === "string" && RENAME_RETRY_CODES.has(code)
192
+ }
193
+
194
+ /** Windows 上杀毒软件与索引服务会短暂占用目标文件,rename 失败时重试 */
195
+ async function renameWithRetry(from: string, to: string): Promise<void> {
196
+ const attempts = isWindows ? RENAME_ATTEMPTS : 1
197
+ for (let attempt = 1; ; attempt++) {
198
+ try {
199
+ await rename(from, to)
200
+ return
201
+ } catch (error) {
202
+ if (attempt >= attempts || !isRetryableRenameError(error)) {
203
+ throw error
204
+ }
205
+ await sleep(40 * attempt)
206
+ }
207
+ }
208
+ }
209
+
210
+ async function chmodQuiet(file: string, mode: number): Promise<void> {
211
+ try {
212
+ await chmod(file, mode)
213
+ } catch (error) {
214
+ const code = (error as { code?: unknown }).code
215
+ if (typeof code !== "string" || !CHMOD_IGNORED_CODES.has(code)) {
216
+ throw error
217
+ }
218
+ }
219
+ }
220
+
221
+ async function syncDir(dir: string): Promise<void> {
222
+ if (isWindows) {
223
+ return
224
+ }
225
+ try {
226
+ const handle = await open(dir, "r")
227
+ try {
228
+ await handle.sync()
229
+ } finally {
230
+ await handle.close()
231
+ }
232
+ } catch {
233
+ // 目录 fsync 失败时忽略
234
+ }
235
+ }
@@ -0,0 +1,12 @@
1
+ /*
2
+ * 平台层入口。
3
+ * 平台识别、目录解析与文件落盘;上层不直接读取 process.platform 或拼接系统目录。
4
+ */
5
+
6
+ export * from "./clipboard.ts"
7
+ export * from "./exec.ts"
8
+ export * from "./file.ts"
9
+ export * from "./notification.ts"
10
+ export * from "./open-external.ts"
11
+ export * from "./os.ts"
12
+ export * from "./path.ts"