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,329 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import time
6
+ import uuid
7
+
8
+ from .contracts import AgentError, JsonValue
9
+ from .postgres_store_backend import PostgresStoreBackend
10
+ from .sqlite_store import RunStoreCapacityError
11
+ from .store import (
12
+ CanonicalEvent,
13
+ IdempotencyConflictError,
14
+ RunRecord,
15
+ status_for,
16
+ )
17
+ from .store_cipher import RunStoreCipher
18
+
19
+
20
+ class PostgresRunStore:
21
+ """Multi-replica Run store with encrypted payloads and renewable leases."""
22
+
23
+ def __init__(
24
+ self,
25
+ database_url: str,
26
+ cipher: RunStoreCipher,
27
+ *,
28
+ max_runs: int = 100_000,
29
+ max_events_per_run: int = 2_000,
30
+ max_output_bytes: int = 16 * 1024 * 1024,
31
+ terminal_ttl_seconds: int = 7 * 24 * 60 * 60,
32
+ lease_seconds: int = 30,
33
+ ) -> None:
34
+ self._cipher = cipher
35
+ self._max_runs = max_runs
36
+ self._max_events = max_events_per_run
37
+ self._max_output_bytes = max_output_bytes
38
+ self._terminal_ttl = terminal_ttl_seconds
39
+ self._lease_seconds = lease_seconds
40
+ self._owner = f"agent_{uuid.uuid4().hex}"
41
+ self._backend = PostgresStoreBackend(
42
+ database_url,
43
+ cipher,
44
+ self._owner,
45
+ terminal_ttl_seconds,
46
+ )
47
+ self._pool = self._backend.pool
48
+
49
+ async def create_or_get(
50
+ self,
51
+ record: RunRecord,
52
+ invocation: JsonValue = None,
53
+ ) -> tuple[RunRecord, bool]:
54
+ await self._backend.ready()
55
+ async with self._pool.connection() as connection:
56
+ async with connection.transaction():
57
+ await connection.execute("select pg_advisory_xact_lock(74123001)")
58
+ await self._backend.prune(connection)
59
+ current = await self._backend.by_idempotency(
60
+ connection,
61
+ record.idempotency_key,
62
+ )
63
+ if current:
64
+ if current.fingerprint != record.fingerprint:
65
+ raise IdempotencyConflictError
66
+ return current, False
67
+ count = await connection.execute(
68
+ "select count(*) as value from agent_runtime_runs"
69
+ )
70
+ row = await count.fetchone()
71
+ if not row:
72
+ raise RuntimeError("Agent Run Store 容量查询失败")
73
+ if int(row["value"]) >= self._max_runs:
74
+ raise RunStoreCapacityError("Agent Run Store 容量已满")
75
+ release_id = await self._backend.ensure_release(
76
+ connection,
77
+ record,
78
+ )
79
+ now = time.time()
80
+ inserted = await connection.execute(
81
+ """
82
+ insert into agent_runtime_runs (
83
+ run_id, operation_key, idempotency_key, fingerprint,
84
+ status, release_id, release_json, trace_id, invocation_cipher,
85
+ lease_owner, lease_until,
86
+ created_at, updated_at, expires_at
87
+ ) values (
88
+ %s, %s, %s, %s, 'accepted', %s, %s, %s, %s, %s,
89
+ extract(epoch from clock_timestamp()) + %s, %s, %s, %s
90
+ )
91
+ on conflict (idempotency_key) do nothing
92
+ returning run_id
93
+ """,
94
+ (
95
+ record.run_id,
96
+ record.operation_key,
97
+ record.idempotency_key,
98
+ record.fingerprint,
99
+ release_id,
100
+ json.dumps(record.release, ensure_ascii=False),
101
+ record.trace_id,
102
+ self._cipher.seal(invocation)
103
+ if invocation is not None
104
+ else None,
105
+ self._owner,
106
+ self._lease_seconds,
107
+ now,
108
+ now,
109
+ now + self._terminal_ttl,
110
+ ),
111
+ )
112
+ if await inserted.fetchone() is None:
113
+ current = await self._backend.by_idempotency(
114
+ connection,
115
+ record.idempotency_key,
116
+ )
117
+ if current is None or current.fingerprint != record.fingerprint:
118
+ raise IdempotencyConflictError
119
+ return current, False
120
+ return record, True
121
+
122
+ async def get(self, run_id: str) -> RunRecord | None:
123
+ await self._backend.ready()
124
+ async with self._pool.connection() as connection:
125
+ return await self._backend.read(connection, run_id)
126
+
127
+ async def append(
128
+ self,
129
+ run_id: str,
130
+ event_type: str,
131
+ fields: dict[str, object] | None = None,
132
+ ) -> CanonicalEvent | None:
133
+ await self._backend.ready()
134
+ async with self._pool.connection() as connection:
135
+ async with connection.transaction():
136
+ record = await self._backend.locked(connection, run_id)
137
+ if record.terminal:
138
+ return None
139
+ sequence = len(record.events) + 1
140
+ if sequence >= self._max_events and not event_type.startswith("run."):
141
+ event_type = "run.failed"
142
+ fields = failure_fields(
143
+ sequence,
144
+ "Agent 事件超过安全上限",
145
+ )
146
+ event: CanonicalEvent = {
147
+ "type": event_type,
148
+ "runId": run_id,
149
+ "sequence": sequence,
150
+ **(fields or {}),
151
+ }
152
+ await self._backend.insert_event(
153
+ connection,
154
+ run_id,
155
+ event,
156
+ status_for(event_type, record.status),
157
+ )
158
+ return event
159
+
160
+ async def complete(self, run_id: str, output: JsonValue) -> None:
161
+ encoded = json.dumps(output, ensure_ascii=False, separators=(",", ":"))
162
+ await self._backend.ready()
163
+ async with self._pool.connection() as connection:
164
+ async with connection.transaction():
165
+ record = await self._backend.locked(connection, run_id)
166
+ if record.terminal:
167
+ return
168
+ sequence = len(record.events) + 1
169
+ if len(encoded.encode()) > self._max_output_bytes:
170
+ event: CanonicalEvent = {
171
+ "type": "run.failed",
172
+ "runId": run_id,
173
+ "sequence": sequence,
174
+ **failure_fields(sequence, "Agent 输出超过安全上限"),
175
+ }
176
+ await self._backend.insert_event(
177
+ connection,
178
+ run_id,
179
+ event,
180
+ "failed",
181
+ )
182
+ return
183
+ event = {
184
+ "type": "run.completed",
185
+ "runId": run_id,
186
+ "sequence": sequence,
187
+ }
188
+ await self._backend.insert_event(
189
+ connection,
190
+ run_id,
191
+ event,
192
+ "completed",
193
+ self._cipher.seal(output),
194
+ )
195
+
196
+ async def events_after(
197
+ self,
198
+ run_id: str,
199
+ after: int,
200
+ wait_seconds: float,
201
+ ) -> tuple[list[CanonicalEvent], bool]:
202
+ deadline = time.monotonic() + wait_seconds
203
+ while True:
204
+ record = await self.get(run_id)
205
+ if record is None:
206
+ raise KeyError(run_id)
207
+ events = [event for event in record.events if sequence_of(event) > after]
208
+ if events or record.terminal or time.monotonic() >= deadline:
209
+ return events, record.terminal
210
+ await asyncio.sleep(min(0.5, max(0, deadline - time.monotonic())))
211
+
212
+ async def save_invocation(self, run_id: str, value: JsonValue) -> None:
213
+ await self._backend.update_run(
214
+ run_id,
215
+ "invocation_cipher = %s",
216
+ (self._cipher.seal(value),),
217
+ )
218
+
219
+ async def recoverable(self) -> list[tuple[RunRecord, JsonValue]]:
220
+ await self._backend.ready()
221
+ async with self._pool.connection() as connection:
222
+ async with connection.transaction():
223
+ cursor = await connection.execute(
224
+ """
225
+ select run_id from agent_runtime_runs
226
+ where status not in ('completed', 'failed', 'cancelled')
227
+ and invocation_cipher is not null
228
+ and (
229
+ lease_until < extract(epoch from clock_timestamp())
230
+ or lease_owner = %s
231
+ )
232
+ order by created_at for update skip locked limit 20
233
+ """,
234
+ (self._owner,),
235
+ )
236
+ rows = await cursor.fetchall()
237
+ result: list[tuple[RunRecord, JsonValue]] = []
238
+ for row in rows:
239
+ run_id = str(row["run_id"])
240
+ await connection.execute(
241
+ """
242
+ update agent_runtime_runs
243
+ set lease_owner = %s,
244
+ lease_until = extract(epoch from clock_timestamp()) + %s
245
+ where run_id = %s
246
+ """,
247
+ (self._owner, self._lease_seconds, run_id),
248
+ )
249
+ record = await self._backend.read(connection, run_id)
250
+ cipher = await connection.execute(
251
+ """
252
+ select invocation_cipher from agent_runtime_runs
253
+ where run_id = %s
254
+ """,
255
+ (run_id,),
256
+ )
257
+ value = await cipher.fetchone()
258
+ if record and value:
259
+ result.append(
260
+ (
261
+ record,
262
+ self._cipher.open(str(value["invocation_cipher"])),
263
+ )
264
+ )
265
+ return result
266
+
267
+ async def clear_invocation(self, run_id: str) -> None:
268
+ await self._backend.update_run(
269
+ run_id,
270
+ "invocation_cipher = null",
271
+ (),
272
+ )
273
+
274
+ async def renew_lease(self, run_id: str) -> None:
275
+ await self._backend.update_run(
276
+ run_id,
277
+ "lease_until = extract(epoch from clock_timestamp()) + %s",
278
+ (self._lease_seconds,),
279
+ owner_required=True,
280
+ active_required=True,
281
+ )
282
+
283
+ async def release_lease(self, run_id: str) -> None:
284
+ await self._backend.update_run(
285
+ run_id,
286
+ "lease_owner = null, lease_until = 0",
287
+ (),
288
+ owner_required=True,
289
+ )
290
+
291
+ async def record_evaluation(
292
+ self,
293
+ release_id: str,
294
+ evaluation_set_key: str,
295
+ evaluation_set_version: str,
296
+ metrics: dict[str, float],
297
+ ) -> str:
298
+ return await self._backend.record_evaluation(
299
+ release_id,
300
+ evaluation_set_key,
301
+ evaluation_set_version,
302
+ metrics,
303
+ )
304
+
305
+ async def close(self) -> None:
306
+ await self._backend.close()
307
+
308
+ async def probe(self) -> None:
309
+ await self._backend.ready()
310
+ async with self._pool.connection() as connection:
311
+ await connection.execute("select 1")
312
+
313
+
314
+ def sequence_of(event: CanonicalEvent) -> int:
315
+ value = event.get("sequence")
316
+ if not isinstance(value, int):
317
+ raise RuntimeError("Agent Event sequence 无效")
318
+ return value
319
+
320
+
321
+ def failure_fields(sequence: int, message: str) -> dict[str, object]:
322
+ return {
323
+ "error": AgentError(
324
+ code="BUDGET_EXCEEDED",
325
+ message=message,
326
+ request_id=f"request_store_{sequence}",
327
+ retryable=False,
328
+ ).model_dump(by_alias=True)
329
+ }
@@ -0,0 +1,332 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import time
6
+ import uuid
7
+ from typing import Any, cast
8
+
9
+ from psycopg import AsyncConnection
10
+ from psycopg.rows import dict_row
11
+ from psycopg_pool import AsyncConnectionPool
12
+
13
+ from .contracts import JsonValue, RunStatus
14
+ from .store import (
15
+ CanonicalEvent,
16
+ IdempotencyConflictError,
17
+ ReleaseNotFoundError,
18
+ RunRecord,
19
+ )
20
+ from .store_cipher import RunStoreCipher
21
+
22
+ type DatabaseConnection = AsyncConnection[dict[str, Any]]
23
+
24
+
25
+ class PostgresStoreBackend:
26
+ def __init__(
27
+ self,
28
+ database_url: str,
29
+ cipher: RunStoreCipher,
30
+ owner: str,
31
+ terminal_ttl_seconds: int,
32
+ ) -> None:
33
+ self.pool = cast(
34
+ AsyncConnectionPool[DatabaseConnection],
35
+ AsyncConnectionPool(
36
+ database_url,
37
+ min_size=1,
38
+ max_size=10,
39
+ open=False,
40
+ kwargs={"row_factory": dict_row},
41
+ ),
42
+ )
43
+ self.cipher = cipher
44
+ self.owner = owner
45
+ self.terminal_ttl = terminal_ttl_seconds
46
+ self._open_lock = asyncio.Lock()
47
+ self._opened = False
48
+
49
+ async def ready(self) -> None:
50
+ if self._opened:
51
+ return
52
+ async with self._open_lock:
53
+ if not self._opened:
54
+ await self.pool.open()
55
+ self._opened = True
56
+
57
+ async def close(self) -> None:
58
+ if self._opened:
59
+ await self.pool.close()
60
+
61
+ async def read(
62
+ self,
63
+ connection: DatabaseConnection,
64
+ run_id: str,
65
+ ) -> RunRecord | None:
66
+ cursor = await connection.execute(
67
+ "select * from agent_runtime_runs where run_id = %s",
68
+ (run_id,),
69
+ )
70
+ row = await cursor.fetchone()
71
+ if not row:
72
+ return None
73
+ events_cursor = await connection.execute(
74
+ """
75
+ select event_cipher from agent_runtime_events
76
+ where run_id = %s order by sequence
77
+ """,
78
+ (run_id,),
79
+ )
80
+ events = [
81
+ cast(CanonicalEvent, self.cipher.open(str(item["event_cipher"])))
82
+ for item in await events_cursor.fetchall()
83
+ ]
84
+ output = (
85
+ self.cipher.open(str(row["output_cipher"]))
86
+ if row["output_cipher"]
87
+ else None
88
+ )
89
+ return RunRecord(
90
+ run_id=str(row["run_id"]),
91
+ operation_key=str(row["operation_key"]),
92
+ input=None,
93
+ idempotency_key=str(row["idempotency_key"]),
94
+ fingerprint=str(row["fingerprint"]),
95
+ release=cast(JsonValue, row["release_json"]),
96
+ trace_id=str(row["trace_id"]),
97
+ status=cast(RunStatus, str(row["status"])),
98
+ events=events,
99
+ output=output,
100
+ )
101
+
102
+ async def locked(
103
+ self,
104
+ connection: DatabaseConnection,
105
+ run_id: str,
106
+ ) -> RunRecord:
107
+ await connection.execute(
108
+ "select run_id from agent_runtime_runs where run_id = %s for update",
109
+ (run_id,),
110
+ )
111
+ record = await self.read(connection, run_id)
112
+ if record is None:
113
+ raise KeyError(run_id)
114
+ return record
115
+
116
+ async def insert_event(
117
+ self,
118
+ connection: DatabaseConnection,
119
+ run_id: str,
120
+ event: CanonicalEvent,
121
+ status: RunStatus,
122
+ output_cipher: str | None = None,
123
+ ) -> None:
124
+ await connection.execute(
125
+ """
126
+ insert into agent_runtime_events (run_id, sequence, event_cipher)
127
+ values (%s, %s, %s)
128
+ """,
129
+ (
130
+ run_id,
131
+ event["sequence"],
132
+ self.cipher.seal(cast(JsonValue, event)),
133
+ ),
134
+ )
135
+ now = time.time()
136
+ await connection.execute(
137
+ """
138
+ update agent_runtime_runs
139
+ set status = %s, output_cipher = coalesce(%s, output_cipher),
140
+ updated_at = %s, expires_at = %s
141
+ where run_id = %s
142
+ """,
143
+ (
144
+ status,
145
+ output_cipher,
146
+ now,
147
+ now + self.terminal_ttl,
148
+ run_id,
149
+ ),
150
+ )
151
+
152
+ async def update_run(
153
+ self,
154
+ run_id: str,
155
+ assignment: str,
156
+ values: tuple[object, ...],
157
+ *,
158
+ owner_required: bool = False,
159
+ active_required: bool = False,
160
+ ) -> None:
161
+ await self.ready()
162
+ owner_clause = " and lease_owner = %s" if owner_required else ""
163
+ active_clause = (
164
+ " and status not in ('completed', 'failed', 'cancelled')"
165
+ if active_required
166
+ else ""
167
+ )
168
+ parameters = (
169
+ *values,
170
+ run_id,
171
+ *((self.owner,) if owner_required else ()),
172
+ )
173
+ async with self.pool.connection() as connection:
174
+ cursor = await connection.execute(
175
+ f"update agent_runtime_runs set {assignment} "
176
+ f"where run_id = %s{owner_clause}{active_clause}",
177
+ parameters,
178
+ )
179
+ if cursor.rowcount != 1:
180
+ raise KeyError(run_id)
181
+
182
+ async def by_idempotency(
183
+ self,
184
+ connection: DatabaseConnection,
185
+ idempotency_key: str,
186
+ ) -> RunRecord | None:
187
+ cursor = await connection.execute(
188
+ """
189
+ select run_id from agent_runtime_runs
190
+ where idempotency_key = %s
191
+ """,
192
+ (idempotency_key,),
193
+ )
194
+ row = await cursor.fetchone()
195
+ return await self.read(connection, str(row["run_id"])) if row else None
196
+
197
+ async def ensure_release(
198
+ self,
199
+ connection: DatabaseConnection,
200
+ record: RunRecord,
201
+ ) -> str:
202
+ if not isinstance(record.release, dict):
203
+ raise IdempotencyConflictError
204
+ release_id = record.release.get("releaseId")
205
+ if not isinstance(release_id, str):
206
+ raise IdempotencyConflictError
207
+ encoded = json.dumps(
208
+ record.release,
209
+ sort_keys=True,
210
+ separators=(",", ":"),
211
+ )
212
+ await connection.execute(
213
+ """
214
+ insert into agent_operation_releases (
215
+ release_id, operation_key, snapshot_json, created_at
216
+ ) values (%s, %s, %s::jsonb, %s)
217
+ on conflict (release_id) do nothing
218
+ """,
219
+ (release_id, record.operation_key, encoded, time.time()),
220
+ )
221
+ cursor = await connection.execute(
222
+ """
223
+ select operation_key, snapshot_json from agent_operation_releases
224
+ where release_id = %s
225
+ """,
226
+ (release_id,),
227
+ )
228
+ existing = await cursor.fetchone()
229
+ if not existing:
230
+ raise IdempotencyConflictError
231
+ actual = json.dumps(
232
+ existing["snapshot_json"],
233
+ sort_keys=True,
234
+ separators=(",", ":"),
235
+ )
236
+ if str(existing["operation_key"]) != record.operation_key or actual != encoded:
237
+ raise IdempotencyConflictError
238
+ return release_id
239
+
240
+ async def prune(self, connection: DatabaseConnection) -> None:
241
+ await connection.execute(
242
+ """
243
+ delete from agent_runtime_runs
244
+ where status in ('completed', 'failed', 'cancelled')
245
+ and expires_at < %s
246
+ """,
247
+ (time.time(),),
248
+ )
249
+
250
+ async def record_evaluation(
251
+ self,
252
+ release_id: str,
253
+ evaluation_set_key: str,
254
+ evaluation_set_version: str,
255
+ metrics: dict[str, float],
256
+ ) -> str:
257
+ await self.ready()
258
+ encoded = json.dumps(metrics, sort_keys=True, separators=(",", ":"))
259
+ async with self.pool.connection() as connection:
260
+ async with connection.transaction():
261
+ release = await connection.execute(
262
+ """
263
+ select release_id from agent_operation_releases
264
+ where release_id = %s
265
+ """,
266
+ (release_id,),
267
+ )
268
+ if await release.fetchone() is None:
269
+ raise ReleaseNotFoundError
270
+ cursor = await connection.execute(
271
+ """
272
+ select evaluation_id, metrics_json
273
+ from agent_evaluation_results
274
+ where release_id = %s and evaluation_set_key = %s
275
+ and evaluation_set_version = %s
276
+ """,
277
+ (release_id, evaluation_set_key, evaluation_set_version),
278
+ )
279
+ existing = await cursor.fetchone()
280
+ if existing:
281
+ actual = json.dumps(
282
+ existing["metrics_json"],
283
+ sort_keys=True,
284
+ separators=(",", ":"),
285
+ )
286
+ if actual != encoded:
287
+ raise IdempotencyConflictError
288
+ return str(existing["evaluation_id"])
289
+ evaluation_id = f"eval_{uuid.uuid4().hex}"
290
+ inserted = await connection.execute(
291
+ """
292
+ insert into agent_evaluation_results (
293
+ evaluation_id, release_id, evaluation_set_key,
294
+ evaluation_set_version, metrics_json, created_at
295
+ ) values (%s, %s, %s, %s, %s::jsonb, %s)
296
+ on conflict (
297
+ release_id, evaluation_set_key, evaluation_set_version
298
+ ) do nothing
299
+ returning evaluation_id
300
+ """,
301
+ (
302
+ evaluation_id,
303
+ release_id,
304
+ evaluation_set_key,
305
+ evaluation_set_version,
306
+ encoded,
307
+ time.time(),
308
+ ),
309
+ )
310
+ row = await inserted.fetchone()
311
+ if row:
312
+ return str(row["evaluation_id"])
313
+ repeated = await connection.execute(
314
+ """
315
+ select evaluation_id, metrics_json
316
+ from agent_evaluation_results
317
+ where release_id = %s and evaluation_set_key = %s
318
+ and evaluation_set_version = %s
319
+ """,
320
+ (release_id, evaluation_set_key, evaluation_set_version),
321
+ )
322
+ raced = await repeated.fetchone()
323
+ if not raced:
324
+ raise IdempotencyConflictError
325
+ actual = json.dumps(
326
+ raced["metrics_json"],
327
+ sort_keys=True,
328
+ separators=(",", ":"),
329
+ )
330
+ if actual != encoded:
331
+ raise IdempotencyConflictError
332
+ return str(raced["evaluation_id"])