create-windy 0.8.3 → 0.9.1

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 (505) hide show
  1. package/dist/cli.js +838 -560
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/apps/agent-server/Dockerfile +92 -22
  5. package/template/apps/agent-server/README.md +47 -85
  6. package/template/apps/agent-server/documents/office.py +216 -0
  7. package/template/apps/agent-server/documents/query_report.py +136 -0
  8. package/template/apps/agent-server/documents/smoke.py +177 -0
  9. package/template/apps/agent-server/documents/test_boundaries.py +185 -0
  10. package/template/apps/agent-server/host-contracts.ts +56 -0
  11. package/template/apps/agent-server/install-native.ts +167 -0
  12. package/template/apps/agent-server/main.ts +179 -0
  13. package/template/apps/agent-server/native/EXTENSIONS.md +46 -0
  14. package/template/apps/agent-server/native/admission.ts +73 -0
  15. package/template/apps/agent-server/native/authorization.test.ts +176 -0
  16. package/template/apps/agent-server/native/authorization.ts +118 -0
  17. package/template/apps/agent-server/native/boundary-io.ts +93 -0
  18. package/template/apps/agent-server/native/boundary-product.ts +107 -0
  19. package/template/apps/agent-server/native/boundary-project.test.ts +247 -0
  20. package/template/apps/agent-server/native/boundary-project.ts +188 -0
  21. package/template/apps/agent-server/native/boundary.ts +188 -0
  22. package/template/apps/agent-server/native/compaction-policy.test.ts +175 -0
  23. package/template/apps/agent-server/native/config.ts +95 -0
  24. package/template/apps/agent-server/native/conversation-instructions.ts +9 -0
  25. package/template/apps/agent-server/native/extensions.test.ts +190 -0
  26. package/template/apps/agent-server/native/extensions.ts +31 -0
  27. package/template/apps/agent-server/native/host-profile.test.ts +91 -0
  28. package/template/apps/agent-server/native/host-profile.ts +33 -0
  29. package/template/apps/agent-server/native/host-test-fixture.ts +13 -0
  30. package/template/apps/agent-server/native/http.ts +129 -0
  31. package/template/apps/agent-server/native/lifecycle.test.ts +285 -0
  32. package/template/apps/agent-server/native/lifecycle.ts +136 -0
  33. package/template/apps/agent-server/native/manager.ts +292 -0
  34. package/template/apps/agent-server/native/native.test.ts +207 -0
  35. package/template/apps/agent-server/native/office-preview.test.ts +50 -0
  36. package/template/apps/agent-server/native/office-preview.ts +25 -0
  37. package/template/apps/agent-server/native/parallel-harness.ts +242 -0
  38. package/template/apps/agent-server/native/parallel-mcp-fixture.ts +97 -0
  39. package/template/apps/agent-server/native/parallel.test.ts +256 -0
  40. package/template/apps/agent-server/native/profile-composition.test.ts +147 -0
  41. package/template/apps/agent-server/native/profile.ts +307 -0
  42. package/template/apps/agent-server/native/request.ts +61 -0
  43. package/template/apps/agent-server/native/session-conflict.test.ts +235 -0
  44. package/template/apps/agent-server/native/session-conflict.ts +21 -0
  45. package/template/apps/agent-server/native/shared-skills.test.ts +128 -0
  46. package/template/apps/agent-server/native/shared-skills.ts +240 -0
  47. package/template/apps/agent-server/native/socket-lifecycle.test.ts +48 -0
  48. package/template/apps/agent-server/native/socket.ts +130 -0
  49. package/template/apps/agent-server/native/storage-lifecycle.test.ts +217 -0
  50. package/template/apps/agent-server/native/storage.ts +188 -0
  51. package/template/apps/agent-server/native/stream-authorization.test.ts +89 -0
  52. package/template/apps/agent-server/native/worker.ts +77 -0
  53. package/template/apps/agent-server/native-download.test.ts +119 -0
  54. package/template/apps/agent-server/native-download.ts +145 -0
  55. package/template/apps/agent-server/package.json +63 -0
  56. package/template/apps/agent-server/plugins/access.mjs +126 -0
  57. package/template/apps/agent-server/plugins/branding/README.md +9 -0
  58. package/template/apps/agent-server/plugins/branding/brand.css +85 -0
  59. package/template/apps/agent-server/plugins/branding/client.js +338 -0
  60. package/template/apps/agent-server/plugins/branding/index.mjs +35 -0
  61. package/template/apps/agent-server/plugins/branding/loading.d.mts +2 -0
  62. package/template/apps/agent-server/plugins/branding/loading.mjs +66 -0
  63. package/template/apps/agent-server/plugins/branding/loading.test.ts +89 -0
  64. package/template/apps/agent-server/plugins/branding/package.json +21 -0
  65. package/template/apps/agent-server/plugins/branding.test.ts +294 -0
  66. package/template/apps/agent-server/plugins/compaction.mjs +83 -0
  67. package/template/apps/agent-server/plugins/compaction.test.ts +91 -0
  68. package/template/apps/agent-server/plugins/confine-network.py +57 -0
  69. package/template/apps/agent-server/plugins/default-skills.test.ts +62 -0
  70. package/template/apps/agent-server/plugins/downloads/README.md +28 -0
  71. package/template/apps/agent-server/plugins/downloads/client.js +126 -0
  72. package/template/apps/agent-server/plugins/downloads/downloads.test.ts +222 -0
  73. package/template/apps/agent-server/plugins/downloads/index.d.mts +19 -0
  74. package/template/apps/agent-server/plugins/downloads/index.mjs +180 -0
  75. package/template/apps/agent-server/plugins/downloads/package.json +20 -0
  76. package/template/apps/agent-server/plugins/drop-overlay/README.md +24 -0
  77. package/template/apps/agent-server/plugins/drop-overlay/drop-overlay.test.ts +199 -0
  78. package/template/apps/agent-server/plugins/drop-overlay/index.d.mts +8 -0
  79. package/template/apps/agent-server/plugins/drop-overlay/index.mjs +12 -0
  80. package/template/apps/agent-server/plugins/drop-overlay/style.css +102 -0
  81. package/template/apps/agent-server/plugins/locale/client.js +61 -0
  82. package/template/apps/agent-server/plugins/locale/index.d.mts +9 -0
  83. package/template/apps/agent-server/plugins/locale/index.mjs +16 -0
  84. package/template/apps/agent-server/plugins/locale/locale.test.ts +217 -0
  85. package/template/apps/agent-server/plugins/locale/package.json +19 -0
  86. package/template/apps/agent-server/plugins/parallel.mjs +146 -0
  87. package/template/apps/agent-server/plugins/preset-registry.d.mts +2 -0
  88. package/template/apps/agent-server/plugins/preset-registry.mjs +41 -0
  89. package/template/apps/agent-server/plugins/preset-registry.test.ts +46 -0
  90. package/template/apps/agent-server/plugins/questions.d.mts +25 -0
  91. package/template/apps/agent-server/plugins/questions.mjs +142 -0
  92. package/template/apps/agent-server/plugins/questions.test.ts +105 -0
  93. package/template/apps/agent-server/plugins/settings/README.md +17 -0
  94. package/template/apps/agent-server/plugins/settings/client.js +102 -0
  95. package/template/apps/agent-server/plugins/settings/index.mjs +17 -0
  96. package/template/apps/agent-server/plugins/settings/package.json +19 -0
  97. package/template/apps/agent-server/plugins/settings/settings.test.ts +129 -0
  98. package/template/apps/agent-server/plugins/settings-controller.d.mts +4 -0
  99. package/template/apps/agent-server/plugins/settings-controller.mjs +31 -0
  100. package/template/apps/agent-server/plugins/settings-controller.test.ts +17 -0
  101. package/template/apps/agent-server/plugins/settings-dispatch.test.ts +34 -0
  102. package/template/apps/agent-server/plugins/shared-skills.d.mts +15 -0
  103. package/template/apps/agent-server/plugins/shared-skills.mjs +82 -0
  104. package/template/apps/agent-server/plugins/shared-skills.test.ts +51 -0
  105. package/template/apps/agent-server/plugins/skill-library/README.md +11 -0
  106. package/template/apps/agent-server/plugins/skill-library/archive.test.ts +43 -0
  107. package/template/apps/agent-server/plugins/skill-library/bundle.mjs +234 -0
  108. package/template/apps/agent-server/plugins/skill-library/client-chunks.test.ts +107 -0
  109. package/template/apps/agent-server/plugins/skill-library/client-form-mount.test.ts +139 -0
  110. package/template/apps/agent-server/plugins/skill-library/client-form.test.ts +261 -0
  111. package/template/apps/agent-server/plugins/skill-library/client-ui.test.ts +268 -0
  112. package/template/apps/agent-server/plugins/skill-library/client-use.test.ts +158 -0
  113. package/template/apps/agent-server/plugins/skill-library/client.editor.js +93 -0
  114. package/template/apps/agent-server/plugins/skill-library/client.form.js +316 -0
  115. package/template/apps/agent-server/plugins/skill-library/client.js +348 -0
  116. package/template/apps/agent-server/plugins/skill-library/client.request.js +52 -0
  117. package/template/apps/agent-server/plugins/skill-library/client.transfer.js +48 -0
  118. package/template/apps/agent-server/plugins/skill-library/client.use.js +46 -0
  119. package/template/apps/agent-server/plugins/skill-library/default-roots.test.ts +84 -0
  120. package/template/apps/agent-server/plugins/skill-library/draft.d.mts +17 -0
  121. package/template/apps/agent-server/plugins/skill-library/draft.mjs +152 -0
  122. package/template/apps/agent-server/plugins/skill-library/draft.test.ts +287 -0
  123. package/template/apps/agent-server/plugins/skill-library/index.d.mts +49 -0
  124. package/template/apps/agent-server/plugins/skill-library/index.mjs +226 -0
  125. package/template/apps/agent-server/plugins/skill-library/package.json +24 -0
  126. package/template/apps/agent-server/plugins/skill-library/presentation/README.md +27 -0
  127. package/template/apps/agent-server/plugins/skill-library/presentation/UPSTREAM.md +54 -0
  128. package/template/apps/agent-server/plugins/skill-library/presentation/boot.test.ts +150 -0
  129. package/template/apps/agent-server/plugins/skill-library/presentation/client.js +146 -0
  130. package/template/apps/agent-server/plugins/skill-library/presentation/client.test.ts +195 -0
  131. package/template/apps/agent-server/plugins/skill-library/presentation/index.d.mts +9 -0
  132. package/template/apps/agent-server/plugins/skill-library/presentation/index.mjs +30 -0
  133. package/template/apps/agent-server/plugins/skill-library/presentation/metadata.test.ts +82 -0
  134. package/template/apps/agent-server/plugins/skill-library/presentation/package.json +21 -0
  135. package/template/apps/agent-server/plugins/skill-library/presentation/react-boot.test.ts +162 -0
  136. package/template/apps/agent-server/plugins/skill-library/skills.test.ts +342 -0
  137. package/template/apps/agent-server/plugins/skill-library/storage.mjs +293 -0
  138. package/template/apps/agent-server/plugins/skill-library/style.css +229 -0
  139. package/template/apps/agent-server/plugins/skill-library/test-fixture.ts +77 -0
  140. package/template/apps/agent-server/plugins/skill-library/workflow.d.mts +18 -0
  141. package/template/apps/agent-server/plugins/skill-library/workflow.mjs +189 -0
  142. package/template/apps/agent-server/plugins/skill-library/workflow.test.ts +164 -0
  143. package/template/apps/agent-server/plugins/skills/README.md +5 -0
  144. package/template/apps/agent-server/plugins/skills/windy-data-summary/SKILL.md +14 -0
  145. package/template/apps/agent-server/plugins/skills/windy-extension-guide/SKILL.md +14 -0
  146. package/template/apps/agent-server/plugins/skills/windy-office/SKILL.md +14 -0
  147. package/template/apps/agent-server/plugins/skills/windy-skill-capture/SKILL.md +14 -0
  148. package/template/apps/agent-server/plugins/skills/windy-source-compare/SKILL.md +14 -0
  149. package/template/apps/agent-server/plugins/subprocess.mjs +40 -0
  150. package/template/apps/agent-server/plugins/theme/README.md +18 -0
  151. package/template/apps/agent-server/plugins/theme/client.js +18 -0
  152. package/template/apps/agent-server/plugins/theme/index.d.mts +9 -0
  153. package/template/apps/agent-server/plugins/theme/index.mjs +18 -0
  154. package/template/apps/agent-server/plugins/theme/package.json +19 -0
  155. package/template/apps/agent-server/plugins/theme/source-tokens.ts +82 -0
  156. package/template/apps/agent-server/plugins/theme/sync-tokens.ts +12 -0
  157. package/template/apps/agent-server/plugins/theme/theme.test.ts +193 -0
  158. package/template/apps/agent-server/plugins/theme/tokens.json +166 -0
  159. package/template/apps/agent-server/plugins/tool-catalog/README.md +79 -0
  160. package/template/apps/agent-server/plugins/tool-catalog/categories.mjs +111 -0
  161. package/template/apps/agent-server/plugins/tool-catalog/categories.test.ts +118 -0
  162. package/template/apps/agent-server/plugins/tool-catalog/client-cache.test.ts +264 -0
  163. package/template/apps/agent-server/plugins/tool-catalog/client.js +333 -0
  164. package/template/apps/agent-server/plugins/tool-catalog/client.test.ts +136 -0
  165. package/template/apps/agent-server/plugins/tool-catalog/discovery.d.mts +42 -0
  166. package/template/apps/agent-server/plugins/tool-catalog/discovery.mjs +242 -0
  167. package/template/apps/agent-server/plugins/tool-catalog/discovery.test.ts +229 -0
  168. package/template/apps/agent-server/plugins/tool-catalog/index.d.mts +5 -0
  169. package/template/apps/agent-server/plugins/tool-catalog/index.mjs +91 -0
  170. package/template/apps/agent-server/plugins/tool-catalog/package.json +23 -0
  171. package/template/apps/agent-server/plugins/tool-catalog/presentation.d.mts +21 -0
  172. package/template/apps/agent-server/plugins/tool-catalog/presentation.mjs +153 -0
  173. package/template/apps/agent-server/plugins/tool-catalog/presentation.test.ts +166 -0
  174. package/template/apps/agent-server/plugins/tool-catalog/routes.test.ts +209 -0
  175. package/template/apps/agent-server/plugins/tool-catalog/style.css +155 -0
  176. package/template/apps/agent-server/plugins/tool-catalog/use-tool.test.ts +160 -0
  177. package/template/apps/agent-server/plugins/tool-presentation/README.md +7 -0
  178. package/template/apps/agent-server/plugins/tool-presentation/build-client.ts +29 -0
  179. package/template/apps/agent-server/plugins/tool-presentation/client.js +312 -0
  180. package/template/apps/agent-server/plugins/tool-presentation/client.results.js +9 -0
  181. package/template/apps/agent-server/plugins/tool-presentation/client.test.ts +178 -0
  182. package/template/apps/agent-server/plugins/tool-presentation/index.mjs +10 -0
  183. package/template/apps/agent-server/plugins/tool-presentation/package.json +24 -0
  184. package/template/apps/agent-server/plugins/tool-presentation/result-discovery.mjs +43 -0
  185. package/template/apps/agent-server/plugins/tool-presentation/result-model.d.mts +51 -0
  186. package/template/apps/agent-server/plugins/tool-presentation/result-model.mjs +96 -0
  187. package/template/apps/agent-server/plugins/workspace/README.md +5 -0
  188. package/template/apps/agent-server/plugins/workspace/index.mjs +8 -0
  189. package/template/apps/agent-server/plugins/workspace/package.json +10 -0
  190. package/template/apps/agent-server/plugins/workspace/style.css +124 -0
  191. package/template/apps/agent-server/project/host.ts +13 -0
  192. package/template/apps/agent-server/runtime/artifacts.ts +115 -0
  193. package/template/apps/agent-server/runtime/audit.nodecheck.ts +60 -0
  194. package/template/apps/agent-server/runtime/capacity.ts +61 -0
  195. package/template/apps/agent-server/runtime/client-completion-budget.nodecheck.ts +92 -0
  196. package/template/apps/agent-server/runtime/compatibility.ts +33 -0
  197. package/template/apps/agent-server/runtime/completions.ts +320 -0
  198. package/template/apps/agent-server/runtime/config.ts +246 -0
  199. package/template/apps/agent-server/runtime/context-budget.ts +70 -0
  200. package/template/apps/agent-server/runtime/context-plugin.ts +306 -0
  201. package/template/apps/agent-server/runtime/context.nodecheck.ts +241 -0
  202. package/template/apps/agent-server/runtime/contracts.ts +201 -0
  203. package/template/apps/agent-server/runtime/dsh-engine.ts +196 -0
  204. package/template/apps/agent-server/runtime/evaluation-plugin.ts +115 -0
  205. package/template/apps/agent-server/runtime/evaluation.nodecheck.ts +194 -0
  206. package/template/apps/agent-server/runtime/events.ts +71 -0
  207. package/template/apps/agent-server/runtime/governance.ts +194 -0
  208. package/template/apps/agent-server/runtime/index.ts +339 -0
  209. package/template/apps/agent-server/runtime/internal-output.ts +37 -0
  210. package/template/apps/agent-server/runtime/isolation.nodecheck.ts +287 -0
  211. package/template/apps/agent-server/runtime/model-payload.ts +97 -0
  212. package/template/apps/agent-server/runtime/model-plugin.ts +308 -0
  213. package/template/apps/agent-server/runtime/model-route.ts +34 -0
  214. package/template/apps/agent-server/runtime/model-usage.ts +51 -0
  215. package/template/apps/agent-server/runtime/model-wire.ts +45 -0
  216. package/template/apps/agent-server/runtime/native-authorization.nodecheck.ts +126 -0
  217. package/template/apps/agent-server/runtime/native-completion.nodecheck.ts +81 -0
  218. package/template/apps/agent-server/runtime/output-validation.ts +66 -0
  219. package/template/apps/agent-server/runtime/production-capacity.nodecheck.ts +121 -0
  220. package/template/apps/agent-server/runtime/questions-workflow.nodecheck.ts +249 -0
  221. package/template/apps/agent-server/runtime/report-breakdown.ts +79 -0
  222. package/template/apps/agent-server/runtime/reporting.ts +219 -0
  223. package/template/apps/agent-server/runtime/run-completion.ts +39 -0
  224. package/template/apps/agent-server/runtime/run-request-digest.ts +18 -0
  225. package/template/apps/agent-server/runtime/runtime-test-fixtures.ts +264 -0
  226. package/template/apps/agent-server/runtime/runtime.nodecheck.ts +150 -0
  227. package/template/apps/agent-server/runtime/store-idempotency.nodecheck.ts +74 -0
  228. package/template/apps/agent-server/runtime/store.ts +324 -0
  229. package/template/apps/agent-server/runtime/structured-output-mode.nodecheck.ts +51 -0
  230. package/template/apps/agent-server/runtime/tool-name.ts +6 -0
  231. package/template/apps/agent-server/runtime/tool-plugin.ts +201 -0
  232. package/template/apps/agent-server/runtime/vision-readiness.ts +200 -0
  233. package/template/apps/agent-server/scripts/confinement-check.mjs +103 -0
  234. package/template/apps/agent-server/scripts/coordinator-check.mjs +344 -0
  235. package/template/apps/agent-server/scripts/documents-delivery-check.mjs +309 -0
  236. package/template/apps/agent-server/scripts/office-preview-check.mjs +92 -0
  237. package/template/apps/agent-server/scripts/test-confinement.ts +58 -0
  238. package/template/apps/agent-server/scripts/test-coordinator.ts +36 -0
  239. package/template/apps/agent-server/scripts/test-runtime.ts +46 -0
  240. package/template/apps/agent-server/tsconfig.json +21 -0
  241. package/template/apps/server/Dockerfile +3 -0
  242. package/template/apps/server/package.json +5 -1
  243. package/template/apps/server/scripts/check-dsh-platform-example.ts +251 -0
  244. package/template/apps/server/src/agent/dsh-runtime-wire.test.ts +33 -0
  245. package/template/apps/server/src/agent/governance-runtime.ts +41 -0
  246. package/template/apps/server/src/agent/remote-runtime-response.ts +1 -1
  247. package/template/apps/server/src/agent/run-contracts.ts +1 -0
  248. package/template/apps/server/src/agent/run-definition-support.ts +9 -0
  249. package/template/apps/server/src/agent/runtime-tool-composition.ts +2 -0
  250. package/template/apps/server/src/agent/workflow-runtime-bootstrap.ts +9 -0
  251. package/template/apps/server/src/agent-skills/access.ts +88 -0
  252. package/template/apps/server/src/agent-skills/bootstrap.ts +49 -0
  253. package/template/apps/server/src/agent-skills/builtin-registration.ts +83 -0
  254. package/template/apps/server/src/agent-skills/builtins.ts +19 -0
  255. package/template/apps/server/src/agent-skills/bundle-zip.ts +163 -0
  256. package/template/apps/server/src/agent-skills/bundle.test.ts +79 -0
  257. package/template/apps/server/src/agent-skills/bundle.ts +133 -0
  258. package/template/apps/server/src/agent-skills/contracts.ts +46 -0
  259. package/template/apps/server/src/agent-skills/drizzle-repository.ts +125 -0
  260. package/template/apps/server/src/agent-skills/input.ts +29 -0
  261. package/template/apps/server/src/agent-skills/postgres.integration.test.ts +348 -0
  262. package/template/apps/server/src/agent-skills/read-routes.test.ts +116 -0
  263. package/template/apps/server/src/agent-skills/reader.ts +108 -0
  264. package/template/apps/server/src/agent-skills/repository.ts +105 -0
  265. package/template/apps/server/src/agent-skills/routes.ts +176 -0
  266. package/template/apps/server/src/agent-skills/service.test.ts +295 -0
  267. package/template/apps/server/src/agent-skills/service.ts +301 -0
  268. package/template/apps/server/src/agent-workflow/preset-registry.test.ts +137 -0
  269. package/template/apps/server/src/agent-workflow/preset-registry.ts +191 -0
  270. package/template/apps/server/src/agent-workflow/preset-routes.test.ts +94 -0
  271. package/template/apps/server/src/agent-workflow/preset-routes.ts +45 -0
  272. package/template/apps/server/src/ai-runtime-report/service.test.ts +10 -7
  273. package/template/apps/server/src/ai-runtime-report/service.ts +1 -1
  274. package/template/apps/server/src/app/agent-workflow-presets.ts +4 -0
  275. package/template/apps/server/src/app/dsh-skill-draft-audit.test.ts +195 -0
  276. package/template/apps/server/src/app/dsh-skill-draft-audit.ts +115 -0
  277. package/template/apps/server/src/app/dsh-workbench-audit.test.ts +71 -0
  278. package/template/apps/server/src/app/dsh-workbench-audit.ts +34 -0
  279. package/template/apps/server/src/app/dsh-workbench-handshake.test.ts +63 -0
  280. package/template/apps/server/src/app/dsh-workbench-handshake.ts +48 -0
  281. package/template/apps/server/src/app/dsh-workbench-http.test.ts +96 -0
  282. package/template/apps/server/src/app/dsh-workbench-lan-origin.test.ts +114 -0
  283. package/template/apps/server/src/app/dsh-workbench-launch.test.ts +70 -0
  284. package/template/apps/server/src/app/dsh-workbench-origin.test.ts +54 -0
  285. package/template/apps/server/src/app/dsh-workbench-proxy.ts +205 -0
  286. package/template/apps/server/src/app/dsh-workbench-session-conflict.test.ts +61 -0
  287. package/template/apps/server/src/app/dsh-workbench-session.ts +59 -0
  288. package/template/apps/server/src/app/dsh-workbench-socket-audit.test.ts +115 -0
  289. package/template/apps/server/src/app/dsh-workbench-socket.test.ts +246 -0
  290. package/template/apps/server/src/app/dsh-workbench-socket.ts +272 -0
  291. package/template/apps/server/src/app/dsh-workbench-test-support.ts +158 -0
  292. package/template/apps/server/src/app/dsh-workbench.test.ts +213 -0
  293. package/template/apps/server/src/app/dsh-workbench.ts +279 -0
  294. package/template/apps/server/src/app/workbench-public-origin.ts +16 -0
  295. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +3 -0
  296. package/template/apps/server/src/http-app.ts +31 -0
  297. package/template/apps/server/src/mcp/adapter.ts +1 -0
  298. package/template/apps/server/src/module-composition.test.ts +23 -0
  299. package/template/apps/server/src/module-composition.ts +9 -2
  300. package/template/apps/server/src/observability/bootstrap.ts +12 -0
  301. package/template/apps/server/src/observability/diagnostic-failure-recorder.ts +94 -0
  302. package/template/apps/server/src/observability/diagnostic-http-contract.test.ts +42 -0
  303. package/template/apps/server/src/observability/diagnostic-index.test.ts +89 -0
  304. package/template/apps/server/src/observability/diagnostic-index.ts +88 -0
  305. package/template/apps/server/src/observability/diagnostic-repository.ts +67 -0
  306. package/template/apps/server/src/platform-app-bootstrap.ts +42 -0
  307. package/template/apps/server/src/platform-http-runtime.ts +6 -0
  308. package/template/apps/server/src/runtime-development.test.ts +25 -0
  309. package/template/apps/server/src/runtime-development.ts +6 -44
  310. package/template/apps/server/src/system/diagnostic-routes.test.ts +78 -0
  311. package/template/apps/server/src/system/diagnostic-routes.ts +66 -0
  312. package/template/apps/web/Dockerfile +9 -1
  313. package/template/apps/web/dev/start.ts +21 -10
  314. package/template/apps/web/dsh-workbench-location.ts +21 -0
  315. package/template/apps/web/dsh-workbench-proxy.test.ts +121 -0
  316. package/template/apps/web/dsh-workbench-proxy.ts +194 -0
  317. package/template/apps/web/package.json +3 -1
  318. package/template/apps/web/server.ts +27 -6
  319. package/template/apps/web/src/composables/useLoginFlow.ts +13 -1
  320. package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +6 -0
  321. package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +14 -1
  322. package/template/apps/web/src/pages/errors/error-page-extension.ts +1 -0
  323. package/template/apps/web/src/pages/system/AgentNativePage.vue +44 -0
  324. package/template/apps/web/src/pages/system/AgentWorkflowPage.vue +20 -7
  325. package/template/apps/web/src/pages/system/DiagnosticLookupPage.vue +56 -0
  326. package/template/apps/web/src/pages/system/DiagnosticLookupPage.webtest.ts +99 -0
  327. package/template/apps/web/src/pages/system/SystemAgentSkillsPage.vue +88 -0
  328. package/template/apps/web/src/pages/system/components/DiagnosticRecordDetails.vue +52 -0
  329. package/template/apps/web/src/pages/system/components/agent-skills/AgentSkillDetailDialog.vue +166 -0
  330. package/template/apps/web/src/pages/system/components/agent-skills/AgentSkillGovernanceActionDialog.vue +51 -0
  331. package/template/apps/web/src/pages/system/components/agent-skills/AgentSkillImportDialog.vue +320 -0
  332. package/template/apps/web/src/pages/system/components/agent-skills/AgentSkillImportDialog.webtest.ts +96 -0
  333. package/template/apps/web/src/pages/system/components/agent-skills/AgentSkillsTable.vue +69 -0
  334. package/template/apps/web/src/pages/system/components/agent-workflow/WorkflowPresetCatalog.vue +82 -0
  335. package/template/apps/web/src/pages/system/components/agent-workflow/WorkflowPresetCatalog.webtest.ts +57 -0
  336. package/template/apps/web/src/pages/system/composables/useAgentSkillsGovernance.ts +105 -0
  337. package/template/apps/web/src/pages/system/composables/useAgentWorkflowPresets.ts +44 -0
  338. package/template/apps/web/src/pages/system/composables/useDiagnosticLookup.ts +64 -0
  339. package/template/apps/web/src/router/error-navigation.ts +3 -1
  340. package/template/apps/web/src/router/error-navigation.webtest.ts +14 -2
  341. package/template/apps/web/src/router/index.ts +8 -49
  342. package/template/apps/web/src/router/platform-admin-pages.ts +79 -0
  343. package/template/apps/web/src/router/platform-admin-route-bindings.ts +8 -0
  344. package/template/apps/web/src/services/agent-skills-api.ts +55 -0
  345. package/template/apps/web/src/services/agent-workflow-api.ts +12 -0
  346. package/template/apps/web/src/services/diagnostics-api.ts +12 -0
  347. package/template/apps/web/src/services/http.ts +18 -0
  348. package/template/apps/web/src/services/http.webtest.ts +51 -0
  349. package/template/apps/web/vite.config.ts +39 -0
  350. package/template/deploy/dsh/README.md +5 -0
  351. package/template/deploy/dsh/documents/README.md +9 -0
  352. package/template/deploy/dsh/documents/requirements.in +6 -0
  353. package/template/deploy/dsh/documents/requirements.lock +72 -0
  354. package/template/docker-compose.yml +24 -10
  355. package/template/docs/adr/0004-embedded-dsh-runtime.md +9 -0
  356. package/template/docs/architecture/agent-admin-workbench-implementation.md +73 -0
  357. package/template/docs/architecture/agent-admin-workbench-plan.md +41 -0
  358. package/template/docs/architecture/agent-harness.md +2 -0
  359. package/template/docs/architecture/agent-runtime-compatibility.md +2 -0
  360. package/template/docs/architecture/embedded-dsh-integration.md +44 -0
  361. package/template/docs/development/dsh-plugins.md +55 -0
  362. package/template/docs/operations/dsh-agent-migration.md +98 -0
  363. package/template/docs/platform/agent-production-store.md +5 -78
  364. package/template/docs/platform/agent-runtime-observability.md +2 -0
  365. package/template/docs/platform/agent-runtime.md +2 -0
  366. package/template/docs/platform/agent-skills.md +25 -0
  367. package/template/package.json +4 -3
  368. package/template/packages/database/drizzle/0064_dsh_skills_diagnostics.sql +42 -0
  369. package/template/packages/database/drizzle/meta/0064_snapshot.json +15349 -0
  370. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  371. package/template/packages/database/package.json +2 -2
  372. package/template/packages/database/src/schema/agent-skills.ts +65 -0
  373. package/template/packages/database/src/schema/diagnostics.ts +27 -0
  374. package/template/packages/database/src/schema/index.ts +8 -0
  375. package/template/packages/modules/package.json +1 -1
  376. package/template/packages/modules/src/system-admin-routes.ts +24 -0
  377. package/template/packages/modules/src/system-admin-ui-catalog.ts +17 -10
  378. package/template/packages/modules/src/system-agent-admin-bindings.ts +2 -0
  379. package/template/packages/modules/src/system-agent-admin-menus.ts +20 -0
  380. package/template/packages/modules/src/system-agent-audit-actions.ts +3 -0
  381. package/template/packages/modules/src/system-agent-dsh.ts +119 -0
  382. package/template/packages/modules/src/system-api-permissions.ts +9 -0
  383. package/template/packages/modules/src/system-audit-actions.ts +11 -0
  384. package/template/packages/modules/src/system-features.ts +5 -0
  385. package/template/packages/modules/src/system-module-audit-selection.ts +25 -0
  386. package/template/packages/modules/src/system-module-catalog.ts +12 -32
  387. package/template/packages/modules/src/system-modules.ts +14 -3
  388. package/template/packages/modules/src/system-permissions.ts +10 -0
  389. package/template/packages/server-sdk/package.json +2 -2
  390. package/template/packages/shared/index.ts +12 -0
  391. package/template/packages/shared/package.json +1 -1
  392. package/template/packages/shared/src/agent-skills.ts +68 -0
  393. package/template/packages/shared/src/agent-workflow-presets.ts +26 -0
  394. package/template/packages/shared/src/diagnostics.ts +13 -0
  395. package/template/packages/shared/src/rbac.ts +1 -0
  396. package/template/apps/agent-server/install-python-dependencies.sh +0 -92
  397. package/template/apps/agent-server/pyproject.toml +0 -51
  398. package/template/apps/agent-server/src/southwind_agent_server/__init__.py +0 -5
  399. package/template/apps/agent-server/src/southwind_agent_server/app.py +0 -344
  400. package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +0 -30
  401. package/template/apps/agent-server/src/southwind_agent_server/artifact_input.py +0 -153
  402. package/template/apps/agent-server/src/southwind_agent_server/asgi.py +0 -8
  403. package/template/apps/agent-server/src/southwind_agent_server/attempt_context.py +0 -199
  404. package/template/apps/agent-server/src/southwind_agent_server/attempt_context_projection.py +0 -203
  405. package/template/apps/agent-server/src/southwind_agent_server/client_completion_contracts.py +0 -61
  406. package/template/apps/agent-server/src/southwind_agent_server/client_completion_governance.py +0 -82
  407. package/template/apps/agent-server/src/southwind_agent_server/client_completion_routes.py +0 -121
  408. package/template/apps/agent-server/src/southwind_agent_server/client_completion_runtime.py +0 -158
  409. package/template/apps/agent-server/src/southwind_agent_server/config.py +0 -267
  410. package/template/apps/agent-server/src/southwind_agent_server/context_message_digest.py +0 -24
  411. package/template/apps/agent-server/src/southwind_agent_server/context_version_contracts.py +0 -71
  412. package/template/apps/agent-server/src/southwind_agent_server/context_version_port.py +0 -307
  413. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +0 -297
  414. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +0 -63
  415. package/template/apps/agent-server/src/southwind_agent_server/engine.py +0 -134
  416. package/template/apps/agent-server/src/southwind_agent_server/engine_capacity.py +0 -31
  417. package/template/apps/agent-server/src/southwind_agent_server/engine_identity.py +0 -2
  418. package/template/apps/agent-server/src/southwind_agent_server/fingerprint.py +0 -61
  419. package/template/apps/agent-server/src/southwind_agent_server/health_routes.py +0 -93
  420. package/template/apps/agent-server/src/southwind_agent_server/http_helpers.py +0 -62
  421. package/template/apps/agent-server/src/southwind_agent_server/lease.py +0 -15
  422. package/template/apps/agent-server/src/southwind_agent_server/multimodal.py +0 -76
  423. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +0 -329
  424. package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +0 -165
  425. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +0 -345
  426. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +0 -37
  427. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +0 -235
  428. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +0 -44
  429. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +0 -83
  430. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +0 -336
  431. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +0 -304
  432. package/template/apps/agent-server/src/southwind_agent_server/postgres_store.py +0 -335
  433. package/template/apps/agent-server/src/southwind_agent_server/postgres_store_backend.py +0 -333
  434. package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +0 -144
  435. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +0 -309
  436. package/template/apps/agent-server/src/southwind_agent_server/provider_output_guidance.py +0 -34
  437. package/template/apps/agent-server/src/southwind_agent_server/readiness_pin.py +0 -37
  438. package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +0 -218
  439. package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +0 -149
  440. package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +0 -300
  441. package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +0 -132
  442. package/template/apps/agent-server/src/southwind_agent_server/report_store.py +0 -298
  443. package/template/apps/agent-server/src/southwind_agent_server/report_store_protocol.py +0 -75
  444. package/template/apps/agent-server/src/southwind_agent_server/report_types.py +0 -118
  445. package/template/apps/agent-server/src/southwind_agent_server/run_admission.py +0 -64
  446. package/template/apps/agent-server/src/southwind_agent_server/run_capacity.py +0 -59
  447. package/template/apps/agent-server/src/southwind_agent_server/runtime_budget.py +0 -74
  448. package/template/apps/agent-server/src/southwind_agent_server/runtime_compatibility.py +0 -89
  449. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +0 -105
  450. package/template/apps/agent-server/src/southwind_agent_server/runtime_logging.py +0 -57
  451. package/template/apps/agent-server/src/southwind_agent_server/service.py +0 -325
  452. package/template/apps/agent-server/src/southwind_agent_server/service_control.py +0 -64
  453. package/template/apps/agent-server/src/southwind_agent_server/service_errors.py +0 -4
  454. package/template/apps/agent-server/src/southwind_agent_server/service_execution.py +0 -34
  455. package/template/apps/agent-server/src/southwind_agent_server/service_tasks.py +0 -34
  456. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +0 -53
  457. package/template/apps/agent-server/src/southwind_agent_server/sqlite_catalog.py +0 -118
  458. package/template/apps/agent-server/src/southwind_agent_server/sqlite_errors.py +0 -2
  459. package/template/apps/agent-server/src/southwind_agent_server/sqlite_schema.py +0 -73
  460. package/template/apps/agent-server/src/southwind_agent_server/sqlite_serialization.py +0 -16
  461. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +0 -350
  462. package/template/apps/agent-server/src/southwind_agent_server/sse.py +0 -41
  463. package/template/apps/agent-server/src/southwind_agent_server/store.py +0 -255
  464. package/template/apps/agent-server/src/southwind_agent_server/store_cipher.py +0 -40
  465. package/template/apps/agent-server/src/southwind_agent_server/structured_logging.py +0 -145
  466. package/template/apps/agent-server/src/southwind_agent_server/telemetry.py +0 -93
  467. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +0 -152
  468. package/template/apps/agent-server/src/southwind_agent_server/trajectory.py +0 -137
  469. package/template/apps/agent-server/src/southwind_agent_server/unavailable_engine.py +0 -58
  470. package/template/apps/agent-server/src/southwind_agent_server/vision_probe.py +0 -87
  471. package/template/apps/agent-server/src/southwind_agent_server/vision_requirement.py +0 -28
  472. package/template/apps/agent-server/src/southwind_agent_server/vlm_readiness.py +0 -247
  473. package/template/apps/agent-server/tests/__init__.py +0 -1
  474. package/template/apps/agent-server/tests/openai_provider_test_support.py +0 -131
  475. package/template/apps/agent-server/tests/test_api.py +0 -305
  476. package/template/apps/agent-server/tests/test_artifact_input.py +0 -110
  477. package/template/apps/agent-server/tests/test_client_completion.py +0 -230
  478. package/template/apps/agent-server/tests/test_config.py +0 -106
  479. package/template/apps/agent-server/tests/test_fingerprint.py +0 -10
  480. package/template/apps/agent-server/tests/test_multimodal.py +0 -81
  481. package/template/apps/agent-server/tests/test_openai_provider.py +0 -320
  482. package/template/apps/agent-server/tests/test_postgres_report_status.py +0 -59
  483. package/template/apps/agent-server/tests/test_postgres_report_summary.py +0 -298
  484. package/template/apps/agent-server/tests/test_postgres_store.py +0 -135
  485. package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +0 -140
  486. package/template/apps/agent-server/tests/test_provider_governance.py +0 -276
  487. package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +0 -126
  488. package/template/apps/agent-server/tests/test_report_resilience.py +0 -183
  489. package/template/apps/agent-server/tests/test_report_routes.py +0 -143
  490. package/template/apps/agent-server/tests/test_report_store.py +0 -347
  491. package/template/apps/agent-server/tests/test_report_summary_parity.py +0 -116
  492. package/template/apps/agent-server/tests/test_report_terminal_sync.py +0 -177
  493. package/template/apps/agent-server/tests/test_run_capacity.py +0 -112
  494. package/template/apps/agent-server/tests/test_run_operational_logging.py +0 -114
  495. package/template/apps/agent-server/tests/test_runtime_compatibility.py +0 -119
  496. package/template/apps/agent-server/tests/test_runtime_factory_readiness.py +0 -52
  497. package/template/apps/agent-server/tests/test_sqlite_store.py +0 -182
  498. package/template/apps/agent-server/tests/test_structured_logging.py +0 -142
  499. package/template/apps/agent-server/tests/test_telemetry.py +0 -25
  500. package/template/apps/agent-server/tests/test_tool_approval.py +0 -28
  501. package/template/apps/agent-server/tests/test_tool_structured_output.py +0 -347
  502. package/template/apps/agent-server/tests/test_trajectory.py +0 -91
  503. package/template/apps/agent-server/tests/test_vision_readiness_enforcement.py +0 -72
  504. package/template/apps/agent-server/tests/test_vlm_readiness.py +0 -106
  505. package/template/apps/agent-server/uv.lock +0 -831
