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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.3.2",
3
+ "version": "0.3.5",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,14 +28,26 @@ description: "Windy 业务模块、Manifest、Repository、Service、Agent Tool
28
28
  project-owned Repository 回查资源与 fileId 绑定及对象级授权,只返回布尔判定,
29
29
  再通过 `context.storage.openResourceFile()` 取得最小元数据与流。禁止注入
30
30
  UploadRepository、BlobStore 或申请平台 manage Repository。
31
- 6. 若项目包含 `system.agent`,`operation=query` 的 Tool 会由同一 Registry 自动进入
31
+ 6. 业务资源包含敏感字段时,在 Manifest 一次声明 Data Policy、standard read、
32
+ sensitive-disclosure reveal、governed-export Permission 与 masked/reveal API 绑定,
33
+ 再用 `registerGovernedResources()` 分别注册对象授权 Resolver 和去除治理字段的投影。
34
+ 需要导出时再登记四个 Manifest API 绑定、受控查询校验和 cursor 分页 Source;审批、
35
+ Durable Job、水印、TTL、下载与取消由宿主负责。业务不自行签发明文 Grant、校验密码,
36
+ 也不接触 Workflow、BlobStore 或平台 Repository。
37
+ 7. 若项目包含 `system.agent`,`operation=query` 的 Tool 会由同一 Registry 自动进入
32
38
  MCP;不要编辑 MCP Adapter 或复制 Tool 定义。写操作在幂等、审批和结果追踪闭环前
33
39
  不通过 MCP 暴露。
34
- 7. 补 Manifest/Composition、Repository/Service、Guard 拒绝和 Handler 契约测试。
40
+ 对无需 Actor、Repository、Secret、文件系统或外网的纯计算 Tool,可改用
41
+ `setupSandbox()` 注册;此入口必须同步且无副作用,Handler 只能使用输入、
42
+ `idempotencyKey` 与 `AbortSignal`。需要业务数据或外部服务的 Tool 必须保留在普通
43
+ `setup()`,不能为了使用 Sandbox 绕过依赖边界。
44
+ 8. 补 Manifest/Composition、Repository/Service、Guard 拒绝和 Handler 契约测试。
35
45
  涉及 Tool 时还要覆盖 Schema 拒绝、数据范围与未授权发现/调用;涉及可选模块时覆盖
36
46
  Starter 的文件、依赖和数据库裁剪。涉及资源文件时还要覆盖跨上传者成功、资源绑定/
37
47
  purpose/Feature/Permission/Scope 任一拒绝、最小元数据与成功/拒绝审计。
38
- 8. 更新长期文档,运行受影响测试、`bun run typecheck` 和 `bun run lint`,按一个完整
48
+ 涉及 Sandbox Tool 时还要覆盖无网络、无平台 Secret、无宿主数据挂载、Worker
49
+ 重启恢复,以及策略拒绝与业务失败审计分类。
50
+ 9. 更新长期文档,运行受影响测试、`bun run typecheck` 和 `bun run lint`,按一个完整
39
51
  小切片提交。
40
52
 
41
53
  ## 代码事实源
@@ -44,6 +56,7 @@ description: "Windy 业务模块、Manifest、Repository、Service、Agent Tool
44
56
  - Tool 组合校验:`packages/modules/src/tool-composition.ts`
45
57
  - 模块 Host 契约:`packages/server-sdk/src/module-host.ts`
46
58
  - Tool Handler 契约:`packages/server-sdk/src/tool-registration.ts`
59
+ - Sandbox Tool 契约:`packages/server-sdk/src/sandbox-tool.ts`
47
60
  - 可移除业务示例:`packages/example-work-order/`、`apps/server/src/work-order/`
48
61
  - 业务模块安装入口:`apps/server/src/installed-business-modules.ts`
49
62
  - MCP 与统一执行边界:`docs/architecture/mcp-business-capabilities.md`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "cb75f345710c9db90091819fd917d999cca88d05",
