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,180 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+ import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
3
+ import type { RequestGuardContext } from "../guards.js";
4
+ import type { AgentRunAuthorizationSnapshot } from "@southwind-ai/shared";
5
+ import type {
6
+ AgentRunBinding,
7
+ AgentRunBindingRepository,
8
+ } from "./run-binding-repository.js";
9
+
10
+ export interface AgentExecutionAuthorization {
11
+ readonly binding: AgentRunBinding;
12
+ readonly token: string;
13
+ }
14
+
15
+ export class AgentRunAuthorizationStore {
16
+ constructor(
17
+ private readonly repository: AgentRunBindingRepository,
18
+ private readonly secret: string,
19
+ private readonly now: () => number = Date.now,
20
+ private readonly retentionMs = 7 * 24 * 60 * 60 * 1_000,
21
+ ) {
22
+ if (new TextEncoder().encode(secret).byteLength < 32) {
23
+ throw new Error("Agent Run Authorization Secret 至少需要 32 字节");
24
+ }
25
+ }
26
+
27
+ async issue(input: {
28
+ operationKey: string;
29
+ releaseId: string;
30
+ idempotencyKey: string;
31
+ authorizationSnapshot: AgentRunAuthorizationSnapshot;
32
+ context: RequestGuardContext;
33
+ }): Promise<AgentExecutionAuthorization> {
34
+ const issuedAt = this.now();
35
+ await this.repository.prune(issuedAt);
36
+ const scope = captureAuthorizationScope(input.context.actor.scopeContext);
37
+ const coordinate = [
38
+ input.context.actor.id,
39
+ input.operationKey,
40
+ input.idempotencyKey,
41
+ ].join("\u0000");
42
+ const idempotencyHash = this.hmac(`idempotency\u0000${coordinate}`);
43
+ const token = `grant_${this.hmac(`token\u0000${coordinate}`)}`;
44
+ const candidate: AgentRunBinding = {
45
+ bindingId: `binding_${idempotencyHash.slice(0, 40)}`,
46
+ operationKey: input.operationKey,
47
+ actorId: input.context.actor.id,
48
+ idempotencyHash,
49
+ tokenHash: sha256(token),
50
+ scopeHash: scope.hash,
51
+ scopeSnapshot: scope.snapshot,
52
+ authorizationSnapshot: structuredClone(input.authorizationSnapshot),
53
+ releaseId: input.releaseId,
54
+ createdAt: issuedAt,
55
+ expiresAt: issuedAt + this.retentionMs,
56
+ };
57
+ const binding = await this.repository.createOrGet(candidate);
58
+ if (
59
+ !sameAuthorization(binding, candidate) ||
60
+ binding.expiresAt <= issuedAt
61
+ ) {
62
+ throw new AgentRunAuthorizationError();
63
+ }
64
+ return { binding, token };
65
+ }
66
+
67
+ async bind(
68
+ authorization: AgentExecutionAuthorization,
69
+ runId: string,
70
+ ): Promise<AgentRunBinding> {
71
+ const binding = await this.repository.bind(
72
+ authorization.binding.bindingId,
73
+ runId,
74
+ this.now(),
75
+ );
76
+ if (!binding || binding.expiresAt <= this.now()) {
77
+ throw new AgentRunAuthorizationError();
78
+ }
79
+ return binding;
80
+ }
81
+
82
+ async authorizeTool(token: string, runId: string) {
83
+ const tokenHash = sha256(token);
84
+ let binding = await this.repository.findByTokenHash(tokenHash);
85
+ if (!binding || binding.expiresAt <= this.now()) {
86
+ throw new AgentRunAuthorizationError();
87
+ }
88
+ if (!binding.runId) {
89
+ binding =
90
+ (await this.repository.bind(binding.bindingId, runId, this.now())) ??
91
+ undefined;
92
+ }
93
+ if (!binding || binding.runId !== runId) {
94
+ throw new AgentRunAuthorizationError();
95
+ }
96
+ return binding;
97
+ }
98
+
99
+ async authorizeBrowser(
100
+ operationKey: string,
101
+ runId: string,
102
+ context: RequestGuardContext,
103
+ ) {
104
+ const binding = await this.repository.findByRunId(runId);
105
+ if (
106
+ !binding ||
107
+ binding.expiresAt <= this.now() ||
108
+ binding.actorId !== context.actor.id ||
109
+ binding.operationKey !== operationKey
110
+ ) {
111
+ throw new AgentRunAuthorizationError();
112
+ }
113
+ const scope = captureAuthorizationScope(context.actor.scopeContext);
114
+ if (scope.hash !== binding.scopeHash) {
115
+ throw new AgentRunAuthorizationError();
116
+ }
117
+ return binding;
118
+ }
119
+
120
+ assertCurrentScope(
121
+ binding: AgentRunBinding,
122
+ context: RequestGuardContext,
123
+ ): void {
124
+ const scope = captureAuthorizationScope(context.actor.scopeContext);
125
+ if (scope.hash !== binding.scopeHash) {
126
+ throw new AgentRunAuthorizationError();
127
+ }
128
+ }
129
+
130
+ async discard(authorization: AgentExecutionAuthorization) {
131
+ await this.repository.deleteUnbound(authorization.binding.bindingId);
132
+ }
133
+
134
+ private hmac(value: string) {
135
+ return createHmac("sha256", this.secret).update(value).digest("hex");
136
+ }
137
+ }
138
+
139
+ export class AgentRunAuthorizationError extends Error {
140
+ constructor() {
141
+ super("Agent Run 不存在或不可访问");
142
+ }
143
+ }
144
+
145
+ function sameAuthorization(
146
+ existing: AgentRunBinding,
147
+ candidate: AgentRunBinding,
148
+ ) {
149
+ return (
150
+ existing.bindingId === candidate.bindingId &&
151
+ existing.operationKey === candidate.operationKey &&
152
+ existing.actorId === candidate.actorId &&
153
+ existing.tokenHash === candidate.tokenHash &&
154
+ existing.scopeHash === candidate.scopeHash &&
155
+ sameSnapshot(
156
+ existing.authorizationSnapshot,
157
+ candidate.authorizationSnapshot,
158
+ ) &&
159
+ existing.releaseId === candidate.releaseId
160
+ );
161
+ }
162
+
163
+ function sameSnapshot(
164
+ left: AgentRunAuthorizationSnapshot,
165
+ right: AgentRunAuthorizationSnapshot,
166
+ ) {
167
+ return (
168
+ left.permissionKey === right.permissionKey &&
169
+ left.featureKey === right.featureKey &&
170
+ left.toolNetworkMode === right.toolNetworkMode &&
171
+ left.allowedToolKeys.length === right.allowedToolKeys.length &&
172
+ left.allowedToolKeys.every(
173
+ (key, index) => key === right.allowedToolKeys[index],
174
+ )
175
+ );
176
+ }
177
+
178
+ function sha256(value: string) {
179
+ return createHash("sha256").update(value).digest("hex");
180
+ }
@@ -0,0 +1,93 @@
1
+ import type {
2
+ AgentRunAuthorizationSnapshot,
3
+ AuthorizationScopeSnapshot,
4
+ } from "@southwind-ai/shared";
5
+
6
+ export interface AgentRunBinding {
7
+ readonly bindingId: string;
8
+ readonly runId?: string;
9
+ readonly operationKey: string;
10
+ readonly actorId: string;
11
+ readonly idempotencyHash: string;
12
+ readonly tokenHash: string;
13
+ readonly scopeHash: string;
14
+ readonly scopeSnapshot: AuthorizationScopeSnapshot;
15
+ readonly authorizationSnapshot: AgentRunAuthorizationSnapshot;
16
+ readonly releaseId: string;
17
+ readonly createdAt: number;
18
+ readonly boundAt?: number;
19
+ readonly expiresAt: number;
20
+ }
21
+
22
+ export interface AgentRunBindingRepository {
23
+ createOrGet(binding: AgentRunBinding): Promise<AgentRunBinding>;
24
+ bind(
25
+ bindingId: string,
26
+ runId: string,
27
+ boundAt: number,
28
+ ): Promise<AgentRunBinding | undefined>;
29
+ findByRunId(runId: string): Promise<AgentRunBinding | undefined>;
30
+ findByTokenHash(tokenHash: string): Promise<AgentRunBinding | undefined>;
31
+ deleteUnbound(bindingId: string): Promise<void>;
32
+ prune(expiredBefore: number): Promise<void>;
33
+ }
34
+
35
+ export class InMemoryAgentRunBindingRepository implements AgentRunBindingRepository {
36
+ readonly #bindings = new Map<string, AgentRunBinding>();
37
+
38
+ async createOrGet(binding: AgentRunBinding) {
39
+ const existing = [...this.#bindings.values()].find(
40
+ ({ idempotencyHash }) => idempotencyHash === binding.idempotencyHash,
41
+ );
42
+ if (existing) return clone(existing);
43
+ this.#bindings.set(binding.bindingId, clone(binding));
44
+ return clone(binding);
45
+ }
46
+
47
+ async bind(bindingId: string, runId: string, boundAt: number) {
48
+ const binding = this.#bindings.get(bindingId);
49
+ const runOwner = [...this.#bindings.values()].find(
50
+ (current) => current.runId === runId,
51
+ );
52
+ if (
53
+ !binding ||
54
+ (binding.runId && binding.runId !== runId) ||
55
+ (runOwner && runOwner.bindingId !== bindingId)
56
+ ) {
57
+ return undefined;
58
+ }
59
+ const bound = { ...binding, runId, boundAt };
60
+ this.#bindings.set(bindingId, bound);
61
+ return clone(bound);
62
+ }
63
+
64
+ async findByRunId(runId: string) {
65
+ const found = [...this.#bindings.values()].find(
66
+ (binding) => binding.runId === runId,
67
+ );
68
+ return found ? clone(found) : undefined;
69
+ }
70
+
71
+ async findByTokenHash(tokenHash: string) {
72
+ const found = [...this.#bindings.values()].find(
73
+ (binding) => binding.tokenHash === tokenHash,
74
+ );
75
+ return found ? clone(found) : undefined;
76
+ }
77
+
78
+ async deleteUnbound(bindingId: string) {
79
+ if (!this.#bindings.get(bindingId)?.runId) {
80
+ this.#bindings.delete(bindingId);
81
+ }
82
+ }
83
+
84
+ async prune(expiredBefore: number) {
85
+ for (const [id, binding] of this.#bindings) {
86
+ if (binding.expiresAt <= expiredBefore) this.#bindings.delete(id);
87
+ }
88
+ }
89
+ }
90
+
91
+ function clone(binding: AgentRunBinding): AgentRunBinding {
92
+ return structuredClone(binding);
93
+ }
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ AiOperationInputModality,
2
3
  AiProviderDataPolicy,
