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
@@ -0,0 +1,935 @@
1
+ """Managed DeepSeek Harness toolchain and standard bundle developer workflow.
2
+
3
+ KsADK does not copy, fork, or rebuild DeepSeek Harness. It installs one
4
+ published CLI package into an isolated AgentEngine configuration directory and
5
+ delegates bundle composition to that exact executable. Plugin source remains
6
+ an ordinary npm package that follows DSH's public ``dsh.bundle.patch`` format.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import shutil
15
+ import subprocess
16
+ import tempfile
17
+ import time
18
+ import uuid
19
+ from collections.abc import Callable, Mapping, Sequence
20
+ from contextlib import contextmanager
21
+ from dataclasses import dataclass
22
+ from pathlib import Path
23
+ from typing import Any, Iterator, Literal
24
+
25
+ from pydantic import BaseModel, ConfigDict
26
+
27
+ from ksadk.configs.global_config import get_global_config_dir
28
+ from ksadk.plugins.bridges.dsh import (
29
+ DshBridgeError,
30
+ DshProfilePluginBridge,
31
+ dsh_subprocess_environment,
32
+ )
33
+
34
+ DSH_PACKAGE = "@deepseek-ai/dsh"
35
+ DSH_VERSION = "0.1.1-rc.2"
36
+ DSH_PACKAGE_SPEC = f"{DSH_PACKAGE}@{DSH_VERSION}"
37
+ CORDIS_VERSION_RANGE = "^4.0.1"
38
+ PNPM_VERSION = "11.7.0"
39
+
40
+ TOOLCHAIN_HOME_ENV = "AGENTENGINE_PLUGIN_TOOLCHAIN_HOME"
41
+ PNPM_BIN_ENV = "AGENTENGINE_PNPM_BIN"
42
+
43
+ _VERSION = re.compile(r"\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b")
44
+ _PACKAGE_NAME = re.compile(
45
+ r"^(?:@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*$"
46
+ )
47
+ _LOCK_TIMEOUT_SECONDS = 30.0
48
+ _STALE_LOCK_SECONDS = 10 * 60
49
+ _MAX_MANIFEST_BYTES = 2 * 1024 * 1024
50
+
51
+
52
+ def _to_camel(value: str) -> str:
53
+ head, *tail = value.split("_")
54
+ return head + "".join(part.capitalize() for part in tail)
55
+
56
+
57
+ class _ToolchainModel(BaseModel):
58
+ model_config = ConfigDict(
59
+ alias_generator=_to_camel,
60
+ populate_by_name=True,
61
+ extra="forbid",
62
+ frozen=True,
63
+ )
64
+
65
+
66
+ class DshToolchainStatus(_ToolchainModel):
67
+ package: Literal["@deepseek-ai/dsh"] = DSH_PACKAGE
68
+ expected_version: Literal["0.1.1-rc.2"] = DSH_VERSION
69
+ installed: bool
70
+ usable: bool
71
+ root: str
72
+ executable: str | None = None
73
+ actual_version: str | None = None
74
+ lockfile_present: bool = False
75
+ pnpm_available: bool = False
76
+ pnpm_version: str | None = None
77
+ problem: str | None = None
78
+
79
+
80
+ class DshPluginCreateResult(_ToolchainModel):
81
+ ecosystem: Literal["dsh"] = "dsh"
82
+ package_name: str
83
+ target: str
84
+ entrypoint: str
85
+ bundle_patch: str
86
+
87
+
88
+ class DshPluginValidationResult(_ToolchainModel):
89
+ ecosystem: Literal["dsh"] = "dsh"
90
+ package_name: str
91
+ package_version: str
92
+ host_version: str
93
+ profile_digest: str
94
+ lifecycle: tuple[str, ...] = (
95
+ "install",
96
+ "project",
97
+ "disable",
98
+ "enable",
99
+ "uninstall",
100
+ )
101
+
102
+
103
+ class DshPluginPackResult(_ToolchainModel):
104
+ ecosystem: Literal["dsh"] = "dsh"
105
+ package_name: str
106
+ package_version: str
107
+ artifact: str
108
+
109
+
110
+ class DshToolchainError(RuntimeError):
111
+ """Base error for the managed DSH development toolchain."""
112
+
113
+
114
+ class DshToolchainUnavailableError(DshToolchainError):
115
+ pass
116
+
117
+
118
+ class DshToolchainVersionMismatchError(DshToolchainError):
119
+ pass
120
+
121
+
122
+ class DshToolchainInstallError(DshToolchainError):
123
+ pass
124
+
125
+
126
+ class DshPluginSourceError(DshToolchainError):
127
+ pass
128
+
129
+
130
+ class DshPluginValidationError(DshToolchainError):
131
+ def __init__(self, stage: str, message: str = "DSH plugin validation failed") -> None:
132
+ super().__init__(message)
133
+ self.stage = stage
134
+
135
+
136
+ class DshPluginPackError(DshToolchainError):
137
+ pass
138
+
139
+
140
+ @dataclass(frozen=True)
141
+ class CommandResult:
142
+ stdout: str = ""
143
+ stderr: str = ""
144
+
145
+
146
+ CommandRunner = Callable[[Sequence[str], Path, Mapping[str, str]], CommandResult]
147
+
148
+
149
+ class DshToolchainManager:
150
+ """Install and resolve the one supported published DSH CLI version."""
151
+
152
+ def __init__(
153
+ self,
154
+ *,
155
+ base_dir: Path | None = None,
156
+ pnpm_command: Sequence[str] | None = None,
157
+ command_runner: CommandRunner | None = None,
158
+ lock_timeout_seconds: float = _LOCK_TIMEOUT_SECONDS,
159
+ ) -> None:
160
+ configured = os.environ.get(TOOLCHAIN_HOME_ENV, "").strip()
161
+ base = (
162
+ base_dir
163
+ or (Path(configured).expanduser() if configured else None)
164
+ or get_global_config_dir() / "plugin-toolchains"
165
+ )
166
+ self._base_dir = base.expanduser().resolve()
167
+ self._root = self._base_dir / "dsh" / DSH_VERSION
168
+ self._pnpm_command = tuple(pnpm_command) if pnpm_command is not None else None
169
+ if self._pnpm_command is not None and not self._pnpm_command:
170
+ raise ValueError("pnpm command cannot be empty")
171
+ self._runner = command_runner or self._run_command
172
+ self._lock_timeout_seconds = lock_timeout_seconds
173
+ self._assert_safe_managed_root(self._root)
174
+
175
+ @property
176
+ def root(self) -> Path:
177
+ return self._root
178
+
179
+ @property
180
+ def executable(self) -> Path:
181
+ return self._root / "node_modules" / ".bin" / "dsh"
182
+
183
+ def status(self) -> DshToolchainStatus:
184
+ """Inspect the managed installation without mutating it."""
185
+
186
+ pnpm_path, pnpm_version = self._inspect_pnpm()
187
+ manifest_valid = self._manifest_is_pinned()
188
+ lock_valid = self._lockfile_is_pinned()
189
+ executable = self.executable
190
+ if not manifest_valid:
191
+ problem = "not_installed" if not self._root.exists() else "manifest_mismatch"
192
+ return self._status(
193
+ installed=False,
194
+ usable=False,
195
+ pnpm_path=pnpm_path,
196
+ pnpm_version=pnpm_version,
197
+ problem=problem,
198
+ )
199
+ if not lock_valid:
200
+ return self._status(
201
+ installed=True,
202
+ usable=False,
203
+ pnpm_path=pnpm_path,
204
+ pnpm_version=pnpm_version,
205
+ problem="lockfile_missing_or_mismatched",
206
+ )
207
+ try:
208
+ resolved = executable.resolve(strict=True)
209
+ except (FileNotFoundError, OSError):
210
+ return self._status(
211
+ installed=True,
212
+ usable=False,
213
+ pnpm_path=pnpm_path,
214
+ pnpm_version=pnpm_version,
215
+ problem="executable_missing",
216
+ )
217
+ if not resolved.is_relative_to(self._root.resolve()):
218
+ return self._status(
219
+ installed=True,
220
+ usable=False,
221
+ pnpm_path=pnpm_path,
222
+ pnpm_version=pnpm_version,
223
+ executable=str(resolved),
224
+ problem="executable_outside_managed_root",
225
+ )
226
+ try:
227
+ actual = self._command_version((str(executable),), cwd=self._root)
228
+ except DshToolchainUnavailableError:
229
+ return self._status(
230
+ installed=True,
231
+ usable=False,
232
+ pnpm_path=pnpm_path,
233
+ pnpm_version=pnpm_version,
234
+ executable=str(executable),
235
+ problem="executable_unavailable",
236
+ )
237
+ if actual != DSH_VERSION:
238
+ return self._status(
239
+ installed=True,
240
+ usable=False,
241
+ pnpm_path=pnpm_path,
242
+ pnpm_version=pnpm_version,
243
+ executable=str(executable),
244
+ actual_version=actual,
245
+ problem="version_mismatch",
246
+ )
247
+ return self._status(
248
+ installed=True,
249
+ usable=True,
250
+ pnpm_path=pnpm_path,
251
+ pnpm_version=pnpm_version,
252
+ executable=str(executable),
253
+ actual_version=actual,
254
+ )
255
+
256
+ def install(self) -> DshToolchainStatus:
257
+ """Atomically install the pinned official npm package with a pnpm lock."""
258
+
259
+ pnpm = self.require_pnpm()
260
+ with self._install_lock():
261
+ current = self.status()
262
+ if current.usable:
263
+ return current
264
+ parent = self._root.parent
265
+ parent.mkdir(parents=True, exist_ok=True, mode=0o700)
266
+ self._clean_abandoned_staging(parent)
267
+ staging = Path(tempfile.mkdtemp(prefix=f".{DSH_VERSION}.install-", dir=parent))
268
+ backup: Path | None = None
269
+ try:
270
+ self._write_install_manifest(staging)
271
+ environment = self._installation_environment()
272
+ self._runner(
273
+ (
274
+ *pnpm,
275
+ "install",
276
+ "--lockfile-only",
277
+ "--ignore-scripts",
278
+ "--config.auto-install-peers=true",
279
+ ),
280
+ staging,
281
+ environment,
282
+ )
283
+ lockfile = staging / "pnpm-lock.yaml"
284
+ if not self._lockfile_is_pinned(lockfile):
285
+ raise DshToolchainInstallError(
286
+ "pnpm did not create a lockfile pinned to the supported DSH package"
287
+ )
288
+ self._runner(
289
+ (
290
+ *pnpm,
291
+ "install",
292
+ "--frozen-lockfile",
293
+ "--ignore-scripts",
294
+ "--config.auto-install-peers=true",
295
+ ),
296
+ staging,
297
+ environment,
298
+ )
299
+ staged_executable = staging / "node_modules" / ".bin" / "dsh"
300
+ resolved = staged_executable.resolve(strict=True)
301
+ if not resolved.is_relative_to(staging.resolve()):
302
+ raise DshToolchainInstallError(
303
+ "installed DSH executable escapes the managed toolchain directory"
304
+ )
305
+ actual = self._command_version((str(staged_executable),), cwd=staging)
306
+ if actual != DSH_VERSION:
307
+ raise DshToolchainVersionMismatchError(
308
+ f"expected DSH {DSH_VERSION}, got {actual}"
309
+ )
310
+ self._write_receipt(staging, actual)
311
+ if self._root.exists():
312
+ backup = parent / f".{DSH_VERSION}.backup-{uuid.uuid4().hex}"
313
+ os.replace(self._root, backup)
314
+ try:
315
+ os.replace(staging, self._root)
316
+ installed = self.status()
317
+ if not installed.usable:
318
+ raise DshToolchainInstallError(
319
+ f"installed DSH toolchain failed verification: {installed.problem}"
320
+ )
321
+ except BaseException:
322
+ if self._root.exists():
323
+ shutil.rmtree(self._root)
324
+ if backup is not None and backup.exists():
325
+ os.replace(backup, self._root)
326
+ raise
327
+ if backup is not None and backup.exists():
328
+ shutil.rmtree(backup)
329
+ return installed
330
+ except DshToolchainError:
331
+ raise
332
+ except Exception as error:
333
+ raise DshToolchainInstallError("could not install the DSH toolchain") from error
334
+ finally:
335
+ if staging.exists():
336
+ shutil.rmtree(staging)
337
+ if backup is not None and backup.exists() and self._root.exists():
338
+ shutil.rmtree(backup)
339
+
340
+ def require_command(self, explicit: str | Path | None = None) -> tuple[str, ...]:
341
+ """Return an exact-version DSH executable, managed unless explicitly set."""
342
+
343
+ if explicit is not None and str(explicit).strip():
344
+ executable = self._resolve_program(str(explicit))
345
+ actual = self._command_version((executable,), cwd=self._root)
346
+ if actual != DSH_VERSION:
347
+ raise DshToolchainVersionMismatchError(
348
+ f"expected DSH {DSH_VERSION}, got {actual}"
349
+ )
350
+ return (executable,)
351
+ state = self.status()
352
+ if state.problem == "version_mismatch":
353
+ raise DshToolchainVersionMismatchError(
354
+ f"expected DSH {DSH_VERSION}, got {state.actual_version or 'unknown'}"
355
+ )
356
+ if not state.usable or state.executable is None:
357
+ raise DshToolchainUnavailableError(
358
+ "managed DSH toolchain is not installed; run "
359
+ "`agentengine plugin toolchain install`"
360
+ )
361
+ return (state.executable,)
362
+
363
+ def resolve_module_entry(self, package_name: str) -> Path:
364
+ """Resolve one dependency from the pinned DSH installation.
365
+
366
+ Provider sidecars may need the exact Cordis implementation owned by
367
+ their DSH host. Resolution is anchored at the installed DSH package,
368
+ never at a source checkout or the caller's ambient ``node_modules``.
369
+ The result is fenced to the managed toolchain root.
370
+ """
371
+
372
+ self._validate_package_name(package_name)
373
+ self.require_command()
374
+ dsh_manifest = self._root / "node_modules" / DSH_PACKAGE / "package.json"
375
+ try:
376
+ dsh_manifest = dsh_manifest.resolve(strict=True)
377
+ except (FileNotFoundError, OSError) as error:
378
+ raise DshToolchainUnavailableError(
379
+ "managed DSH package manifest is unavailable"
380
+ ) from error
381
+ node = self._resolve_program("node")
382
+ script = (
383
+ "const {createRequire}=require('node:module');"
384
+ f"const resolve=createRequire({json.dumps(str(dsh_manifest))}).resolve;"
385
+ f"process.stdout.write(resolve({json.dumps(package_name)}));"
386
+ )
387
+ try:
388
+ result = self._invoke((node, "-e", script), cwd=self._root)
389
+ except DshToolchainError as error:
390
+ raise DshToolchainUnavailableError(
391
+ f"managed DSH dependency is unavailable: {package_name}"
392
+ ) from error
393
+ raw = result.stdout.strip()
394
+ try:
395
+ resolved = Path(raw).resolve(strict=True)
396
+ except (FileNotFoundError, OSError) as error:
397
+ raise DshToolchainUnavailableError(
398
+ f"managed DSH dependency is unavailable: {package_name}"
399
+ ) from error
400
+ if not resolved.is_relative_to(self._root):
401
+ raise DshToolchainUnavailableError(
402
+ f"managed DSH dependency escapes the toolchain root: {package_name}"
403
+ )
404
+ return resolved
405
+
406
+ def require_pnpm(self) -> tuple[str, ...]:
407
+ command = self._resolve_pnpm_command()
408
+ result = self._runner(
409
+ (*command, "--version"),
410
+ self._pnpm_probe_cwd(),
411
+ self._pnpm_environment(),
412
+ )
413
+ match = _VERSION.search(result.stdout or result.stderr)
414
+ if match is None:
415
+ raise DshToolchainUnavailableError("pnpm did not report a parseable version")
416
+ if match.group(1) != PNPM_VERSION:
417
+ raise DshToolchainVersionMismatchError(
418
+ f"expected pnpm {PNPM_VERSION}, got {match.group(1)}"
419
+ )
420
+ return command
421
+
422
+ def _status(
423
+ self,
424
+ *,
425
+ installed: bool,
426
+ usable: bool,
427
+ pnpm_path: str | None,
428
+ pnpm_version: str | None,
429
+ problem: str | None = None,
430
+ executable: str | None = None,
431
+ actual_version: str | None = None,
432
+ ) -> DshToolchainStatus:
433
+ return DshToolchainStatus(
434
+ installed=installed,
435
+ usable=usable,
436
+ root=str(self._root),
437
+ executable=executable,
438
+ actual_version=actual_version,
439
+ lockfile_present=(self._root / "pnpm-lock.yaml").is_file(),
440
+ pnpm_available=pnpm_path is not None,
441
+ pnpm_version=pnpm_version,
442
+ problem=problem,
443
+ )
444
+
445
+ def _inspect_pnpm(self) -> tuple[str | None, str | None]:
446
+ try:
447
+ command = self.require_pnpm()
448
+ result = self._runner(
449
+ (*command, "--version"),
450
+ self._root,
451
+ self._pnpm_environment(),
452
+ )
453
+ except DshToolchainError:
454
+ return None, None
455
+ match = _VERSION.search(result.stdout or result.stderr)
456
+ return " ".join(command), match.group(1) if match else None
457
+
458
+ def _resolve_pnpm_command(self) -> tuple[str, ...]:
459
+ if self._pnpm_command is not None:
460
+ command = self._pnpm_command
461
+ else:
462
+ configured = os.environ.get(PNPM_BIN_ENV, "").strip()
463
+ if configured:
464
+ command = (configured,)
465
+ else:
466
+ corepack = shutil.which("corepack")
467
+ command = (
468
+ (corepack, f"pnpm@{PNPM_VERSION}")
469
+ if corepack is not None
470
+ else ("pnpm",)
471
+ )
472
+ executable = self._resolve_program(command[0])
473
+ return (executable, *command[1:])
474
+
475
+ def _pnpm_probe_cwd(self) -> Path:
476
+ # require_pnpm() must also work before the managed toolchain root
477
+ # exists (first install), so the version probe cannot unconditionally
478
+ # use self._root as cwd.
479
+ if self._root.exists():
480
+ return self._root
481
+ self._base_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
482
+ return self._base_dir
483
+
484
+ def _manifest_is_pinned(self, path: Path | None = None) -> bool:
485
+ manifest_path = path or self._root / "package.json"
486
+ try:
487
+ payload = json.loads(manifest_path.read_text(encoding="utf-8"))
488
+ except (FileNotFoundError, OSError, UnicodeError, json.JSONDecodeError):
489
+ return False
490
+ return (
491
+ isinstance(payload, dict)
492
+ and payload.get("private") is True
493
+ and payload.get("packageManager") == f"pnpm@{PNPM_VERSION}"
494
+ and isinstance(payload.get("dependencies"), dict)
495
+ and payload["dependencies"].get(DSH_PACKAGE) == DSH_VERSION
496
+ )
497
+
498
+ def _lockfile_is_pinned(self, path: Path | None = None) -> bool:
499
+ lockfile = path or self._root / "pnpm-lock.yaml"
500
+ try:
501
+ if lockfile.stat().st_size > _MAX_MANIFEST_BYTES:
502
+ return False
503
+ value = lockfile.read_text(encoding="utf-8")
504
+ except (FileNotFoundError, OSError, UnicodeError):
505
+ return False
506
+ return DSH_PACKAGE in value and DSH_VERSION in value
507
+
508
+ def _command_version(self, command: Sequence[str], *, cwd: Path) -> str:
509
+ result = self._invoke((*command, "--version"), cwd=cwd)
510
+ match = _VERSION.search(result.stdout or result.stderr)
511
+ if match is None:
512
+ raise DshToolchainUnavailableError("DSH did not report a parseable version")
513
+ return match.group(1)
514
+
515
+ def _invoke(self, command: Sequence[str], *, cwd: Path) -> CommandResult:
516
+ try:
517
+ return self._runner(tuple(command), cwd, dsh_subprocess_environment())
518
+ except DshToolchainError:
519
+ raise
520
+ except Exception as error:
521
+ raise DshToolchainUnavailableError("toolchain command could not be executed") from error
522
+
523
+ @staticmethod
524
+ def _resolve_program(value: str) -> str:
525
+ candidate = Path(value).expanduser()
526
+ if candidate.is_absolute() or len(candidate.parts) > 1:
527
+ if not candidate.is_file():
528
+ raise DshToolchainUnavailableError(f"required executable is unavailable: {value}")
529
+ return str(candidate.resolve())
530
+ resolved = shutil.which(value)
531
+ if resolved is None:
532
+ raise DshToolchainUnavailableError(f"required executable is unavailable: {value}")
533
+ return resolved
534
+
535
+ @staticmethod
536
+ def _validate_package_name(name: str) -> None:
537
+ if len(name) > 214 or not _PACKAGE_NAME.fullmatch(name):
538
+ raise DshToolchainUnavailableError("invalid managed DSH package name")
539
+
540
+ def _write_install_manifest(self, root: Path) -> None:
541
+ self._write_json(
542
+ root / "package.json",
543
+ {
544
+ "name": "agentengine-managed-dsh-toolchain",
545
+ "private": True,
546
+ "packageManager": f"pnpm@{PNPM_VERSION}",
547
+ "dependencies": {DSH_PACKAGE: DSH_VERSION},
548
+ },
549
+ )
550
+
551
+ def _write_receipt(self, root: Path, actual: str) -> None:
552
+ self._write_json(
553
+ root / "toolchain.json",
554
+ {
555
+ "schemaVersion": 1,
556
+ "package": DSH_PACKAGE,
557
+ "requestedVersion": DSH_VERSION,
558
+ "actualVersion": actual,
559
+ "packageManager": f"pnpm@{PNPM_VERSION}",
560
+ },
561
+ )
562
+
563
+ @staticmethod
564
+ def _write_json(path: Path, value: object) -> None:
565
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
566
+ temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
567
+ try:
568
+ temporary.write_text(
569
+ json.dumps(value, ensure_ascii=False, indent=2) + "\n",
570
+ encoding="utf-8",
571
+ )
572
+ temporary.chmod(0o600)
573
+ os.replace(temporary, path)
574
+ finally:
575
+ temporary.unlink(missing_ok=True)
576
+
577
+ @staticmethod
578
+ def _installation_environment() -> dict[str, str]:
579
+ environment = dsh_subprocess_environment()
580
+ environment.update(
581
+ {
582
+ "CI": "1",
583
+ "COREPACK_ENABLE_PROJECT_SPEC": "0",
584
+ "NPM_CONFIG_AUDIT": "false",
585
+ "NPM_CONFIG_FUND": "false",
586
+ }
587
+ )
588
+ return environment
589
+
590
+ @staticmethod
591
+ def _pnpm_environment() -> dict[str, str]:
592
+ environment = dsh_subprocess_environment()
593
+ environment["COREPACK_ENABLE_PROJECT_SPEC"] = "0"
594
+ return environment
595
+
596
+ @contextmanager
597
+ def _install_lock(self) -> Iterator[None]:
598
+ parent = self._root.parent
599
+ parent.mkdir(parents=True, exist_ok=True, mode=0o700)
600
+ lock = parent / f".{DSH_VERSION}.install.lock"
601
+ deadline = time.monotonic() + self._lock_timeout_seconds
602
+ while True:
603
+ try:
604
+ lock.mkdir(mode=0o700)
605
+ self._write_json(lock / "owner.json", {"pid": os.getpid()})
606
+ break
607
+ except FileExistsError:
608
+ if self._lock_is_stale(lock):
609
+ try:
610
+ shutil.rmtree(lock)
611
+ except FileNotFoundError:
612
+ pass
613
+ continue
614
+ if time.monotonic() >= deadline:
615
+ raise DshToolchainInstallError(
616
+ "another DSH toolchain installation is still in progress"
617
+ )
618
+ time.sleep(0.05)
619
+ try:
620
+ yield
621
+ finally:
622
+ shutil.rmtree(lock, ignore_errors=True)
623
+
624
+ @staticmethod
625
+ def _lock_is_stale(lock: Path) -> bool:
626
+ try:
627
+ age = time.time() - lock.stat().st_mtime
628
+ except FileNotFoundError:
629
+ return False
630
+ if age <= _STALE_LOCK_SECONDS:
631
+ return False
632
+ try:
633
+ payload = json.loads((lock / "owner.json").read_text(encoding="utf-8"))
634
+ pid = int(payload.get("pid", -1))
635
+ except (OSError, ValueError, TypeError, json.JSONDecodeError):
636
+ return True
637
+ if pid <= 0:
638
+ return True
639
+ try:
640
+ os.kill(pid, 0)
641
+ except ProcessLookupError:
642
+ return True
643
+ except (PermissionError, OSError):
644
+ return False
645
+ return False
646
+
647
+ @staticmethod
648
+ def _clean_abandoned_staging(parent: Path) -> None:
649
+ for candidate in parent.glob(f".{DSH_VERSION}.install-*"):
650
+ if candidate.is_dir() and candidate.parent == parent:
651
+ shutil.rmtree(candidate)
652
+
653
+ def _assert_safe_managed_root(self, root: Path) -> None:
654
+ if root.name != DSH_VERSION or root.parent.name != "dsh":
655
+ raise ValueError("managed DSH root must end in dsh/<pinned-version>")
656
+ if root in {Path(root.anchor), Path.home().resolve(), self._base_dir}:
657
+ raise ValueError("managed DSH root is too broad")
658
+
659
+ @staticmethod
660
+ def _run_command(
661
+ command: Sequence[str],
662
+ cwd: Path,
663
+ environment: Mapping[str, str],
664
+ ) -> CommandResult:
665
+ try:
666
+ completed = subprocess.run(
667
+ list(command),
668
+ cwd=cwd,
669
+ env=dict(environment),
670
+ check=False,
671
+ capture_output=True,
672
+ text=True,
673
+ timeout=180,
674
+ )
675
+ except (OSError, subprocess.TimeoutExpired) as error:
676
+ raise DshToolchainUnavailableError("toolchain command did not complete") from error
677
+ if completed.returncode != 0:
678
+ diagnostic = _redact_diagnostic(completed.stderr or completed.stdout)
679
+ raise DshToolchainInstallError(
680
+ f"toolchain command failed with exit code {completed.returncode}"
681
+ + (f": {diagnostic[-2000:]}" if diagnostic else "")
682
+ )
683
+ return CommandResult(stdout=completed.stdout, stderr=completed.stderr)
684
+
685
+
686
+ class DshPluginDeveloper:
687
+ """Create, validate, and pack standard DSH npm bundle projects."""
688
+
689
+ def __init__(
690
+ self,
691
+ *,
692
+ toolchain: DshToolchainManager | None = None,
693
+ explicit_dsh: str | Path | None = None,
694
+ bridge_factory: Callable[..., DshProfilePluginBridge] = DshProfilePluginBridge,
695
+ command_runner: CommandRunner | None = None,
696
+ ) -> None:
697
+ self._toolchain = toolchain or DshToolchainManager(command_runner=command_runner)
698
+ self._explicit_dsh = explicit_dsh
699
+ self._bridge_factory = bridge_factory
700
+ self._runner = command_runner or DshToolchainManager._run_command
701
+
702
+ def create(self, target: Path, *, package_name: str | None = None) -> DshPluginCreateResult:
703
+ target = target.expanduser().resolve()
704
+ if target.exists():
705
+ raise DshPluginSourceError("plugin target already exists")
706
+ name = package_name or self._default_package_name(target.name)
707
+ self._validate_package_name(name)
708
+ target.parent.mkdir(parents=True, exist_ok=True)
709
+ target.mkdir(mode=0o755)
710
+ try:
711
+ package = {
712
+ "name": name,
713
+ "version": "0.1.0",
714
+ "description": "A DeepSeek Harness Cordis plugin bundle.",
715
+ "type": "module",
716
+ "main": "index.js",
717
+ "types": "index.d.ts",
718
+ "files": ["index.js", "index.d.ts", "cordis.patch.yml"],
719
+ "engines": {"node": ">=22.19.0"},
720
+ "peerDependencies": {"@deepseek-ai/cordis": CORDIS_VERSION_RANGE},
721
+ "dsh": {"bundle": {"patch": "./cordis.patch.yml"}},
722
+ }
723
+ (target / "package.json").write_text(
724
+ json.dumps(package, ensure_ascii=False, indent=2) + "\n",
725
+ encoding="utf-8",
726
+ )
727
+ (target / "cordis.patch.yml").write_text(
728
+ "- insert:\n"
729
+ " - id: plugin-main\n"
730
+ f" name: {json.dumps(name)}\n",
731
+ encoding="utf-8",
732
+ )
733
+ symbol = re.sub(r"[^A-Za-z0-9]+", "-", name).strip("-") or "plugin"
734
+ (target / "index.js").write_text(
735
+ f"export const name = {json.dumps(symbol)}\n\n"
736
+ "/** @param {import('@deepseek-ai/cordis').Context} ctx */\n"
737
+ "export function apply(ctx) {\n"
738
+ " // Register resources through Cordis effects so unload is reversible.\n"
739
+ " ctx.effect(() => () => {})\n"
740
+ "}\n",
741
+ encoding="utf-8",
742
+ )
743
+ (target / "index.d.ts").write_text(
744
+ "import type { Context } from '@deepseek-ai/cordis'\n\n"
745
+ "export declare const name: string\n"
746
+ "export declare function apply(ctx: Context): void\n",
747
+ encoding="utf-8",
748
+ )
749
+ except BaseException:
750
+ shutil.rmtree(target, ignore_errors=True)
751
+ raise
752
+ return DshPluginCreateResult(
753
+ package_name=name,
754
+ target=str(target),
755
+ entrypoint="index.js",
756
+ bundle_patch="cordis.patch.yml",
757
+ )
758
+
759
+ def validate(self, source: str | Path) -> DshPluginValidationResult:
760
+ local_source, _package = self._resolve_local_bundle(source)
761
+ source_value = str(local_source) if local_source is not None else str(source).strip()
762
+ command = self._toolchain.require_command(self._explicit_dsh)
763
+ stage = "start"
764
+ with tempfile.TemporaryDirectory(prefix="agentengine-dsh-validate-") as directory:
765
+ root = Path(directory)
766
+ try:
767
+ with self._bridge_factory(
768
+ dsh_home=root / "home",
769
+ profile="validate",
770
+ dsh_command=command,
771
+ cwd=(local_source.parent if local_source is not None else Path.cwd()),
772
+ ) as bridge:
773
+ stage = "install"
774
+ installed = bridge.install_plugin(
775
+ source_value,
776
+ accept_host_permissions=True,
777
+ )
778
+ stage = "project"
779
+ projection = bridge.project_profile()
780
+ stage = "disable"
781
+ bridge.set_enabled(installed.name, enabled=False)
782
+ stage = "enable"
783
+ bridge.set_enabled(installed.name, enabled=True)
784
+ stage = "uninstall"
785
+ bridge.uninstall_plugin(installed.name)
786
+ return DshPluginValidationResult(
787
+ package_name=installed.name,
788
+ package_version=installed.version,
789
+ host_version=projection.host_version,
790
+ profile_digest=projection.config_digest,
791
+ )
792
+ except DshToolchainError:
793
+ raise
794
+ except DshBridgeError as error:
795
+ raise DshPluginValidationError(stage) from error
796
+ except Exception as error:
797
+ raise DshPluginValidationError(stage) from error
798
+
799
+ def pack(
800
+ self,
801
+ source: Path,
802
+ *,
803
+ output_dir: Path | None = None,
804
+ ) -> DshPluginPackResult:
805
+ local_source, package = self._resolve_local_bundle(source)
806
+ assert local_source is not None
807
+ if not local_source.is_dir():
808
+ raise DshPluginSourceError("only a DSH bundle source directory can be packed")
809
+ output = (output_dir or local_source / "dist").expanduser().resolve()
810
+ output.mkdir(parents=True, exist_ok=True)
811
+ before = {item.resolve() for item in output.glob("*.tgz") if item.is_file()}
812
+ pnpm = self._toolchain.require_pnpm()
813
+ environment = dsh_subprocess_environment()
814
+ # Keep the pinned pnpm selected above even when an ancestor workspace
815
+ # declares another package manager through Corepack.
816
+ environment["COREPACK_ENABLE_PROJECT_SPEC"] = "0"
817
+ try:
818
+ self._runner(
819
+ (*pnpm, "pack", "--pack-destination", str(output)),
820
+ local_source,
821
+ environment,
822
+ )
823
+ except DshToolchainError as error:
824
+ raise DshPluginPackError("pnpm could not pack the DSH bundle") from error
825
+ except Exception as error:
826
+ raise DshPluginPackError("pnpm could not pack the DSH bundle") from error
827
+ created = [
828
+ item.resolve()
829
+ for item in output.glob("*.tgz")
830
+ if item.is_file() and item.resolve() not in before
831
+ ]
832
+ if len(created) != 1 or not created[0].is_relative_to(output):
833
+ raise DshPluginPackError("pnpm pack did not produce exactly one bounded npm tarball")
834
+ return DshPluginPackResult(
835
+ package_name=str(package["name"]),
836
+ package_version=str(package["version"]),
837
+ artifact=str(created[0]),
838
+ )
839
+
840
+ @classmethod
841
+ def _resolve_local_bundle(
842
+ cls,
843
+ source: str | Path,
844
+ ) -> tuple[Path | None, dict[str, Any]]:
845
+ raw = str(source).strip()
846
+ candidate = Path(raw).expanduser()
847
+ looks_local = isinstance(source, Path) or candidate.is_absolute() or raw.startswith(".")
848
+ if not candidate.exists():
849
+ if looks_local:
850
+ raise DshPluginSourceError("local DSH plugin source does not exist")
851
+ if not raw or raw.startswith("-") or any(char in raw for char in "\r\n\0"):
852
+ raise DshPluginSourceError("invalid DSH plugin source")
853
+ return None, {}
854
+ root = candidate.resolve()
855
+ if root.is_file() and root.name.endswith(".tgz"):
856
+ return root, {}
857
+ if root.is_file() and root.name == "package.json":
858
+ root = root.parent
859
+ if not root.is_dir():
860
+ raise DshPluginSourceError("local DSH plugin source must be a directory")
861
+ manifest_path = root / "package.json"
862
+ try:
863
+ if manifest_path.stat().st_size > _MAX_MANIFEST_BYTES:
864
+ raise DshPluginSourceError("DSH plugin package.json is too large")
865
+ package = json.loads(manifest_path.read_text(encoding="utf-8"))
866
+ except FileNotFoundError:
867
+ raise DshPluginSourceError("DSH plugin package.json is missing") from None
868
+ except (OSError, UnicodeError, json.JSONDecodeError) as error:
869
+ raise DshPluginSourceError("DSH plugin package.json is invalid") from error
870
+ if not isinstance(package, dict):
871
+ raise DshPluginSourceError("DSH plugin package.json must be an object")
872
+ name = package.get("name")
873
+ version = package.get("version")
874
+ if not isinstance(name, str) or not isinstance(version, str) or not version.strip():
875
+ raise DshPluginSourceError("DSH plugin package name/version is invalid")
876
+ cls._validate_package_name(name)
877
+ dsh = package.get("dsh")
878
+ bundle = dsh.get("bundle") if isinstance(dsh, dict) else None
879
+ patch = bundle.get("patch") if isinstance(bundle, dict) else None
880
+ if not isinstance(patch, str) or not patch.strip():
881
+ raise DshPluginSourceError("package.json must declare dsh.bundle.patch")
882
+ relative = Path(patch)
883
+ target = (root / relative).resolve()
884
+ if relative.is_absolute() or ".." in relative.parts or not target.is_relative_to(root):
885
+ raise DshPluginSourceError("dsh.bundle.patch must stay inside the package")
886
+ if not target.is_file():
887
+ raise DshPluginSourceError("declared DSH bundle patch does not exist")
888
+ return root, package
889
+
890
+ @staticmethod
891
+ def _default_package_name(target_name: str) -> str:
892
+ value = re.sub(r"[^a-z0-9._-]+", "-", target_name.casefold()).strip("-._")
893
+ if not value:
894
+ value = "plugin"
895
+ return value if value.startswith("dsh-") else f"dsh-{value}"
896
+
897
+ @staticmethod
898
+ def _validate_package_name(name: str) -> None:
899
+ if len(name) > 214 or not _PACKAGE_NAME.fullmatch(name):
900
+ raise DshPluginSourceError("plugin name must be a valid lowercase npm package name")
901
+
902
+
903
+ __all__ = [
904
+ "CORDIS_VERSION_RANGE",
905
+ "DSH_PACKAGE",
906
+ "DSH_PACKAGE_SPEC",
907
+ "DSH_VERSION",
908
+ "PNPM_BIN_ENV",
909
+ "PNPM_VERSION",
910
+ "TOOLCHAIN_HOME_ENV",
911
+ "CommandResult",
912
+ "DshPluginCreateResult",
913
+ "DshPluginDeveloper",
914
+ "DshPluginPackError",
915
+ "DshPluginPackResult",
916
+ "DshPluginSourceError",
917
+ "DshPluginValidationError",
918
+ "DshPluginValidationResult",
919
+ "DshToolchainError",
920
+ "DshToolchainInstallError",
921
+ "DshToolchainManager",
922
+ "DshToolchainStatus",
923
+ "DshToolchainUnavailableError",
924
+ "DshToolchainVersionMismatchError",
925
+ ]
926
+
927
+
928
+ def _redact_diagnostic(value: str) -> str:
929
+ value = re.sub(r"(https?://)[^/\s:@]+:[^/\s@]+@", r"\1[redacted]@", value, flags=re.I)
930
+ return re.sub(
931
+ r"((?:token|password|authorization|_authToken)\s*[:=]\s*)[^\s]+",
932
+ r"\1[redacted]",
933
+ value,
934
+ flags=re.I,
935
+ ).strip()