4
- "generatorVersion": "0.3.2",
3
+ "templateCommit": "0e87efcbc9fb6805a73439e73e030e6ec5783ad7",
4
+ "generatorVersion": "0.3.5",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -47,6 +47,10 @@
47
47
  - 定制 404、500、维护中或网络异常页时,只修改 project-owned 的
48
48
  `apps/web/src/app/error-pages.ts`,并把业务 SFC 放在 `apps/web/src/app/`;
49
49
  保留四个类型化注册项,不要为换视觉修改 Router、HTTP 核心或平台默认错误页。
50
+ - 新增业务管理页面时,在业务 Module Manifest 注册 Feature、Permission、菜单和
51
+ Admin Route,再把 Vue 页面绑定到 project-owned 的
52
+ `apps/web/src/app/business-routes.ts`;不要修改 Router 或 AppSidebar。导航来自
53
+ 服务端 Manifest 快照,接入见 `docs/development/business-web-routes.md`。
50
54
  - 业务 Agent Operation 开启 Provider 出网时,必须在 Manifest 同时声明最小
51
55
  `providerDataPolicy.inputFields` JSON Pointer allowlist 与
52
56
  `envelopeFields`;raw input 会在进入 `prepare` 前裁剪并执行 DLP。接入与升级
@@ -60,9 +60,9 @@ Skill 完成该能力”。Skill 只指导开发流程;Feature、Permission、
60
60
  运行时事实仍以 Module Manifest 与平台 Registry 为准。
61
61
 
62
62
  当前组合未包含 `system.agent`,MCP Server、Agent Tool Host 和相关文档已被
63
- 物理裁剪。若项目尚可重新生成,请在创建时增加 `--module system.agent` 或使用
64
- `--all-modules`;部署后的通用 `module add` 尚未交付,不要手工复制 MCP Adapter
65
- 或另建授权链。仓库 Skill 仍可用于普通业务模块开发。
63
+ 物理裁剪。创建时可增加 `--module system.agent` 或使用 `--all-modules`;已部署
64
+ 项目使用 `bun run windy module add system.agent`,不要手工复制 MCP Adapter 或
65
+ 另建授权链。仓库 Skill 仍可用于普通业务模块开发。
66
66
 
67
67
 
68
68
 
@@ -99,8 +99,8 @@ create-windy 在本机 `.env` 中随机生成的 `WINDY_BOOTSTRAP_ADMIN_PASSWORD
99
99
  | 法律许可 | `UNLICENSED` |
100
100
 
101
101
  平台当前注册的全部模块如下。H0 表示 profile 锁定核心;H2 表示创建时可以物理包含或
102
- 排除,并已有生成、类型检查、测试和构建证据。H2 不代表项目部署后已经支持无损安装、
103
- 升级或卸载;那需要 H3 的生命周期命令与数据处置证据。
102
+ 排除;H3 还具备部署期安装、更新、数据保留卸载和重装证据。通用生命周期命令会保守
103
+ 处理所有目录模块,但只有通过完整 H3 回归的模块才标记为 H3。
104
104
 
105
105
  | Module Key | 当前项目 | 热插拔可靠度 | 说明 |
106
106
  | ---------- | -------- | ------------ | ---- |
@@ -119,14 +119,17 @@ create-windy 在本机 `.env` 中随机生成的 `WINDY_BOOTSTRAP_ADMIN_PASSWORD
119
119
  | `system.bulk-data` | 未包含 | H2 | 批量导入导出与结果留存。 |
120
120
  | `system.search` | 未包含 | H2 | 权限感知搜索与 Provider 聚合。 |
121
121
  | `system.data-governance` | 未包含 | H2 | 敏感字段、明文访问和治理导出。 |
122
- | `work-order` | 未包含 | H2 | 可替换的业务模块接入示例。 |
122
+ | `work-order` | 未包含 | H3 | 可替换的业务模块接入示例。 |
123
123
 