3
4
  AiProviderEgressPolicy,
4
5
  AiToolNetworkPolicy,
@@ -6,36 +7,104 @@ import type {
6
7
  import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
7
8
 
8
9
  export interface AgentRuntimeOperation {
10
+ kind: "agent";
9
11
  operationKey: string;
10
12
  input: ModuleAiJsonValue;
13
+ inputModalities: readonly AiOperationInputModality[];
14
+ requiredCapabilities: readonly string[];
15
+ artifacts: readonly AgentRuntimeArtifact[];
11
16
  instructions: string;
12
17
  userPrompt: string;
18
+ outputSchemaKey: string;
19
+ outputSchema?: ModuleAiJsonValue;
13
20
  allowedTools: Array<{
14
21
  key: string;
15
22
  label: string;
16
23
  description: string;
24
+ approval: "none" | "required";
25
+ idempotency: "none" | "supported" | "required";
26
+ approvalTimeoutSeconds?: number;
17
27
  }>;
18
28
  limits: {
19
29
  maxDurationSeconds: number;
30
+ maxInputBytes: number;
20
31
  maxOutputBytes: number;
32
+ maxArtifacts?: number;
33
+ maxPixelsPerArtifact?: number;
21
34
  };
22
35
  providerEgress: AiProviderEgressPolicy;
23
36
  providerDataPolicy: AiProviderDataPolicy;
24
37
  toolNetworkPolicy: AiToolNetworkPolicy;
25
38
  executionGrant: string;
39
+ release: AgentRuntimeRelease;
40
+ traceId: string;
41
+ }
42
+
43
+ export interface AgentRuntimeRelease {
44
+ readonly releaseId: string;
45
+ readonly operationVersion: string;
46
+ readonly workflowVersion: string;
47
+ readonly promptVersion: string;
48
+ readonly routeVersion: string;
49
+ readonly toolSchemaVersion: string;
50
+ readonly manifestDigest: string;
51
+ }
52
+
53
+ export interface AgentRuntimeArtifact {
54
+ readonly value: string;
55
+ readonly mediaType: string;
56
+ readonly byteSize: number;
57
+ readonly sha256: string;
26
58
  }
27
59
 
28
60
  export interface AgentRuntimeStartResponse {
29
61
  runId: string;
30
- status: string;
62
+ status: AgentRuntimeRunStatus;
31
63
  eventsUrl: string;
32
64
  }
33
65
 
66
+ export type AgentRuntimeRunStatus =
67
+ | "accepted"
68
+ | "running"
69
+ | "awaiting-approval"
70
+ | "completed"
71
+ | "failed"
72
+ | "cancelled";
73
+
74
+ export interface AgentRuntimeStatusResponse {
75
+ readonly runId: string;
76
+ readonly status: AgentRuntimeRunStatus;
77
+ readonly lastSequence: number;
78
+ readonly terminalEvent?: AgentRuntimeTerminalEvent;
79
+ }
80
+
81
+ export type AgentRuntimeTerminalEvent =
82
+ | {
83
+ readonly type: "run.completed" | "run.cancelled";
84
+ readonly runId: string;
85
+ readonly sequence: number;
86
+ }
87
+ | {
88
+ readonly type: "run.failed";
89
+ readonly runId: string;
90
+ readonly sequence: number;
91
+ readonly error: {
92
+ readonly code: string;
93
+ readonly message: string;
94
+ readonly requestId: string;
95
+ readonly retryable: boolean;
96
+ };
97
+ };
98
+
34
99
  export interface AgentRuntimePort {
35
100
  start(
36
101
  request: AgentRuntimeOperation,
37
102
  idempotencyKey: string,
38
103
  ): Promise<AgentRuntimeStartResponse>;
104
+ status(
105
+ runId: string,
106
+ signal?: AbortSignal,
107
+ ): Promise<AgentRuntimeStatusResponse>;
39
108
  events(
40
109
  runId: string,
41
110
  cursor: number,
@@ -49,3 +118,21 @@ export interface AgentRuntimePort {
49
118
  ): Promise<Response>;
50
119
  health(signal?: AbortSignal): Promise<Response>;
51
120
  }
121
+
122
+ export class AgentRuntimeError extends Error {
123
+ constructor(
124
+ readonly code:
125
+ | "CAPABILITY_UNAVAILABLE"
126
+ | "MODEL_ROUTE_UNAVAILABLE"
127
+ | "RUNTIME_UNAVAILABLE"
128
+ | "RUN_CONFLICT"
129
+ | "FORBIDDEN"
130
+ | "BUDGET_EXCEEDED"
131
+ | "INVALID_INPUT",
132
+ message: string,
133
+ readonly retryable: boolean,
134
+ ) {
135
+ super(message);
136
+ this.name = "AgentRuntimeError";
137
+ }
138
+ }
@@ -0,0 +1,58 @@
1
+ import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import type { ModuleAiJsonValue } from "@southwind-ai/server-sdk";
3
+ import type { AgentRuntimeError } from "./run-contracts.js";
4
+ import { AgentRunFacadeError } from "./run-errors.js";
5
+
6
+ export function toolDefinitions(
7
+ modules: readonly ModuleManifest[],
8
+ allowedKeys: readonly string[],
9
+ ) {
10
+ const tools = new Map(
11
+ modules.flatMap(({ tools }) => tools.map((tool) => [tool.key, tool])),
12
+ );
13
+ return allowedKeys.map((key) => {
14
+ const tool = tools.get(key);
15
+ if (!tool) throw new Error(`AI Operation 引用了未知 Tool:${key}`);
16
+ return {
17
+ key: tool.key,
18
+ label: tool.label,
19
+ description: tool.description,
20
+ approval: tool.approval,
21
+ idempotency: tool.idempotency,
22
+ ...(tool.approvalTimeoutSeconds
23
+ ? { approvalTimeoutSeconds: tool.approvalTimeoutSeconds }
24
+ : {}),
25
+ };
26
+ });
27
+ }
28
+
29
+ export function assertInputSize(
30
+ value: ModuleAiJsonValue,
31
+ maxBytes: number,
32
+ ): void {
33
+ const size = new TextEncoder().encode(JSON.stringify(value)).byteLength;
34
+ if (size > maxBytes) {
35
+ throw new AgentRunFacadeError("INVALID_INPUT", "Agent 输入超过上限", 400);
36
+ }
37
+ }
38
+
39
+ export function digest(value: unknown): string {
40
+ return new Bun.CryptoHasher("sha256")
41
+ .update(JSON.stringify(value))
42
+ .digest("hex");
43
+ }
44
+
45
+ export function runtimeErrorStatus(code: AgentRuntimeError["code"]) {
46
+ if (code === "FORBIDDEN") return 403;
47
+ if (code === "RUN_CONFLICT") return 409;
48
+ if (code === "BUDGET_EXCEEDED" || code === "INVALID_INPUT") return 400;
49
+ return 503;
50
+ }
51
+
52
+ export function publicRunPath(
53
+ runId: string,
54
+ resource: "events" | "result" | "cancel",
55
+ operationKey: string,
56
+ ) {
57
+ return `/api/agent/runs/${encodeURIComponent(runId)}/${resource}?operationKey=${encodeURIComponent(operationKey)}`;
58
+ }
@@ -0,0 +1,25 @@
1
+ export class AgentRunFacadeError extends Error {
2
+ constructor(
3
+ readonly code:
4
+ | "OPERATION_NOT_FOUND"
5
+ | "INVALID_INPUT"
6
+ | "FORBIDDEN"
7
+ | "MODEL_ROUTE_UNAVAILABLE"
8
+ | "CAPABILITY_UNAVAILABLE"
9
+ | "RUN_CONFLICT"
10
+ | "BUDGET_EXCEEDED"
11
+ | "RUNTIME_UNAVAILABLE",
12
+ message: string,
13
+ readonly status: number,
14
+ ) {
15
+ super(message);
16
+ }
17
+ }
18
+
19
+ export function inaccessibleRun() {
20
+ return new AgentRunFacadeError(
21
+ "RUN_CONFLICT",
22
+ "Agent Run 不存在或不可访问",
23
+ 404,
24
+ );
25
+ }
@@ -1,15 +1,12 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import type { ModuleManifest } from "@southwind-ai/modules";
3
3
  import { featureKey } from "@southwind-ai/shared";
4
- import type {
5
- AgentRuntimeOperation,
6
- AgentRuntimePort,
7
- } from "./run-contracts.js";
8
4
  import { buildScopeContext } from "../data-access/context.js";
9
5
  import { createRequestGuardContext } from "../guards.js";
10
6
  import { initializeModuleHosts } from "../module-host.js";
11
7
  import { createServerRuntime } from "../runtime.js";
12
- import { ExecutionGrantStore } from "./execution-grants.js";
8
+ import { createTestRunAuthorizationStore } from "./run-authorization.test-support.js";
9
+ import { FakeAgentRuntime } from "./fake-runtime.test-support.js";
13
10
  import { AgentRunFacade } from "./run-facade.js";
14
11
 
15
12
  describe("Guarded Agent Run facade", () => {
@@ -25,7 +22,7 @@ describe("Guarded Agent Run facade", () => {
25
22
  expect(created).toEqual({
26
23
  runId: "run_1",
27
24
  status: "accepted",
28
- eventsUrl: "/api/agent/runs/run_1/events",
25
+ eventsUrl: "/api/agent/runs/run_1/events?operationKey=business.answer",
29
26
  });
30
27
  expect(fixture.remote.started).toHaveLength(1);
31
28
  expect(fixture.remote.started[0]).toMatchObject({
@@ -41,13 +38,29 @@ describe("Guarded Agent Run facade", () => {
41
38
  expect.objectContaining({ field: "instructions" }),
42
39
  ]),
43
40
  },
41
+ release: {
42
+ operationVersion: "0.1.0",
43
+ workflowVersion: "workflow-2",
44
+ promptVersion: "prompt-3",
45
+ routeVersion: "route-4",
46
+ },
47
+ });
48
+ expect(fixture.remote.started[0]?.release.releaseId).toMatch(
49
+ /^release_[a-f0-9]{40}$/,
50
+ );
51
+ expect(fixture.remote.started[0]?.traceId).toMatch(/^[a-f0-9]{32}$/);
52
+ expect(fixture.runtime.auditSink.list().at(-1)?.metadata).toMatchObject({
53
+ releaseId: fixture.remote.started[0]?.release.releaseId,
54
+ traceId: fixture.remote.started[0]?.traceId,
44
55
  });
45
56
  expect(fixture.remote.started[0]?.executionGrant).toMatch(/^grant_/);
46
57
  expect(fixture.remote.started[0]?.input).toEqual({ question: "你好" });
58
+ expect(fixture.remote.startedIdempotencyKeys[0]).toMatch(/^[a-f0-9]{64}$/);
59
+ expect(fixture.remote.startedIdempotencyKeys[0]).not.toBe("request-1");
47
60
  expect(JSON.stringify(fixture.prepared)).not.toContain("case-secret");
48
- expect(() =>
49
- fixture.facade.authorizeRun("run_1", fixture.context),
50
- ).not.toThrow();
61
+ await expect(
62
+ fixture.facade.authorizeRun("business.answer", "run_1", fixture.context),
63
+ ).resolves.toBeDefined();
51
64
  });
52
65
 
53
66
  test("客户端输入不能覆盖 Operation、Tool 或授权", async () => {
@@ -83,36 +96,25 @@ describe("Guarded Agent Run facade", () => {
83
96
  expect(fixture.remote.started).toEqual([]);
84
97
  expect(fixture.prepared).toEqual([]);
85
98
  });
86
- });
87
99
 
88
- class FakeRuntime implements AgentRuntimePort {
89
- readonly started: AgentRuntimeOperation[] = [];
100
+ test("恢复时权限已收回会返回不泄露 Run 存在性的 404", async () => {
101
+ const fixture = await createFixture();
102
+ await fixture.facade.start(
103
+ "business.answer",
104
+ { question: "稍后恢复" },
105
+ "request-revoked",
106
+ fixture.context,
107
+ );
90
108
 
91
- start(request: AgentRuntimeOperation) {
92
- this.started.push(request);
93
- return Promise.resolve({
94
- runId: "run_1",
95
- status: "accepted",
96
- eventsUrl: "/internal/events",
109
+ await expect(
110
+ fixture.facade.authorizeRun("business.answer", "run_1", guardContext([])),
111
+ ).rejects.toMatchObject({
112
+ code: "RUN_CONFLICT",
113
+ status: 404,
114
+ message: "Agent Run 不存在或不可访问",
97
115
  });
98
- }
99
-
100
- events() {
101
- return Promise.resolve(new Response());
102
- }
103
-
104
- result() {
105
- return Promise.resolve(new Response());
106
- }
107
-
108
- cancel() {
109
- return Promise.resolve(new Response());
110
- }
111
-
112
- health() {
113
- return Promise.resolve(new Response());
114
- }
115
- }
116
+ });
117
+ });
116
118
 
117
119
  async function createFixture(
118
120
  permissionKeys: string[] = ["business.ai.execute", "business.records.read"],
@@ -128,6 +130,11 @@ async function createFixture(
128
130
  host.registerAiOperationHandlers([
129
131
  {
130
132
  operationKey: "business.answer",
133
+ release: {
134
+ workflowVersion: "workflow-2",
135
+ promptVersion: "prompt-3",
136
+ routeVersion: "route-4",
137
+ },
131
138
  prepare(input, context) {
132
139
  prepared.push({ input, actorId: context.actor.id });
133
140
  const question =
@@ -155,7 +162,20 @@ async function createFixture(
155
162
  licenseResolver: { resolve: () => ({ status: "valid" }) },
156
163
  },
157
164
  );
158
- const context = createRequestGuardContext(
165
+ const context = guardContext(permissionKeys);
166
+ const remote = new FakeAgentRuntime();
167
+ const facade = new AgentRunFacade(
168
+ [module],
169
+ hosts,
170
+ runtime,
171
+ remote,
172
+ createTestRunAuthorizationStore(),
173
+ );
174
+ return { context, facade, prepared, remote, runtime };
175
+ }
176
+
177
+ function guardContext(permissionKeys: string[]) {
178
+ return createRequestGuardContext(
159
179
  new Request("http://localhost/api/agent/runs"),
160
180
  {
161
181
  resolveActor: () => ({
@@ -176,15 +196,6 @@ async function createFixture(
176
196
  resolveLicense: () => ({ status: "valid" }),
177
197
  },
178
198
  );
179
- const remote = new FakeRuntime();
180
- const facade = new AgentRunFacade(
181
- [module],
182
- hosts,
183
- runtime,
184
- remote,
185
- new ExecutionGrantStore(),
186
- );
187
- return { context, facade, prepared, remote };
188
199
  }
189
200
 
190
201
  function manifest(): ModuleManifest {