create-windy 0.3.2 → 0.3.5

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 (343) hide show
  1. package/README.md +27 -9
  2. package/dist/cli.js +825 -117
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +16 -3
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +4 -0
  7. package/template/README.md +18 -11
  8. package/template/_gitignore +1 -0
  9. package/template/apps/agent-server/README.md +2 -0
  10. package/template/apps/agent-server/pyproject.toml +8 -0
  11. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +87 -10
  12. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +18 -3
  13. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +78 -11
  14. package/template/apps/agent-server/src/southwind_agent_server/app.py +120 -115
  15. package/template/apps/agent-server/src/southwind_agent_server/artifact_input.py +153 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/config.py +29 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +81 -1
  18. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +8 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/engine.py +28 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/fingerprint.py +41 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/http_helpers.py +62 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/lease.py +15 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/multimodal.py +76 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +112 -20
  25. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +14 -1
  26. package/template/apps/agent-server/src/southwind_agent_server/postgres_store.py +329 -0
  27. package/template/apps/agent-server/src/southwind_agent_server/postgres_store_backend.py +332 -0
  28. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +64 -3
  29. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +68 -0
  30. package/template/apps/agent-server/src/southwind_agent_server/service.py +218 -56
  31. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +36 -0
  32. package/template/apps/agent-server/src/southwind_agent_server/sqlite_catalog.py +107 -0
  33. package/template/apps/agent-server/src/southwind_agent_server/sqlite_schema.py +65 -0
  34. package/template/apps/agent-server/src/southwind_agent_server/sqlite_serialization.py +16 -0
  35. package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +89 -57
  36. package/template/apps/agent-server/src/southwind_agent_server/store.py +105 -2
  37. package/template/apps/agent-server/src/southwind_agent_server/store_cipher.py +40 -0
  38. package/template/apps/agent-server/src/southwind_agent_server/telemetry.py +89 -0
  39. package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +72 -0
  40. package/template/apps/agent-server/tests/test_adk_adapter.py +6 -2
  41. package/template/apps/agent-server/tests/test_api.py +24 -1
  42. package/template/apps/agent-server/tests/test_artifact_input.py +110 -0
  43. package/template/apps/agent-server/tests/test_config.py +4 -0
  44. package/template/apps/agent-server/tests/test_fingerprint.py +10 -0
  45. package/template/apps/agent-server/tests/test_multimodal.py +51 -0
  46. package/template/apps/agent-server/tests/test_openai_provider.py +6 -2
  47. package/template/apps/agent-server/tests/test_postgres_store.py +131 -0
  48. package/template/apps/agent-server/tests/test_provider_governance.py +61 -0
  49. package/template/apps/agent-server/tests/test_sqlite_store.py +95 -10
  50. package/template/apps/agent-server/tests/test_telemetry.py +25 -0
  51. package/template/apps/agent-server/tests/test_tool_approval.py +28 -0
  52. package/template/apps/agent-server/uv.lock +165 -0
  53. package/template/apps/document-server/Dockerfile +19 -0
  54. package/template/apps/document-server/pyproject.toml +43 -0
  55. package/template/apps/document-server/src/southwind_document_server/__init__.py +1 -0
  56. package/template/apps/document-server/src/southwind_document_server/adapter.py +337 -0
  57. package/template/apps/document-server/src/southwind_document_server/app.py +151 -0
  58. package/template/apps/document-server/src/southwind_document_server/config.py +94 -0
  59. package/template/apps/document-server/src/southwind_document_server/contracts.py +103 -0
  60. package/template/apps/document-server/src/southwind_document_server/service.py +149 -0
  61. package/template/apps/document-server/src/southwind_document_server/store.py +185 -0
  62. package/template/apps/document-server/tests/__init__.py +1 -0
  63. package/template/apps/document-server/tests/test_config.py +42 -0
  64. package/template/apps/document-server/tests/test_service.py +140 -0
  65. package/template/apps/document-server/uv.lock +430 -0
  66. package/template/apps/embedding-server/Dockerfile +17 -0
  67. package/template/apps/embedding-server/pyproject.toml +44 -0
  68. package/template/apps/embedding-server/src/southwind_embedding_server/__init__.py +1 -0
  69. package/template/apps/embedding-server/src/southwind_embedding_server/adapter.py +254 -0
  70. package/template/apps/embedding-server/src/southwind_embedding_server/app.py +119 -0
  71. package/template/apps/embedding-server/src/southwind_embedding_server/config.py +120 -0
  72. package/template/apps/embedding-server/src/southwind_embedding_server/contracts.py +106 -0
  73. package/template/apps/embedding-server/src/southwind_embedding_server/error.py +6 -0
  74. package/template/apps/embedding-server/src/southwind_embedding_server/image_input.py +87 -0
  75. package/template/apps/embedding-server/tests/__init__.py +1 -0
  76. package/template/apps/embedding-server/tests/test_adapter.py +92 -0
  77. package/template/apps/embedding-server/tests/test_config.py +38 -0
  78. package/template/apps/embedding-server/uv.lock +449 -0
  79. package/template/apps/server/src/agent/background-operation-port.ts +37 -0
  80. package/template/apps/server/src/agent/background-operation.test.ts +231 -0
  81. package/template/apps/server/src/agent/background-operation.ts +252 -0
  82. package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +114 -0
  83. package/template/apps/server/src/agent/execution-grants.ts +7 -1
  84. package/template/apps/server/src/agent/fake-runtime.test-support.ts +43 -0
  85. package/template/apps/server/src/agent/idempotency-key.test.ts +28 -0
  86. package/template/apps/server/src/agent/idempotency-key.ts +11 -0
  87. package/template/apps/server/src/agent/remote-runtime.test.ts +115 -1
  88. package/template/apps/server/src/agent/remote-runtime.ts +173 -5
  89. package/template/apps/server/src/agent/run-authorization.test-support.ts +9 -0
  90. package/template/apps/server/src/agent/run-authorization.test.ts +126 -0
  91. package/template/apps/server/src/agent/run-authorization.ts +180 -0
  92. package/template/apps/server/src/agent/run-binding-repository.ts +93 -0
  93. package/template/apps/server/src/agent/run-contracts.ts +88 -1
  94. package/template/apps/server/src/agent/run-definition-support.ts +58 -0
  95. package/template/apps/server/src/agent/run-errors.ts +25 -0
  96. package/template/apps/server/src/agent/run-facade.test.ts +57 -46
  97. package/template/apps/server/src/agent/run-facade.ts +185 -65
  98. package/template/apps/server/src/agent/run-release.ts +78 -0
  99. package/template/apps/server/src/agent/run-route-input.ts +169 -0
  100. package/template/apps/server/src/agent/run-routes.ts +63 -76
  101. package/template/apps/server/src/agent/runtime-bootstrap.ts +169 -13
  102. package/template/apps/server/src/agent/sandbox-client.ts +200 -0
  103. package/template/apps/server/src/agent/sandbox-protocol.ts +76 -0
  104. package/template/apps/server/src/agent/sandbox-registry.ts +205 -0
  105. package/template/apps/server/src/agent/sandbox-runtime.test.ts +137 -0
  106. package/template/apps/server/src/agent/sandbox-worker-server.ts +195 -0
  107. package/template/apps/server/src/agent/sandbox-worker.ts +12 -0
  108. package/template/apps/server/src/agent/tool-approval.test.ts +75 -0
  109. package/template/apps/server/src/agent/tool-approval.ts +115 -0
  110. package/template/apps/server/src/agent/tool-audit.ts +62 -0
  111. package/template/apps/server/src/agent/tool-host.test.ts +39 -11
  112. package/template/apps/server/src/agent/tool-host.ts +184 -17
  113. package/template/apps/server/src/agent/tool-registry.ts +52 -47
  114. package/template/apps/server/src/agent/vision-facade.test.ts +256 -0
  115. package/template/apps/server/src/agent/vision-input.ts +120 -0
  116. package/template/apps/server/src/ai-artifact/codec.test.ts +44 -0
  117. package/template/apps/server/src/ai-artifact/codec.ts +137 -0
  118. package/template/apps/server/src/ai-artifact/routes.ts +37 -0
  119. package/template/apps/server/src/ai-artifact/runtime.ts +205 -0
  120. package/template/apps/server/src/application-services.ts +62 -2
  121. package/template/apps/server/src/auth/reauthentication.test.ts +40 -0
  122. package/template/apps/server/src/auth/reauthentication.ts +19 -0
  123. package/template/apps/server/src/auth/runtime.ts +9 -1
  124. package/template/apps/server/src/data-access/context.ts +6 -4
  125. package/template/apps/server/src/data-access/scope-snapshot.ts +27 -0
  126. package/template/apps/server/src/data-governance/export/approval-port.ts +6 -3
  127. package/template/apps/server/src/data-governance/export/authorization-port.test.ts +23 -4
  128. package/template/apps/server/src/data-governance/export/authorization-port.ts +89 -25
  129. package/template/apps/server/src/data-governance/export/drizzle-repository.integration.test.ts +2 -0
  130. package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -0
  131. package/template/apps/server/src/data-governance/export/job-runtime.test.ts +15 -4
  132. package/template/apps/server/src/data-governance/export/job-runtime.ts +68 -15
  133. package/template/apps/server/src/data-governance/export/ndjson-stream.ts +25 -6
  134. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +4 -2
  135. package/template/apps/server/src/data-governance/export/renderer-job-runtime.test.ts +235 -0
  136. package/template/apps/server/src/data-governance/export/renderer-runtime.ts +293 -0
  137. package/template/apps/server/src/data-governance/export/renderer-selection.test.ts +67 -0
  138. package/template/apps/server/src/data-governance/export/repository.test.ts +2 -0
  139. package/template/apps/server/src/data-governance/export/request-fingerprint.test.ts +13 -0
  140. package/template/apps/server/src/data-governance/export/request-fingerprint.ts +2 -0
  141. package/template/apps/server/src/data-governance/export/routes.ts +11 -1
  142. package/template/apps/server/src/data-governance/export/scope-snapshot.ts +6 -18
  143. package/template/apps/server/src/data-governance/export/service-support.ts +188 -0
  144. package/template/apps/server/src/data-governance/export/service.ts +90 -113
  145. package/template/apps/server/src/data-governance/export/source.ts +101 -0
  146. package/template/apps/server/src/data-governance/export/types.ts +27 -0
  147. package/template/apps/server/src/document/contracts.ts +76 -0
  148. package/template/apps/server/src/document/facade.ts +339 -0
  149. package/template/apps/server/src/document/remote-runtime.test.ts +96 -0
  150. package/template/apps/server/src/document/remote-runtime.ts +223 -0
  151. package/template/apps/server/src/document/routes.ts +193 -0
  152. package/template/apps/server/src/embedding/contracts.ts +83 -0
  153. package/template/apps/server/src/embedding/error.ts +10 -0
  154. package/template/apps/server/src/embedding/facade.ts +217 -0
  155. package/template/apps/server/src/embedding/input.ts +172 -0
  156. package/template/apps/server/src/embedding/remote-runtime.test.ts +66 -0
  157. package/template/apps/server/src/embedding/remote-runtime.ts +218 -0
  158. package/template/apps/server/src/embedding/routes.ts +152 -0
  159. package/template/apps/server/src/index.ts +40 -43
  160. package/template/apps/server/src/jobs/artifact-service.test.ts +14 -2
  161. package/template/apps/server/src/jobs/artifact-service.ts +10 -0
  162. package/template/apps/server/src/jobs/drizzle-execution-repository.ts +17 -18
  163. package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +19 -1
  164. package/template/apps/server/src/jobs/drizzle-mappers.ts +4 -1
  165. package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +49 -2
  166. package/template/apps/server/src/jobs/drizzle-repository.ts +1 -0
  167. package/template/apps/server/src/jobs/execution-repository.ts +17 -0
  168. package/template/apps/server/src/jobs/execution.test.ts +34 -0
  169. package/template/apps/server/src/jobs/in-memory-control.ts +135 -0
  170. package/template/apps/server/src/jobs/in-memory-execution.ts +34 -65
  171. package/template/apps/server/src/jobs/legacy-service-runtime.ts +5 -2
  172. package/template/apps/server/src/jobs/repository.ts +3 -0
  173. package/template/apps/server/src/jobs/worker.ts +80 -41
  174. package/template/apps/server/src/module-host/ai-operation-port.ts +170 -0
  175. package/template/apps/server/src/module-host/durable-context.test.ts +312 -0
  176. package/template/apps/server/src/module-host/durable-context.ts +124 -0
  177. package/template/apps/server/src/module-host/durable-handler-registration.ts +72 -0
  178. package/template/apps/server/src/module-host/governed-export-route-support.ts +171 -0
  179. package/template/apps/server/src/module-host/governed-export-routes.test.ts +327 -0
  180. package/template/apps/server/src/module-host/governed-export-routes.ts +238 -0
  181. package/template/apps/server/src/module-host/governed-renderer-registration.test.ts +247 -0
  182. package/template/apps/server/src/module-host/governed-resource-routes.ts +331 -0
  183. package/template/apps/server/src/module-host/governed-resources.test.ts +344 -0
  184. package/template/apps/server/src/module-host/governed-resources.ts +203 -0
  185. package/template/apps/server/src/module-host/http-runtime.ts +95 -0
  186. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  187. package/template/apps/server/src/module-host/initialize.test.ts +49 -1
  188. package/template/apps/server/src/module-host/initialize.ts +39 -16
  189. package/template/apps/server/src/module-host/job-port.test.ts +262 -0
  190. package/template/apps/server/src/module-host/job-port.ts +334 -0
  191. package/template/apps/server/src/module-host/request-context.test.ts +88 -2
  192. package/template/apps/server/src/module-host/request-context.ts +56 -22
  193. package/template/apps/server/src/module-host/route-installer.test.ts +10 -0
  194. package/template/apps/server/src/module-host/route-installer.ts +36 -1
  195. package/template/apps/server/src/module-host/sandbox-tool-registration.ts +42 -0
  196. package/template/apps/server/src/module-host/tool-handlers.ts +67 -31
  197. package/template/apps/server/src/module-host.ts +1 -0
  198. package/template/apps/server/src/module-runtime-validation.ts +7 -3
  199. package/template/apps/server/src/speech/contracts.ts +76 -0
  200. package/template/apps/server/src/speech/facade.ts +316 -0
  201. package/template/apps/server/src/speech/remote-runtime.test.ts +88 -0
  202. package/template/apps/server/src/speech/remote-runtime.ts +227 -0
  203. package/template/apps/server/src/speech/routes.ts +180 -0
  204. package/template/apps/server/src/system/actor.ts +39 -0
  205. package/template/apps/server/src/system/route-helpers.ts +1 -0
  206. package/template/apps/server/src/workflow/service.ts +2 -1
  207. package/template/apps/speech-server/Dockerfile +22 -0
  208. package/template/apps/speech-server/pyproject.toml +43 -0
  209. package/template/apps/speech-server/src/southwind_speech_server/__init__.py +1 -0
  210. package/template/apps/speech-server/src/southwind_speech_server/adapter.py +249 -0
  211. package/template/apps/speech-server/src/southwind_speech_server/app.py +183 -0
  212. package/template/apps/speech-server/src/southwind_speech_server/config.py +100 -0
  213. package/template/apps/speech-server/src/southwind_speech_server/contracts.py +103 -0
  214. package/template/apps/speech-server/src/southwind_speech_server/errors.py +6 -0
  215. package/template/apps/speech-server/src/southwind_speech_server/normalizer.py +108 -0
  216. package/template/apps/speech-server/src/southwind_speech_server/output_validation.py +128 -0
  217. package/template/apps/speech-server/src/southwind_speech_server/service.py +162 -0
  218. package/template/apps/speech-server/src/southwind_speech_server/store.py +209 -0
  219. package/template/apps/speech-server/tests/__init__.py +1 -0
  220. package/template/apps/speech-server/tests/test_config.py +33 -0
  221. package/template/apps/speech-server/tests/test_service.py +154 -0
  222. package/template/apps/speech-server/uv.lock +430 -0
  223. package/template/apps/web/src/app/business-routes.ts +22 -0
  224. package/template/apps/web/src/components/common/AccessSnapshotBoundary.vue +112 -0
  225. package/template/apps/web/src/components/common/AccessSnapshotBoundary.webtest.ts +130 -0
  226. package/template/apps/web/src/components/common/AccessSnapshotNotice.vue +117 -0
  227. package/template/apps/web/src/components/common/FormDialog.vue +6 -0
  228. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +13 -0
  229. package/template/apps/web/src/composables/access-snapshot-change.ts +131 -0
  230. package/template/apps/web/src/composables/access-snapshot-change.webtest.ts +103 -0
  231. package/template/apps/web/src/composables/useAccessSnapshot.ts +48 -2
  232. package/template/apps/web/src/composables/useAccessSnapshot.webtest.ts +54 -0
  233. package/template/apps/web/src/composables/useAccessSnapshotMonitor.ts +42 -0
  234. package/template/apps/web/src/layout/AppShell.vue +28 -25
  235. package/template/apps/web/src/layout/AppShell.webtest.ts +1 -0
  236. package/template/apps/web/src/router/business-routes.ts +68 -0
  237. package/template/apps/web/src/router/business-routes.webtest.ts +85 -0
  238. package/template/apps/web/src/router/index.ts +7 -0
  239. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  240. package/template/apps/web/src/services/access-api.ts +5 -1
  241. package/template/apps/web/src/services/access-api.webtest.ts +35 -0
  242. package/template/docker-compose.yml +127 -0
  243. package/template/docs/architecture/ai-egress.md +12 -9
  244. package/template/docs/architecture/ai-runtime.md +23 -11
  245. package/template/docs/architecture/durable-jobs.md +25 -3
  246. package/template/docs/architecture/foundation-infrastructure.md +163 -0
  247. package/template/docs/architecture/module-host.md +115 -0
  248. package/template/docs/architecture/server-module-interface.md +39 -0
  249. package/template/docs/architecture/web-access-snapshot-resilience.md +60 -0
  250. package/template/docs/development/business-web-routes.md +66 -0
  251. package/template/docs/development/custom-login-page.md +3 -3
  252. package/template/docs/development/module-lifecycle.md +71 -0
  253. package/template/docs/operations/observability.md +5 -1
  254. package/template/docs/platform/agent-runtime.md +21 -11
  255. package/template/docs/platform/agent-tool-sandbox.md +58 -0
  256. package/template/docs/platform/agent-tools.md +5 -0
  257. package/template/docs/platform/document-ai-runtime.md +204 -0
  258. package/template/docs/platform/governed-export-renderers.md +132 -0
  259. package/template/docs/platform/navigation-registry.md +5 -0
  260. package/template/docs/platform/organization-membership.md +2 -1
  261. package/template/docs/platform/speech-ai-runtime.md +156 -0
  262. package/template/docs/platform/system-crud-api.md +7 -1
  263. package/template/docs/platform/vision-ai-runtime.md +97 -0
  264. package/template/docs/platform/web-system-crud.md +6 -0
  265. package/template/package.json +16 -2
  266. package/template/packages/ai-client/index.ts +4 -0
  267. package/template/packages/ai-client/package.json +5 -2
  268. package/template/packages/ai-client/src/agent/client/artifact.ts +26 -0
  269. package/template/packages/ai-client/src/agent/client/attach.test.ts +124 -0
  270. package/template/packages/ai-client/src/agent/client/client.ts +119 -7
  271. package/template/packages/ai-client/src/agent/client/run.ts +14 -0
  272. package/template/packages/ai-client/src/agent/client/types.ts +18 -0
  273. package/template/packages/ai-client/src/agent/client/vision.test.ts +53 -0
  274. package/template/packages/ai-client/src/agent/contracts/contracts.test.ts +1 -0
  275. package/template/packages/ai-client/src/agent/contracts/events.ts +33 -0
  276. package/template/packages/ai-client/src/agent/contracts/runs.ts +71 -0
  277. package/template/packages/ai-client/src/agent/index.ts +1 -0
  278. package/template/packages/ai-client/src/artifact-ref.ts +8 -0
  279. package/template/packages/ai-client/src/document/client.test.ts +114 -0
  280. package/template/packages/ai-client/src/document/client.ts +134 -0
  281. package/template/packages/ai-client/src/document/contracts.ts +152 -0
  282. package/template/packages/ai-client/src/document/error.ts +43 -0
  283. package/template/packages/ai-client/src/document/index.ts +3 -0
  284. package/template/packages/ai-client/src/document/run.ts +104 -0
  285. package/template/packages/ai-client/src/document/validation.ts +115 -0
  286. package/template/packages/ai-client/src/embedding/client.test.ts +77 -0
  287. package/template/packages/ai-client/src/embedding/client.ts +96 -0
  288. package/template/packages/ai-client/src/embedding/contracts.ts +46 -0
  289. package/template/packages/ai-client/src/embedding/error.ts +61 -0
  290. package/template/packages/ai-client/src/embedding/index.ts +3 -0
  291. package/template/packages/ai-client/src/embedding/validation.ts +117 -0
  292. package/template/packages/ai-client/src/speech/client.test.ts +135 -0
  293. package/template/packages/ai-client/src/speech/client.ts +104 -0
  294. package/template/packages/ai-client/src/speech/contracts.ts +129 -0
  295. package/template/packages/ai-client/src/speech/error.ts +43 -0
  296. package/template/packages/ai-client/src/speech/index.ts +3 -0
  297. package/template/packages/ai-client/src/speech/run.ts +133 -0
  298. package/template/packages/ai-client/src/speech/validation.ts +132 -0
  299. package/template/packages/config/package.json +2 -2
  300. package/template/packages/database/drizzle/0039_early_ezekiel_stane.sql +1 -0
  301. package/template/packages/database/drizzle/0040_colossal_baron_strucker.sql +63 -0
  302. package/template/packages/database/drizzle/0041_yummy_prowler.sql +21 -0
  303. package/template/packages/database/drizzle/0042_puzzling_rumiko_fujikawa.sql +27 -0
  304. package/template/packages/database/drizzle/meta/0039_snapshot.json +7755 -0
  305. package/template/packages/database/drizzle/meta/0040_snapshot.json +8150 -0
  306. package/template/packages/database/drizzle/meta/0041_snapshot.json +8323 -0
  307. package/template/packages/database/drizzle/meta/0042_snapshot.json +8541 -0
  308. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  309. package/template/packages/database/package.json +2 -2
  310. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  311. package/template/packages/database/src/schema/agent-runtime.ts +149 -0
  312. package/template/packages/database/src/schema/common.ts +2 -14
  313. package/template/packages/database/src/schema/durable-jobs.ts +5 -0
  314. package/template/packages/database/src/schema/governed-exports.ts +17 -5
  315. package/template/packages/database/src/schema/index.ts +3 -0
  316. package/template/packages/database/src/schema-durable-recovery.test.ts +31 -0
  317. package/template/packages/database/src/schema-governed-exports.test.ts +21 -1
  318. package/template/packages/database/src/schema.test.ts +6 -23
  319. package/template/packages/jobs/package.json +3 -2
  320. package/template/packages/jobs/src/handlers.test.ts +13 -0
  321. package/template/packages/jobs/src/handlers.ts +7 -1
  322. package/template/packages/jobs/src/types.ts +4 -0
  323. package/template/packages/modules/package.json +2 -2
  324. package/template/packages/modules/src/ai-operation-composition.test-support.ts +165 -0
  325. package/template/packages/modules/src/ai-operation-composition.test.ts +91 -127
  326. package/template/packages/modules/src/ai-operation-composition.ts +59 -1
  327. package/template/packages/modules/src/manifest.ts +25 -1
  328. package/template/packages/modules/src/tool-composition.ts +18 -0
  329. package/template/packages/server-sdk/index.ts +50 -0
  330. package/template/packages/server-sdk/package.json +10 -4
  331. package/template/packages/server-sdk/src/ai-artifact-port.ts +34 -0
  332. package/template/packages/server-sdk/src/ai-operation-port.ts +65 -0
  333. package/template/packages/server-sdk/src/ai-operation-registration.ts +42 -1
  334. package/template/packages/server-sdk/src/background-ai-port.ts +43 -0
  335. package/template/packages/server-sdk/src/data-governance-registration.ts +116 -0
  336. package/template/packages/server-sdk/src/durable-handler.ts +29 -0
  337. package/template/packages/server-sdk/src/jobs-port.ts +69 -0
  338. package/template/packages/server-sdk/src/module-host.ts +16 -2
  339. package/template/packages/server-sdk/src/request-context.ts +6 -2
  340. package/template/packages/server-sdk/src/sandbox-tool.ts +21 -0
  341. package/template/packages/server-sdk/src/tool-registration.ts +2 -0
  342. package/template/packages/shared/package.json +1 -1
  343. package/template/packages/shared/src/rbac.ts +30 -0