124
124
  项目自有业务模块以 `apps/server/src/installed-business-modules.ts` 装配的 Module Manifest
125
- 为准;shared-scaffold README 不复制项目业务 Key,避免平台升级固化产品命名。
125
+ 为准;对应 Vue 页面在 project-owned 的
126
+ `apps/web/src/app/business-routes.ts` 注册,侧栏继续从服务端 Manifest 快照生成。
127
+ 不要为增加业务页面修改 shared-scaffold Router 或 AppSidebar。shared-scaffold README
128
+ 不复制项目业务 Key,避免平台升级固化产品命名。
126
129
 
127
130
  精确生成来源和选择记录在 `.windy/generation.json`。当前版本已提供可回滚的
128
- `windy update`;通用 `module add/remove` 尚未交付,不要把 Feature Flag 关闭当成
129
- 模块已经卸载。
131
+ `windy update` 与 `windy module list/add/update/remove`。Feature Flag 关闭仍不
132
+ 等于模块卸载。
130
133
 
131
134
  ## 常用命令
132
135
 
@@ -142,6 +145,9 @@ bun run build
142
145
  bun run windy doctor
143
146
  bun run windy update --check
144
147
  bun run windy update --dry-run
148
+ bun run windy module list
149
+ bun run windy module add work-order --dry-run
150
+ bun run windy module remove work-order --dry-run
145
151
  bun run windy reconcile --to <精确目标版本>
146
152
  bun run lint
147
153
  bun run format
@@ -178,8 +184,9 @@ create-windy 默认建立 `main` 分支并创建 `chore: initialize project` 首
178
184
  没有配置 Git 用户身份,文件会保留在暂存区;配置身份后手动提交即可。
179
185
 
180
186
  当前已交付的 Windy 更新器会根据 Project Lock 和文件所有权,从 npm 历史 artifact
181
- 重建 Base 并执行三方合并;它不连接私有源仓库,也不会静默覆盖项目代码。部署期通用
182
- `module add/update/remove` 尚未交付。
187
+ 重建 Base 并执行三方合并;它不连接私有源仓库,也不会静默覆盖项目代码。模块
188
+ 生命周期命令复用同一计划、事务、验证与恢复边界;卸载默认保留数据库数据,并要求
189
+ 显式确认数据处置计划。
183
190
 
184
191
  ## License
185
192
 
@@ -13,6 +13,7 @@ dist/
13
13
  __pycache__/
14
14
  packages/create-windy/template/
15
15
  .tmp/
16
+ .artifacts/
16
17
  .windy/backups/
17
18
  .windy/update-state.json
18
19
  *.tsbuildinfo
@@ -32,6 +32,8 @@ export QWEN_API_URL='https://dashscope-intl.aliyuncs.com/compatible-mode/v1'
32
32
  export QWEN_API_KEY='<server-secret>'
33
33
  export QWEN_MODEL='qwen-plus'
34
34
  export AI_PROVIDER_HTTPS_HOST_ALLOWLIST='dashscope-intl.aliyuncs.com'
35
+ export AI_PROVIDER_CAPABILITIES='text-generation'
36
+ export AI_PROVIDER_INPUT_MODALITIES='text'
35
37
  export AGENT_RUN_STORE_PATH='.data/agent-runs.sqlite3'
36
38
  export AI_PROVIDER_AUDIT_STORE_PATH='.data/provider-audit.sqlite3'
37
39
  export AI_PROVIDER_AUDIT_RETENTION_DAYS='90'
@@ -4,9 +4,16 @@ version = "0.1.0"
4
4
  description = "Southwind AI canonical Agent Runtime server"
5
5
  requires-python = ">=3.12,<3.13"
