agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__py3-none-any.whl

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 (250) hide show
  1. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
  2. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
  3. ksadk/_build_provenance.json +1 -0
  4. ksadk/agui/_agent_helpers.py +4 -0
  5. ksadk/api/client.py +3 -0
  6. ksadk/builders/managed_runtime_builder.py +3 -0
  7. ksadk/cli/__init__.py +6 -0
  8. ksadk/cli/cmd_plugin.py +657 -0
  9. ksadk/codex/runtime.py +50 -8
  10. ksadk/configs/env_registry.py +31 -3
  11. ksadk/configs/settings.py +9 -0
  12. ksadk/context_engine/contributors.py +73 -8
  13. ksadk/conversations/__init__.py +19 -0
  14. ksadk/conversations/context.py +104 -24
  15. ksadk/conversations/contracts.py +266 -0
  16. ksadk/conversations/message_projection.py +19 -1
  17. ksadk/conversations/projector.py +616 -0
  18. ksadk/conversations/reducer.py +98 -0
  19. ksadk/events/_v1_compat/projection.py +2 -0
  20. ksadk/events/adapters/codex.py +190 -0
  21. ksadk/harness/__init__.py +45 -35
  22. ksadk/harness/runtime.py +60 -1
  23. ksadk/kernel/contract_fingerprints.py +5 -4
  24. ksadk/kernel/contracts.py +8 -0
  25. ksadk/kernel/control.py +1 -0
  26. ksadk/kernel/ingress.py +21 -0
  27. ksadk/kernel/memory_store.py +1 -0
  28. ksadk/kernel/postgres_store.py +1 -0
  29. ksadk/kernel/sqlite_store.py +1 -0
  30. ksadk/kernel/worker.py +11 -0
  31. ksadk/memory/providers/local_sqlite.py +7 -0
  32. ksadk/plugins/__init__.py +167 -0
  33. ksadk/plugins/bridges/__init__.py +45 -0
  34. ksadk/plugins/bridges/codex.py +552 -0
  35. ksadk/plugins/bridges/dsh.py +1116 -0
  36. ksadk/plugins/builtins.py +747 -0
  37. ksadk/plugins/bundle.py +246 -0
  38. ksadk/plugins/bundle_security.py +167 -0
  39. ksadk/plugins/composition.py +529 -0
  40. ksadk/plugins/context_contributor.py +437 -0
  41. ksadk/plugins/contracts.py +451 -0
  42. ksadk/plugins/dsh_toolchain.py +935 -0
  43. ksadk/plugins/ecosystem_bridge.py +1105 -0
  44. ksadk/plugins/ecosystem_probe.py +191 -0
  45. ksadk/plugins/host.py +724 -0
  46. ksadk/plugins/providers/__init__.py +85 -0
  47. ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
  48. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
  49. ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
  50. ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
  51. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
  52. ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
  53. ksadk/plugins/providers/codex.py +722 -0
  54. ksadk/plugins/providers/codex_dsh.py +83 -0
  55. ksadk/plugins/providers/dsh.py +951 -0
  56. ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
  57. ksadk/plugins/providers/harness.py +627 -0
  58. ksadk/plugins/providers/harness_dsh.py +91 -0
  59. ksadk/plugins/providers/legacy.py +215 -0
  60. ksadk/plugins/providers/legacy_catalog.py +100 -0
  61. ksadk/plugins/providers/shipped_dsh.py +370 -0
  62. ksadk/plugins/resolver.py +331 -0
  63. ksadk/plugins/subagent_providers/__init__.py +12 -0
  64. ksadk/plugins/subagent_providers/codex.py +443 -0
  65. ksadk/plugins/subagents.py +333 -0
  66. ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
  67. ksadk/runtime/adapter.py +1 -0
  68. ksadk/runtime/factory.py +76 -2
  69. ksadk/runtime/framework_adapters.py +18 -0
  70. ksadk/runtime/runner_adapter.py +5 -0
  71. ksadk/sandbox/__init__.py +2 -1
  72. ksadk/sandbox/factory.py +72 -0
  73. ksadk/scheduler/__init__.py +18 -0
  74. ksadk/scheduler/calendar.py +102 -0
  75. ksadk/scheduler/contracts.py +254 -0
  76. ksadk/scheduler/dispatcher.py +129 -0
  77. ksadk/scheduler/engine.py +416 -0
  78. ksadk/scheduler/sqlite_store.py +563 -0
  79. ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
  80. ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
  81. ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
  82. ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
  83. ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
  84. ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
  85. ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
  86. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
  87. ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
  88. ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
  89. ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
  90. ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
  91. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
  92. ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
  93. ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
  94. ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
  95. ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
  96. ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
  97. ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
  98. ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
  99. ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
  100. ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
  101. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
  102. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
  103. ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
  104. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
  105. ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
  106. ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
  107. ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
  108. ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
  109. ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
  110. ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
  111. ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
  112. ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
  113. ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
  114. ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
  115. ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
  116. ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
  117. ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
  118. ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
  119. ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
  120. ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
  121. ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
  122. ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
  123. ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
  124. ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
  125. ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
  126. ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
  127. ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
  128. ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
  129. ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
  130. ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
  131. ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
  132. ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
  133. ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
  134. ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
  135. ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
  136. ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
  137. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
  138. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
  139. ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
  140. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
  141. ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
  142. ksadk/server/static/assets/index-B2k_urY8.js +241 -0
  143. ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
  144. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
  145. ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
  146. ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
  147. ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
  148. ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
  149. ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
  150. ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
  151. ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
  152. ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
  153. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
  154. ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
  155. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
  156. ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
  157. ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
  158. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
  159. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
  160. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
  161. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
  162. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
  163. ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
  164. ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
  165. ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
  166. ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
  167. ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
  168. ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
  169. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
  170. ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
  171. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
  172. ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
  173. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
  174. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
  175. ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
  176. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
  177. ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
  178. ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
  179. ksadk/server/static/index.html +2 -2
  180. ksadk/skills/service_client.py +48 -0
  181. ksadk/studio/agent_lifecycle.py +26 -20
  182. ksadk/studio/api.py +513 -71
  183. ksadk/studio/api_catalog_routes.py +5 -0
  184. ksadk/studio/api_contracts.py +34 -1
  185. ksadk/studio/api_plugin_routes.py +480 -0
  186. ksadk/studio/attachment_store.py +187 -0
  187. ksadk/studio/authoring.py +10 -4
  188. ksadk/studio/authoring_coordinator.py +12 -2
  189. ksadk/studio/builder.py +123 -43
  190. ksadk/studio/cloud.py +4 -0
  191. ksadk/studio/codex_agent_service.py +12 -1
  192. ksadk/studio/codex_builder.py +54 -15
  193. ksadk/studio/codex_manifest.py +21 -1
  194. ksadk/studio/codex_run.py +16 -4
  195. ksadk/studio/compatibility_report.py +445 -0
  196. ksadk/studio/compiler.py +6 -1
  197. ksadk/studio/contracts.py +123 -4
  198. ksadk/studio/dsh_provider_registration.py +768 -0
  199. ksadk/studio/event_store.py +54 -6
  200. ksadk/studio/framework_run.py +37 -4
  201. ksadk/studio/hosted_kernel.py +1 -1
  202. ksadk/studio/operations.py +12 -2
  203. ksadk/studio/plugin_composition.py +301 -0
  204. ksadk/studio/plugin_kernel_adapter.py +128 -0
  205. ksadk/studio/plugin_runtime.py +652 -0
  206. ksadk/studio/resource_catalog.py +6 -1
  207. ksadk/studio/run_service.py +713 -75
  208. ksadk/studio/runtime_source.py +9 -1
  209. ksadk/studio/scheduler_runtime.py +374 -0
  210. ksadk/studio/scheduler_service.py +211 -0
  211. ksadk/studio/service.py +672 -82
  212. ksadk/studio/shared_web.py +167 -28
  213. ksadk/studio/soul.py +52 -0
  214. ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
  215. ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
  216. ksadk/studio/static/index.html +2 -2
  217. ksadk/studio/validator.py +1 -1
  218. ksadk/studio/workspace.py +1 -0
  219. ksadk/version.py +1 -1
  220. ksadk_runtime_common/workspace_files/path_utils.py +41 -0
  221. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
  222. ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
  223. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
  224. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
  225. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
  227. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
  228. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
  229. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
  230. ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
  231. ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
  232. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
  233. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
  234. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
  235. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
  236. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
  237. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
  238. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
  239. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
  240. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
  241. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
  242. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
  243. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
  244. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
  245. ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
  246. ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
  247. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
  248. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
  249. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
  250. {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
@@ -185,6 +185,11 @@ class RunnerRuntimeAdapter(_RunnerStreamMappingMixin, RuntimeAdapter):
185
185
  if durable_supported
186
186
  else _unavailable("durable_restore_requires_cross_process_checkpoint")
187
187
  ),
