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
@@ -30,6 +30,7 @@ _REDACTED = "[REDACTED]"
30
30
  class GovernedAttempt:
31
31
  messages: list[dict[str, JsonValue]]
32
32
  tools: tuple[RuntimeToolDefinition, ...]
33
+ output_schema: dict[str, JsonValue] | None
33
34
 
34
35
 
35
36
  class ProviderRequestGovernance:
@@ -47,8 +48,15 @@ class ProviderRequestGovernance:
47
48
  tools: tuple[RuntimeToolDefinition, ...],
48
49
  policy: ProviderDataPolicy,
49
50
  input_value: JsonValue = None,
51
+ artifact_bytes: int = 0,
52
+ output_schema: dict[str, JsonValue] | None = None,
50
53
  measure: Callable[
51
- [list[dict[str, JsonValue]], tuple[RuntimeToolDefinition, ...]], int
54
+ [
55
+ list[dict[str, JsonValue]],
56
+ tuple[RuntimeToolDefinition, ...],
57
+ dict[str, JsonValue] | None,
58
+ ],
59
+ int,
52
60
  ]
53
61
  | None = None,
54
62
  ) -> GovernedAttempt:
@@ -64,7 +72,7 @@ class ProviderRequestGovernance:
64
72
  if rule is None:
65
73
  raise GovernanceDenied(f"Provider 未授权发送字段:{field}")
66
74
  classified.add(rule.classification)
67
- value, changed = govern_value(message, rule.dlp_action)
75
+ value, changed = govern_message(message, rule.dlp_action)
68
76
  redacted = redacted or changed
69
77
  governed_messages.append(cast(dict[str, JsonValue], value))
70
78
  governed_tools = tools
@@ -80,9 +88,27 @@ class ProviderRequestGovernance:
80
88
  RuntimeToolDefinition.model_validate(item)
81
89
  for item in cast(list[object], governed)
82
90
  )
91
+ governed_schema = output_schema
92
+ if artifact_bytes:
93
+ artifact_rule = rules.get("artifact-content")
94
+ if artifact_rule is None:
95
+ raise GovernanceDenied("Provider 未授权发送字段:artifact-content")
96
+ classified.add(artifact_rule.classification)
97
+ if output_schema is not None:
98
+ schema_rule = rules.get("output-schema")
99
+ if schema_rule is None:
100
+ raise GovernanceDenied("Provider 未授权发送字段:output-schema")
101
+ classified.add(schema_rule.classification)
102
+ raw_schema, changed = govern_value(
103
+ output_schema,
104
+ schema_rule.dlp_action,
105
+ )
106
+ redacted = redacted or changed
107
+ governed_schema = cast(dict[str, JsonValue], raw_schema)
83
108
  sent_bytes = (measure or payload_size)(
84
109
  governed_messages,
85
110
  governed_tools,
111
+ governed_schema,
86
112
  )
87
113
  decision = "redact" if redacted else "allow"
88
114
  self._record(
@@ -94,7 +120,11 @@ class ProviderRequestGovernance:
94
120
  sent_bytes,
95
121
  decision,
96
122
  )
97
- return GovernedAttempt(governed_messages, governed_tools)
123
+ return GovernedAttempt(
124
+ governed_messages,
125
+ governed_tools,
126
+ governed_schema,
127
+ )
98
128
  except GovernanceDenied:
99
129
  self._record(
100
130
  deployment_key,
@@ -210,13 +240,44 @@ def govern_value(value: object, action: str) -> tuple[object, bool]:
210
240
  return value, False
211
241
 
212
242
 
243
+ def govern_message(
244
+ message: dict[str, JsonValue],
245
+ action: str,
246
+ ) -> tuple[object, bool]:
247
+ content = message.get("content")
248
+ if message.get("role") != "user" or not isinstance(content, list):
249
+ return govern_value(message, action)
250
+ changed = False
251
+ governed_parts: list[object] = []
252
+ for part in content:
253
+ if not isinstance(part, dict):
254
+ raise GovernanceDenied("Provider 多模态消息结构无效")
255
+ if part.get("type") == "text":
256
+ governed, item_changed = govern_value(part, action)
257
+ governed_parts.append(governed)
258
+ changed = changed or item_changed
259
+ continue
260
+ image = part.get("image_url")
261
+ url = image.get("url") if isinstance(image, dict) else None
262
+ if (
263
+ part.get("type") != "image_url"
264
+ or not isinstance(url, str)
265
+ or not url.startswith("data:image/png;base64,")
266
+ ):
267
+ raise GovernanceDenied("Provider 图片输入结构无效")
268
+ governed_parts.append(part)
269
+ return {**message, "content": governed_parts}, changed
270
+
271
+
213
272
  def payload_size(
214
273
  messages: list[dict[str, JsonValue]],
215
274
  tools: tuple[RuntimeToolDefinition, ...],
275
+ output_schema: dict[str, JsonValue] | None = None,
216
276
  ) -> int:
217
277
  payload = {
218
278
  "messages": messages,
219
279
  "tools": [tool.model_dump(by_alias=True) for tool in tools],
280
+ "outputSchema": output_schema,
220
281
  }
221
282
  return len(
222
283
  json.dumps(
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from .adk_adapter import AdkEngineAdapter
6
+ from .config import EnvironmentSecretResolver, OpenAiCompatibleConfig
7
+ from .engine import AgentEngineAdapter
8
+ from .openai_provider import OpenAiCompatibleProvider
9
+ from .postgres_store import PostgresRunStore
10
+ from .provider_audit import ProviderAuditStore
11
+ from .provider_governance import ProviderRequestGovernance
12
+ from .sqlite_store import SqliteRunStore
13
+ from .store import InMemoryRunStore, RunStore
14
+ from .store_cipher import RunStoreCipher
15
+ from .tool_host import ToolHostClient
16
+
17
+
18
+ def default_engine() -> AgentEngineAdapter:
19
+ config = OpenAiCompatibleConfig.from_environment()
20
+ if config is None:
21
+ return AdkEngineAdapter()
22
+ internal_token = os.environ.get("AGENT_SERVER_INTERNAL_TOKEN", "").strip()
23
+ if not internal_token:
24
+ raise ValueError("Agent Server Provider 已配置但内部 Token 缺失")
25
+ return AdkEngineAdapter(
26
+ OpenAiCompatibleProvider(
27
+ config,
28
+ EnvironmentSecretResolver(os.environ),
29
+ ),
30
+ ToolHostClient(internal_token),
31
+ ProviderRequestGovernance(
32
+ ProviderAuditStore(
33
+ required_environment("AI_PROVIDER_AUDIT_STORE_PATH"),
34
+ retention_days=provider_audit_retention_days(),
35
+ )
36
+ ),
37
+ )
38
+
39
+
40
+ def default_store() -> RunStore:
41
+ database_url = os.environ.get("AGENT_DATABASE_URL", "").strip()
42
+ path = os.environ.get("AGENT_RUN_STORE_PATH", "").strip()
43
+ if not database_url and not path:
44
+ return InMemoryRunStore()
45
+ cipher = RunStoreCipher(required_environment("AGENT_RUN_ENCRYPTION_SECRET"))
46
+ return (
47
+ PostgresRunStore(database_url, cipher)
48
+ if database_url
49
+ else SqliteRunStore(path, cipher)
50
+ )
51
+
52
+
53
+ def required_environment(key: str) -> str:
54
+ value = os.environ.get(key, "").strip()
55
+ if not value:
56
+ raise ValueError(f"Provider 已配置但缺少 {key}")
57
+ return value
58
+
59
+
60
+ def provider_audit_retention_days() -> int:
61
+ raw = os.environ.get("AI_PROVIDER_AUDIT_RETENTION_DAYS", "90")
62
+ try:
63
+ value = int(raw)
64
+ except ValueError as error:
65
+ raise ValueError("AI_PROVIDER_AUDIT_RETENTION_DAYS 必须是整数") from error
66
+ if not 1 <= value <= 3650:
67
+ raise ValueError("AI_PROVIDER_AUDIT_RETENTION_DAYS 超出允许范围")
68
+ return value
@@ -1,23 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- import hashlib
5
- import json
4
+ import logging
6
5
  import uuid
7
6
 
8
- from .contracts import AgentError, JsonValue, StartRunRequest
7
+ from .artifact_input import ArtifactLoader
8
+ from .contracts import AgentError, StartRunRequest
9
9
  from .engine import (
10
10
  AgentEngineAdapter,
11
- ApprovalRequired,
12
11
  EngineCompleted,
13
12
  EngineRunRequest,
14
- OutputDelta,
15
13
  SafeEngineError,
16
- ToolCompleted,
17
- ToolRequested,
18
- UsageReported,
19
14
  )
15
+ from .fingerprint import request_fingerprint, resolved_release
16
+ from .lease import cancel_on_lease_loss, lease_failed
17
+ from .signal_events import canonical_signal_event
20
18
  from .store import IdempotencyConflictError, RunRecord, RunStore
19
+ from .telemetry import AgentRunTrace
20
+
21
+ logger = logging.getLogger(__name__)
21
22
 
22
23
 
23
24
  class RunNotFoundError(Exception):
@@ -29,10 +30,18 @@ class OperationNotFoundError(Exception):
29
30
 
30
31
 
31
32
  class AgentRunService:
32
- def __init__(self, engine: AgentEngineAdapter, store: RunStore) -> None:
33
+ def __init__(
34
+ self,
35
+ engine: AgentEngineAdapter,
36
+ store: RunStore,
37
+ artifact_loader: ArtifactLoader | None = None,
38
+ ) -> None:
33
39
  self.engine = engine
34
40
  self.store = store
41
+ self.artifact_loader = artifact_loader
35
42
  self._tasks: set[asyncio.Task[None]] = set()
43
+ self._active_run_ids: set[str] = set()
44
+ self._recovery_task: asyncio.Task[None] | None = None
36
45
 
37
46
  async def start(
38
47
  self,
@@ -41,48 +50,148 @@ class AgentRunService:
41
50
  ) -> RunRecord:
42
51
  if not self.engine.supports(request.operation_key):
43
52
  raise OperationNotFoundError
44
- fingerprint = request_fingerprint(request.operation_key, request.input)
53
+ missing = set(request.required_capabilities) - set(self.engine.capabilities)
54
+ unsupported = set(request.input_modalities) - set(self.engine.input_modalities)
55
+ if missing or unsupported:
56
+ raise SafeEngineError(
57
+ "MODEL_ROUTE_UNAVAILABLE",
58
+ "模型 Deployment 缺少 Operation 所需能力或输入模态",
59
+ retryable=True,
60
+ )
61
+ fingerprint = request_fingerprint(request)
45
62
  candidate = RunRecord(
46
63
  run_id=f"run_{uuid.uuid4().hex}",
47
64
  operation_key=request.operation_key,
48
65
  input=request.input,
49
66
  idempotency_key=idempotency_key,
50
67
  fingerprint=fingerprint,
68
+ release=resolved_release(request),
69
+ trace_id=request.trace_id,
70
+ )
71
+ record, created = await self.store.create_or_get(
72
+ candidate,
73
+ request.model_dump(by_alias=True),
51
74
  )
52
- record, created = await self.store.create_or_get(candidate)
53
75
  if created:
54
- task = asyncio.create_task(self._execute(record, request))
55
- self._tasks.add(task)
56
- task.add_done_callback(self._tasks.discard)
76
+ self._spawn(record, request)
57
77
  return record
58
78
 
79
+ async def recover(self) -> None:
80
+ await self._recover_once()
81
+ if self._recovery_task is None:
82
+ self._recovery_task = asyncio.create_task(self._recovery_loop())
83
+
84
+ async def close(self) -> None:
85
+ if self._recovery_task:
86
+ self._recovery_task.cancel()
87
+ for task in list(self._tasks):
88
+ task.cancel()
89
+ await asyncio.gather(
90
+ *([self._recovery_task] if self._recovery_task else []),
91
+ *self._tasks,
92
+ return_exceptions=True,
93
+ )
94
+ await self.store.close()
95
+
96
+ async def _recover_once(self) -> None:
97
+ for record, raw in await self.store.recoverable():
98
+ if record.run_id in self._active_run_ids:
99
+ continue
100
+ self._spawn(
101
+ record,
102
+ StartRunRequest.model_validate(raw),
103
+ resumed=True,
104
+ )
105
+
106
+ async def _recovery_loop(self) -> None:
107
+ while True:
108
+ await asyncio.sleep(10)
109
+ try:
110
+ await self._recover_once()
111
+ except asyncio.CancelledError:
112
+ raise
113
+ except Exception:
114
+ logger.exception("Agent Run 恢复轮询失败,将继续重试")
115
+
116
+ async def record_evaluation(
117
+ self,
118
+ release_id: str,
119
+ evaluation_set_key: str,
120
+ evaluation_set_version: str,
121
+ metrics: dict[str, float],
122
+ ) -> str:
123
+ return await self.store.record_evaluation(
124
+ release_id,
125
+ evaluation_set_key,
126
+ evaluation_set_version,
127
+ metrics,
128
+ )
129
+
59
130
  async def get(self, run_id: str) -> RunRecord:
60
131
  record = await self.store.get(run_id)
61
132
  if record is None:
62
133
  raise RunNotFoundError
63
134
  return record
64
135
 
136
+ async def probe(self) -> None:
137
+ await self.store.probe()
138
+
65
139
  async def cancel(self, run_id: str) -> RunRecord:
66
140
  record = await self.get(run_id)
67
141
  if record.terminal:
68
142
  return record
69
143
  await self.engine.cancel(run_id)
144
+ await self._resolve_pending_approval(record, "cancelled")
70
145
  await self.store.append(run_id, "run.cancelled")
71
- return record
146
+ return await self.get(run_id)
72
147
 
73
148
  async def _execute(
74
149
  self,
75
150
  record: RunRecord,
76
151
  invocation: StartRunRequest,
152
+ resumed: bool = False,
77
153
  ) -> None:
78
- await self.store.append(record.run_id, "run.started")
154
+ execution_task = asyncio.current_task()
155
+ if execution_task is None:
156
+ raise RuntimeError("Agent Run 缺少执行任务")
157
+ lease_task = asyncio.create_task(self._renew_lease(record.run_id))
158
+ run_trace = AgentRunTrace(record)
159
+ run_trace.start()
160
+ final_status = "unknown"
161
+ lease_task.add_done_callback(
162
+ lambda completed: cancel_on_lease_loss(completed, execution_task)
163
+ )
79
164
  try:
165
+ await self.store.append(
166
+ record.run_id,
167
+ "run.resumed" if resumed else "run.started",
168
+ )
169
+ if invocation.artifacts and not self.artifact_loader:
170
+ raise SafeEngineError(
171
+ "RUNTIME_UNAVAILABLE",
172
+ "Agent Artifact Runtime 未配置",
173
+ retryable=True,
174
+ )
175
+ prepared = (
176
+ await self.artifact_loader.prepare(
177
+ invocation.artifacts,
178
+ invocation.input_modalities,
179
+ invocation.limits,
180
+ )
181
+ if self.artifact_loader
182
+ else ()
183
+ )
80
184
  request = EngineRunRequest(
81
185
  run_id=record.run_id,
82
186
  operation_key=record.operation_key,
83
- input=record.input,
187
+ input=invocation.input,
188
+ input_modalities=tuple(invocation.input_modalities),
189
+ required_capabilities=tuple(invocation.required_capabilities),
190
+ artifacts=prepared,
84
191
  instructions=invocation.instructions,
85
192
  user_prompt=invocation.user_prompt,
193
+ output_schema_key=invocation.output_schema_key,
194
+ output_schema=invocation.output_schema,
86
195
  allowed_tools=tuple(invocation.allowed_tools),
87
196
  limits=invocation.limits,
88
197
  provider_egress=invocation.provider_egress,
@@ -91,12 +200,26 @@ class AgentRunService:
91
200
  execution_grant=invocation.execution_grant,
92
201
  tool_host_url=invocation.tool_host_url,
93
202
  )
94
- async with asyncio.timeout(invocation.limits.max_duration_seconds):
203
+ approval_timeout = max(
204
+ (
205
+ tool.approval_timeout_seconds or 0
206
+ for tool in invocation.allowed_tools
207
+ if tool.approval == "required"
208
+ ),
209
+ default=0,
210
+ )
211
+ async with asyncio.timeout(
212
+ max(
213
+ invocation.limits.max_duration_seconds,
214
+ approval_timeout + (5 if approval_timeout else 0),
215
+ )
216
+ ):
95
217
  async for signal in self.engine.execute(request):
96
218
  if isinstance(signal, EngineCompleted):
97
219
  await self.store.complete(record.run_id, signal.output)
98
220
  return
99
- await self._append_signal(record.run_id, signal)
221
+ event_type, fields = canonical_signal_event(signal)
222
+ await self.store.append(record.run_id, event_type, fields)
100
223
  await self._fail(
101
224
  record.run_id,
102
225
  SafeEngineError(
@@ -108,6 +231,8 @@ class AgentRunService:
108
231
  except SafeEngineError as error:
109
232
  await self._fail(record.run_id, error)
110
233
  except TimeoutError:
234
+ current = await self.get(record.run_id)
235
+ await self._resolve_pending_approval(current, "timed-out")
111
236
  await self._fail(
112
237
  record.run_id,
113
238
  SafeEngineError(
@@ -116,6 +241,18 @@ class AgentRunService:
116
241
  retryable=True,
117
242
  ),
118
243
  )
244
+ except asyncio.CancelledError:
245
+ if lease_failed(lease_task):
246
+ await self._fail(
247
+ record.run_id,
248
+ SafeEngineError(
249
+ "RUNTIME_UNAVAILABLE",
250
+ "Agent Run 租约已丢失",
251
+ retryable=True,
252
+ ),
253
+ )
254
+ return
255
+ raise
119
256
  except Exception:
120
257
  await self._fail(
121
258
  record.run_id,
@@ -125,36 +262,71 @@ class AgentRunService:
125
262
  retryable=True,
126
263
  ),
127
264
  )
265
+ finally:
266
+ try:
267
+ lease_task.cancel()
268
+ await asyncio.gather(lease_task, return_exceptions=True)
269
+ final_record = await self.store.get(record.run_id)
270
+ if final_record and final_record.terminal:
271
+ await self.store.clear_invocation(record.run_id)
272
+ if final_record:
273
+ final_status = final_record.status
274
+ try:
275
+ await self.store.release_lease(record.run_id)
276
+ except KeyError:
277
+ pass
278
+ finally:
279
+ run_trace.finish(final_status)
128
280
 
129
- async def _append_signal(self, run_id: str, signal: object) -> None:
130
- if isinstance(signal, OutputDelta):
131
- await self.store.append(run_id, "output.delta", {"text": signal.text})
132
- elif isinstance(signal, ToolRequested):
133
- await self.store.append(
134
- run_id,
135
- "tool.requested",
136
- {"toolCallId": signal.tool_call_id, "toolKey": signal.tool_key},
137
- )
138
- elif isinstance(signal, ApprovalRequired):
139
- await self.store.append(
140
- run_id,
141
- "approval.required",
142
- {"workItemId": signal.work_item_id},
143
- )
144
- elif isinstance(signal, ToolCompleted):
145
- await self.store.append(
146
- run_id,
147
- "tool.completed",
148
- {"toolCallId": signal.tool_call_id},
149
- )
150
- elif isinstance(signal, UsageReported):
281
+ def _spawn(
282
+ self,
283
+ record: RunRecord,
284
+ request: StartRunRequest,
285
+ resumed: bool = False,
286
+ ) -> None:
287
+ if record.run_id in self._active_run_ids:
288
+ return
289
+ self._active_run_ids.add(record.run_id)
290
+ task = asyncio.create_task(self._execute(record, request, resumed))
291
+ self._tasks.add(task)
292
+ task.add_done_callback(
293
+ lambda completed: self._completed(record.run_id, completed)
294
+ )
295
+
296
+ async def _renew_lease(self, run_id: str) -> None:
297
+ while True:
298
+ await asyncio.sleep(10)
299
+ await self.store.renew_lease(run_id)
300
+
301
+ def _completed(self, run_id: str, task: asyncio.Task[None]) -> None:
302
+ self._tasks.discard(task)
303
+ self._active_run_ids.discard(run_id)
304
+
305
+ async def _resolve_pending_approval(
306
+ self,
307
+ record: RunRecord,
308
+ outcome: str,
309
+ ) -> None:
310
+ pending: dict[str, object] | None = None
311
+ for event in record.events:
312
+ if event.get("type") == "approval.required":
313
+ pending = event
314
+ elif event.get("type") == "approval.resolved":
315
+ pending = None
316
+ if not pending:
317
+ return
318
+ work_item_id = pending.get("workItemId")
319
+ tool_call_id = pending.get("toolCallId")
320
+ if isinstance(work_item_id, str) and isinstance(tool_call_id, str):
151
321
  await self.store.append(
152
- run_id,
153
- "usage.reported",
154
- {"usage": signal.usage},
322
+ record.run_id,
323
+ "approval.resolved",
324
+ {
325
+ "workItemId": work_item_id,
326
+ "toolCallId": tool_call_id,
327
+ "outcome": outcome,
328
+ },
155
329
  )
156
- else:
157
- raise TypeError("unsupported engine signal")
158
330
 
159
331
  async def _fail(self, run_id: str, failure: SafeEngineError) -> None:
160
332
  error = AgentError(
@@ -170,14 +342,4 @@ class AgentRunService:
170
342
  )
171
343
 
172
344
 
173
- def request_fingerprint(operation_key: str, input_value: JsonValue) -> str:
174
- encoded = json.dumps(
175
- {"operationKey": operation_key, "input": input_value},
176
- ensure_ascii=False,
177
- separators=(",", ":"),
178
- sort_keys=True,
179
- ).encode()
180
- return hashlib.sha256(encoded).hexdigest()
181
-
182
-
183
345
  __all__ = ["AgentRunService", "IdempotencyConflictError"]
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from .engine import (
4
+ ApprovalRequired,
5
+ ApprovalResolved,
6
+ OutputDelta,
7
+ ToolCompleted,
8
+ ToolRequested,
9
+ UsageReported,
10
+ )
11
+
12
+
13
+ def canonical_signal_event(signal: object) -> tuple[str, dict[str, object]]:
14
+ if isinstance(signal, OutputDelta):
15
+ return "output.delta", {"text": signal.text}
16
+ if isinstance(signal, ToolRequested):
17
+ return "tool.requested", {
18
+ "toolCallId": signal.tool_call_id,
19
+ "toolKey": signal.tool_key,
20
+ }
21
+ if isinstance(signal, ApprovalRequired):
22
+ return "approval.required", {
23
+ "workItemId": signal.work_item_id,
24
+ "toolCallId": signal.tool_call_id,
25
+ }
26
+ if isinstance(signal, ApprovalResolved):
27
+ return "approval.resolved", {
28
+ "workItemId": signal.work_item_id,
29
+ "toolCallId": signal.tool_call_id,
30
+ "outcome": signal.outcome,
31
+ }
32
+ if isinstance(signal, ToolCompleted):
33
+ return "tool.completed", {"toolCallId": signal.tool_call_id}
34
+ if isinstance(signal, UsageReported):
35
+ return "usage.reported", {"usage": signal.usage}
36
+ raise TypeError("unsupported engine signal")