6
6
  dependencies = [
7
+ "cryptography==49.0.0",
7
8
  "fastapi==0.139.2",
8
9
  "google-adk==2.5.0",
9
10
  "httpx==0.28.1",
11
+ "jsonschema==4.26.0",
12
+ "opentelemetry-api==1.42.1",
13
+ "opentelemetry-exporter-otlp-proto-http==1.42.1",
14
+ "opentelemetry-sdk==1.42.1",
15
+ "pillow==12.3.0",
16
+ "psycopg[binary,pool]==3.3.4",
10
17
  "pydantic==2.13.4",
11
18
  "uvicorn==0.51.0",
12
19
  ]
@@ -17,6 +24,7 @@ dev = [
17
24
  "pytest==9.1.1",
18
25
  "pytest-asyncio==1.4.0",
19
26
  "ruff==0.15.22",
27
+ "types-jsonschema==4.26.0.20260518",
20
28
  ]
21
29
 
22
30
  [tool.pytest.ini_options]
@@ -12,7 +12,7 @@ from google.genai import types
12
12
 
13
13
  from .adk_failure import AdkFailureState
14
14
  from .adk_model import GovernedOpenAiLlm
15
- from .adk_tools import GovernedTool
15
+ from .adk_tools import ApprovalSignalState, GovernedTool
16
16
  from .contracts import JsonValue
17
17
  from .engine import (
18
18
  AgentEngineAdapter,
@@ -25,10 +25,11 @@ from .engine import (
25
25
  ToolRequested,
26
26
  UsageReported,
27
27
  )
28
+ from .multimodal import final_output
28
29
  from .openai_engine import cancelled, validate_runtime_request
29
30
  from .openai_provider import OpenAiCompatibleProvider
30
31
  from .provider_governance import ProviderRequestGovernance
31
- from .tool_host import ToolHostClient
32
+ from .tool_host import ToolHostClient, canonical_tool_call_id
32
33
 
33
34
 
34
35
  class AdkEngineAdapter(AgentEngineAdapter):
@@ -45,6 +46,14 @@ class AdkEngineAdapter(AgentEngineAdapter):
45
46
  self._governance = governance
46
47
  self._cancellations: dict[str, asyncio.Event] = {}
47
48
 
49
+ @property
50
+ def capabilities(self) -> tuple[str, ...]:
51
+ return self._provider.config.capabilities if self._provider else ()
52
+
53
+ @property
54
+ def input_modalities(self) -> tuple[str, ...]:
55
+ return self._provider.config.input_modalities if self._provider else ()
56
+
48
57
  def supports(self, operation_key: str) -> bool:
49
58
  return bool(operation_key.strip())
50
59
 
@@ -53,7 +62,12 @@ class AdkEngineAdapter(AgentEngineAdapter):
53
62
  request: EngineRunRequest,
54
63
  ) -> AsyncIterator[EngineSignal]:
55
64
  provider, tool_host, governance = self._configured()
56
- validate_runtime_request(request, provider.config.deployment_key)
65
+ validate_runtime_request(
66
+ request,
67
+ provider.config.deployment_key,
68
+ provider.config.capabilities,
69
+ provider.config.input_modalities,
70
+ )
57
71
  cancellation = self._cancellations.setdefault(
58
72
  request.run_id,
59
73
  asyncio.Event(),
@@ -61,7 +75,9 @@ class AdkEngineAdapter(AgentEngineAdapter):
61
75
  output = ""
62
76
  output_bytes = 0
63
77
  failures = AdkFailureState()
78
+ approvals = ApprovalSignalState()
64
79
  event_failure: SafeEngineError | None = None
80
+ tool_call_ids: dict[str, str] = {}
65
81
  try:
66
82
  session_service = InMemorySessionService() # type: ignore[no-untyped-call]
67
83
  await session_service.create_session(
@@ -88,13 +104,14 @@ class AdkEngineAdapter(AgentEngineAdapter):
88
104
  tool_host,
89
105
  cancellation,
90
106
  failures,
107
+ approvals,
91
108
  )
92
109
  for tool in request.allowed_tools
93
110
  ],
94
111
  ),
95
112
  session_service=session_service,
96
113
  )