188
+ # A generic Runner cannot claim interaction delivery merely from a
189
+ # checkpoint capability. ADK is forward-only and only the
190
+ # LangGraph specialization below binds a checkpoint to the
191
+ # original interrupt identity.
192
+ interaction_mode="unavailable",
188
193
  )
189
194
 
190
195
  async def durable_restore(self, handle: RunHandle) -> RunHandle:
ksadk/sandbox/__init__.py CHANGED
@@ -12,7 +12,7 @@ from ksadk.sandbox.base import (
12
12
  SandboxSpec,
13
13
  SandboxType,
14
14
  )
15
- from ksadk.sandbox.factory import create_sandbox_backend, sandbox_spec_from_env
15
+ from ksadk.sandbox.factory import create_sandbox_backend, sandbox_spec_from_env, setup_sandbox_api_url_if_needed
16
16
 
17
17
  __all__ = [
18
18
  "E2BSandboxBackend",
@@ -28,4 +28,5 @@ __all__ = [
28
28
  "SandboxType",
29
29
  "create_sandbox_backend",
30
30
  "sandbox_spec_from_env",
31
+ "setup_sandbox_api_url_if_needed",
31
32
  ]
ksadk/sandbox/factory.py CHANGED
@@ -16,11 +16,83 @@ def bool_env(name: str, default: bool = True) -> bool:
16
16
  return raw.strip().lower() in {"1", "true", "yes", "on"}
17
17
 
18
18
 
19
+ def _resolve_sandbox_api_url() -> str:
20
+ """Probe sandbox control-plane URL at runtime (in-Pod).
21
+
22
+ agentengine-server injects public E2B_API_URL on the management cluster.
23
+ public_only compute pods can reach the public address directly; private_only
24
+ pods have no public egress. Strategy: try the original (public) URL first;
25
+ if reachable keep it, otherwise fall back to the 198 public-service-net
26
+ internal address. Mirrors KSPMAS get_kspmas_api_base(): probe runs in-Pod.
27
+ """
28
+ raw = (os.environ.get("E2B_API_URL") or "").strip()
29
+ if not raw:
30
+ return raw
31
+
32
+ from urllib.parse import urlsplit, urlunsplit
33
+
34
+ parsed = urlsplit(raw)
35
+ host = (parsed.hostname or "").lower()
36
+ if host.endswith(".sdns.ksyun.com"):
37
+ return raw
38
+ if not host.endswith(".sandbox.ksyun.com"):
39
+ return raw
40
+
41
+ parts = host.split(".")
42
+ region = ""
43
+ if len(parts) >= 4 and parts[0] == "mgr":
44
+ region = parts[1]
45
+ elif len(parts) >= 4:
46
+ region = parts[1] if parts[1] != "sandbox" else ""
47
+ if not region:
48
+ return raw
49
+
50
+ import socket
51
+
52
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
53
+
54
+ # 1) Try the original (public) URL first; reachable => keep it.
55
+ try:
56
+ s = socket.socket()
57
+ s.settimeout(1.5)
58
+ s.connect((host, port))
59
+ s.close()
60
+ return raw
61
+ except OSError:
62
+ pass
63
+
64
+ # 2) Public unreachable (e.g. private_only node) => fall back to internal.
65
+ internal_host = f"sandbox.{region}.sandbox.sdns.ksyun.com"
66
+ try:
67
+ s = socket.socket()
68
+ s.settimeout(1.5)
69
+ s.connect((internal_host, port))
70
+ s.close()
71
+ except OSError:
72
+ return raw
73
+
74
+ internal_url = urlunsplit((parsed.scheme or "https", internal_host, parsed.path, parsed.query, ""))
75
+ return internal_url
76
+
77
+
78
+ def setup_sandbox_api_url_if_needed() -> None:
79
+ """Override E2B_API_URL to internal address before creating E2B backend.
80
+
81
+ Only runs in managed runtime (AGENT_RUNTIME_ID / K8S); local dev unaffected.
82
+ """
83
+ if not os.environ.get("AGENT_RUNTIME_ID") and not os.environ.get("KUBERNETES_SERVICE_HOST"):
84
+ return
85
+ resolved = _resolve_sandbox_api_url()
86
+ if resolved != (os.environ.get("E2B_API_URL") or ""):
87
+ os.environ["E2B_API_URL"] = resolved
88
+
89
+
19
90
  def create_sandbox_backend(
20
91
  backend: str | None = None,
21
92
  *,
22
93
  sandbox_cls: Any | None = None,
23
94
  ) -> SandboxBackend:
95
+ setup_sandbox_api_url_if_needed()
24
96
  resolved = (backend or os.environ.get("KSADK_SANDBOX_BACKEND") or "e2b").strip().lower()
25
97
  if resolved in {"local", "local_process"}:
26
98
  return LocalProcessSandboxBackend(
@@ -0,0 +1,18 @@
1
+ """Local Scheduler Lite contracts and deterministic calendar calculations."""
2
+
3
+ from ksadk.scheduler.calendar import next_schedule_time
4
+ from ksadk.scheduler.contracts import ScheduledTask, ScheduleOccurrence, ScheduleSpec
5
+ from ksadk.scheduler.dispatcher import AgentControlSchedulerDispatcher
6
+ from ksadk.scheduler.engine import SchedulerDispatchError, SchedulerEngine
7
+ from ksadk.scheduler.sqlite_store import SchedulerSQLiteStore
8
+
9
+ __all__ = [
10
+ "ScheduleOccurrence",
11
+ "ScheduledTask",
12
+ "ScheduleSpec",
13
+ "AgentControlSchedulerDispatcher",
14
+ "SchedulerDispatchError",
15
+ "SchedulerEngine",
16
+ "SchedulerSQLiteStore",
17
+ "next_schedule_time",
18
+ ]
@@ -0,0 +1,102 @@
1
+ """Deterministic, dependency-free calendar calculations for Scheduler Lite.
2
+
3
+ The supported cron dialect intentionally stays small and explicit: five fields,
4
+ numeric values, comma lists, ranges and ``*/step``. It is sufficient for the
5
+ Studio scheduler and avoids inheriting an accidental transitive dependency.
6
+ All matching happens after UTC -> IANA timezone conversion. That means a
7
+ nonexistent DST local minute is skipped and the repeated fall-back minute runs
8
+ once (the first ``fold`` only).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from datetime import datetime, timedelta, timezone
14
+ from zoneinfo import ZoneInfo
15
+
16
+ from ksadk.scheduler.contracts import ScheduleSpec, _parse_cron
17
+
18
+
19
+ def next_schedule_time(
20
+ schedule: ScheduleSpec,
21
+ *,
22
+ after: datetime,
23
+ anchor_at: datetime | None = None,
24
+ ) -> datetime | None:
25
+ """Return the first scheduled UTC timestamp strictly after ``after``."""
26
+
27
+ if after.tzinfo is None:
28
+ raise ValueError("after must include a timezone")
29
+ after = after.astimezone(timezone.utc)
30
+ if schedule.kind == "once":
31
+ assert schedule.at is not None
32
+ return schedule.at if schedule.at > after else None
33
+ if schedule.kind == "interval":
34
+ assert schedule.every_seconds is not None
35
+ anchor = (schedule.anchor_at or anchor_at or after).astimezone(timezone.utc)
36
+ if anchor > after:
37
+ return anchor
38
+ elapsed = (after - anchor).total_seconds()
39
+ step = schedule.every_seconds
40
+ return anchor + timedelta(seconds=(int(elapsed // step) + 1) * step)
41
+ assert schedule.expression is not None
42
+ return _next_cron(schedule.expression, schedule.timezone, after)
43
+
44
+
45
+ def _next_cron(expression: str, timezone_name: str, after: datetime) -> datetime:
46
+ minute, hour, day, month, weekday = _parse_cron(expression)
47
+ fields = (
48
+ _expand_field(minute, 0, 59),
49
+ _expand_field(hour, 0, 23),
50
+ _expand_field(day, 1, 31),
51
+ _expand_field(month, 1, 12),
52
+ _expand_field(weekday, 0, 6),
53
+ )
54
+ zone = ZoneInfo(timezone_name)
55
+ candidate = after.replace(second=0, microsecond=0) + timedelta(minutes=1)
56
+ # Five years is a deliberate guard against impossible expressions such as
57
+ # 31 February. The caller receives a typed validation error rather than a
58
+ # scheduler loop that runs forever.
59
+ deadline = candidate + timedelta(days=366 * 5)
60
+ while candidate <= deadline:
61
+ local = candidate.astimezone(zone)
62
+ # datetime.weekday uses Monday=0. Cron uses Sunday=0.
63
+ cron_weekday = (local.weekday() + 1) % 7
64
+ if (
65
+ local.fold == 0
66
+ and local.minute in fields[0]
67
+ and local.hour in fields[1]
68
+ and local.day in fields[2]
69
+ and local.month in fields[3]
70
+ and cron_weekday in fields[4]
71
+ ):
72
+ return candidate
73
+ candidate += timedelta(minutes=1)
74
+ raise ValueError("cron expression has no matching time within five years")
75
+
76
+
77
+ def _expand_field(value: str, minimum: int, maximum: int) -> set[int]:
78
+ result: set[int] = set()
79
+ for item in value.split(","):
80
+ if "/" in item:
81
+ base, step_text = item.split("/", 1)
82
+ else:
83
+ base, step_text = item, None
84
+ step = int(step_text) if step_text is not None else 1
85
+ if step <= 0:
86
+ raise ValueError("cron step must be positive")
87
+ if base == "*":
88
+ start, end = minimum, maximum
89
+ elif "-" in base:
90
+ start_text, end_text = base.split("-", 1)
91
+ start, end = int(start_text), int(end_text)
92
+ else:
93
+ start = end = int(base)
94
+ if start < minimum or end > maximum or start > end:
95
+ raise ValueError("cron field is outside its valid range")
96
+ result.update(range(start, end + 1, step))
97
+ if not result:
98
+ raise ValueError("cron field must not be empty")
99
+ return result
100
+
101
+
102
+ __all__ = ["next_schedule_time"]
@@ -0,0 +1,254 @@
1
+ """Versioned Scheduler Lite source contracts (P2-06 foundation)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Literal
8
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
11
+
12
+
13
+ def _to_camel(value: str) -> str:
14
+ head, *tail = value.split("_")
15
+ return head + "".join(part.capitalize() for part in tail)
16
+
17
+
18
+ class ScheduleModel(BaseModel):
19
+ model_config = ConfigDict(
20
+ alias_generator=_to_camel,
21
+ populate_by_name=True,
22
+ extra="forbid",
23
+ frozen=True,
24
+ )
25
+
26
+
27
+ _TASK_ID = re.compile(r"^[a-z][a-z0-9-]{2,62}$")
28
+ ScheduleOccurrenceState = Literal[
29
+ "claimed",
30
+ "accepted",
31
+ "running",
32
+ "succeeded",
33
+ "failed",
34
+ "skipped",
35
+ "cancelled",
36
+ ]
37
+
38
+
39
+ class ScheduleSpec(ScheduleModel):
40
+ kind: Literal["once", "interval", "cron"]
41
+ timezone: str = "UTC"
42
+ at: datetime | None = None
43
+ every_seconds: int | None = Field(default=None, ge=60, le=31_536_000)
44
+ anchor_at: datetime | None = None
45
+ expression: str | None = Field(default=None, min_length=9, max_length=128)
46
+ misfire_policy: Literal["skip", "run_once"] = "skip"
47
+
48
+ @field_validator("timezone")
49
+ @classmethod
50
+ def validate_timezone(cls, value: str) -> str:
51
+ try:
52
+ ZoneInfo(value)
53
+ except ZoneInfoNotFoundError as error:
54
+ raise ValueError("timezone must be an IANA timezone") from error
55
+ return value
56
+
57
+ @field_validator("at", "anchor_at")
58
+ @classmethod
59
+ def normalize_timestamp(cls, value: datetime | None) -> datetime | None:
60
+ if value is None:
61
+ return None
62
+ if value.tzinfo is None:
63
+ raise ValueError("schedule timestamps must include a timezone")
64
+ return value.astimezone(timezone.utc)
65
+
66
+ @model_validator(mode="after")
67
+ def validate_kind_shape(self) -> "ScheduleSpec":
68
+ if self.kind == "once":
69
+ if self.at is None or self.every_seconds is not None or self.expression is not None:
70
+ raise ValueError("once schedule requires only at")
71
+ elif self.kind == "interval":
72
+ if self.every_seconds is None or self.at is not None or self.expression is not None:
73
+ raise ValueError("interval schedule requires everySeconds and no expression")
74
+ elif self.kind == "cron":
75
+ if self.expression is None or self.at is not None or self.every_seconds is not None:
76
+ raise ValueError("cron schedule requires only expression")
77
+ _parse_cron(self.expression)
78
+ return self
79
+
80
+
81
+ class ScheduledTaskTarget(ScheduleModel):
82
+ # ``agent_instance_id`` identifies the concrete Kernel owner which will
83
+ # consume an Inbox command. ``agent_id`` is the Studio-facing stable
84
+ # logical Agent identity used to retain a task on its detail page even when
85
+ # the task is pinned to an older immutable Build. It is optional only so
86
+ # existing v1 SQLite rows remain readable; all Studio-authored tasks set it.
87
+ agent_id: str | None = Field(default=None, min_length=1, max_length=256)
88
+ tenant_id: str = Field(min_length=1, max_length=256)
89
+ agent_instance_id: str = Field(min_length=1, max_length=256)
90
+ agent_version_ref: str | None = Field(default=None, min_length=1, max_length=256)
91
+ session_id: str | None = Field(default=None, min_length=1, max_length=256)
92
+ authorization_ref: str = Field(min_length=1, max_length=512)
93
+
94
+
95
+ class ScheduleCommandTemplate(ScheduleModel):
96
+ """The sole supported Scheduler Lite action: durable enqueue via AgentControl."""
97
+
98
+ command_type: Literal["enqueue"] = "enqueue"
99
+ payload: dict[str, Any]
100
+
101
+ @model_validator(mode="after")
102
+ def validate_payload(self) -> "ScheduleCommandTemplate":
103
+ if "content" not in self.payload:
104
+ raise ValueError("scheduled enqueue payload requires content")
105
+ _reject_clear_secrets(self.payload)
106
+ return self
107
+
108
+
109
+ class ScheduledTask(ScheduleModel):
110
+ api_version: Literal["schedule.ksadk.io/v1"] = "schedule.ksadk.io/v1"
111
+ kind: Literal["ScheduledTask"] = "ScheduledTask"
112
+ schema_version: Literal[1] = 1
113
+ task_id: str = Field(min_length=3, max_length=63)
114
+ # A human-facing task label belongs to the durable schedule intent, not a
115
+ # transient Studio card. Optional keeps the previously frozen v1 rows
116
+ # and third-party producers readable during this additive transition.
117
+ display_name: str | None = Field(default=None, min_length=1, max_length=128)
118
+ target: ScheduledTaskTarget
119
+ schedule: ScheduleSpec
120
+ command: ScheduleCommandTemplate
121
+ enabled: bool = True
122
+ continuity: Literal["new_session", "continue_session"] = "new_session"
123
+ concurrency_policy: Literal["forbid"] = "forbid"
124
+ next_run_at: datetime | None = None
125
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
126
+ updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
127
+
128
+ @field_validator("task_id")
129
+ @classmethod
130
+ def validate_task_id(cls, value: str) -> str:
131
+ if not _TASK_ID.fullmatch(value):
132
+ raise ValueError("taskId must be a lowercase stable identifier")
133
+ return value
134
+
135
+ @model_validator(mode="after")
136
+ def validate_continuity_target(self) -> "ScheduledTask":
137
+ if self.continuity == "continue_session" and not self.target.session_id:
138
+ raise ValueError("continue_session schedule requires target.sessionId")
139
+ return self
140
+
141
+ @field_validator("next_run_at", "created_at", "updated_at")
142
+ @classmethod
143
+ def normalize_timestamp(cls, value: datetime | None) -> datetime | None:
144
+ if value is None:
145
+ return None
146
+ if value.tzinfo is None:
147
+ raise ValueError("scheduler timestamps must include a timezone")
148
+ return value.astimezone(timezone.utc)
149
+
150
+
151
+ class ScheduleOccurrenceTransition(ScheduleModel):
152
+ """One durable state change displayed by Studio's execution history."""
153
+
154
+ state: ScheduleOccurrenceState
155
+ at: datetime
156
+ detail: str | None = Field(default=None, max_length=1024)
157
+ error_code: str | None = Field(default=None, max_length=128)
158
+
159
+ @field_validator("at")
160
+ @classmethod
161
+ def normalize_timestamp(cls, value: datetime) -> datetime:
162
+ if value.tzinfo is None:
163
+ raise ValueError("occurrence transition timestamps must include a timezone")
164
+ return value.astimezone(timezone.utc)
165
+
166
+
167
+ class ScheduleOccurrence(ScheduleModel):
168
+ api_version: Literal["schedule.ksadk.io/v1"] = "schedule.ksadk.io/v1"
169
+ kind: Literal["ScheduleOccurrence"] = "ScheduleOccurrence"
170
+ schema_version: Literal[1] = 1
171
+ occurrence_id: str = Field(min_length=8, max_length=256)
172
+ task_id: str = Field(min_length=3, max_length=63)
173
+ # Keep the execution target as an immutable occurrence snapshot. Tasks can
174
+ # later be edited or deleted, but history/reconciliation must still know
175
+ # exactly which Agent revision accepted this occurrence.
176
+ target: ScheduledTaskTarget | None = None
177
+ scheduled_for: datetime
178
+ session_id: str = Field(min_length=1, max_length=256)
179
+ trigger: Literal["schedule", "manual"] = "schedule"
180
+ state: ScheduleOccurrenceState
181
+ attempt: int = Field(default=1, ge=1, le=100)
182
+ command_id: str | None = None
183
+ accepted_seq: int | None = Field(default=None, ge=0)
184
+ last_event_seq: int | None = Field(default=None, ge=0)
185
+ run_id: str | None = Field(default=None, min_length=1, max_length=256)
186
+ claimed_at: datetime | None = None
187
+ accepted_at: datetime | None = None
188
+ started_at: datetime | None = None
189
+ error_code: str | None = Field(default=None, max_length=128)
190
+ detail: str | None = Field(default=None, max_length=1024)
191
+ completed_at: datetime | None = None
192
+ transitions: tuple[ScheduleOccurrenceTransition, ...] = ()
193
+
194
+ @field_validator("scheduled_for", "claimed_at", "accepted_at", "started_at", "completed_at")
195
+ @classmethod
196
+ def normalize_timestamp(cls, value: datetime | None) -> datetime | None:
197
+ if value is None:
198
+ return None
199
+ if value.tzinfo is None:
200
+ raise ValueError("occurrence timestamps must include a timezone")
201
+ return value.astimezone(timezone.utc)
202
+
203
+ @model_validator(mode="after")
204
+ def validate_terminal_fields(self) -> "ScheduleOccurrence":
205
+ terminal = {"succeeded", "failed", "skipped", "cancelled"}
206
+ if self.state in terminal and self.completed_at is None:
207
+ raise ValueError("terminal occurrence requires completedAt")
208
+ if self.state == "failed" and not self.error_code:
209
+ raise ValueError("failed occurrence requires errorCode")
210
+ if self.state == "running" and (not self.run_id or self.started_at is None):
211
+ raise ValueError("running occurrence requires runId and startedAt")
212
+ if self.transitions:
213
+ timestamps = [transition.at for transition in self.transitions]
214
+ if timestamps != sorted(timestamps):
215
+ raise ValueError("occurrence transitions must be chronological")
216
+ if self.transitions[-1].state != self.state:
217
+ raise ValueError("last occurrence transition must match state")
218
+ return self
219
+
220
+
221
+ def _reject_clear_secrets(value: Any, *, key: str = "payload") -> None:
222
+ if isinstance(value, dict):
223
+ for name, item in value.items():
224
+ lowered = str(name).lower()
225
+ if any(term in lowered for term in ("secret", "password", "token", "api_key")):
226
+ if not isinstance(item, str) or not item.startswith(
227
+ ("env://", "secret://", "credential://", "vault://")
228
+ ):
229
+ raise ValueError(f"{key}.{name} must be a secret reference, not a value")
230
+ _reject_clear_secrets(item, key=f"{key}.{name}")
231
+ elif isinstance(value, list):
232
+ for index, item in enumerate(value):
233
+ _reject_clear_secrets(item, key=f"{key}[{index}]")
234
+
235
+
236
+ def _parse_cron(expression: str) -> tuple[str, str, str, str, str]:
237
+ fields = tuple(part for part in expression.split() if part)
238
+ if len(fields) != 5:
239
+ raise ValueError("cron expression must use exactly five fields")
240
+ for value in fields:
241
+ if not re.fullmatch(r"[0-9*/,-]+", value):
242
+ raise ValueError("cron fields only support numbers, ranges, lists and steps")
243
+ return fields # detailed field grammar is shared with calendar.py
244
+
245
+
246
+ __all__ = [
247
+ "ScheduleCommandTemplate",
248
+ "ScheduleOccurrence",
249
+ "ScheduleOccurrenceState",
250
+ "ScheduleOccurrenceTransition",
251
+ "ScheduleSpec",
252
+ "ScheduledTask",
253
+ "ScheduledTaskTarget",
254
+ ]
@@ -0,0 +1,129 @@
1
+ """Local Scheduler Lite's explicit AgentControl dispatcher."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import Awaitable, Callable, Sequence
7
+
8
+ from ksadk.kernel.contracts import (
9
+ AgentControlCommand,
10
+ AgentControlPermit,
11
+ AgentControlReceipt,
12
+ )
13
+ from ksadk.kernel.ingress import (
14
+ map_scheduler_request,
15
+ submit_command,
16
+ subscribe_projected,
17
+ trusted_context,
18
+ )
19
+ from ksadk.scheduler.contracts import ScheduledTask, ScheduledTaskTarget, ScheduleOccurrence
20
+ from ksadk.scheduler.engine import SchedulerDispatchError, SchedulerDispatchReceipt
21
+
22
+ SubmitCommand = Callable[[AgentControlCommand, AgentControlPermit], Awaitable[AgentControlReceipt]]
23
+ ReadOccurrenceEvents = Callable[
24
+ [ScheduleOccurrence], Awaitable[Sequence[tuple[int, object]]]
25
+ ]
26
+ PrepareTarget = Callable[[ScheduledTaskTarget], Awaitable[object]]
27
+
28
+
29
+ class AgentControlSchedulerDispatcher:
30
+ """Translate one occurrence into a single durable AgentControl enqueue.
31
+
32
+ This dispatcher is intentionally local-runtime only. A hosted scheduler
33
+ must obtain a Server-issued permit at its admission boundary in Phase 3;
34
+ this class never makes local permits look valid against hosted JWKS.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ submitter: SubmitCommand | None = None,
40
+ *,
41
+ event_reader: ReadOccurrenceEvents | None = None,
42
+ target_preparer: PrepareTarget | None = None,
43
+ ) -> None:
44
+ self._submitter = submitter or _submit_via_ingress
45
+ self._event_reader = event_reader
46
+ self._target_preparer = target_preparer
47
+
48
+ async def dispatch(
49
+ self, task: ScheduledTask, occurrence: ScheduleOccurrence
50
+ ) -> SchedulerDispatchReceipt:
51
+ target = task.target
52
+ if self._target_preparer is not None:
53
+ await self._target_preparer(target)
54
+ trusted = trusted_context(
55
+ source_kind="scheduler",
56
+ source_ref=occurrence.occurrence_id,
57
+ tenant_id=target.tenant_id,
58
+ agent_instance_id=target.agent_instance_id,
59
+ session_id=occurrence.session_id,
60
+ operations=("enqueue",),
61
+ )
62
+ command = map_scheduler_request(
63
+ session_id=occurrence.session_id,
64
+ idempotency_key=occurrence.occurrence_id,
65
+ content=task.command.payload["content"],
66
+ occurrence_id=occurrence.occurrence_id,
67
+ trusted=trusted,
68
+ )
69
+ receipt = await self._submitter(command, trusted.permit)
70
+ if receipt.status not in {"accepted", "duplicate"}:
71
+ error = receipt.error
72
+ raise SchedulerDispatchError(
73
+ error.code if error else f"KERNEL_{receipt.status.upper()}",
74
+ error.message if error else f"kernel rejected occurrence: {receipt.status}",
75
+ )
76
+ return SchedulerDispatchReceipt(str(receipt.command_id), accepted_seq=receipt.accepted_seq)
77
+
78
+ async def read_events(self, occurrence: ScheduleOccurrence) -> tuple[tuple[int, object], ...]:
79
+ """Read currently available canonical facts without holding an SSE open.
80
+
81
+ The local scheduler wakes frequently; each read resumes from the
82
+ durable session cursor persisted on the occurrence. This avoids a
83
+ hidden second event log and lets a Studio restart continue settling an
84
+ already-accepted run.
85
+ """
86
+
87
+ if self._event_reader is not None:
88
+ return tuple(await self._event_reader(occurrence))
89
+
90
+ target = occurrence.target
91
+ if target is None:
92
+ return ()
93
+ trusted = trusted_context(
94
+ source_kind="scheduler",
95
+ source_ref=occurrence.occurrence_id,
96
+ tenant_id=target.tenant_id,
97
+ agent_instance_id=target.agent_instance_id,
98
+ session_id=occurrence.session_id,
99
+ operations=("subscribe_events",),
100
+ )
101
+ cursor = occurrence.last_event_seq
102
+ if cursor is None:
103
+ cursor = occurrence.accepted_seq or 0
104
+ stream = subscribe_projected(
105
+ occurrence.session_id,
106
+ trusted=trusted,
107
+ after_seq=cursor,
108
+ projector=lambda event: event,
109
+ )
110
+ events: list[tuple[int, object]] = []
111
+ try:
112
+ while len(events) < 100:
113
+ try:
114
+ value = await asyncio.wait_for(anext(stream), timeout=0.05)
115
+ except TimeoutError:
116
+ break
117
+ events.append(value)
118
+ finally:
119
+ await stream.aclose()
120
+ return tuple(events)
121
+
122
+
123
+ async def _submit_via_ingress(
124
+ command: AgentControlCommand, permit: AgentControlPermit
125
+ ) -> AgentControlReceipt:
126
+ return await submit_command(command, permit=permit)
127
+
128
+
129
+ __all__ = ["AgentControlSchedulerDispatcher"]