@@ -0,0 +1,205 @@
1
+ import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import type {
3
+ InstallableServerModule,
4
+ ModuleSandboxToolHandlerRegistration,
5
+ ModuleToolInput,
6
+ ModuleToolResult,
7
+ } from "@southwind-ai/server-sdk";
8
+ import { requireModuleNamespace } from "@southwind-ai/server-sdk";
9
+
10
+ export class SandboxToolRegistry {
11
+ readonly #handlers: ReadonlyMap<string, SandboxedHandler>;
12
+
13
+ constructor(
14
+ installations: readonly InstallableServerModule<ModuleManifest>[],
15
+ ) {
16
+ const handlers = new Map<string, SandboxedHandler>();
17
+ for (const installation of installations) {
18
+ installation.setupSandbox?.({
19
+ moduleName: installation.manifest.name,
20
+ registerToolHandlers: (registrations) => {
21
+ for (const registration of registrations) {
22
+ register(installation.manifest, registration, handlers);
23
+ }
24
+ },
25
+ });
26
+ }
27
+ this.#handlers = handlers;
28
+ }
29
+
30
+ keys(): readonly string[] {
31
+ return [...this.#handlers.keys()].sort();
32
+ }
33
+
34
+ async execute(
35
+ toolKey: string,
36
+ input: ModuleToolInput,
37
+ idempotencyKey: string,
38
+ ): Promise<ModuleToolResult> {
39
+ const handler = this.#handlers.get(toolKey);
40
+ if (!handler) {
41
+ throw new SandboxExecutionError(
42
+ "SANDBOX_POLICY_DENIED",
43
+ "Tool 未注册为隔离执行",
44
+ false,
45
+ );
46
+ }
47
+ enforceBytes(input, handler.maxInputBytes, "SANDBOX_INPUT_INVALID");
48
+ const controller = new AbortController();
49
+ const timeout = setTimeout(
50
+ () => controller.abort("timeout"),
51
+ handler.maxDurationSeconds * 1_000,
52
+ );
53
+ try {
54
+ const result = await executeHandler(
55
+ handler,
56
+ input,
57
+ idempotencyKey,
58
+ controller.signal,
59
+ );
60
+ if (!isRecord(result)) {
61
+ throw new SandboxExecutionError(
62
+ "SANDBOX_HANDLER_FAILED",
63
+ "隔离 Tool 输出必须是 JSON 对象",
64
+ false,
65
+ );
66
+ }
67
+ enforceBytes(result, handler.maxOutputBytes, "SANDBOX_HANDLER_FAILED");
68
+ return structuredClone(result);
69
+ } catch (error) {
70
+ if (error instanceof SandboxExecutionError) throw error;
71
+ if (controller.signal.aborted) {
72
+ throw new SandboxExecutionError(
73
+ "SANDBOX_TIMEOUT",
74
+ "隔离 Tool 执行超时",
75
+ true,
76
+ );
77
+ }
78
+ if (isNetworkDenied(error)) {
79
+ throw new SandboxExecutionError(
80
+ "SANDBOX_POLICY_DENIED",
81
+ "隔离策略拒绝 Tool 出网",
82
+ false,
83
+ );
84
+ }
85
+ throw new SandboxExecutionError(
86
+ "SANDBOX_HANDLER_FAILED",
87
+ "隔离 Tool 业务执行失败",
88
+ false,
89
+ );
90
+ } finally {
91
+ clearTimeout(timeout);
92
+ }
93
+ }
94
+ }
95
+
96
+ async function executeHandler(
97
+ handler: SandboxedHandler,
98
+ input: ModuleToolInput,
99
+ idempotencyKey: string,
100
+ signal: AbortSignal,
101
+ ): Promise<ModuleToolResult> {
102
+ return new Promise((resolve, reject) => {
103
+ const abort = () => reject(signal.reason);
104
+ signal.addEventListener("abort", abort, { once: true });
105
+ Promise.resolve()
106
+ .then(() => handler.execute(input, { idempotencyKey, signal }))
107
+ .then(
108
+ (value) => {
109
+ signal.removeEventListener("abort", abort);
110
+ resolve(value);
111
+ },
112
+ (error: unknown) => {
113
+ signal.removeEventListener("abort", abort);
114
+ reject(error);
115
+ },
116
+ );
117
+ });
118
+ }
119
+
120
+ export class SandboxExecutionError extends Error {
121
+ constructor(
122
+ readonly code:
123
+ | "SANDBOX_POLICY_DENIED"
124
+ | "SANDBOX_INPUT_INVALID"
125
+ | "SANDBOX_HANDLER_FAILED"
126
+ | "SANDBOX_TIMEOUT",
127
+ message: string,
128
+ readonly retryable: boolean,
129
+ ) {
130
+ super(message);
131
+ }
132
+ }
133
+
134
+ interface SandboxedHandler extends ModuleSandboxToolHandlerRegistration {
135
+ readonly maxDurationSeconds: number;
136
+ readonly maxInputBytes: number;
137
+ readonly maxOutputBytes: number;
138
+ }
139
+
140
+ function register(
141
+ manifest: ModuleManifest,
142
+ registration: ModuleSandboxToolHandlerRegistration,
143
+ handlers: Map<string, SandboxedHandler>,
144
+ ) {
145
+ requireModuleNamespace(
146
+ "Sandbox Agent Tool",
147
+ registration.toolKey,
148
+ manifest.name,
149
+ );
150
+ const definition = manifest.tools.find(
151
+ ({ key }) => key === registration.toolKey,
152
+ );
153
+ if (!definition) {
154
+ throw new Error(
155
+ `Sandbox Agent Tool Handler 未在 Manifest 声明:${registration.toolKey}`,
156
+ );
157
+ }
158
+ if (handlers.has(registration.toolKey)) {
159
+ throw new Error(`Sandbox Agent Tool Handler 重复:${registration.toolKey}`);
160
+ }
161
+ handlers.set(registration.toolKey, {
162
+ ...registration,
163
+ maxDurationSeconds: definition.limits.maxDurationSeconds,
164
+ maxInputBytes: definition.limits.maxInputBytes,
165
+ maxOutputBytes: definition.limits.maxOutputBytes,
166
+ });
167
+ }
168
+
169
+ function enforceBytes(
170
+ value: ModuleToolInput | ModuleToolResult,
171
+ maximum: number,
172
+ code: "SANDBOX_INPUT_INVALID" | "SANDBOX_HANDLER_FAILED",
173
+ ) {
174
+ const bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
175
+ if (bytes > maximum) {
176
+ throw new SandboxExecutionError(
177
+ code,
178
+ `隔离 Tool JSON 超过 ${maximum} 字节`,
179
+ false,
180
+ );
181
+ }
182
+ }
183
+
184
+ function isNetworkDenied(error: unknown): boolean {
185
+ const code =
186
+ isRecord(error) && typeof error.code === "string"
187
+ ? error.code
188
+ : isRecord(error) &&
189
+ isRecord(error.cause) &&
190
+ typeof error.cause.code === "string"
191
+ ? error.cause.code
192
+ : "";
193
+ return [
194
+ "EAI_AGAIN",
195
+ "ENETDOWN",
196
+ "ENETUNREACH",
197
+ "EHOSTUNREACH",
198
+ "EPERM",
199
+ "EACCES",
200
+ ].includes(code);
201
+ }
202
+
203
+ function isRecord(value: unknown): value is Record<string, unknown> {
204
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
205
+ }
@@ -0,0 +1,137 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type { ModuleManifest } from "@southwind-ai/modules";
6
+ import type { InstallableServerModule } from "@southwind-ai/server-sdk";
7
+ import { UnixSandboxToolClient } from "./sandbox-client.js";
8
+ import { SandboxToolRegistry } from "./sandbox-registry.js";
9
+ import { startSandboxWorker } from "./sandbox-worker-server.js";
10
+
11
+ const roots: string[] = [];
12
+
13
+ afterEach(async () => {
14
+ await Promise.all(
15
+ roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
16
+ );
17
+ });
18
+
19
+ describe("Tool Sandbox Runtime", () => {
20
+ test("只通过 UDS 和短时 Grant 执行显式隔离 Handler", async () => {
21
+ const root = await temporaryRoot();
22
+ const socketPath = join(root, "worker.sock");
23
+ const registry = new SandboxToolRegistry([sandboxModule()]);
24
+ const worker = await startSandboxWorker(socketPath, registry);
25
+ const client = new UnixSandboxToolClient(socketPath);
26
+
27
+ await expect(
28
+ client.execute(
29
+ "business.safe.echo",
30
+ { value: "hello" },
31
+ "call-1",
32
+ new AbortController().signal,
33
+ ),
34
+ ).resolves.toEqual({ value: "hello", idempotencyKey: "call-1" });
35
+
36
+ await worker.close();
37
+ });
38
+
39
+ test("网络系统调用失败与业务异常使用不同稳定错误", async () => {
40
+ const registry = new SandboxToolRegistry([sandboxModule()]);
41
+
42
+ await expect(
43
+ registry.execute("business.safe.network", {}, "call-network"),
44
+ ).rejects.toMatchObject({
45
+ code: "SANDBOX_POLICY_DENIED",
46
+ message: "隔离策略拒绝 Tool 出网",
47
+ });
48
+ await expect(
49
+ registry.execute("business.safe.failure", {}, "call-failure"),
50
+ ).rejects.toMatchObject({
51
+ code: "SANDBOX_HANDLER_FAILED",
52
+ message: "隔离 Tool 业务执行失败",
53
+ });
54
+ });
55
+ });
56
+
57
+ function sandboxModule(): InstallableServerModule<ModuleManifest> {
58
+ const manifest = sandboxManifest();
59
+ return {
60
+ manifest,
61
+ setup() {},
62
+ setupSandbox(host) {
63
+ host.registerToolHandlers([
64
+ {
65
+ toolKey: "business.safe.echo",
66
+ execute(input, context) {
67
+ return {
68
+ value: input.value ?? null,
69
+ idempotencyKey: context.idempotencyKey,
70
+ };
71
+ },
72
+ },
73
+ {
74
+ toolKey: "business.safe.network",
75
+ execute() {
76
+ throw Object.assign(new Error("blocked"), {
77
+ code: "ENETUNREACH",
78
+ });
79
+ },
80
+ },
81
+ {
82
+ toolKey: "business.safe.failure",
83
+ execute() {
84
+ throw new Error("private business detail");
85
+ },
86
+ },
87
+ ]);
88
+ },
89
+ };
90
+ }
91
+
92
+ function sandboxManifest(): ModuleManifest {
93
+ const tools = ["echo", "network", "failure"].map((name) => ({
94
+ key: `business.safe.${name}`,
95
+ label: name,
96
+ description: name,
97
+ inputSchemaKey: "business.safe.input.v1",
98
+ outputSchemaKey: "business.safe.output.v1",
99
+ operation: "query" as const,
100
+ permissionKey: "business.safe.read",
101
+ featureKey: "business.safe",
102
+ auditAction: "business.safe.execute",
103
+ scopeMode: "request" as const,
104
+ dataClassification: "internal" as const,
105
+ idempotency: "supported" as const,
106
+ approval: "none" as const,
107
+ limits: {
108
+ maxDurationSeconds: 5,
109
+ maxInputBytes: 4_096,
110
+ maxOutputBytes: 4_096,
111
+ },
112
+ }));
113
+ return {
114
+ name: "business",
115
+ version: "1.0.0",
116
+ label: "业务",
117
+ description: "业务",
118
+ menus: [],
119
+ adminRoutes: [],
120
+ permissions: [],
121
+ apiPermissions: [],
122
+ features: [],
123
+ migrations: [],
124
+ schemaExports: [],
125
+ jsonSchemas: [],
126
+ tasks: [],
127
+ tools,
128
+ providers: [],
129
+ auditActions: [],
130
+ };
131
+ }
132
+
133
+ async function temporaryRoot() {
134
+ const root = await mkdtemp(join(tmpdir(), "windy-tool-sandbox-"));
135
+ roots.push(root);
136
+ return root;
137
+ }
@@ -0,0 +1,195 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdir, unlink, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { createServer, type Socket } from "node:net";
5
+ import {
6
+ type SandboxToolFailure,
7
+ type SandboxToolRequest,
8
+ type SandboxToolResponse,
9
+ validSandboxGrant,
10
+ } from "./sandbox-protocol.js";
11
+ import {
12
+ SandboxExecutionError,
13
+ type SandboxToolRegistry,
14
+ } from "./sandbox-registry.js";
15
+
16
+ const maxMessageBytes = 2 * 1_024 * 1_024;
17
+
18
+ export async function startSandboxWorker(
19
+ socketPath: string,
20
+ registry: SandboxToolRegistry,
21
+ ) {
22
+ assertSocketPath(socketPath);
23
+ await mkdir(dirname(socketPath), { recursive: true });
24
+ await removeIfPresent(socketPath);
25
+ const key = randomBytes(32);
26
+ await writeFile(`${socketPath}.key`, key, { mode: 0o600 });
27
+ const server = createServer((socket) => consume(socket, registry, key));
28
+ await new Promise<void>((resolve, reject) => {
29
+ server.once("error", reject);
30
+ server.listen(socketPath, () => {
31
+ server.off("error", reject);
32
+ resolve();
33
+ });
34
+ });
35
+ return {
36
+ async close() {
37
+ await new Promise<void>((resolve, reject) => {
38
+ server.close((error) => (error ? reject(error) : resolve()));
39
+ });
40
+ await Promise.all([
41
+ removeIfPresent(socketPath),
42
+ removeIfPresent(`${socketPath}.key`),
43
+ ]);
44
+ },
45
+ };
46
+ }
47
+
48
+ export async function runSandboxWorkerUntilSignal(
49
+ socketPath: string,
50
+ registry: SandboxToolRegistry,
51
+ ) {
52
+ const worker = await startSandboxWorker(socketPath, registry);
53
+ await new Promise<void>((resolve) => {
54
+ process.once("SIGINT", resolve);
55
+ process.once("SIGTERM", resolve);
56
+ });
57
+ await worker.close();
58
+ }
59
+
60
+ function consume(
61
+ socket: Socket,
62
+ registry: SandboxToolRegistry,
63
+ key: Uint8Array,
64
+ ) {
65
+ let body = "";
66
+ socket.setEncoding("utf8");
67
+ socket.on("data", (chunk: string) => {
68
+ body += chunk;
69
+ if (Buffer.byteLength(body) > maxMessageBytes) {
70
+ socket.end(
71
+ response({
72
+ invocationId: "unknown",
73
+ ok: false,
74
+ code: "SANDBOX_INPUT_INVALID",
75
+ message: "隔离 Tool 请求超过上限",
76
+ retryable: false,
77
+ }),
78
+ );
79
+ return;
80
+ }
81
+ const newline = body.indexOf("\n");
82
+ if (newline < 0) return;
83
+ const line = body.slice(0, newline);
84
+ body = "";
85
+ void handle(line, registry, key).then((value) =>
86
+ socket.end(response(value)),
87
+ );
88
+ });
89
+ }
90
+
91
+ async function handle(
92
+ line: string,
93
+ registry: SandboxToolRegistry,
94
+ key: Uint8Array,
95
+ ): Promise<SandboxToolResponse> {
96
+ let request: SandboxToolRequest;
97
+ try {
98
+ request = parseRequest(JSON.parse(line));
99
+ } catch {
100
+ return failure("unknown", "SANDBOX_INPUT_INVALID", "隔离 Tool 请求无效");
101
+ }
102
+ if (
103
+ request.deadline <= Date.now() ||
104
+ request.deadline > Date.now() + 10 * 60_000 ||
105
+ !validSandboxGrant(request, key)
106
+ ) {
107
+ return failure(
108
+ request.invocationId,
109
+ "SANDBOX_AUTH_INVALID",
110
+ "隔离 Tool 调用授权无效",
111
+ );
112
+ }
113
+ try {
114
+ return {
115
+ invocationId: request.invocationId,
116
+ ok: true,
117
+ result: await registry.execute(
118
+ request.toolKey,
119
+ request.input,
120
+ request.idempotencyKey,
121
+ ),
122
+ };
123
+ } catch (error) {
124
+ if (error instanceof SandboxExecutionError) {
125
+ return {
126
+ invocationId: request.invocationId,
127
+ ok: false,
128
+ code: error.code,
129
+ message: error.message,
130
+ retryable: error.retryable,
131
+ };
132
+ }
133
+ return failure(
134
+ request.invocationId,
135
+ "SANDBOX_HANDLER_FAILED",
136
+ "隔离 Tool 执行失败",
137
+ );
138
+ }
139
+ }
140
+
141
+ function parseRequest(value: unknown): SandboxToolRequest {
142
+ if (
143
+ !isRecord(value) ||
144
+ Object.keys(value).some(
145
+ (key) =>
146
+ ![
147
+ "invocationId",
148
+ "toolKey",
149
+ "idempotencyKey",
150
+ "deadline",
151
+ "input",
152
+ "grant",
153
+ ].includes(key),
154
+ ) ||
155
+ typeof value.invocationId !== "string" ||
156
+ typeof value.toolKey !== "string" ||
157
+ typeof value.idempotencyKey !== "string" ||
158
+ !Number.isSafeInteger(value.deadline) ||
159
+ !isRecord(value.input) ||
160
+ typeof value.grant !== "string"
161
+ ) {
162
+ throw new Error("invalid request");
163
+ }
164
+ return value as unknown as SandboxToolRequest;
165
+ }
166
+
167
+ function failure(
168
+ invocationId: string,
169
+ code: SandboxToolFailure["code"],
170
+ message: string,
171
+ ): SandboxToolFailure {
172
+ return { invocationId, ok: false, code, message, retryable: false };
173
+ }
174
+
175
+ function response(value: SandboxToolResponse) {
176
+ return `${JSON.stringify(value)}\n`;
177
+ }
178
+
179
+ function assertSocketPath(value: string) {
180
+ if (!value.startsWith("/") || value.includes("..") || value.length > 180) {
181
+ throw new Error("Tool Sandbox Socket 路径无效");
182
+ }
183
+ }
184
+
185
+ async function removeIfPresent(path: string) {
186
+ try {
187
+ await unlink(path);
188
+ } catch (error) {
189
+ if (!isRecord(error) || error.code !== "ENOENT") throw error;
190
+ }
191
+ }
192
+
193
+ function isRecord(value: unknown): value is Record<string, unknown> {
194
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
195
+ }
@@ -0,0 +1,12 @@
1
+ import { installedBusinessModules } from "../installed-business-modules.js";
2
+ import { SandboxToolRegistry } from "./sandbox-registry.js";
3
+ import { runSandboxWorkerUntilSignal } from "./sandbox-worker-server.js";
4
+
5
+ const socketPath =
6
+ process.env.TOOL_SANDBOX_SOCKET?.trim() ||
7
+ "/run/windy-tool-sandbox/worker.sock";
8
+
9
+ await runSandboxWorkerUntilSignal(
10
+ socketPath,
11
+ new SandboxToolRegistry(installedBusinessModules),
12
+ );
@@ -0,0 +1,75 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { ModuleAgentToolInvocation } from "@southwind-ai/server-sdk";
3
+ import type { RequestActor, RequestGuardContext } from "../guards.js";
4
+ import { InMemoryWorkflowRepository } from "../workflow/repository.js";
5
+ import { WorkflowService } from "../workflow/service.js";
6
+ import {
7
+ AgentToolApprovalCoordinator,
8
+ AgentToolApprovalPending,
9
+ } from "./tool-approval.js";
10
+
11
+ const actor: RequestActor = {
12
+ id: "actor-1",
13
+ type: "user",
14
+ name: "复核员",
15
+ roleCodes: ["case-reviewer"],
16
+ permissionKeys: [],
17
+ };
18
+ const context: RequestGuardContext = {
19
+ requestId: "request-1",
20
+ actor,
21
+ startedAt: "2026-07-27T00:00:00.000Z",
22
+ };
23
+ const invocation: ModuleAgentToolInvocation = {
24
+ runId: "run-1",
25
+ toolCallId: "tool-1",
26
+ toolKey: "case.finding.merge",
27
+ arguments: { expectedVersion: 3 },
28
+ };
29
+ const tool = {
30
+ key: "case.finding.merge",
31
+ label: "归并线索",
32
+ approval: "required" as const,
33
+ approvalRoles: [{ code: "case-reviewer", name: "案件复核员" }],
34
+ approvalTimeoutSeconds: 3_600,
35
+ };
36
+
37
+ describe("Agent Tool 审批协调器", () => {
38
+ test("副作用前等待持久化 Work Item,批准后复用同一审批", async () => {
39
+ const workflow = new WorkflowService(
40
+ new InMemoryWorkflowRepository(),
41
+ { resolve: async () => undefined },
42
+ () => new Date("2026-07-27T00:00:00.000Z"),
43
+ );
44
+ const coordinator = new AgentToolApprovalCoordinator(workflow);
45
+ let workItemId = "";
46
+
47
+ try {
48
+ await coordinator.requireApproved(tool, invocation, context);
49
+ } catch (error) {
50
+ expect(error).toBeInstanceOf(AgentToolApprovalPending);
51
+ workItemId = (error as AgentToolApprovalPending).workItemId;
52
+ }
53
+
54
+ const pending = await workflow.get(workItemId, actor);
55
+ expect(pending?.target.id).toBe("run-1:tool-1");
56
+ const claimed = await workflow.claim(
57
+ workItemId,
58
+ { expectedVersion: pending!.version, idempotencyKey: "claim-1" },
59
+ actor,
60
+ );
61
+ await workflow.approve(
62
+ workItemId,
63
+ {
64
+ expectedVersion: claimed.workItem.version,
65
+ idempotencyKey: "approve-1",
66
+ reason: "事实与版本均已核对",
67
+ },
68
+ actor,
69
+ );
70
+
71
+ await expect(
72
+ coordinator.requireApproved(tool, invocation, context),
73
+ ).resolves.toBe(workItemId);
74
+ });
75
+ });