97
- async for event in runner.run_async(
114
+ events = runner.run_async(
98
115
  user_id="platform",
99
116
  session_id=request.run_id,
100
117
  new_message=types.Content(
@@ -105,7 +122,12 @@ class AdkEngineAdapter(AgentEngineAdapter):
105
122
  streaming_mode=StreamingMode.SSE,
106
123
  max_llm_calls=provider.config.max_tool_rounds,
107
124
  ),
108
- ):
125
+ )
126
+ async for item in merge_adk_events(events, approvals.queue):
127
+ if not isinstance(item, Event):
128
+ yield item
129
+ continue
130
+ event = item
109
131
  cancelled(cancellation)
110
132
  if event.error_code:
111
133
  event_failure = failures.failure or SafeEngineError(
@@ -114,7 +136,11 @@ class AdkEngineAdapter(AgentEngineAdapter):
114
136
  retryable=True,
115
137
  )
116
138
  continue
117
- async for signal in signals_for_event(event):
139
+ async for signal in signals_for_event(
140
+ event,
141
+ request,
142
+ tool_call_ids,
143
+ ):
118
144
  if isinstance(signal, OutputDelta):
119
145
  output_bytes += len(signal.text.encode())
120
146
  if output_bytes > request.limits.max_output_bytes:
@@ -128,7 +154,7 @@ class AdkEngineAdapter(AgentEngineAdapter):
128
154
  if event_failure is not None:
129
155
  raise event_failure
130
156
  cancelled(cancellation)
131
- yield EngineCompleted({"text": output})
157
+ yield EngineCompleted(final_output(output, request.output_schema))
132
158
  except SafeEngineError:
133
159
  raise
134
160
  except asyncio.CancelledError:
@@ -175,7 +201,11 @@ class AdkEngineAdapter(AgentEngineAdapter):
175
201
  return self._provider, self._tool_host, self._governance
176
202
 
177
203
 
178
- async def signals_for_event(event: Event) -> AsyncIterator[EngineSignal]:
204
+ async def signals_for_event(
205
+ event: Event,
206
+ request: EngineRunRequest,
207
+ tool_call_ids: dict[str, str],
208
+ ) -> AsyncIterator[EngineSignal]:
179
209
  for call in event.get_function_calls():
180
210
  if not call.id or not call.name:
181
211
  raise SafeEngineError(
@@ -183,7 +213,14 @@ async def signals_for_event(event: Event) -> AsyncIterator[EngineSignal]:
183
213
  "模型请求了无效 Tool",
184
214
  retryable=False,
185
215
  )
186
- yield ToolRequested(call.id, call.name)
216
+ arguments = call.args if isinstance(call.args, dict) else {}
217
+ stable_id = canonical_tool_call_id(
218
+ request.run_id,
219
+ call.name,
220
+ arguments,
221
+ )
222
+ tool_call_ids[call.id] = stable_id
223
+ yield ToolRequested(stable_id, call.name)
187
224
  for response in event.get_function_responses():
188
225
  if not response.id:
189
226
  raise SafeEngineError(
@@ -191,7 +228,14 @@ async def signals_for_event(event: Event) -> AsyncIterator[EngineSignal]:
191
228
  "Agent Tool 响应缺少稳定标识",
192
229
  retryable=False,
193
230
  )
194
- yield ToolCompleted(response.id)
231
+ response_tool_call_id = tool_call_ids.get(response.id)
232
+ if not response_tool_call_id:
233
+ raise SafeEngineError(
234
+ "TOOL_DENIED",
235
+ "Agent Tool 响应与请求不匹配",
236
+ retryable=False,
237
+ )
238
+ yield ToolCompleted(response_tool_call_id)
195
239
  if event.partial and event.content:
196
240
  for part in event.content.parts or []:
197
241
  if part.text and not part.thought:
@@ -212,3 +256,36 @@ def southwind_usage(event: Event) -> dict[str, JsonValue] | None:
212
256
  if isinstance(key, str)