@@ -0,0 +1,130 @@
1
+ import type { IncomingMessage, Server } from "node:http";
2
+ import type { Duplex } from "node:stream";
3
+ import WebSocket, { WebSocketServer } from "ws";
4
+ import { authorityFrom } from "./config.js";
5
+ import { WorkbenchManager } from "./manager.js";
6
+ import { workerTransportSignal } from "./lifecycle.js";
7
+ import { isWorkbenchAuthorizationUnavailable } from "./authorization.js";
8
+ import { WorkbenchSessionConflict } from "./session-conflict.js";
9
+
10
+ export function attachWorkbenchSockets(
11
+ server: Server,
12
+ manager: WorkbenchManager,
13
+ authenticated: (headers: Headers) => boolean,
14
+ ) {
15
+ const wss = new WebSocketServer({
16
+ noServer: true,
17
+ maxPayload: 32 * 1024 * 1024,
18
+ });
19
+ const upgrade = async (
20
+ request: IncomingMessage,
21
+ socket: Duplex,
22
+ head: Buffer,
23
+ ) => {
24
+ // A browser may disconnect during worker startup/admission, before ws owns
25
+ // the raw socket. Handle resets throughout that asynchronous window.
26
+ socket.on("error", () => socket.destroy());
27
+ let transportSignal: AbortSignal | undefined;
28
+ let pendingUpstream: WebSocket | undefined;
29
+ try {
30
+ const url = new URL(request.url ?? "/", "http://agent.internal");
31
+ if (url.pathname !== "/internal/workbench/api/remote.mux" || url.search)
32
+ throw new Error("ROUTE_DENIED");
33
+ const headers = new Headers();
34
+ for (const [key, value] of Object.entries(request.headers))
35
+ if (typeof value === "string") headers.set(key, value);
36
+ if (!authenticated(headers)) throw new Error("UNAUTHENTICATED");
37
+ const authority = authorityFrom(new Request(url, { headers }));
38
+ if (
39
+ !(await manager.authorizeAuthority(
40
+ authority,
41
+ new AbortController().signal,
42
+ ))
43
+ )
44
+ throw new Error("UNAUTHENTICATED");
45
+ const worker = await manager.get(authority);
46
+ if (!(await manager.current(worker)) || socket.destroyed)
47
+ throw new Error("UNAUTHENTICATED");
48
+ const signal = workerTransportSignal(worker);
49
+ transportSignal = signal;
50
+ signal.throwIfAborted();
51
+ const upstream = new WebSocket(
52
+ `ws://127.0.0.1:${worker.port}/api/remote.mux`,
53
+ {
54
+ headers: {
55
+ host: new URL(authority.publicOrigin).host,
56
+ origin: authority.publicOrigin,
57
+ cookie: worker.cookie,
58
+ },
59
+ maxPayload: 32 * 1024 * 1024,
60
+ handshakeTimeout: 5000,
61
+ },
62
+ );
63
+ pendingUpstream = upstream;
64
+ const abortUpstream = () => upstream.terminate();
65
+ signal.addEventListener("abort", abortUpstream, { once: true });
66
+ upstream.once("close", () =>
67
+ signal.removeEventListener("abort", abortUpstream),
68
+ );
69
+ await new Promise<void>((accept, reject) => {
70
+ upstream.once("open", accept);
71
+ upstream.once("error", reject);
72
+ });
73
+ signal.throwIfAborted();
74
+ if (socket.destroyed) throw new Error("CLIENT_DISCONNECTED");
75
+ wss.handleUpgrade(request, socket, head, (client) => {
76
+ const close = () => {
77
+ client.terminate();
78
+ upstream.terminate();
79
+ };
80
+ signal.addEventListener("abort", close, {
81
+ once: true,
82
+ });
83
+ client.on("message", (data, binary) => {
84
+ if (
85
+ !signal.aborted &&
86
+ upstream.readyState === WebSocket.OPEN &&
87
+ upstream.bufferedAmount < 32 * 1024 * 1024
88
+ )
89
+ upstream.send(data, { binary });
90
+ else close();
91
+ });
92
+ upstream.on("message", (data, binary) => {
93
+ if (
94
+ !signal.aborted &&
95
+ client.readyState === WebSocket.OPEN &&
96
+ client.bufferedAmount < 32 * 1024 * 1024
97
+ )
98
+ client.send(data, { binary });
99
+ else close();
100
+ });
101
+ client.once("error", close);
102
+ upstream.once("error", close);
103
+ client.once("close", () => {
104
+ signal.removeEventListener("abort", close);
105
+ upstream.terminate();
106
+ });
107
+ upstream.once("close", () =>
108
+ client.close(1001, "Agent connection ended"),
109
+ );
110
+ });
111
+ } catch (error) {
112
+ pendingUpstream?.terminate();
113
+ if (!socket.destroyed)
114
+ socket.end(
115
+ error instanceof WorkbenchSessionConflict
116
+ ? "HTTP/1.1 409 Conflict\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n"
117
+ : isWorkbenchAuthorizationUnavailable(error) ||
118
+ isWorkbenchAuthorizationUnavailable(transportSignal?.reason)
119
+ ? "HTTP/1.1 503 Service Unavailable\r\nRetry-After: 2\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n"
120
+ : "HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n",
121
+ );
122
+ }
123
+ };
124
+ server.on("upgrade", upgrade);
125
+ return () => {
126
+ server.off("upgrade", upgrade);
127
+ for (const client of wss.clients) client.terminate();
128
+ wss.close();
129
+ };
130
+ }
@@ -0,0 +1,217 @@
1
+ import { expect, test } from "bun:test";
2
+ import { EventEmitter } from "node:events";
3
+ import type { ChildProcess } from "node:child_process";
4
+ import {
5
+ chmod,
6
+ mkdir,
7
+ mkdtemp,
8
+ rename,
9
+ rm,
10
+ symlink,
11
+ unlink,
12
+ writeFile,
13
+ } from "node:fs/promises";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
16
+ import { diskUsage } from "./storage.js";
17
+ import { WorkbenchManager, type NativeWorker } from "./manager.js";
18
+ import { actorKey } from "./config.js";
19
+
20
+ async function temporary(run: (home: string) => Promise<void>) {
21
+ const home = await mkdtemp(join(tmpdir(), "windy-storage-life-"));
22
+ try {
23
+ await run(home);
24
+ } finally {
25
+ await chmod(home, 0o700).catch(() => {});
26
+ await rm(home, { recursive: true, force: true });
27
+ }
28
+ }
29
+
30
+ test("normal quota totals files, stops above budget and excludes symbolic links", async () => {
31
+ await temporary(async (home) => {
32
+ const outside = await mkdtemp(join(tmpdir(), "windy-storage-outside-"));
33
+ try {
34
+ await mkdir(join(home, "nested"));
35
+ await writeFile(join(home, "a"), "123");
36
+ await writeFile(join(home, "nested", "b"), "4567");
37
+ await writeFile(join(outside, "private"), "not-counted");
38
+ await symlink(outside, join(home, "external-directory"));
39
+ await symlink(join(outside, "private"), join(home, "external-file"));
40
+ expect(await diskUsage(home, 1000)).toBe(7);
41
+ expect(await diskUsage(home, 2)).toBeGreaterThan(2);
42
+ } finally {
43
+ await rm(outside, { recursive: true, force: true });
44
+ }
45
+ });
46
+ });
47
+
48
+ test("missing, non-directory and symlink roots fail closed", async () => {
49
+ await temporary(async (home) => {
50
+ await expect(diskUsage(join(home, "missing"), 1000)).rejects.toMatchObject({
51
+ code: "ENOENT",
52
+ });
53
+ await writeFile(join(home, "file"), "synthetic");
54
+ await expect(diskUsage(join(home, "file"), 1000)).rejects.toThrow();
55
+ await mkdir(join(home, "directory"));
56
+ await symlink(join(home, "directory"), join(home, "link"));
57
+ await expect(diskUsage(join(home, "link"), 1000)).rejects.toThrow();
58
+ });
59
+ });
60
+
61
+ test.skipIf(process.getuid?.() === 0)(
62
+ "permission errors are not mistaken for disappeared children",
63
+ async () => {
64
+ await temporary(async (home) => {
65
+ const child = join(home, "restricted");
66
+ await mkdir(child);
67
+ await chmod(child, 0);
68
+ try {
69
+ await expect(diskUsage(home, 1000)).rejects.toMatchObject({
70
+ code: "EACCES",
71
+ });
72
+ } finally {
73
+ await chmod(child, 0o700);
74
+ }
75
+ });
76
+ },
77
+ );
78
+
79
+ test("root replacement during a walk is rejected even when the replacement exists", async () => {
80
+ await temporary(async (parent) => {
81
+ const home = join(parent, "home"),
82
+ moved = join(parent, "original");
83
+ await mkdir(home);
84
+ await writeFile(join(home, "fixture"), "synthetic");
85
+ const captured = Promise.withResolvers<void>();
86
+ const replaced = Promise.withResolvers<void>();
87
+ const scan = diskUsage(home, 1_000_000, {
88
+ async afterRootCaptured() {
89
+ captured.resolve();
90
+ await replaced.promise;
91
+ },
92
+ });
93
+ const outcome = scan.then(
94
+ () => undefined,
95
+ (error: unknown) => error,
96
+ );
97
+ try {
98
+ await captured.promise;
99
+ await rename(home, moved);
100
+ await mkdir(home);
101
+ } finally {
102
+ replaced.resolve();
103
+ }
104
+ expect(await outcome).toMatchObject({
105
+ message: "DSH quota root changed during inspection",
106
+ });
107
+ });
108
+ });
109
+
110
+ test("real file churn no longer stops a worker during quota inspection", async () => {
111
+ await temporary(async (home) => {
112
+ await Promise.all(
113
+ Array.from({ length: 128 }, (_, index) =>
114
+ writeFile(join(home, `fixture-${index}`), "synthetic"),
115
+ ),
116
+ );
117
+ const manager = new WorkbenchManager({
118
+ gateOrigin: "http://synthetic.invalid",
119
+ authorize: async () => true,
120
+ });
121
+ const worker: NativeWorker = {
122
+ uid: 20000,
123
+ key: actorKey("synthetic-user"),
124
+ authority: {
125
+ actorId: "synthetic-user",
126
+ session: "synthetic",
127
+ publicOrigin: "http://synthetic.invalid",
128
+ },
129
+ token: "synthetic",
130
+ port: 0,
131
+ cookie: "",
132
+ home,
133
+ child: Object.assign(new EventEmitter(), {
134
+ exitCode: 0,
135
+ signalCode: null,
136
+ }) as unknown as ChildProcess,
137
+ controller: new AbortController(),
138
+ admissionController: new AbortController(),
139
+ };
140
+ manager.workers.set(worker.key, worker);
141
+ let running = true,
142
+ mutations = 0;
143
+ const churn = (async () => {
144
+ while (running) {
145
+ const original = join(home, `fixture-${mutations % 128}`);
146
+ const moving = join(home, `moving-${mutations % 128}`);
147
+ await rename(original, moving);
148
+ await unlink(moving);
149
+ await writeFile(original, "synthetic");
150
+ mutations++;
151
+ }
152
+ })();
153
+ try {
154
+ for (let index = 0; index < 100; index++) await manager.inspect(worker);
155
+ expect(mutations).toBeGreaterThan(0);
156
+ expect(worker.controller.signal.aborted).toBe(false);
157
+ expect(manager.workers.get(worker.key)).toBe(worker);
158
+ manager.config.workspaceBytes = 1;
159
+ await manager.inspect(worker);
160
+ expect(worker.controller.signal.aborted).toBe(true);
161
+ expect(manager.workers.has(worker.key)).toBe(false);
162
+ } finally {
163
+ running = false;
164
+ await churn;
165
+ await manager.close();
166
+ }
167
+ });
168
+ });
169
+
170
+ test("missing root still stops a worker through the real inspection path", async () => {
171
+ await temporary(async (home) => {
172
+ const manager = new WorkbenchManager({
173
+ gateOrigin: "http://synthetic.invalid",
174
+ authorize: async () => true,
175
+ });
176
+ const worker: NativeWorker = {
177
+ uid: 20000,
178
+ key: actorKey("synthetic-user"),
179
+ authority: {
180
+ actorId: "synthetic-user",
181
+ session: "synthetic",
182
+ publicOrigin: "http://synthetic.invalid",
183
+ },
184
+ token: "synthetic",
185
+ port: 0,
186
+ cookie: "",
187
+ home: join(home, "missing"),
188
+ child: Object.assign(new EventEmitter(), {
189
+ exitCode: 0,
190
+ signalCode: null,
191
+ }) as unknown as ChildProcess,
192
+ controller: new AbortController(),
193
+ admissionController: new AbortController(),
194
+ };
195
+ manager.workers.set(worker.key, worker);
196
+ try {
197
+ await manager.inspect(worker);
198
+ expect(worker.controller.signal.aborted).toBe(true);
199
+ expect(manager.workers.has(worker.key)).toBe(false);
200
+ } finally {
201
+ await manager.close();
202
+ }
203
+ });
204
+ });
205
+
206
+ test("deep directory trees fail closed before exhausting file descriptors", async () => {
207
+ await temporary(async (home) => {
208
+ let directory = home;
209
+ for (let depth = 0; depth < 66; depth++) {
210
+ directory = join(directory, "nested");
211
+ await mkdir(directory);
212
+ }
213
+ await expect(diskUsage(home, 1000)).rejects.toThrow(
214
+ "directory depth exceeded",
215
+ );
216
+ });
217
+ });
@@ -0,0 +1,188 @@
1
+ import {
2
+ chown,
3
+ chmod,
4
+ lstat,
5
+ mkdir,
6
+ open,
7
+ readFile,
8
+ readdir,
9
+ writeFile,
10
+ } from "node:fs/promises";
11
+ import { constants } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+
14
+ export async function userStorage(root: string, key: string) {
15
+ if (process.platform !== "linux" || process.getuid?.() !== 0)
16
+ throw new Error(
17
+ "Managed multi-user DSH requires Linux credential isolation",
18
+ );
19
+ const parent = dirname(root);
20
+ if (root === parent || parent === "/")
21
+ throw new Error("DSH requires a dedicated Agent data directory");
22
+ await mkdir(parent, { recursive: true, mode: 0o711 });
23
+ await protectCoordinatorFiles(parent);
24
+ await chown(parent, 0, 0);
25
+ await chmod(parent, 0o711);
26
+ await mkdir(root, { recursive: true, mode: 0o711 });
27
+ await chown(root, 0, 0);
28
+ await chmod(root, 0o711);
29
+ const ledgerPath = join(root, "users.json");
30
+ let ledger: Record<string, number> = {};
31
+ try {
32
+ ledger = JSON.parse(await readFile(ledgerPath, "utf8"));
33
+ } catch (error) {
34
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
35
+ }
36
+ if (
37
+ !Object.values(ledger).every(
38
+ (uid) => Number.isSafeInteger(uid) && uid >= 20000 && uid < 60000,
39
+ )
40
+ )
41
+ throw new Error("Invalid DSH user ledger");
42
+ const uid = ledger[key] ?? Math.max(19999, ...Object.values(ledger)) + 1;
43
+ if (uid >= 60000) throw new Error("DSH user namespace exhausted");
44
+ if (!ledger[key]) {
45
+ ledger[key] = uid;
46
+ await writeFile(ledgerPath, JSON.stringify(ledger), { mode: 0o600 });
47
+ }
48
+ const home = join(root, key);
49
+ const workspace = join(home, "workspace");
50
+ for (const path of [home, workspace]) {
51
+ await mkdir(path, { recursive: true, mode: 0o700 });
52
+ // Bind privileged mutations to this exact directory inode. A worker can
53
+ // rename its workspace but cannot redirect root chown through a symlink.
54
+ const handle = await open(
55
+ path,
56
+ constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
57
+ );
58
+ try {
59
+ if (!(await handle.stat()).isDirectory())
60
+ throw new Error("Invalid user directory");
61
+ await handle.chown(0, 0);
62
+ await handle.chmod(0o700);
63
+ await handle.chown(uid, uid);
64
+ } finally {
65
+ await handle.close();
66
+ }
67
+ }
68
+ return { uid, home, workspace };
69
+ }
70
+
71
+ /** Symlinks do not widen quota walks or disclose another actor's file tree. */
72
+ export async function diskUsage(
73
+ path: string,
74
+ budget: number,
75
+ observation?: { afterRootCaptured(): Promise<void> },
76
+ ): Promise<number> {
77
+ const root = await lstat(path);
78
+ if (!root.isDirectory()) throw new Error("Invalid DSH quota root");
79
+ // Allows a test to replace this exact observed inode before the walk starts.
80
+ if (observation) await observation.afterRootCaptured();
81
+ let size = 0;
82
+ let entries = 0;
83
+ const sameDirectory = (value: Awaited<ReturnType<typeof lstat>>) =>
84
+ value.isDirectory() && value.dev === root.dev && value.ino === root.ino;
85
+ const assertRoot = async () => {
86
+ if (!sameDirectory(await lstat(path)))
87
+ throw new Error("DSH quota root changed during inspection");
88
+ };
89
+ const walk = async (
90
+ directory: string,
91
+ isRoot = false,
92
+ depth = 0,
93
+ ): Promise<void> => {
94
+ if (depth > 64) throw new Error("DSH quota directory depth exceeded");
95
+ let handle;
96
+ try {
97
+ handle = await open(
98
+ directory,
99
+ constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
100
+ );
101
+ } catch (error) {
102
+ if (
103
+ !isRoot &&
104
+ ["ENOENT", "ELOOP", "ENOTDIR"].includes(
105
+ (error as NodeJS.ErrnoException).code ?? "",
106
+ )
107
+ )
108
+ return;
109
+ throw error;
110
+ }
111
+ try {
112
+ const identity = await handle.stat();
113
+ if (isRoot && !sameDirectory(identity))
114
+ throw new Error("DSH quota root changed during inspection");
115
+ // Production Linux walks the opened inode even when its pathname is renamed.
116
+ // Other platforms retain path traversal with explicit inode checks.
117
+ const bound =
118
+ process.platform === "linux" ? `/proc/self/fd/${handle.fd}` : directory;
119
+ let items;
120
+ try {
121
+ items = await readdir(bound, { withFileTypes: true });
122
+ } catch (error) {
123
+ if (!isRoot && (error as NodeJS.ErrnoException).code === "ENOENT")
124
+ return;
125
+ throw error;
126
+ }
127
+ if (process.platform !== "linux") {
128
+ let current;
129
+ try {
130
+ current = await lstat(directory);
131
+ } catch (error) {
132
+ if (!isRoot && (error as NodeJS.ErrnoException).code === "ENOENT")
133
+ return;
134
+ throw error;
135
+ }
136
+ if (
137
+ !current.isDirectory() ||
138
+ current.dev !== identity.dev ||
139
+ current.ino !== identity.ino
140
+ ) {
141
+ if (!isRoot) return; // The inspected child inode is no longer at this path.
142
+ throw new Error("DSH quota directory changed during inspection");
143
+ }
144
+ }
145
+ for (const item of items) {
146
+ if (++entries > 100_000)
147
+ throw new Error("DSH file count budget exceeded");
148
+ const child = join(bound, item.name);
149
+ let stat;
150
+ try {
151
+ stat = await lstat(child);
152
+ } catch (error) {
153
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
154
+ throw error;
155
+ }
156
+ if (stat.isSymbolicLink()) continue;
157
+ if (stat.isDirectory()) await walk(child, false, depth + 1);
158
+ else size += stat.size;
159
+ if (size > budget) return;
160
+ }
161
+ } finally {
162
+ await handle.close();
163
+ }
164
+ };
165
+ await walk(path, true);
166
+ await assertRoot();
167
+ return size;
168
+ }
169
+
170
+ /** Old Agent volumes may contain non-root-owned 0644 databases; traversal must not expose them. */
171
+ async function protectCoordinatorFiles(parent: string) {
172
+ for (const entry of await readdir(parent, { withFileTypes: true })) {
173
+ if (!entry.isFile()) continue;
174
+ const handle = await open(
175
+ join(parent, entry.name),
176
+ constants.O_RDONLY | constants.O_NOFOLLOW,
177
+ );
178
+ try {
179
+ const stat = await handle.stat();
180
+ if (stat.isFile()) {
181
+ await handle.chown(0, 0);
182
+ await handle.chmod(0o600);
183
+ }
184
+ } finally {
185
+ await handle.close();
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,89 @@
1
+ import { expect, test } from "bun:test";
2
+ import { EventEmitter } from "node:events";
3
+ import type { ChildProcess } from "node:child_process";
4
+ import { WorkbenchManager, type NativeWorker } from "./manager.js";
5
+ import { WorkbenchAuthorizationUnavailable } from "./authorization.js";
6
+ import { createBoundary } from "./boundary.js";
7
+
8
+ test("unavailable admission aborts an active model stream and releases its capacity without killing the worker", async () => {
9
+ const oldGlobal = process.env.AI_PROVIDER_MAX_CONCURRENT_RUNS;
10
+ const oldNative = process.env.AGENT_DSH_MAX_ACTIVE_RUNS;
11
+ process.env.AI_PROVIDER_MAX_CONCURRENT_RUNS = "2";
12
+ process.env.AGENT_DSH_MAX_ACTIVE_RUNS = "1";
13
+ let available = true;
14
+ const manager = new WorkbenchManager({
15
+ gateOrigin: "http://fixture",
16
+ authorize: async () => {
17
+ if (!available) throw new WorkbenchAuthorizationUnavailable("network");
18
+ return true;
19
+ },
20
+ });
21
+ const worker: NativeWorker = {
22
+ uid: 20000,
23
+ key: "fixture",
24
+ authority: {
25
+ actorId: "fixture",
26
+ session: "s".repeat(43),
27
+ publicOrigin: "http://fixture",
28
+ },
29
+ token: "t".repeat(43),
30
+ port: 0,
31
+ cookie: "",
32
+ home: "",
33
+ child: Object.assign(new EventEmitter(), {
34
+ exitCode: 0,
35
+ signalCode: null,
36
+ }) as ChildProcess,
37
+ controller: new AbortController(),
38
+ admissionController: new AbortController(),
39
+ };
40
+ manager.workers.set(worker.key, worker);
41
+ let calls = 0,
42
+ cancelled = 0;
43
+ let streamSignal: AbortSignal | undefined;
44
+ const boundary = createBoundary({
45
+ manager,
46
+ completion: async (request) => {
47
+ calls++;
48
+ streamSignal = request.signal;
49
+ return calls === 1
50
+ ? new Response(
51
+ new ReadableStream({
52
+ cancel() {
53
+ cancelled++;
54
+ },
55
+ }),
56
+ )
57
+ : new Response("recovered");
58
+ },
59
+ });
60
+ const request = () =>
61
+ new Request("http://fixture/internal/boundary/v1/chat/completions", {
62
+ method: "POST",
63
+ headers: { authorization: `Bearer ${worker.token}` },
64
+ body: "{}",
65
+ });
66
+ try {
67
+ const first = await boundary(request());
68
+ available = false;
69
+ await expect(manager.current(worker)).rejects.toBeInstanceOf(
70
+ WorkbenchAuthorizationUnavailable,
71
+ );
72
+ expect(streamSignal?.aborted).toBe(true);
73
+ expect((await first.body!.getReader().read()).done).toBe(true);
74
+ expect(cancelled).toBe(1);
75
+ expect(worker.controller.signal.aborted).toBe(false);
76
+ available = true;
77
+ const resumed = await boundary(request());
78
+ expect(await resumed.text()).toBe("recovered");
79
+ expect(calls).toBe(2);
80
+ expect(manager.workers.get(worker.key)).toBe(worker);
81
+ } finally {
82
+ await manager.close();
83
+ if (oldGlobal === undefined)
84
+ delete process.env.AI_PROVIDER_MAX_CONCURRENT_RUNS;
85
+ else process.env.AI_PROVIDER_MAX_CONCURRENT_RUNS = oldGlobal;
86
+ if (oldNative === undefined) delete process.env.AGENT_DSH_MAX_ACTIVE_RUNS;
87
+ else process.env.AGENT_DSH_MAX_ACTIVE_RUNS = oldNative;
88
+ }
89
+ });
@@ -0,0 +1,77 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { runProfile } from "@deepseek-ai/dsh/profile-boot";
5
+ import { createLaunchEnvironmentSnapshot } from "@deepseek-ai/dsh-launch-environment";
6
+ import { prepareNativeProfile, packageManager } from "./extensions.js";
7
+ import { nativeProfile } from "./profile.js";
8
+
9
+ async function start() {
10
+ const appRoot =
11
+ process.env.WINDY_APP_ROOT ??
12
+ resolve(dirname(fileURLToPath(import.meta.url)), "../..");
13
+ const home = process.env.DSH_HOME;
14
+ if (!home || !process.env.WINDY_GATE_URL || !process.env.WINDY_PUBLIC_URL)
15
+ throw new Error("Missing managed native worker environment");
16
+ await prepareNativeProfile(home);
17
+ // Recreate the application overlay, while keeping personal extension files intact.
18
+ const patch = join(home, "managed.patch.json");
19
+ await writeFile(
20
+ patch,
21
+ JSON.stringify(
22
+ nativeProfile({
23
+ appRoot,
24
+ gateUrl: process.env.WINDY_GATE_URL,
25
+ model: process.env.DSH_MODEL ?? "unconfigured",
26
+ contextWindow: Number(process.env.DSH_CONTEXT_WINDOW),
27
+ maxOutputTokens: Number(process.env.DSH_MAX_OUTPUT_TOKENS),
28
+ userExtensions: process.env.WINDY_USER_EXTENSIONS === "1",
29
+ }),
30
+ ),
31
+ { mode: 0o600 },
32
+ );
33
+ const values = Object.fromEntries(
34
+ Object.entries(process.env).filter(
35
+ (entry): entry is [string, string] => entry[1] !== undefined,
36
+ ),
37
+ );
38
+ const runtime = await runProfile({
39
+ environment: createLaunchEnvironmentSnapshot([
40
+ { source: "process", values },
41
+ ]),
42
+ profile: "web",
43
+ packageManager: packageManager(),
44
+ patchFiles: [patch],
45
+ args: [
46
+ "--host",
47
+ "127.0.0.1",
48
+ "--port",
49
+ "0",
50
+ "--no-open",
51
+ "--public-url",
52
+ process.env.WINDY_PUBLIC_URL,
53
+ "--trusted-host",
54
+ new URL(process.env.WINDY_PUBLIC_URL).host,
55
+ ],
56
+ });
57
+ await runtime.ctx.get("loader")?.await();
58
+ const port = runtime.ctx.get("webServer")?.port;
59
+ if (!port) throw new Error("DSH worker did not bind");
60
+ process.send?.({ type: "ready", port });
61
+ let closing = false;
62
+ const stop = async () => {
63
+ if (closing) return;
64
+ closing = true;
65
+ await runtime.shutdown.shutdown(0);
66
+ process.exit(0);
67
+ };
68
+ process.on("SIGTERM", () => void stop());
69
+ process.on("SIGINT", () => void stop());
70
+ process.on("disconnect", () => void stop());
71
+ }
72
+
73
+ start().catch(() => {
74
+ // Never log raw model, plugin, credential, or workspace error content.
75
+ process.send?.({ type: "failed" });
76
+ process.exit(1);
77
+ });