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
ksadk/plugins/host.py ADDED
@@ -0,0 +1,724 @@
1
+ """Transactional, side-effect-owning PluginHost foundation (P2-03A).
2
+
3
+ The host has no knowledge of AgentControl, SessionEvent sequencing, or cloud
4
+ deployment. It only stages a fully resolved profile, atomically swaps it once
5
+ healthy, and disposes owned effects in reverse dependency order.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from collections.abc import Coroutine, Mapping
11
+ from dataclasses import dataclass
12
+ from typing import Any, Protocol, runtime_checkable
13
+ from uuid import uuid4
14
+
15
+ from ksadk.plugins.bundle import ResolvedPluginBundle
16
+ from ksadk.plugins.contracts import (
17
+ CompositionProfile,
18
+ PluginInventory,
19
+ PluginInventoryItem,
20
+ PluginLockEntry,
21
+ PluginManifest,
22
+ )
23
+ from ksadk.plugins.resolver import (
24
+ PluginRegistry,
25
+ PluginResolutionError,
26
+ ResolvedComposition,
27
+ )
28
+
29
+
30
+ class PluginHostError(RuntimeError):
31
+ """Stable reject/failure from PluginHost profile transactions."""
32
+
33
+ def __init__(self, code: str, message: str) -> None:
34
+ super().__init__(message)
35
+ self.code = code
36
+
37
+
38
+ @runtime_checkable
39
+ class ManagedPlugin(Protocol):
40
+ """Effects owned by one staged plugin instance."""
41
+
42
+ async def start(self) -> None: ...
43
+
44
+ async def health(self) -> bool: ...
45
+
46
+ async def drain(self) -> None: ...
47
+
48
+ async def dispose(self) -> None: ...
49
+
50
+
51
+ @runtime_checkable
52
+ class PluginFactory(Protocol):
53
+ """Factory seam; `stage` must not expose partially started effects."""
54
+
55
+ async def stage(
56
+ self,
57
+ manifest: PluginManifest,
58
+ *,
59
+ profile: CompositionProfile,
60
+ services: Mapping[str, Any],
61
+ ) -> ManagedPlugin: ...
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class PluginCapabilityBinding:
66
+ """One locked capability and the active runtime that owns it."""
67
+
68
+ plugin_id: str
69
+ plugin_version: str
70
+ definition: str
71
+ slot: str
72
+ runtime: ManagedPlugin
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class PluginExecutionContext:
77
+ """Read-only capability view passed to the active AgentProvider."""
78
+
79
+ profile_digest: str
80
+ plugin_lock_digest: str
81
+ bindings: tuple[PluginCapabilityBinding, ...]
82
+
83
+ def all(
84
+ self,
85
+ definition: str,
86
+ *,
87
+ slot: str | None = None,
88
+ ) -> tuple[PluginCapabilityBinding, ...]:
89
+ return tuple(
90
+ binding
91
+ for binding in self.bindings
92
+ if binding.definition == definition
93
+ and (slot is None or binding.slot == slot)
94
+ )
95
+
96
+ def require(
97
+ self,
98
+ definition: str,
99
+ *,
100
+ slot: str | None = None,
101
+ ) -> PluginCapabilityBinding:
102
+ matches = self.all(definition, slot=slot)
103
+ if not matches:
104
+ suffix = f" in slot {slot!r}" if slot is not None else ""
105
+ raise PluginHostError(
106
+ "plugin_capability_unavailable",
107
+ f"active profile does not provide {definition!r}{suffix}",
108
+ )
109
+ if len(matches) != 1:
110
+ raise PluginHostError(
111
+ "plugin_capability_ambiguous",
112
+ f"active profile provides more than one {definition!r}; select a slot",
113
+ )
114
+ return matches[0]
115
+
116
+
117
+ @runtime_checkable
118
+ class PreparedAgent(ManagedPlugin, Protocol):
119
+ """One provider-owned activation prepared from an immutable Bundle."""
120
+
121
+ async def execute(self, request: Any) -> Any: ...
122
+
123
+
124
+ @runtime_checkable
125
+ class ExecutableAgentProvider(ManagedPlugin, Protocol):
126
+ """AgentProvider execution seam used by the first PluginHost vertical.
127
+
128
+ ``prepare`` must either return an unstarted activation or clean up all of
129
+ its own partial effects before raising. PluginHost owns start, health,
130
+ drain, and dispose for every returned activation.
131
+ """
132
+
133
+ async def prepare(
134
+ self,
135
+ bundle: ResolvedPluginBundle,
136
+ *,
137
+ capabilities: PluginExecutionContext,
138
+ ) -> PreparedAgent: ...
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class _ActivePlugin:
143
+ entry: PluginLockEntry
144
+ runtime: ManagedPlugin
145
+
146
+
147
+ @dataclass(frozen=True)
148
+ class _ActiveGraph:
149
+ resolved: ResolvedComposition
150
+ plugins: tuple[_ActivePlugin, ...]
151
+
152
+
153
+ @dataclass
154
+ class _ActiveActivation:
155
+ key: str
156
+ graph: _ActiveGraph
157
+ bundle_digest: str
158
+ runtime: PreparedAgent
159
+ operation_lock: asyncio.Lock
160
+ closed: bool = False
161
+
162
+
163
+ class PluginActivationSession:
164
+ """Provider-owned activation retained across turns for one session.
165
+
166
+ The handle is deliberately small: PluginHost still owns lifecycle and
167
+ profile fencing, while the provider owns any native thread/checkpoint state
168
+ inside ``PreparedAgent``. Calls are serialized per activation because a
169
+ conversational session is an ordered command stream.
170
+ """
171
+
172
+ def __init__(self, host: "PluginHost", active: _ActiveActivation) -> None:
173
+ self._host = host
174
+ self._active = active
175
+
176
+ @property
177
+ def key(self) -> str:
178
+ return self._active.key
179
+
180
+ @property
181
+ def bundle_digest(self) -> str:
182
+ return self._active.bundle_digest
183
+
184
+ @property
185
+ def closed(self) -> bool:
186
+ return self._active.closed
187
+
188
+ async def execute(self, request: Any) -> Any:
189
+ return await self._host._execute_activation(self._active, request)
190
+
191
+ async def runtime_adapter(self) -> Any:
192
+ """Return an optional provider-owned RuntimeAdapter for AgentKernel.
193
+
194
+ ``execute`` remains the minimum cross-provider ABI. Providers that
195
+ need durable AgentControl/SessionEvent ownership (for example local
196
+ Scheduler Lite) may expose this additive seam. PluginHost retains the
197
+ activation/profile fence; unsupported providers fail explicitly.
198
+ """
199
+
200
+ return await self._host._runtime_adapter_activation(self._active)
201
+
202
+ async def close(self) -> None:
203
+ await self._host.close_activation(self._active.key, expected=self._active)
204
+
205
+
206
+ class PluginHost:
207
+ """Apply resolved profiles without tearing down a healthy old graph first."""
208
+
209
+ def __init__(
210
+ self,
211
+ registry: PluginRegistry,
212
+ factories: Mapping[str, PluginFactory],
213
+ *,
214
+ allowed_permissions: frozenset[str] = frozenset(),
215
+ services: Mapping[str, Any] | None = None,
216
+ ) -> None:
217
+ self._registry = registry
218
+ self._factories = dict(factories)
219
+ self._allowed_permissions = frozenset(allowed_permissions)
220
+ self._services = dict(services or {})
221
+ self._transaction_lock = asyncio.Lock()
222
+ self._active: _ActiveGraph | None = None
223
+ # A profile switch moves default admission atomically, while already
224
+ # prepared conversations may hold provider-native thread/checkpoint
225
+ # state. Retired graphs are reclaimed only after their last session
226
+ # activation closes.
227
+ self._retired: list[_ActiveGraph] = []
228
+ self._activations: dict[str, _ActiveActivation] = {}
229
+ self._last_failure: PluginHostError | None = None
230
+
231
+ async def apply(self, profile: CompositionProfile) -> PluginInventory:
232
+ """Resolve, admit, stage, health-check, then atomically switch profile.
233
+
234
+ Any resolve/admission/stage/health error leaves the current graph
235
+ untouched. Staged effects from the failed candidate are drained and
236
+ disposed before the error is returned.
237
+ """
238
+
239
+ async with self._transaction_lock:
240
+ resolved = self.preflight(profile)
241
+ if self._active and (
242
+ self._active.resolved.profile_digest == resolved.profile_digest
243
+ and self._active.resolved.plugin_lock_digest == resolved.plugin_lock_digest
244
+ ):
245
+ return self._inventory_for(self._active)
246
+
247
+ ordered_entries = _dependency_order(resolved.plugin_lock.plugins)
248
+ staged: list[_ActivePlugin] = []
249
+ try:
250
+ for entry in ordered_entries:
251
+ manifest = self._registry.manifest_for(entry.id, entry.version)
252
+ factory = self._factories[entry.id]
253
+ runtime = await factory.stage(
254
+ manifest,
255
+ profile=resolved.profile,
256
+ services=self._services,
257
+ )
258
+ staged.append(_ActivePlugin(entry=entry, runtime=runtime))
259
+ for plugin in staged:
260
+ await plugin.runtime.start()
261
+ if not await plugin.runtime.health():
262
+ raise PluginHostError(
263
+ "plugin_health_failed",
264
+ f"plugin {plugin.entry.id}@{plugin.entry.version} failed health check",
265
+ )
266
+ except asyncio.CancelledError:
267
+ await self._finish_cleanup(self._dispose_staged(staged))
268
+ raise
269
+ except PluginHostError as error:
270
+ await self._finish_cleanup(self._dispose_staged(staged))
271
+ self._last_failure = error
272
+ raise
273
+ except Exception as error: # noqa: BLE001 - boundary adapts plugins
274
+ await self._finish_cleanup(self._dispose_staged(staged))
275
+ failure = PluginHostError("plugin_stage_failed", str(error))
276
+ self._last_failure = failure
277
+ raise failure from error
278
+
279
+ candidate = _ActiveGraph(resolved=resolved, plugins=tuple(staged))
280
+ previous = self._active
281
+ # This single assignment is the profile switch. It only happens
282
+ # after every staged effect has passed health, preserving the old
283
+ # graph on all earlier failures.
284
+ self._active = candidate
285
+ self._last_failure = None
286
+ if previous is not None:
287
+ self._retired.append(previous)
288
+ await self._finish_cleanup(self._dispose_unpinned_retired_graphs())
289
+ return self._inventory_for(candidate)
290
+
291
+ def preflight(self, profile: CompositionProfile) -> ResolvedComposition:
292
+ """Resolve and admit a profile without importing or starting a plugin."""
293
+
294
+ try:
295
+ resolved = self._registry.resolve(profile)
296
+ except PluginResolutionError as error:
297
+ failure = PluginHostError(error.code, str(error))
298
+ self._last_failure = failure
299
+ raise failure from error
300
+ entries = _dependency_order(resolved.plugin_lock.plugins)
301
+ self._admit(entries)
302
+ for entry in entries:
303
+ if entry.id not in self._factories:
304
+ failure = PluginHostError(
305
+ "plugin_factory_unavailable",
306
+ f"no factory is registered for {entry.id}@{entry.version}",
307
+ )
308
+ self._last_failure = failure
309
+ raise failure
310
+ return resolved
311
+
312
+ def inventory(self) -> PluginInventory | None:
313
+ """Return only the currently active profile inventory, if any."""
314
+
315
+ if self._active is None:
316
+ return None
317
+ return self._inventory_for(self._active)
318
+
319
+ @property
320
+ def activation_count(self) -> int:
321
+ """Number of live provider-owned activations (diagnostic only)."""
322
+
323
+ return sum(not active.closed for active in self._activations.values())
324
+
325
+ async def execute(self, bundle: ResolvedPluginBundle, request: Any) -> Any:
326
+ """Execute one disposable activation (compatibility convenience API)."""
327
+
328
+ session = await self.open_activation(
329
+ bundle,
330
+ activation_key=f"one-shot:{uuid4().hex}",
331
+ )
332
+ try:
333
+ return await session.execute(request)
334
+ finally:
335
+ await session.close()
336
+
337
+ async def open_activation(
338
+ self,
339
+ bundle: ResolvedPluginBundle,
340
+ *,
341
+ activation_key: str,
342
+ ) -> PluginActivationSession:
343
+ """Open or reuse one profile-fenced, provider-owned activation.
344
+
345
+ Reuse is permitted for the exact graph and immutable Bundle digest. A
346
+ profile switch retires its old graph for new sessions, but an existing
347
+ session key remains pinned to that graph until it closes.
348
+ """
349
+
350
+ key = str(activation_key).strip()
351
+ if not key or len(key) > 512:
352
+ raise PluginHostError(
353
+ "agent_activation_key_invalid",
354
+ "activation_key must be a non-empty value of at most 512 characters",
355
+ )
356
+ async with self._transaction_lock:
357
+ existing = self._activations.get(key)
358
+ if existing is not None:
359
+ if (
360
+ not existing.closed
361
+ and existing.bundle_digest == bundle.bundle_digest
362
+ and self._bundle_matches_graph(bundle, existing.graph)
363
+ ):
364
+ # This activation may belong to a retired graph: it was
365
+ # pinned before a newer profile became active.
366
+ return PluginActivationSession(self, existing)
367
+ await self._close_activation_record(existing)
368
+ if self._activations.get(key) is existing:
369
+ self._activations.pop(key, None)
370
+ await self._finish_cleanup(self._dispose_unpinned_retired_graphs())
371
+
372
+ graph = self._require_bundle_graph(bundle)
373
+ runtime = await self._prepare_activation(graph, bundle)
374
+ active = _ActiveActivation(
375
+ key=key,
376
+ graph=graph,
377
+ bundle_digest=bundle.bundle_digest,
378
+ runtime=runtime,
379
+ operation_lock=asyncio.Lock(),
380
+ )
381
+ self._activations[key] = active
382
+ return PluginActivationSession(self, active)
383
+
384
+ async def close_activation(
385
+ self,
386
+ activation_key: str,
387
+ *,
388
+ expected: _ActiveActivation | None = None,
389
+ ) -> None:
390
+ """Drain and dispose a retained activation if it is still current."""
391
+
392
+ async with self._transaction_lock:
393
+ active = self._activations.get(activation_key)
394
+ if active is None or (expected is not None and active is not expected):
395
+ return
396
+ await self._close_activation_record(active)
397
+ if self._activations.get(activation_key) is active:
398
+ self._activations.pop(activation_key, None)
399
+ await self._finish_cleanup(self._dispose_unpinned_retired_graphs())
400
+
401
+ @property
402
+ def last_failure(self) -> PluginHostError | None:
403
+ return self._last_failure
404
+
405
+ async def dispose(self) -> None:
406
+ """Drain and dispose active plus retired graphs; there is no implicit restart."""
407
+
408
+ async with self._transaction_lock:
409
+ graphs = [
410
+ graph
411
+ for graph in [self._active, *self._retired]
412
+ if graph is not None
413
+ ]
414
+ self._active = None
415
+ self._retired = []
416
+ if graphs:
417
+ await self._finish_cleanup(self._dispose_all_graphs(graphs))
418
+
419
+ @staticmethod
420
+ async def _finish_cleanup(cleanup: Coroutine[Any, Any, None]) -> None:
421
+ """Defer caller cancellation until an owned-effect cleanup finishes."""
422
+
423
+ cleanup_task = asyncio.create_task(cleanup)
424
+ interrupted = False
425
+ while not cleanup_task.done():
426
+ try:
427
+ await asyncio.shield(cleanup_task)
428
+ except asyncio.CancelledError:
429
+ interrupted = True
430
+ cleanup_task.result()
431
+ if interrupted:
432
+ raise asyncio.CancelledError
433
+
434
+ async def _dispose_graph(self, graph: _ActiveGraph) -> None:
435
+ await self._close_graph_activations(graph)
436
+ await self._dispose_staged(list(graph.plugins))
437
+
438
+ async def _dispose_all_graphs(self, graphs: list[_ActiveGraph]) -> None:
439
+ await self._close_all_activations()
440
+ for graph in graphs:
441
+ await self._dispose_staged(list(graph.plugins))
442
+
443
+ async def _dispose_unpinned_retired_graphs(self) -> None:
444
+ for graph in tuple(self._retired):
445
+ if any(
446
+ not activation.closed and activation.graph is graph
447
+ for activation in self._activations.values()
448
+ ):
449
+ continue
450
+ await self._dispose_staged(list(graph.plugins))
451
+ self._retired.remove(graph)
452
+
453
+ def _admit(self, entries: list[PluginLockEntry]) -> None:
454
+ for entry in entries:
455
+ manifest = self._registry.manifest_for(entry.id, entry.version)
456
+ missing = sorted(set(manifest.spec.permissions) - self._allowed_permissions)
457
+ if missing:
458
+ raise PluginHostError(
459
+ "plugin_permission_denied",
460
+ f"plugin {entry.id}@{entry.version} requests unapproved permissions: "
461
+ + ", ".join(missing),
462
+ )
463
+
464
+ @staticmethod
465
+ async def _dispose_staged(staged: list[_ActivePlugin]) -> None:
466
+ """Best-effort cleanup preserves the primary failure while draining all effects."""
467
+
468
+ for plugin in reversed(staged):
469
+ try:
470
+ await plugin.runtime.drain()
471
+ except Exception: # noqa: BLE001 - disposal must continue
472
+ pass
473
+ try:
474
+ await plugin.runtime.dispose()
475
+ except Exception: # noqa: BLE001 - disposal must continue
476
+ pass
477
+
478
+ @staticmethod
479
+ async def _dispose_activation(activation: PreparedAgent) -> None:
480
+ """Best-effort cleanup for a prepared one-shot activation."""
481
+
482
+ try:
483
+ await activation.drain()
484
+ except Exception: # noqa: BLE001 - disposal must continue
485
+ pass
486
+ try:
487
+ await activation.dispose()
488
+ except Exception: # noqa: BLE001 - disposal must continue
489
+ pass
490
+
491
+ def _require_bundle_graph(self, bundle: ResolvedPluginBundle) -> _ActiveGraph:
492
+ graph = self._active
493
+ if graph is None:
494
+ raise PluginHostError(
495
+ "plugin_profile_inactive", "no plugin profile is active"
496
+ )
497
+ if not self._bundle_matches_graph(bundle, graph):
498
+ raise PluginHostError(
499
+ "plugin_bundle_profile_mismatch",
500
+ "Bundle composition does not match the active plugin graph",
501
+ )
502
+ return graph
503
+
504
+ @staticmethod
505
+ def _bundle_matches_graph(bundle: ResolvedPluginBundle, graph: _ActiveGraph) -> bool:
506
+ return (
507
+ bundle.composition.profile_digest == graph.resolved.profile_digest
508
+ and bundle.composition.plugin_lock_digest == graph.resolved.plugin_lock_digest
509
+ )
510
+
511
+ async def _prepare_activation(
512
+ self,
513
+ graph: _ActiveGraph,
514
+ bundle: ResolvedPluginBundle,
515
+ ) -> PreparedAgent:
516
+ capabilities = self._execution_context(graph)
517
+ provider_binding = capabilities.require(
518
+ "agent.provider/v1", slot="agent.execution"
519
+ )
520
+ provider = provider_binding.runtime
521
+ if not isinstance(provider, ExecutableAgentProvider):
522
+ raise PluginHostError(
523
+ "agent_provider_not_executable",
524
+ f"plugin {provider_binding.plugin_id}@"
525
+ f"{provider_binding.plugin_version} does not implement prepare",
526
+ )
527
+
528
+ activation: PreparedAgent | None = None
529
+ try:
530
+ try:
531
+ activation = await provider.prepare(bundle, capabilities=capabilities)
532
+ except PluginHostError:
533
+ raise
534
+ except Exception as error: # noqa: BLE001 - provider boundary
535
+ raise PluginHostError("agent_prepare_failed", str(error)) from error
536
+ if not isinstance(activation, PreparedAgent):
537
+ raise PluginHostError(
538
+ "agent_activation_invalid",
539
+ "AgentProvider.prepare returned an invalid activation",
540
+ )
541
+ try:
542
+ await activation.start()
543
+ except Exception as error: # noqa: BLE001 - activation boundary
544
+ raise PluginHostError(
545
+ "agent_activation_start_failed", str(error)
546
+ ) from error
547
+ try:
548
+ healthy = await activation.health()
549
+ except Exception as error: # noqa: BLE001 - activation boundary
550
+ raise PluginHostError(
551
+ "agent_activation_health_failed", str(error)
552
+ ) from error
553
+ if not healthy:
554
+ raise PluginHostError(
555
+ "agent_activation_health_failed",
556
+ "prepared Agent activation failed health check",
557
+ )
558
+ return activation
559
+ except asyncio.CancelledError:
560
+ if activation is not None:
561
+ await self._dispose_activation(activation)
562
+ raise
563
+ except PluginHostError as error:
564
+ self._last_failure = error
565
+ if activation is not None:
566
+ await self._dispose_activation(activation)
567
+ raise
568
+
569
+ async def _execute_activation(
570
+ self,
571
+ active: _ActiveActivation,
572
+ request: Any,
573
+ ) -> Any:
574
+ async with active.operation_lock:
575
+ if active.closed:
576
+ raise PluginHostError(
577
+ "agent_activation_closed",
578
+ f"Agent activation {active.key!r} is closed",
579
+ )
580
+ try:
581
+ result = await active.runtime.execute(request)
582
+ except asyncio.CancelledError:
583
+ await self._dispose_activation(active.runtime)
584
+ active.closed = True
585
+ raise
586
+ except PluginHostError as error:
587
+ self._last_failure = error
588
+ await self._dispose_activation(active.runtime)
589
+ active.closed = True
590
+ raise
591
+ except Exception as error: # noqa: BLE001 - provider boundary
592
+ failure = PluginHostError("agent_execution_failed", str(error))
593
+ self._last_failure = failure
594
+ await self._dispose_activation(active.runtime)
595
+ active.closed = True
596
+ raise failure from error
597
+ self._last_failure = None
598
+ return result
599
+
600
+ async def _runtime_adapter_activation(self, active: _ActiveActivation) -> Any:
601
+ async with active.operation_lock:
602
+ if active.closed:
603
+ raise PluginHostError(
604
+ "agent_activation_closed",
605
+ f"Agent activation {active.key!r} is closed",
606
+ )
607
+ provide = getattr(active.runtime, "runtime_adapter", None)
608
+ if not callable(provide):
609
+ raise PluginHostError(
610
+ "agent_provider_runtime_adapter_unavailable",
611
+ "AgentProvider does not expose a Kernel RuntimeAdapter",
612
+ )
613
+ try:
614
+ adapter = provide()
615
+ if asyncio.iscoroutine(adapter):
616
+ adapter = await adapter
617
+ except PluginHostError:
618
+ raise
619
+ except Exception as error: # noqa: BLE001 - provider boundary
620
+ raise PluginHostError(
621
+ "agent_provider_runtime_adapter_failed", str(error)
622
+ ) from error
623
+ return adapter
624
+
625
+ async def _close_activation_record(self, active: _ActiveActivation) -> None:
626
+ async with active.operation_lock:
627
+ if active.closed:
628
+ return
629
+ await self._dispose_activation(active.runtime)
630
+ active.closed = True
631
+
632
+ async def _close_graph_activations(self, graph: _ActiveGraph) -> None:
633
+ for key, active in tuple(self._activations.items()):
634
+ if active.graph is not graph:
635
+ continue
636
+ await self._close_activation_record(active)
637
+ if self._activations.get(key) is active:
638
+ self._activations.pop(key, None)
639
+
640
+ async def _close_all_activations(self) -> None:
641
+ for key, active in tuple(self._activations.items()):
642
+ await self._close_activation_record(active)
643
+ if self._activations.get(key) is active:
644
+ self._activations.pop(key, None)
645
+
646
+ @staticmethod
647
+ def _execution_context(graph: _ActiveGraph) -> PluginExecutionContext:
648
+ bindings = tuple(
649
+ PluginCapabilityBinding(
650
+ plugin_id=plugin.entry.id,
651
+ plugin_version=plugin.entry.version,
652
+ definition=capability.definition,
653
+ slot=capability.slot,
654
+ runtime=plugin.runtime,
655
+ )
656
+ for plugin in graph.plugins
657
+ for capability in plugin.entry.provides
658
+ )
659
+ return PluginExecutionContext(
660
+ profile_digest=graph.resolved.profile_digest,
661
+ plugin_lock_digest=graph.resolved.plugin_lock_digest,
662
+ bindings=bindings,
663
+ )
664
+
665
+ @staticmethod
666
+ def _inventory_for(graph: _ActiveGraph) -> PluginInventory:
667
+ return PluginInventory(
668
+ profile_digest=graph.resolved.profile_digest,
669
+ plugin_lock_digest=graph.resolved.plugin_lock_digest,
670
+ plugins=[
671
+ PluginInventoryItem(
672
+ id=plugin.entry.id,
673
+ version=plugin.entry.version,
674
+ digest=plugin.entry.digest,
675
+ state="ready",
676
+ health="healthy",
677
+ )
678
+ for plugin in graph.plugins
679
+ ],
680
+ )
681
+
682
+
683
+ def _dependency_order(entries: list[PluginLockEntry]) -> list[PluginLockEntry]:
684
+ """Dependency-first deterministic topological order from the locked graph."""
685
+
686
+ by_id = {entry.id: entry for entry in entries}
687
+ visiting: set[str] = set()
688
+ visited: set[str] = set()
689
+ ordered: list[PluginLockEntry] = []
690
+
691
+ def visit(plugin_id: str) -> None:
692
+ if plugin_id in visiting:
693
+ raise PluginHostError("plugin_dependency_cycle", "plugin lock dependency cycle")
694
+ if plugin_id in visited:
695
+ return
696
+ entry = by_id.get(plugin_id)
697
+ if entry is None:
698
+ raise PluginHostError(
699
+ "plugin_dependency_unresolved",
700
+ f"plugin dependency {plugin_id!r} is missing from the lock",
701
+ )
702
+ visiting.add(plugin_id)
703
+ for dependency in sorted(entry.dependencies, key=lambda item: item.id):
704
+ visit(dependency.id)
705
+ visiting.remove(plugin_id)
706
+ visited.add(plugin_id)
707
+ ordered.append(entry)
708
+
709
+ for entry in sorted(entries, key=lambda item: item.id):
710
+ visit(entry.id)
711
+ return ordered
712
+
713
+
714
+ __all__ = [
715
+ "ExecutableAgentProvider",
716
+ "ManagedPlugin",
717
+ "PluginCapabilityBinding",
718
+ "PluginActivationSession",
719
+ "PluginExecutionContext",
720
+ "PluginFactory",
721
+ "PluginHost",
722
+ "PluginHostError",
723
+ "PreparedAgent",
724
+ ]