213
257
  and (item is None or isinstance(item, str | int | float | bool | list | dict))
214
258
  }
259
+
260
+
261
+ async def merge_adk_events(
262
+ events: AsyncIterator[Event],
263
+ approval_queue: asyncio.Queue[EngineSignal],
264
+ ) -> AsyncIterator[Event | EngineSignal]:
265
+ iterator = events.__aiter__()
266
+ event_task: asyncio.Task[Event] = asyncio.create_task(next_event(iterator))
267
+ approval_task = asyncio.create_task(approval_queue.get())
268
+ try:
269
+ while True:
270
+ done, _ = await asyncio.wait(
271
+ {event_task, approval_task},
272
+ return_when=asyncio.FIRST_COMPLETED,
273
+ )
274
+ if approval_task in done:
275
+ yield approval_task.result()
276
+ approval_task = asyncio.create_task(approval_queue.get())
277
+ if event_task in done:
278
+ try:
279
+ yield event_task.result()
280
+ except StopAsyncIteration:
281
+ while not approval_queue.empty():
282
+ yield approval_queue.get_nowait()
283
+ return
284
+ event_task = asyncio.create_task(next_event(iterator))
285
+ finally:
286
+ event_task.cancel()
287
+ approval_task.cancel()
288
+
289
+
290
+ async def next_event(iterator: AsyncIterator[Event]) -> Event:
291
+ return await iterator.__anext__()
@@ -13,6 +13,7 @@ from pydantic import PrivateAttr
13
13
  from .adk_failure import AdkFailureState
14
14
  from .contracts import JsonValue
15
15
  from .engine import EngineRunRequest, SafeEngineError
16
+ from .multimodal import attach_artifacts
16
17
  from .openai_engine import cancelled, parse_arguments
17
18
  from .openai_provider import OpenAiCompatibleProvider, ProviderTurn
18
19
  from .provider_governance import ProviderRequestGovernance
@@ -58,7 +59,10 @@ class GovernedOpenAiLlm(BaseLlm):
58
59
  "Agent Tool 调用轮次超过上限",
59
60
  retryable=False,
60
61
  )
61
- messages = adk_messages(llm_request, self._request.instructions)
62
+ messages = attach_artifacts(
63
+ adk_messages(llm_request, self._request.instructions),
64
+ self._request.artifacts,
65
+ )
62
66
  governed = self._governance.authorize(
63
67
  deployment_key=self._provider.config.deployment_key,
64
68
  operation_key=self._request.operation_key,
@@ -68,13 +72,24 @@ class GovernedOpenAiLlm(BaseLlm):
68
72
  tools=self._request.allowed_tools,
69
73
  policy=self._request.provider_data_policy,
70
74
  input_value=self._request.input,
71
- measure=lambda values, tools: len(
72
- self._provider.request_body(values, tools)
75
+ artifact_bytes=sum(
76
+ artifact.byte_size for artifact in self._request.artifacts
77
+ ),
78
+ output_schema=self._request.output_schema,
79
+ measure=lambda values, tools, schema: len(
80
+ self._provider.request_body(
81
+ values,
82
+ tools,
83
+ self._request.output_schema_key,
84
+ schema,
85
+ )
73
86
  ),
74
87
  )
75
88
  turn = await self._provider.complete_turn(
76
89
  governed.messages,
77
90
  governed.tools,
91
+ self._request.output_schema_key,
92
+ governed.output_schema,
78
93
  )
79
94
  cancelled(self._cancellation)
80
95
  validate_tool_calls(
@@ -9,9 +9,26 @@ from google.genai import types
9
9
 
10
10
  from .adk_failure import AdkFailureState
11
11
  from .contracts import JsonValue, RuntimeToolDefinition
12
- from .engine import EngineRunRequest, SafeEngineError
12
+ from .engine import (
13
+ ApprovalRequired,
14
+ ApprovalResolved,
15
+ EngineRunRequest,
16
+ EngineSignal,
17
+ SafeEngineError,
18
+ )
13
19
  from .openai_engine import cancelled
14
- from .tool_host import ToolHostClient, ToolInvocation
20
+ from .tool_host import (
21
+ ToolApprovalPending,
22
+ ToolApprovalResolved,
23
+ ToolHostClient,
24
+ ToolInvocation,
25
+ canonical_tool_call_id,
26
+ )
27
+
28
+
29
+ class ApprovalSignalState:
30
+ def __init__(self) -> None:
31
+ self.queue: asyncio.Queue[EngineSignal] = asyncio.Queue()
15
32
 
16
33
 
17
34
  class GovernedTool(BaseTool):
@@ -24,6 +41,7 @@ class GovernedTool(BaseTool):
24
41
  tool_host: ToolHostClient,
25
42
  cancellation: asyncio.Event,
26
43
  failures: AdkFailureState,
44
+ approvals: ApprovalSignalState,
27
45
  ) -> None:
28
46
  super().__init__(
29
47
  name=definition.key,
@@ -33,6 +51,7 @@ class GovernedTool(BaseTool):
33
51
  self._tool_host = tool_host
34
52
  self._cancellation = cancellation
35
53
  self._failures = failures
54
+ self._approvals = approvals
36
55
 
37
56
  def _get_declaration(self) -> types.FunctionDeclaration:
38
57
  return types.FunctionDeclaration(
@@ -59,16 +78,64 @@ class GovernedTool(BaseTool):
59
78
  "Agent Tool Call 缺少稳定标识",
60
79
  retryable=False,
61
80
  )
62
- result = await self._tool_host.execute(
63
- self._request.tool_host_url,
64
- ToolInvocation(
65
- self._request.run_id,
66
- call_id,
67
- self.name,
68
- cast(dict[str, JsonValue], args),
69
- self._request.execution_grant,
70
- ),
81
+ stable_call_id = canonical_tool_call_id(
82
+ self._request.run_id,
83
+ self.name,
84
+ cast(dict[str, JsonValue], args),
85
+ )
86
+ invocation = ToolInvocation(
87
+ self._request.run_id,
88
+ stable_call_id,
89
+ self.name,
90
+ cast(dict[str, JsonValue], args),
91
+ self._request.execution_grant,
71
92
  )
93
+ approval: ToolApprovalPending | None = None
94
+ while True:
95
+ cancelled(self._cancellation)
96
+ try:
97
+ result = await self._tool_host.execute(
98
+ self._request.tool_host_url,
99
+ invocation,
100
+ )
101
+ if approval:
102
+ await self._approvals.queue.put(
103
+ ApprovalResolved(
104
+ approval.work_item_id,
105
+ stable_call_id,
106
+ "approved",
107
+ )
108
+ )
109
+ break
110
+ except ToolApprovalPending as pending:
111
+ if approval is None:
112
+ approval = pending
113
+ await self._approvals.queue.put(
114
+ ApprovalRequired(
115
+ pending.work_item_id,
116
+ stable_call_id,
117
+ )
118
+ )
119
+ elif approval.work_item_id != pending.work_item_id:
120
+ raise SafeEngineError(
121
+ "TOOL_DENIED",
122
+ "Agent Tool 审批标识漂移",
123
+ retryable=False,
124
+ ) from None
125
+ await asyncio.sleep(2)
126
+ except ToolApprovalResolved as resolved:
127
+ await self._approvals.queue.put(
128
+ ApprovalResolved(
129
+ resolved.work_item_id,
130
+ stable_call_id,
131
+ resolved.outcome,
132
+ )
133
+ )
134
+ raise SafeEngineError(
135
+ "TOOL_DENIED",
136
+ "Agent Tool 审批未通过",
137
+ retryable=False,
138
+ ) from None
72
139
  cancelled(self._cancellation)
73
140
  return result
74
141
  except SafeEngineError as error: