agentengine-sdk-python 0.8.3__py3-none-any.whl → 0.8.5__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 (819) hide show
  1. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/METADATA +13 -9
  2. agentengine_sdk_python-0.8.5.dist-info/RECORD +1296 -0
  3. ksadk/_build_provenance.json +1 -1
  4. ksadk/_process.py +83 -0
  5. ksadk/_str_enum.py +17 -0
  6. ksadk/a2ui/core.py +3 -3
  7. ksadk/api/client.py +111 -91
  8. ksadk/builders/code_builder.py +74 -21
  9. ksadk/builders/framework_requirements.py +22 -3
  10. ksadk/builders/ks3_uploader.py +36 -7
  11. ksadk/builders/managed_runtime_builder.py +1 -0
  12. ksadk/cli/__init__.py +68 -3
  13. ksadk/cli/cmd_config.py +4 -4
  14. ksadk/cli/cmd_create.py +12 -12
  15. ksadk/cli/cmd_hermes.py +24 -2
  16. ksadk/cli/cmd_invoke.py +28 -5
  17. ksadk/cli/cmd_managed_runtime.py +13 -1
  18. ksadk/cli/cmd_openclaw.py +26 -1
  19. ksadk/cli/cmd_plugin.py +76 -13
  20. ksadk/cli/cmd_studio.py +64 -16
  21. ksadk/cli/cmd_web.py +2 -5
  22. ksadk/cli/openclaw_env.py +1 -1
  23. ksadk/cli/resource_common.py +26 -0
  24. ksadk/cli/runtime_bootstrap.py +23 -1
  25. ksadk/codex/client.py +425 -96
  26. ksadk/codex/projection.py +24 -0
  27. ksadk/codex/runtime.py +56 -298
  28. ksadk/common/auth.py +16 -10
  29. ksadk/configs/__init__.py +1 -1
  30. ksadk/configs/env_registry.py +153 -31
  31. ksadk/configs/env_registry_studio.py +86 -0
  32. ksadk/configs/global_config.py +1 -1
  33. ksadk/configs/settings.py +3 -3
  34. ksadk/connector/__init__.py +17 -0
  35. ksadk/connector/channel.py +477 -0
  36. ksadk/context_engine/capabilities.py +52 -4
  37. ksadk/conversations/attachment_storage.py +10 -3
  38. ksadk/conversations/attachments.py +16 -5
  39. ksadk/conversations/message_projection.py +133 -21
  40. ksadk/conversations/normalize.py +28 -10
  41. ksadk/conversations/projector.py +4 -6
  42. ksadk/conversations/runtime_baseline.py +37 -0
  43. ksadk/conversations/runtime_invocation.py +22 -4
  44. ksadk/conversations/runtime_observability.py +39 -10
  45. ksadk/conversations/runtime_payloads.py +9 -0
  46. ksadk/conversations/runtime_persistence.py +105 -9
  47. ksadk/conversations/runtime_preparation.py +42 -33
  48. ksadk/conversations/runtime_resume.py +50 -2
  49. ksadk/conversations/runtime_stream_events.py +34 -35
  50. ksadk/conversations/runtime_streaming.py +33 -7
  51. ksadk/deployment/env_forward.py +2 -2
  52. ksadk/deployment/providers/serverless.py +1 -1
  53. ksadk/events/adapters/_codex_items.py +16 -8
  54. ksadk/events/adapters/_codex_validators.py +18 -7
  55. ksadk/events/adapters/_langgraph_support.py +34 -0
  56. ksadk/events/adapters/langgraph.py +15 -1
  57. ksadk/events/canonical_store.py +3 -1
  58. ksadk/events/projections.py +1 -1
  59. ksadk/events/session_event.py +2 -2
  60. ksadk/harness/__init__.py +14 -0
  61. ksadk/harness/app.py +11 -0
  62. ksadk/harness/artifact_store.py +213 -0
  63. ksadk/harness/capabilities.py +115 -0
  64. ksadk/harness/capability_runtime.py +237 -0
  65. ksadk/harness/closure_contract.py +92 -0
  66. ksadk/harness/compaction_record.py +122 -0
  67. ksadk/harness/compiler.py +284 -0
  68. ksadk/harness/config.py +2 -2
  69. ksadk/harness/conformance/__init__.py +33 -0
  70. ksadk/harness/conformance/contract.py +506 -0
  71. ksadk/harness/conformance/fixtures.py +102 -0
  72. ksadk/harness/context_engine.py +403 -0
  73. ksadk/harness/context_maintenance.py +259 -0
  74. ksadk/harness/context_manifest.py +188 -0
  75. ksadk/harness/context_tuning.py +168 -0
  76. ksadk/harness/draft_runtime.py +322 -0
  77. ksadk/harness/engine/__init__.py +40 -0
  78. ksadk/harness/engine/base.py +122 -0
  79. ksadk/harness/engine/budgets.py +108 -0
  80. ksadk/harness/engine/capability_declarations.py +59 -0
  81. ksadk/harness/engine/completion_guard.py +42 -0
  82. ksadk/harness/engine/context_pipeline.py +579 -0
  83. ksadk/harness/engine/graph_builder.py +859 -0
  84. ksadk/harness/engine/langgraph.py +931 -0
  85. ksadk/harness/engine/mcp_disclosure.py +750 -0
  86. ksadk/harness/engine/policy_runtime.py +70 -0
  87. ksadk/harness/engine/postgres_checkpointer.py +63 -0
  88. ksadk/harness/engine/run_control_bridge.py +80 -0
  89. ksadk/harness/engine/run_types.py +88 -0
  90. ksadk/harness/engine/skill_disclosure.py +189 -0
  91. ksadk/harness/engine/spans.py +41 -0
  92. ksadk/harness/engine/subagents.py +343 -0
  93. ksadk/harness/engine/thread_ids.py +112 -0
  94. ksadk/harness/engine/tool_dispatch.py +144 -0
  95. ksadk/harness/event_tree.py +212 -0
  96. ksadk/harness/events.py +461 -0
  97. ksadk/harness/execution_policy.py +94 -0
  98. ksadk/harness/insights.py +210 -0
  99. ksadk/harness/lifecycle.py +578 -0
  100. ksadk/harness/lifecycle_conformance.py +233 -0
  101. ksadk/harness/lifecycle_local_plane.py +104 -0
  102. ksadk/harness/loop/__init__.py +44 -0
  103. ksadk/harness/loop/reason.py +454 -0
  104. ksadk/harness/loop/tools.py +762 -0
  105. ksadk/harness/managed_runtime.py +884 -0
  106. ksadk/harness/mcp_matrix.py +474 -0
  107. ksadk/harness/mcp_runtime.py +471 -0
  108. ksadk/harness/memory_consolidation.py +157 -0
  109. ksadk/harness/memory_loop.py +156 -0
  110. ksadk/harness/memory_runtime.py +541 -0
  111. ksadk/harness/metrics.py +90 -0
  112. ksadk/harness/model_capability.py +253 -0
  113. ksadk/harness/model_provider.py +220 -0
  114. ksadk/harness/observability/__init__.py +29 -0
  115. ksadk/harness/observability/inspection.py +528 -0
  116. ksadk/harness/observability/metrics.py +167 -0
  117. ksadk/harness/observability/otel_exporter.py +218 -0
  118. ksadk/harness/prompt_cache.py +70 -0
  119. ksadk/harness/public_activity.py +154 -0
  120. ksadk/harness/readiness.py +249 -0
  121. ksadk/harness/reasoner.py +466 -13
  122. ksadk/harness/release_readiness.py +140 -0
  123. ksadk/harness/resource_ref.py +69 -0
  124. ksadk/harness/run_control.py +668 -0
  125. ksadk/harness/runtime.py +171 -136
  126. ksadk/harness/runtime_capabilities.py +158 -0
  127. ksadk/harness/runtime_server.py +514 -0
  128. ksadk/harness/sandbox_adapters.py +739 -0
  129. ksadk/harness/sandbox_backend.py +530 -0
  130. ksadk/harness/sandbox_conformance.py +288 -0
  131. ksadk/harness/sandbox_lease.py +77 -0
  132. ksadk/harness/sandbox_lease_conformance.py +259 -0
  133. ksadk/harness/sandbox_matrix.py +170 -0
  134. ksadk/harness/sandbox_reconciliation.py +140 -0
  135. ksadk/harness/sandbox_recovery.py +586 -0
  136. ksadk/harness/sandbox_recovery_conformance.py +246 -0
  137. ksadk/harness/session_store.py +243 -0
  138. ksadk/harness/skill_composition.py +212 -0
  139. ksadk/harness/skill_feedback.py +123 -0
  140. ksadk/harness/skill_matrix.py +233 -0
  141. ksadk/harness/skill_runtime.py +259 -0
  142. ksadk/harness/spec.py +332 -0
  143. ksadk/harness/state.py +153 -0
  144. ksadk/harness/strategies.py +149 -0
  145. ksadk/harness/subagent.py +253 -0
  146. ksadk/harness/tool_policy.py +131 -0
  147. ksadk/harness/tool_receipts.py +96 -0
  148. ksadk/harness/tool_reliability.py +81 -0
  149. ksadk/harness/tools.py +38 -13
  150. ksadk/harness/user_errors.py +69 -0
  151. ksadk/harness/working_context.py +81 -0
  152. ksadk/harness/working_context_patch.py +99 -0
  153. ksadk/identity/resolver.py +18 -0
  154. ksadk/interaction/__init__.py +1 -1
  155. ksadk/interaction/ledger.py +1 -1
  156. ksadk/interaction/provider.py +2 -1
  157. ksadk/interaction/providers/__init__.py +3 -1
  158. ksadk/interaction/providers/adk.py +1 -1
  159. ksadk/interaction/providers/codex.py +1 -1
  160. ksadk/interaction/providers/harness.py +50 -0
  161. ksadk/interaction/providers/langgraph.py +2 -1
  162. ksadk/kernel/__init__.py +10 -0
  163. ksadk/kernel/approval_presentation.py +91 -0
  164. ksadk/kernel/authorization.py +1 -1
  165. ksadk/kernel/bootstrap.py +114 -106
  166. ksadk/kernel/contract_fingerprints.py +1 -1
  167. ksadk/kernel/contracts.py +5 -0
  168. ksadk/kernel/control.py +32 -1
  169. ksadk/kernel/execution_grants.py +221 -0
  170. ksadk/kernel/execution_grants_memory.py +133 -0
  171. ksadk/kernel/execution_grants_postgres.py +217 -0
  172. ksadk/kernel/execution_grants_sqlite.py +209 -0
  173. ksadk/kernel/ingress.py +42 -29
  174. ksadk/kernel/mapping.py +1 -1
  175. ksadk/kernel/memory_store.py +24 -11
  176. ksadk/kernel/postgres_store.py +63 -33
  177. ksadk/kernel/recovery.py +78 -14
  178. ksadk/kernel/sql/001_agent_kernel.sql +23 -0
  179. ksadk/kernel/sqlite_store.py +331 -25
  180. ksadk/kernel/state.py +2 -3
  181. ksadk/kernel/store.py +72 -2
  182. ksadk/kernel/worker.py +144 -51
  183. ksadk/kernel/worker_history.py +72 -0
  184. ksadk/kernel/worker_identity.py +47 -0
  185. ksadk/knowledge_base/client.py +57 -27
  186. ksadk/mcp_runtime/__init__.py +12 -4
  187. ksadk/memory/adk/backends/sdk_ltm_backend.py +161 -41
  188. ksadk/memory/adk/resilient_session_service.py +25 -17
  189. ksadk/memory/adk/short_term_memory.py +26 -9
  190. ksadk/memory/coordinator.py +22 -6
  191. ksadk/memory/extraction.py +40 -1
  192. ksadk/memory/models.py +84 -5
  193. ksadk/memory/provider_resolver.py +8 -0
  194. ksadk/memory/providers/local_postgres.py +200 -0
  195. ksadk/memory/providers/local_sqlite.py +42 -3
  196. ksadk/memory/retrieval.py +189 -0
  197. ksadk/memory/store_adapter.py +150 -0
  198. ksadk/model_policy.py +2 -2
  199. ksadk/model_proxy/detect.py +254 -0
  200. ksadk/model_proxy/server.py +14 -7
  201. ksadk/model_proxy/transform.py +40 -2
  202. ksadk/plugins/__init__.py +5 -1
  203. ksadk/plugins/artifacts.py +275 -0
  204. ksadk/plugins/bridges/__init__.py +4 -2
  205. ksadk/plugins/bridges/codex.py +120 -13
  206. ksadk/plugins/bridges/dsh.py +351 -156
  207. ksadk/plugins/builtins.py +72 -35
  208. ksadk/plugins/bundle_security.py +28 -4
  209. ksadk/plugins/codex_manifest.py +983 -0
  210. ksadk/plugins/companion_artifacts.py +83 -0
  211. ksadk/plugins/companions.py +368 -0
  212. ksadk/plugins/composition.py +8 -0
  213. ksadk/plugins/contracts.py +119 -1
  214. ksadk/plugins/delivery.py +153 -0
  215. ksadk/plugins/dsh_channels.py +156 -0
  216. ksadk/plugins/dsh_home.py +140 -0
  217. ksadk/plugins/dsh_installation_digest.py +71 -0
  218. ksadk/plugins/dsh_teams.py +105 -0
  219. ksadk/plugins/dsh_toolchain.py +203 -32
  220. ksadk/plugins/ecosystem_probe.py +6 -6
  221. ksadk/plugins/execution_host.py +99 -0
  222. ksadk/plugins/host.py +102 -39
  223. ksadk/plugins/kernel_adapter.py +205 -0
  224. ksadk/plugins/providers/__init__.py +23 -0
  225. ksadk/plugins/providers/bundles/dsh-channels-client/client.js +19 -0
  226. ksadk/plugins/providers/bundles/dsh-channels-client/cordis.patch.yml +3 -0
  227. ksadk/plugins/providers/bundles/dsh-channels-client/index.mjs +7 -0
  228. ksadk/plugins/providers/bundles/dsh-channels-client/package.json +12 -0
  229. ksadk/plugins/providers/bundles/dsh-knowledge/cordis.patch.yml +3 -0
  230. ksadk/plugins/providers/bundles/dsh-knowledge/index.mjs +53 -0
  231. ksadk/plugins/providers/bundles/dsh-knowledge/package.json +16 -0
  232. ksadk/plugins/providers/bundles/dsh-memory/cordis.patch.yml +3 -0
  233. ksadk/plugins/providers/bundles/dsh-memory/index.mjs +175 -0
  234. ksadk/plugins/providers/bundles/dsh-memory/package.json +16 -0
  235. ksadk/plugins/providers/bundles/dsh-platform-resources/client.mjs +166 -0
  236. ksadk/plugins/providers/bundles/dsh-platform-resources/cordis.patch.yml +3 -0
  237. ksadk/plugins/providers/bundles/dsh-platform-resources/index.mjs +9 -0
  238. ksadk/plugins/providers/bundles/dsh-platform-resources/package.json +12 -0
  239. ksadk/plugins/providers/bundles/dsh-skill-center/cordis.patch.yml +3 -0
  240. ksadk/plugins/providers/bundles/dsh-skill-center/index.mjs +125 -0
  241. ksadk/plugins/providers/bundles/dsh-skill-center/package.json +16 -0
  242. ksadk/plugins/providers/bundles/dsh-teams-client/client.js +29 -0
  243. ksadk/plugins/providers/bundles/dsh-teams-client/cordis.patch.yml +3 -0
  244. ksadk/plugins/providers/bundles/dsh-teams-client/index.mjs +11 -0
  245. ksadk/plugins/providers/bundles/dsh-teams-client/package.json +35 -0
  246. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/cordis.patch.yml +3 -0
  247. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/index.mjs +13 -0
  248. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/package.json +27 -0
  249. ksadk/plugins/providers/bundles/dsh-teams-profile/cordis.patch.yml +3 -0
  250. ksadk/plugins/providers/bundles/dsh-teams-profile/index.mjs +12 -0
  251. ksadk/plugins/providers/bundles/dsh-teams-profile/package.json +26 -0
  252. ksadk/plugins/providers/bundles/dsh-teams-router/cordis.patch.yml +3 -0
  253. ksadk/plugins/providers/bundles/dsh-teams-router/index.mjs +16 -0
  254. ksadk/plugins/providers/bundles/dsh-teams-router/package.json +27 -0
  255. ksadk/plugins/providers/bundles/dsh-teams-tasks/cordis.patch.yml +3 -0
  256. ksadk/plugins/providers/bundles/dsh-teams-tasks/index.mjs +16 -0
  257. ksadk/plugins/providers/bundles/dsh-teams-tasks/package.json +27 -0
  258. ksadk/plugins/providers/bundles/dsh-teams-tools/cordis.patch.yml +3 -0
  259. ksadk/plugins/providers/bundles/dsh-teams-tools/index.mjs +25 -0
  260. ksadk/plugins/providers/bundles/dsh-teams-tools/package.json +29 -0
  261. ksadk/plugins/providers/bundles/dsh-teams-tools/tools.json +235 -0
  262. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +1 -1
  263. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/cordis.patch.yml +3 -0
  264. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/index.mjs +920 -0
  265. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/package.json +27 -0
  266. ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/client.mjs +133 -0
  267. ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/index.mjs +14 -0
  268. ksadk/plugins/providers/bundles/ksadk-dsh-studio/client.js +115 -0
  269. ksadk/plugins/providers/bundles/ksadk-dsh-studio/index.mjs +19 -0
  270. ksadk/plugins/providers/bundles/ksadk-dsh-studio/package.json +7 -0
  271. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +1 -1
  272. ksadk/plugins/providers/codex.py +344 -70
  273. ksadk/plugins/providers/codex_native.py +368 -0
  274. ksadk/plugins/providers/codex_turn.py +301 -0
  275. ksadk/plugins/providers/dsh_capabilities.py +1450 -0
  276. ksadk/plugins/providers/dsh_mcp.py +467 -0
  277. ksadk/plugins/providers/harness.py +294 -141
  278. ksadk/plugins/providers/harness_delegation.py +593 -0
  279. ksadk/plugins/providers/harness_managed.py +456 -0
  280. ksadk/plugins/providers/harness_tools.py +326 -0
  281. ksadk/plugins/providers/mcp_projection.py +150 -0
  282. ksadk/plugins/providers/platform_resources.py +657 -0
  283. ksadk/plugins/providers/shipped_dsh.py +4 -1
  284. ksadk/plugins/resolver.py +8 -1
  285. ksadk/plugins/subagent_providers/codex.py +28 -6
  286. ksadk/plugins/teams/__init__.py +5 -0
  287. ksadk/plugins/teams/api.py +405 -0
  288. ksadk/plugins/teams/application.py +680 -0
  289. ksadk/plugins/teams/artifacts.py +60 -0
  290. ksadk/plugins/teams/context.py +96 -0
  291. ksadk/plugins/teams/contracts.py +136 -0
  292. ksadk/plugins/teams/domain.py +1427 -0
  293. ksadk/plugins/teams/domain_values.py +24 -0
  294. ksadk/plugins/teams/errors.py +13 -0
  295. ksadk/plugins/teams/migration.py +311 -0
  296. ksadk/plugins/teams/migration_cli.py +322 -0
  297. ksadk/plugins/teams/postgres_store.py +138 -0
  298. ksadk/plugins/teams/runtime.py +777 -0
  299. ksadk/plugins/teams/source_projection.py +84 -0
  300. ksadk/plugins/teams/store.py +311 -0
  301. ksadk/plugins/teams/task_decisions.py +423 -0
  302. ksadk/plugins/teams/transport.py +91 -0
  303. ksadk/plugins/teams/workspaces.py +255 -0
  304. ksadk/resource_runtime/__init__.py +4 -0
  305. ksadk/resource_runtime/broker.py +616 -0
  306. ksadk/resource_runtime/build_artifacts.py +165 -0
  307. ksadk/resource_runtime/contracts.py +138 -0
  308. ksadk/resource_runtime/discovery_receipts.py +100 -0
  309. ksadk/resource_runtime/discovery_skills.py +172 -0
  310. ksadk/resource_runtime/errors.py +6 -0
  311. ksadk/resource_runtime/ipc.py +142 -0
  312. ksadk/resource_runtime/kernel_approval.py +165 -0
  313. ksadk/resource_runtime/knowledge.py +114 -0
  314. ksadk/resource_runtime/langgraph.py +94 -0
  315. ksadk/resource_runtime/leases.py +191 -0
  316. ksadk/resource_runtime/managed_projection.py +186 -0
  317. ksadk/resource_runtime/mcp_server.py +138 -0
  318. ksadk/resource_runtime/memory_provider.py +318 -0
  319. ksadk/resource_runtime/memory_receipts.py +34 -0
  320. ksadk/resource_runtime/operation_ledger.py +211 -0
  321. ksadk/resource_runtime/plugin_config.py +48 -0
  322. ksadk/resource_runtime/policy_authorization.py +54 -0
  323. ksadk/resource_runtime/process.py +110 -0
  324. ksadk/resource_runtime/skill_artifacts.py +153 -0
  325. ksadk/resource_runtime/skill_runtime.py +42 -0
  326. ksadk/resource_runtime/skill_workspace.py +118 -0
  327. ksadk/resource_runtime/skills.py +215 -0
  328. ksadk/resource_runtime/snapshots.py +180 -0
  329. ksadk/resource_runtime/socket_server.py +103 -0
  330. ksadk/resource_runtime/supervisor.py +380 -0
  331. ksadk/resource_runtime/worker.py +504 -0
  332. ksadk/runners/_langgraph_runner_streams.py +182 -38
  333. ksadk/runners/_langgraph_state_reader.py +106 -0
  334. ksadk/runners/_session_identity.py +285 -0
  335. ksadk/runners/adk_runner.py +166 -63
  336. ksadk/runners/base_runner.py +29 -1
  337. ksadk/runners/langgraph_runner.py +542 -195
  338. ksadk/runners/patch_langchain.py +47 -0
  339. ksadk/runners/usage_accumulator.py +11 -3
  340. ksadk/runtime/_runner_adapter/stream_mapping.py +194 -20
  341. ksadk/runtime/adapter.py +2 -0
  342. ksadk/runtime/conversation_execution.py +117 -17
  343. ksadk/runtime/executor.py +87 -3
  344. ksadk/runtime/factory.py +65 -274
  345. ksadk/runtime/framework_adapters.py +23 -0
  346. ksadk/runtime/preprocessing.py +21 -5
  347. ksadk/runtime/runner_adapter.py +106 -18
  348. ksadk/runtime/skill_eval_result.py +36 -0
  349. ksadk/runtime/timing.py +46 -0
  350. ksadk/runtime/usage.py +49 -18
  351. ksadk/runtime_context.py +142 -2
  352. ksadk/sandbox/backends/e2b.py +288 -25
  353. ksadk/sandbox/backends/local_process.py +72 -39
  354. ksadk/sandbox/base.py +64 -1
  355. ksadk/sandbox/diagnostics.py +82 -0
  356. ksadk/sandbox/e2b_connection.py +66 -0
  357. ksadk/sandbox/local_controls.py +746 -0
  358. ksadk/sandbox/local_launcher.py +90 -0
  359. ksadk/scheduler/dispatcher.py +1 -1
  360. ksadk/scheduler/sqlite_store.py +18 -0
  361. ksadk/server/composition.py +2 -0
  362. ksadk/server/factory.py +5 -0
  363. ksadk/server/invocation_identity.py +118 -0
  364. ksadk/server/persistence_capability.py +516 -0
  365. ksadk/server/routes/common.py +32 -3
  366. ksadk/server/routes/control.py +271 -52
  367. ksadk/server/routes/dependencies.py +16 -0
  368. ksadk/server/routes/kernel_ingress.py +9 -1
  369. ksadk/server/routes/misc.py +233 -25
  370. ksadk/server/routes/models.py +47 -4
  371. ksadk/server/routes/openai_compat.py +76 -13
  372. ksadk/server/routes/projection.py +252 -12
  373. ksadk/server/routes/run.py +106 -35
  374. ksadk/server/routes/sessions.py +491 -138
  375. ksadk/server/routes/streaming.py +43 -9
  376. ksadk/server/routes/workspace.py +130 -21
  377. ksadk/server/static/assets/{ArtifactsPanel-10GPXIJ6.js → ArtifactsPanel-C1YoaKhx.js} +1 -1
  378. ksadk/server/static/assets/{CodeBlock-BXk1l-G1.js → CodeBlock-DHuAWfr5.js} +10 -7
  379. ksadk/server/static/assets/MathMessageMarkdown-cvdrBwYr.js +8 -0
  380. ksadk/server/static/assets/{MermaidBlock--OEYoXIJ.js → MermaidBlock-DeBkbJvI.js} +9 -9
  381. ksadk/server/static/assets/{NativeTerminalPanel-DrEe1syq.js → NativeTerminalPanel-oJj44_sV.js} +1 -1
  382. ksadk/server/static/assets/{abnfDiagram-VCTEODGH-eeTFJLvo.js → abnfDiagram-VCTEODGH-Dhfv25o2.js} +1 -1
  383. ksadk/server/static/assets/{arc-S3ZNbIAd.js → arc-C35w-pn1.js} +1 -1
  384. ksadk/server/static/assets/architecture-7GRP2DOG-BCsa26zi.js +1 -0
  385. ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-CRjgEJDX.js → architectureDiagram-5GKGNRK7-FoMY_u6D.js} +1 -1
  386. ksadk/server/static/assets/{blockDiagram-NRAW4CY4-C7bsBoNH.js → blockDiagram-NRAW4CY4-zD4Rt7oE.js} +1 -1
  387. ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-BX3c33Ob.js → c4Diagram-UCG6FXSJ-BCtGGkhN.js} +1 -1
  388. ksadk/server/static/assets/channel-_KDifNys.js +1 -0
  389. ksadk/server/static/assets/chunk-2Q5K7J3B-HdVkJ9F0.js +1 -0
  390. ksadk/server/static/assets/{chunk-5VM5RSS4-bZ7ws-8m.js → chunk-5VM5RSS4-C2Ms13_k.js} +1 -1
  391. ksadk/server/static/assets/{chunk-F27PBJKO-D2r0pvhY.js → chunk-F27PBJKO-BoTj-qgx.js} +1 -1
  392. ksadk/server/static/assets/{chunk-G27WJ6UU-CZwF3oUP.js → chunk-G27WJ6UU-DOMyyjHH.js} +1 -1
  393. ksadk/server/static/assets/{chunk-JWPE2WC7-DigFYCML.js → chunk-JWPE2WC7-DbJJqDli.js} +1 -1
  394. ksadk/server/static/assets/{chunk-LCL6LL3I-CnorGV5q.js → chunk-LCL6LL3I-CLuRCt_W.js} +1 -1
  395. ksadk/server/static/assets/{chunk-POPQ4Y6H-CexntQA-.js → chunk-POPQ4Y6H-D0Dz847e.js} +1 -1
  396. ksadk/server/static/assets/{chunk-RHFEMEQ7-CHTC7UxA.js → chunk-RHFEMEQ7-BWSRzEcd.js} +1 -1
  397. ksadk/server/static/assets/{chunk-SVP7TREG-BC1EWt_-.js → chunk-SVP7TREG-CPoHqkCY.js} +1 -1
  398. ksadk/server/static/assets/{chunk-XXDRQBXY-BTG24xN0.js → chunk-XXDRQBXY-Bx1jS-X-.js} +1 -1
  399. ksadk/server/static/assets/classDiagram-DTDB5LWJ-BVCW3ZqZ.js +1 -0
  400. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-BVCW3ZqZ.js +1 -0
  401. ksadk/server/static/assets/{cose-bilkent-JH36ORCC-CkonRdAS.js → cose-bilkent-JH36ORCC-LzyWm4m0.js} +1 -1
  402. ksadk/server/static/assets/cynefin-OW5HDTMX-CNXjFctL.js +1 -0
  403. ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-DEOCBe15.js → cynefinDiagram-5FMLGOSQ-BlW_bHED.js} +1 -1
  404. ksadk/server/static/assets/{dagre-3AP2YEHR-DBThPuLa.js → dagre-3AP2YEHR-Bu_S86_i.js} +1 -1
  405. ksadk/server/static/assets/{dagre-ClTPrTht.js → dagre-B7nCawed.js} +1 -1
  406. ksadk/server/static/assets/{diagram-S7CK7UJ4-DsZHrnpJ.js → diagram-S7CK7UJ4-W_Zow2Km.js} +1 -1
  407. ksadk/server/static/assets/{diagram-UQ7AKVKN-BJR4nhVr.js → diagram-UQ7AKVKN-CLIKAHON.js} +1 -1
  408. ksadk/server/static/assets/{diagram-VSXAHHWV-xzcERX0a.js → diagram-VSXAHHWV-Dv6_QAlN.js} +1 -1
  409. ksadk/server/static/assets/{diagram-VX7I27RA-tn2xM1xu.js → diagram-VX7I27RA-BWAWRDtZ.js} +1 -1
  410. ksadk/server/static/assets/{diagram-Z3DM3KII-BZPGBkw5.js → diagram-Z3DM3KII-XA_rWMss.js} +1 -1
  411. ksadk/server/static/assets/dist-0X_j2Rcc.js +1 -0
  412. ksadk/server/static/assets/{dist-4zN1_tq5.js → dist-1nXCdsej.js} +1 -1
  413. ksadk/server/static/assets/{dist-M67Hwu0n.js → dist-2vL2sAdF.js} +1 -1
  414. ksadk/server/static/assets/{dist-BJLUwH6W.js → dist-B3bA05-e.js} +1 -1
  415. ksadk/server/static/assets/{dist-8VWI-2bZ.js → dist-BLpRy_Yx.js} +1 -1
  416. ksadk/server/static/assets/{dist-D1V3f1B4.js → dist-BO_UwatH.js} +1 -1
  417. ksadk/server/static/assets/{dist-Cq4OL6nO.js → dist-BdNFAney.js} +1 -1
  418. ksadk/server/static/assets/{dist-DfGFrb0v.js → dist-C0J5_3qM.js} +1 -1
  419. ksadk/server/static/assets/{dist-DyqV4fL4.js → dist-CGt_UGeO.js} +1 -1
  420. ksadk/server/static/assets/{dist-DjhwGUgZ.js → dist-CIRMvUM4.js} +1 -1
  421. ksadk/server/static/assets/{dist-B1oWRmrH.js → dist-CIpWk_5f.js} +1 -1
  422. ksadk/server/static/assets/{dist-CXpH_Rdd.js → dist-CPyieXEf.js} +1 -1
  423. ksadk/server/static/assets/{dist-Btyo4vX4.js → dist-D-92S-Fd.js} +1 -1
  424. ksadk/server/static/assets/{dist-u8tVH5Ak.js → dist-DNKuMzWc.js} +1 -1
  425. ksadk/server/static/assets/{dist-DQAB0qn_.js → dist-DWfdmz9s.js} +2 -2
  426. ksadk/server/static/assets/{dist-B-46q3Hk.js → dist-DppE_r-D.js} +1 -1
  427. ksadk/server/static/assets/{dist-C71CsujO.js → dist-DyG6Ya-z.js} +1 -1
  428. ksadk/server/static/assets/{dist-DJmCPRVB.js → dist-M-SrdhhZ.js} +1 -1
  429. ksadk/server/static/assets/{dist-Bbwqa8TG.js → dist-mqD5TZup.js} +1 -1
  430. ksadk/server/static/assets/{dist-BI1qQH4n.js → dist-n3n6t_FO.js} +1 -1
  431. ksadk/server/static/assets/{dist-DUwKa_l6.js → dist-qe8xjesy.js} +1 -1
  432. ksadk/server/static/assets/{dist-B0gdk4rl.js → dist-t6MzE0jU.js} +1 -1
  433. ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-DVfMsLge.js → ebnfDiagram-PWID7BFC-BfCzF1Xi.js} +1 -1
  434. ksadk/server/static/assets/{erDiagram-SSCWMZ5O-CWEZSYaw.js → erDiagram-SSCWMZ5O-Dpz9_0zn.js} +1 -1
  435. ksadk/server/static/assets/eventmodeling-NTZA5JFV-B5-8RH9c.js +1 -0
  436. ksadk/server/static/assets/flowDiagram-A5DVABFB-BppFxEKw.js +1 -0
  437. ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-TWIxoyYL.js → ganttDiagram-EL5Y4UJY-DDaG1f4_.js} +1 -1
  438. ksadk/server/static/assets/gitGraph-4MIJSDKK-B-AhYj4C.js +1 -0
  439. ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-DDh2hd2S.js → gitGraphDiagram-WWUBYQGX-DcxzRwYC.js} +1 -1
  440. ksadk/server/static/assets/index-BMd2mucF.css +2 -0
  441. ksadk/server/static/assets/index-D5qyyhTH.js +244 -0
  442. ksadk/server/static/assets/info-A6RAGUB7-0uiKrC35.js +1 -0
  443. ksadk/server/static/assets/{infoDiagram-RXCK75RN-CgoyWiDR.js → infoDiagram-RXCK75RN-CYMdn6lQ.js} +1 -1
  444. ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-CyRIu_om.js → ishikawaDiagram-5VMMS53U-CLHFAD6c.js} +1 -1
  445. ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DN5JXHzN.js → journeyDiagram-EYS64GPL-BHSKQGAl.js} +1 -1
  446. ksadk/server/static/assets/{kanban-definition-3QL26DDD-CNP3ui66.js → kanban-definition-3QL26DDD-D5W3tjTT.js} +1 -1
  447. ksadk/server/static/assets/{katex-DWEzQy1d.js → katex-mtjOi4KM.js} +1 -1
  448. ksadk/server/static/assets/{linear-C-qHp7d2.js → linear-BETqsxE_.js} +1 -1
  449. ksadk/server/static/assets/{mermaid-parser.core-Cl-K943T.js → mermaid-parser.core-DPWrvs90.js} +2 -2
  450. ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-D5DB5mYm.js → mindmap-definition-FBJOCRG2-CJG2mfBP.js} +1 -1
  451. ksadk/server/static/assets/packet-AYTQ26CC-BVTmZ_cw.js +1 -0
  452. ksadk/server/static/assets/{pegDiagram-XKGWAZYB-BQVc9ia_.js → pegDiagram-XKGWAZYB-GjAPNaqn.js} +1 -1
  453. ksadk/server/static/assets/pie-WAS4IAKB-DyfxBU7V.js +1 -0
  454. ksadk/server/static/assets/{pieDiagram-E7YTZNPT-B6UCAvtY.js → pieDiagram-E7YTZNPT-ToIewrqr.js} +1 -1
  455. ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-mIr2d3eb.js → quadrantDiagram-AXDQQJYC-4d78KiEY.js} +1 -1
  456. ksadk/server/static/assets/radar-RG4KPBEZ-FFontjL2.js +1 -0
  457. ksadk/server/static/assets/railroad-74A4TZTK-D0Uidzen.js +1 -0
  458. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BOo_9T2h.js +1 -0
  459. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-D6-BjW7Z.js +1 -0
  460. ksadk/server/static/assets/railroad-peg-WCYAUIDC-CXx__yMm.js +1 -0
  461. ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-Dt3FFDmK.js → railroadDiagram-O6MQD6OU-DKzx4xfv.js} +1 -1
  462. ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-Cz_vQtgF.js → requirementDiagram-EFPCY7ZU-CJYljxQS.js} +1 -1
  463. ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-wlO1TsH5.js → sankeyDiagram-P5KCCOFB-DLU2fV1w.js} +1 -1
  464. ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-CHRbElYq.js → sequenceDiagram-WJ2MYXX4-CVqysrA9.js} +1 -1
  465. ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-DXX54iyO.js → sizeCapture-X5ZJPWSS-BUxfmTHc.js} +1 -1
  466. ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-Dp_lw9II.js → stateDiagram-HBIQ2CUA-CNFgYI9E.js} +1 -1
  467. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BXAFScz8.js +1 -0
  468. ksadk/server/static/assets/{swimlanes-XN3QIQJK-D8O5UlLG.js → swimlanes-XN3QIQJK-B6qCMe8N.js} +1 -1
  469. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uIQ2lJKH.js +8 -0
  470. ksadk/server/static/assets/{timeline-definition-24CTP7MA-8mfKcxvG.js → timeline-definition-24CTP7MA-CMD3x_U_.js} +1 -1
  471. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5GgucZ.js +1 -0
  472. ksadk/server/static/assets/treemap-WGGIJYW6-Bq1u1SpG.js +1 -0
  473. ksadk/server/static/assets/{vennDiagram-4TSXK5OY-EnNl0DE7.js → vennDiagram-4TSXK5OY-BqtUqvXM.js} +1 -1
  474. ksadk/server/static/assets/wardley-WFR3VGLG-QAt06uEz.js +1 -0
  475. ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DBu5fbYr.js → wardleyDiagram-VM6X3IG4-B5KfDjI-.js} +1 -1
  476. ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-BSVXs4IY.js → xychartDiagram-S5SC5T6Z-BaUlSZrn.js} +1 -1
  477. ksadk/server/static/index.html +2 -2
  478. ksadk/server/terminal_sessions.py +34 -2
  479. ksadk/session_context.py +66 -0
  480. ksadk/sessions/__init__.py +11 -21
  481. ksadk/sessions/_local_service_sync.py +263 -1
  482. ksadk/sessions/base.py +176 -0
  483. ksadk/sessions/errors.py +4 -0
  484. ksadk/sessions/in_memory.py +309 -19
  485. ksadk/sessions/invocation_identity.py +235 -0
  486. ksadk/sessions/local_service.py +83 -1
  487. ksadk/sessions/persistence.py +275 -0
  488. ksadk/sessions/postgres_service.py +358 -0
  489. ksadk/sessions/resilience.py +8 -0
  490. ksadk/sessions/resilient.py +362 -4
  491. ksadk/sessions/topology.py +135 -0
  492. ksadk/skills/__init__.py +26 -0
  493. ksadk/skills/events.py +464 -0
  494. ksadk/skills/manifest_cache.py +263 -0
  495. ksadk/skills/mcp_server/__init__.py +0 -0
  496. ksadk/skills/mcp_server/_registry.py +33 -0
  497. ksadk/skills/mcp_server/_routing.py +26 -0
  498. ksadk/skills/mcp_server/_workspace_files.py +160 -0
  499. ksadk/skills/mcp_server/register.py +415 -0
  500. ksadk/skills/mcp_server/server.py +218 -0
  501. ksadk/skills/models.py +37 -4
  502. ksadk/skills/observability.py +132 -0
  503. ksadk/skills/package_store.py +261 -40
  504. ksadk/skills/runtime/agent.py +67 -9
  505. ksadk/skills/runtime/artifact_delivery.py +139 -0
  506. ksadk/skills/runtime/artifacts.py +87 -0
  507. ksadk/skills/runtime/backends/e2b.py +460 -27
  508. ksadk/skills/runtime/backends/local.py +629 -62
  509. ksadk/skills/runtime/base.py +81 -7
  510. ksadk/skills/runtime/executor.py +268 -1
  511. ksadk/skills/runtime/loader.py +118 -5
  512. ksadk/skills/runtime/pinned.py +114 -0
  513. ksadk/skills/runtime/request.py +49 -0
  514. ksadk/skills/service_client.py +167 -28
  515. ksadk/skills/tool_defs.py +108 -3
  516. ksadk/studio/api.py +810 -93
  517. ksadk/studio/api_catalog_routes.py +18 -0
  518. ksadk/studio/api_contracts.py +8 -2
  519. ksadk/studio/api_plugin_routes.py +562 -104
  520. ksadk/studio/api_resource_connections.py +221 -0
  521. ksadk/studio/authoring.py +12 -6
  522. ksadk/studio/authoring_coordinator.py +23 -16
  523. ksadk/studio/builder.py +88 -0
  524. ksadk/studio/capabilities.py +23 -6
  525. ksadk/studio/channel_connections.py +333 -0
  526. ksadk/studio/cloud.py +370 -112
  527. ksadk/studio/cloud_shared_web.py +638 -0
  528. ksadk/studio/codex_agent_service.py +130 -28
  529. ksadk/studio/codex_builder.py +242 -3
  530. ksadk/studio/codex_credentials.py +90 -0
  531. ksadk/studio/codex_manifest.py +31 -4
  532. ksadk/studio/codex_plugin_store.py +746 -0
  533. ksadk/studio/codex_provider_build.py +231 -0
  534. ksadk/studio/codex_run.py +190 -26
  535. ksadk/studio/compatibility_report.py +12 -1
  536. ksadk/studio/compiler.py +88 -14
  537. ksadk/studio/configuration.py +182 -0
  538. ksadk/studio/contracts.py +165 -11
  539. ksadk/studio/dsh_application.py +247 -0
  540. ksadk/studio/dsh_capability_service.py +1374 -0
  541. ksadk/studio/dsh_lifecycle.py +58 -0
  542. ksadk/studio/dsh_models.py +59 -0
  543. ksadk/studio/dsh_provider_registration.py +367 -21
  544. ksadk/studio/entry.py +53 -0
  545. ksadk/studio/event_store.py +36 -5
  546. ksadk/studio/execution_host.py +525 -0
  547. ksadk/studio/framework_run.py +32 -10
  548. ksadk/studio/hosted_kernel.py +89 -14
  549. ksadk/studio/kernel_registry.py +418 -0
  550. ksadk/studio/kernel_sqlite_migration.py +181 -0
  551. ksadk/studio/mcp_runtime.py +11 -3
  552. ksadk/studio/model_client.py +1180 -66
  553. ksadk/studio/model_profile_service.py +26 -11
  554. ksadk/studio/native_documents.py +172 -0
  555. ksadk/studio/plugin_composition.py +202 -24
  556. ksadk/studio/plugin_kernel_adapter.py +21 -106
  557. ksadk/studio/plugin_lifecycle.py +80 -0
  558. ksadk/studio/plugin_runtime.py +448 -56
  559. ksadk/studio/provider_recovery.py +59 -0
  560. ksadk/studio/recovery_page.py +70 -0
  561. ksadk/studio/resource_authority.py +664 -0
  562. ksadk/studio/resource_binding_validation.py +77 -0
  563. ksadk/studio/resource_build_admission.py +196 -0
  564. ksadk/studio/resource_build_materializer.py +149 -0
  565. ksadk/studio/resource_catalog.py +149 -2
  566. ksadk/studio/resource_connections.py +220 -0
  567. ksadk/studio/run_activity.py +895 -0
  568. ksadk/studio/run_documents.py +127 -0
  569. ksadk/studio/run_service.py +789 -83
  570. ksadk/studio/runtime_catalog.py +53 -10
  571. ksadk/studio/runtime_source.py +16 -8
  572. ksadk/studio/scheduler_assistant.py +317 -0
  573. ksadk/studio/scheduler_runtime.py +32 -280
  574. ksadk/studio/scheduler_service.py +3 -0
  575. ksadk/studio/service.py +861 -120
  576. ksadk/studio/shared_web.py +579 -82
  577. ksadk/studio/static/assets/ChannelsPage-D_PGmKVq.js +8 -0
  578. ksadk/studio/static/assets/CodeBlock-DMIf4h33-UIqo-BPW.js +44 -0
  579. ksadk/studio/static/assets/ExecutionTree-v3HexvPo-Bk8wzGSy.js +1 -0
  580. ksadk/studio/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  581. ksadk/studio/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  582. ksadk/studio/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  583. ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  584. ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  585. ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  586. ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  587. ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  588. ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  589. ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  590. ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  591. ksadk/studio/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  592. ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  593. ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  594. ksadk/studio/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  595. ksadk/studio/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  596. ksadk/studio/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  597. ksadk/studio/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  598. ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  599. ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  600. ksadk/studio/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  601. ksadk/studio/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  602. ksadk/studio/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  603. ksadk/studio/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  604. ksadk/studio/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  605. ksadk/studio/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  606. ksadk/studio/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  607. ksadk/studio/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  608. ksadk/studio/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  609. ksadk/studio/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  610. ksadk/studio/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  611. ksadk/studio/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  612. ksadk/studio/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  613. ksadk/studio/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  614. ksadk/studio/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  615. ksadk/studio/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  616. ksadk/studio/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  617. ksadk/studio/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  618. ksadk/studio/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  619. ksadk/studio/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  620. ksadk/studio/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  621. ksadk/studio/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  622. ksadk/studio/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  623. ksadk/studio/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  624. ksadk/studio/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  625. ksadk/studio/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  626. ksadk/studio/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  627. ksadk/studio/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  628. ksadk/studio/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  629. ksadk/studio/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  630. ksadk/studio/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  631. ksadk/studio/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  632. ksadk/studio/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  633. ksadk/studio/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  634. ksadk/studio/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  635. ksadk/studio/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  636. ksadk/studio/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  637. ksadk/studio/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  638. ksadk/studio/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  639. ksadk/studio/static/assets/MathMessageMarkdown-BesSeCYl-BEOyPC3w.css +1 -0
  640. ksadk/studio/static/assets/MathMessageMarkdown-BesSeCYl-OhNwH4K9.js +8 -0
  641. ksadk/studio/static/assets/MermaidBlock-H_0qzQDf-BdXtxug_.js +11 -0
  642. ksadk/studio/static/assets/MessageMarkdown-CwMTvgFj-g0I-I4dD.js +59 -0
  643. ksadk/studio/static/assets/PrismRenderer-5pK6tUdb.js +3 -0
  644. ksadk/studio/static/assets/TeamsPage-DqX4HWKQ.js +3 -0
  645. ksadk/studio/static/assets/abnfDiagram-VCTEODGH-Bnb1O3tA.js +1 -0
  646. ksadk/studio/static/assets/arc-Pry-7G1_.js +1 -0
  647. ksadk/studio/static/assets/architecture-7GRP2DOG-BMOPAkUC.js +1 -0
  648. ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-5Zn1gqJv.js +36 -0
  649. ksadk/studio/static/assets/array-BifhSqXX.js +1 -0
  650. ksadk/studio/static/assets/blockDiagram-I7D4REHJ-CLB4L11u.js +129 -0
  651. ksadk/studio/static/assets/c4Diagram-7LVT6UL2-BOma_Z_F.js +38 -0
  652. ksadk/studio/static/assets/channel-r8itPEik.js +1 -0
  653. ksadk/studio/static/assets/chunk-2E4U76K2-BFf_huql.js +2 -0
  654. ksadk/studio/static/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  655. ksadk/studio/static/assets/chunk-2ZTRR5NV-C2qZJila.js +2 -0
  656. ksadk/studio/static/assets/chunk-3Z5EZCMW-DMniOlPd.js +1 -0
  657. ksadk/studio/static/assets/chunk-4HAMMTFA-DVtDP4rm.js +62 -0
  658. ksadk/studio/static/assets/chunk-5V3GS4D5-i-ZDOEnP.js +1 -0
  659. ksadk/studio/static/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  660. ksadk/studio/static/assets/chunk-6AZGARVD-BB6FqMl-.js +1 -0
  661. ksadk/studio/static/assets/chunk-6EIED4P4-DdUJkP0f.js +1 -0
  662. ksadk/studio/static/assets/chunk-6K3QC6MW-CqVsVn-y.js +1 -0
  663. ksadk/studio/static/assets/chunk-6TQVIW2G-DKlLbNIP.js +1 -0
  664. ksadk/studio/static/assets/chunk-747NJXEK-BFkZPlab.js +2 -0
  665. ksadk/studio/static/assets/chunk-75Z2AOVW-BZNhM4vB.js +2 -0
  666. ksadk/studio/static/assets/chunk-CLGD4ZFX-D-GTtyKs.js +1 -0
  667. ksadk/studio/static/assets/chunk-DU6HZSFF-CpEZCUgg.js +128 -0
  668. ksadk/studio/static/assets/chunk-F27PBJKO-D7pvKf0C.js +1 -0
  669. ksadk/studio/static/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
  670. ksadk/studio/static/assets/chunk-GMAD6QVW-DE_8H717.js +72 -0
  671. ksadk/studio/static/assets/chunk-GVQU2GXP-BfEB0lNv.js +1 -0
  672. ksadk/studio/static/assets/chunk-I5DQTOEV-Hm2LfiYI.js +1 -0
  673. ksadk/studio/static/assets/chunk-ICYGCRZG-749o-pGJ.js +1 -0
  674. ksadk/studio/static/assets/chunk-IH6LHLGP-CC6I9F3i.js +1 -0
  675. ksadk/studio/static/assets/chunk-IMKFNOWR-D38axYOR.js +231 -0
  676. ksadk/studio/static/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  677. ksadk/studio/static/assets/chunk-KI3K4JFJ-Rma-0k_M.js +1 -0
  678. ksadk/studio/static/assets/chunk-L3NEJ4N5-Dq5xn7aL.js +1 -0
  679. ksadk/studio/static/assets/chunk-LNGE3PJU-DWpGwHrW.js +34 -0
  680. ksadk/studio/static/assets/chunk-OSK3NFVY-DiCQjx9b.js +10 -0
  681. ksadk/studio/static/assets/chunk-OUJLGHUK-D0j2JRSg.js +2 -0
  682. ksadk/studio/static/assets/chunk-P2QGCYS3-VdhKmfYq.js +1 -0
  683. ksadk/studio/static/assets/chunk-POPQ4Y6H-C1_rzO5M.js +1 -0
  684. ksadk/studio/static/assets/chunk-PWAF6VOD-B45HvflI.js +1 -0
  685. ksadk/studio/static/assets/chunk-SHT3W25Y-DsCFnRVE.js +168 -0
  686. ksadk/studio/static/assets/chunk-SVP7TREG-DljApgRX.js +88 -0
  687. ksadk/studio/static/assets/chunk-TICWLB2K-DWqRtyqa.js +206 -0
  688. ksadk/studio/static/assets/chunk-TLUHSLCS-DUeGgnD5.js +2 -0
  689. ksadk/studio/static/assets/chunk-UY3FDG6J-CnmaAkG5.js +1 -0
  690. ksadk/studio/static/assets/chunk-XHIXRSVI-BFQ6B8BE.js +1 -0
  691. ksadk/studio/static/assets/chunk-XXDRQBXY-DqAYeljN.js +1 -0
  692. ksadk/studio/static/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  693. ksadk/studio/static/assets/classDiagram-ZZMXUADV-DjeMNJmu.js +1 -0
  694. ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-DjeMNJmu.js +1 -0
  695. ksadk/studio/static/assets/clsx-DB0hHKMi.js +1 -0
  696. ksadk/studio/static/assets/cose-bilkent-JH36ORCC-om8AY0Mc.js +1 -0
  697. ksadk/studio/static/assets/createLucideIcon-DR0LGTP5.js +1 -0
  698. ksadk/studio/static/assets/cynefin-OW5HDTMX-7tP7QfM3.js +1 -0
  699. ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-6BrmK52S.js +62 -0
  700. ksadk/studio/static/assets/cytoscape.esm-CGd-uY3x.js +321 -0
  701. ksadk/studio/static/assets/dagre-GXQ25YYZ-buZBRlwz.js +4 -0
  702. ksadk/studio/static/assets/dagre-PrKaheQc.js +1 -0
  703. ksadk/studio/static/assets/defaultLocale-BFoDCU3G.js +1 -0
  704. ksadk/studio/static/assets/diagram-S7CK7UJ4-C6aH78c4.js +30 -0
  705. ksadk/studio/static/assets/diagram-UQ7AKVKN-DY_tQ6C_.js +41 -0
  706. ksadk/studio/static/assets/diagram-VSXAHHWV-DX9p5cQT.js +3 -0
  707. ksadk/studio/static/assets/diagram-VX7I27RA-DB2WSH8X.js +24 -0
  708. ksadk/studio/static/assets/diagram-Z3DM3KII-Ce1BJwHE.js +24 -0
  709. ksadk/studio/static/assets/dist-v5Q1xZ2K.js +1 -0
  710. ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-Daukxwqf.js +1 -0
  711. ksadk/studio/static/assets/erDiagram-RLTQ6QDP-CGthTMmJ.js +99 -0
  712. ksadk/studio/static/assets/eventmodeling-NTZA5JFV-BR2FzK4R.js +1 -0
  713. ksadk/studio/static/assets/flowDiagram-HODETNUW-CZ1tEenp.js +1 -0
  714. ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BfoAZW9X.js +292 -0
  715. ksadk/studio/static/assets/gitGraph-4MIJSDKK-DYlXv_yJ.js +1 -0
  716. ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-S985OIJF.js +106 -0
  717. ksadk/studio/static/assets/graphlib-DS17s2tU.js +1 -0
  718. ksadk/studio/static/assets/iconfont-4HN3C9Pg.woff2 +0 -0
  719. ksadk/studio/static/assets/index-CfEnms6Y.css +2 -0
  720. ksadk/studio/static/assets/index-Dp61rOWZ.js +0 -0
  721. ksadk/studio/static/assets/index.dom-BQenBtFm.js +1 -0
  722. ksadk/studio/static/assets/info-A6RAGUB7-BlU-8_xu.js +1 -0
  723. ksadk/studio/static/assets/infoDiagram-27XIBGKW-CO3W8tFn.js +2 -0
  724. ksadk/studio/static/assets/init-C-OQMol4.js +1 -0
  725. ksadk/studio/static/assets/ishikawaDiagram-5VMMS53U-BUGEl8cO.js +70 -0
  726. ksadk/studio/static/assets/journeyDiagram-3NMN7TZE-Ch2ZYxvr.js +139 -0
  727. ksadk/studio/static/assets/kanban-definition-UXKFOSKX-Da-Fp4vC.js +89 -0
  728. ksadk/studio/static/assets/katex-DhON-x74.js +257 -0
  729. ksadk/studio/static/assets/kingsoft-cloud-BOcLiQ3e.ico +0 -0
  730. ksadk/studio/static/assets/line-BDaeq300.js +1 -0
  731. ksadk/studio/static/assets/linear-DmucvK78.js +1 -0
  732. ksadk/studio/static/assets/mermaid-parser.core-Cj5mYdBi.js +4 -0
  733. ksadk/studio/static/assets/mindmap-definition-YA3MSWOX-CvlsVQ38.js +96 -0
  734. ksadk/studio/static/assets/ordinal-BDEzSJ7C.js +1 -0
  735. ksadk/studio/static/assets/packet-AYTQ26CC-BSJEH9Lb.js +1 -0
  736. ksadk/studio/static/assets/path-fybaL0A-.js +1 -0
  737. ksadk/studio/static/assets/pegDiagram-XKGWAZYB-BuHuNgT5.js +1 -0
  738. ksadk/studio/static/assets/pie-WAS4IAKB-qxv01Y8W.js +1 -0
  739. ksadk/studio/static/assets/pieDiagram-E7YTZNPT-DlrWiSOv.js +39 -0
  740. ksadk/studio/static/assets/preload-helper-CO68xz6u.js +1 -0
  741. ksadk/studio/static/assets/presentation-yYw7xK60-BmcZriri.js +1 -0
  742. ksadk/studio/static/assets/quadrantDiagram-AXDQQJYC-BA3hpUZM.js +7 -0
  743. ksadk/studio/static/assets/radar-RG4KPBEZ-B6h6pWOs.js +1 -0
  744. ksadk/studio/static/assets/railroad-74A4TZTK-CGtTWdGc.js +1 -0
  745. ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-Cq7y22IA.js +1 -0
  746. ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-V0aQVv9m.js +1 -0
  747. ksadk/studio/static/assets/railroad-peg-WCYAUIDC-BQiIsX_p.js +1 -0
  748. ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-BxpAazym.js +1 -0
  749. ksadk/studio/static/assets/requirementDiagram-BXWQKSXE-E-Ge0FcV.js +84 -0
  750. ksadk/studio/static/assets/rolldown-runtime-Dy4uBu1J-DK3Fl9T5.js +1 -0
  751. ksadk/studio/static/assets/rough.esm-Dy-Kn_BL.js +1 -0
  752. ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-CA6YKhQ3.js +40 -0
  753. ksadk/studio/static/assets/sequenceDiagram-WJ2MYXX4-CcElXGg4.js +162 -0
  754. ksadk/studio/static/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
  755. ksadk/studio/static/assets/src-B6xuSHsQ.js +1 -0
  756. ksadk/studio/static/assets/stateDiagram-D77RDMKH-DdXZOFqY.js +1 -0
  757. ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-CJbOM-QK.js +1 -0
  758. ksadk/studio/static/assets/swimlanes-42K2YHIH-NtgEEHLY.js +1 -0
  759. ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-D-AVlW8B.js +8 -0
  760. ksadk/studio/static/assets/timeline-definition-24CTP7MA-CIuYhNy3.js +120 -0
  761. ksadk/studio/static/assets/treeView-Q6P3EWNA-BTTYcvrF.js +1 -0
  762. ksadk/studio/static/assets/treemap-WGGIJYW6-Cg7tyfH8.js +1 -0
  763. ksadk/studio/static/assets/vennDiagram-4TSXK5OY-7HbApbrv.js +34 -0
  764. ksadk/studio/static/assets/wardley-WFR3VGLG-C6N1rULY.js +1 -0
  765. ksadk/studio/static/assets/wardleyDiagram-VM6X3IG4-CMJiaedy.js +78 -0
  766. ksadk/studio/static/assets/xychartDiagram-S5SC5T6Z-BdgtMvW2.js +7 -0
  767. ksadk/studio/static/index.html +8 -2
  768. ksadk/studio/teams_catalog.py +133 -0
  769. ksadk/studio/teams_installation.py +214 -0
  770. ksadk/studio/teams_node.py +545 -0
  771. ksadk/studio/teams_node_runner.py +69 -0
  772. ksadk/studio/teams_remote.py +274 -0
  773. ksadk/studio/templates.py +40 -19
  774. ksadk/studio/validator.py +13 -0
  775. ksadk/studio/workspace.py +19 -4
  776. ksadk/studio/workspace_plugins.py +136 -0
  777. ksadk/studio/workspace_registry.py +286 -0
  778. ksadk/toolsets/__init__.py +11 -0
  779. ksadk/toolsets/skills.py +446 -378
  780. ksadk/toolsets/workspace.py +3 -3
  781. ksadk/toolsets/workspace_identity.py +15 -0
  782. ksadk/version.py +1 -1
  783. ksadk_runtime_common/memory_backend/providers/lancedb.py +56 -2
  784. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -148
  785. ksadk_runtime_common/workspace_files/router.py +26 -13
  786. agentengine_sdk_python-0.8.3.dist-info/RECORD +0 -833
  787. ksadk/server/static/assets/MathMessageMarkdown-D9uB55Oh.js +0 -8
  788. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +0 -1
  789. ksadk/server/static/assets/channel-s354Yo6o.js +0 -1
  790. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +0 -1
  791. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +0 -1
  792. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +0 -1
  793. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +0 -1
  794. ksadk/server/static/assets/dist-DdRBIceA.js +0 -1
  795. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +0 -1
  796. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +0 -1
  797. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +0 -1
  798. ksadk/server/static/assets/index-B2k_urY8.js +0 -241
  799. ksadk/server/static/assets/index-ILPt6IWF.css +0 -2
  800. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +0 -1
  801. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +0 -1
  802. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +0 -1
  803. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +0 -1
  804. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +0 -1
  805. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +0 -1
  806. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +0 -1
  807. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +0 -1
  808. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +0 -1
  809. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +0 -8
  810. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +0 -1
  811. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +0 -1
  812. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +0 -1
  813. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +0 -3
  814. ksadk/studio/static/assets/index-DXs1Eq4M.css +0 -1
  815. ksadk/studio/static/assets/index-DeDqwTsD.js +0 -283
  816. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/WHEEL +0 -0
  817. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/entry_points.txt +0 -0
  818. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/licenses/LICENSE +0 -0
  819. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1450 @@
1
+ """Profile-level DSH tool capabilities exposed through a real MCP transport.
2
+
3
+ This module is deliberately orthogonal to ``dsh-agent-provider-host/v1``. It
4
+ boots one complete, immutable DSH Profile with a wheel-owned Cordis plugin,
5
+ reads a one-shot readiness record, and returns a process-scoped authenticated
6
+ Streamable HTTP MCP lease. Tool discovery and execution stay inside the real
7
+ ``ctx.tools`` registry and its policy/cancellation pipeline.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import base64
14
+ import hashlib
15
+ import hmac
16
+ import json
17
+ import math
18
+ import os
19
+ import re
20
+ import secrets
21
+ import shutil
22
+ import signal
23
+ import struct
24
+ import tempfile
25
+ import time
26
+ from collections import deque
27
+ from collections.abc import Mapping, Sequence
28
+ from dataclasses import dataclass, field
29
+ from pathlib import Path
30
+ from typing import Any, Literal, cast
31
+ from urllib.parse import urlsplit
32
+
33
+ import httpx
34
+ from pydantic import Field, field_validator, model_validator
35
+
36
+ from ksadk.plugins.bridges.dsh import DshProfileProjection, dsh_subprocess_environment
37
+ from ksadk.plugins.contracts import PluginContractModel
38
+ from ksadk.plugins.host import PluginHostError
39
+ from ksadk.plugins.providers.dsh import DshCircuitBreaker, DshCircuitSnapshot
40
+
41
+ DSH_CAPABILITY_HOST_PROTOCOL = "ksadk.dsh-capability-host/v1"
42
+ DSH_CAPABILITY_HOST_VERSION = "1.0.0"
43
+ DSH_CAPABILITY_BUNDLE_PACKAGE = "@kingsoftcloud/ksadk-dsh-capability-host"
44
+ DSH_CAPABILITY_DEFINITION = "mcp.connector/v1"
45
+ DSH_CAPABILITY_TRANSPORT = "streamable-http"
46
+ DSH_CAPABILITY_MCP_PROTOCOL = "2025-06-18"
47
+ DSH_CAPABILITY_MIN_NODE_VERSION = (22, 19, 0)
48
+
49
+ _DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
50
+ _SEMVER = re.compile(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$")
51
+ _PROFILE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
52
+ _TOOL_NAME = re.compile(r"^[A-Za-z0-9_.:-]+$")
53
+ _ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
54
+ _NODE_VERSION = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$")
55
+ _SECRET_ENV = re.compile(
56
+ r"(?:^|_)(?:API_?KEY|ACCESS_?KEY|AUTH|BEARER|CREDENTIAL|PASSWORD|"
57
+ r"PRIVATE_?KEY|SECRET|TOKEN)(?:_|$)",
58
+ re.IGNORECASE,
59
+ )
60
+ _DIAGNOSTIC_SECRET = re.compile(
61
+ r"(?ix)(?:\bsk-[a-z0-9_-]{12,}\b|\bBearer\s+[a-z0-9._~+/=-]{8,}|"
62
+ r"\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|password|secret)"
63
+ r"\s*(?:=|:)\s*[^\s,;]+)"
64
+ )
65
+ _BLOCKED_ENV = frozenset(
66
+ {
67
+ "CORDIS_SHARED",
68
+ "DSH_HOME",
69
+ "KSADK_DSH_CAPABILITY_READY_FILE",
70
+ "KSADK_DSH_CAPABILITY_READY_STDOUT",
71
+ "KSADK_DSH_CAPABILITY_TOKEN",
72
+ "KSADK_DSH_PROFILE",
73
+ "KSADK_DSH_PROFILE_DIGEST",
74
+ "KSADK_DSH_VERSION",
75
+ "LD_PRELOAD",
76
+ "NODE_OPTIONS",
77
+ "PYTHONHOME",
78
+ "PYTHONPATH",
79
+ }
80
+ )
81
+ _MAX_READY_BYTES = 2 * 1024 * 1024
82
+ _MAX_CONFIG_BYTES = 8 * 1024 * 1024
83
+ _MAX_DIAGNOSTIC_CHARS = 4096
84
+ _NODE_MEMORY_LIMIT_MB = 512
85
+ _PROCESS_GROUP_ISOLATION_AVAILABLE = os.name == "posix"
86
+ _READY_PREFIX = "@@KSADK_DSH_CAPABILITY_READY@@"
87
+ _TOKEN_PREFIX = "@@KSADK_DSH_CAPABILITY_TOKEN@@"
88
+ _CORE_TOKEN_PREFIX = "@@KSADK_DSH_CORE_TOKEN@@"
89
+ _SCOPED_TOKEN_LIFETIME_SECONDS = 10 * 60
90
+
91
+
92
+ def _canonical_digest(value: Any) -> str:
93
+ try:
94
+ payload = json.dumps(
95
+ value,
96
+ ensure_ascii=False,
97
+ allow_nan=False,
98
+ sort_keys=True,
99
+ separators=(",", ":"),
100
+ ).encode("utf-8")
101
+ except (TypeError, ValueError) as error:
102
+ raise ValueError("value must be strict JSON") from error
103
+ return f"sha256:{hashlib.sha256(payload).hexdigest()}"
104
+
105
+
106
+ def _canonical_json_value_bytes(value: Any) -> bytes:
107
+ """Encode a JSON value identically in Python and the Node capability host.
108
+
109
+ JSON text is not a cross-language canonical form: CPython and ECMAScript
110
+ choose different spellings for valid binary64 values (for example
111
+ ``1e-07`` versus ``1e-7``), and their native object-key ordering differs
112
+ for some Unicode keys. The inventory fence therefore uses a tiny tagged
113
+ value encoding with UTF-8 byte ordering and IEEE-754 bits for numbers.
114
+ It is not a new transport format; only the SHA-256 input uses these bytes.
115
+ """
116
+
117
+ if value is None:
118
+ return b"n;"
119
+ if isinstance(value, bool):
120
+ return b"b1;" if value else b"b0;"
121
+ if isinstance(value, (int, float)):
122
+ try:
123
+ number = float(value)
124
+ except (OverflowError, ValueError) as error:
125
+ raise ValueError("JSON numbers must fit finite IEEE-754 binary64") from error
126
+ if not math.isfinite(number):
127
+ raise ValueError("JSON numbers must fit finite IEEE-754 binary64")
128
+ # JSON.stringify(-0) emits 0, so the digest also normalizes signed zero.
129
+ if number == 0:
130
+ number = 0.0
131
+ return b"d" + struct.pack(">d", number).hex().encode("ascii") + b";"
132
+ if isinstance(value, str):
133
+ try:
134
+ encoded = value.encode("utf-8")
135
+ except UnicodeEncodeError as error:
136
+ raise ValueError("JSON strings must contain Unicode scalar values") from error
137
+ return f"s{len(encoded)}:".encode("ascii") + encoded
138
+ if isinstance(value, (list, tuple)):
139
+ return f"a{len(value)}:".encode("ascii") + b"".join(
140
+ _canonical_json_value_bytes(item) for item in value
141
+ )
142
+ if isinstance(value, Mapping):
143
+ if any(not isinstance(key, str) for key in value):
144
+ raise ValueError("JSON object keys must be strings")
145
+ keys = sorted(value, key=lambda key: key.encode("utf-8"))
146
+ return f"o{len(keys)}:".encode("ascii") + b"".join(
147
+ _canonical_json_value_bytes(key) + _canonical_json_value_bytes(value[key])
148
+ for key in keys
149
+ )
150
+ raise ValueError("tool schemas must contain only JSON values")
151
+
152
+
153
+ class DshCapabilityTool(PluginContractModel):
154
+ """One immutable MCP projection of a DSH ``ToolSchema``."""
155
+
156
+ name: str = Field(min_length=1, max_length=128)
157
+ # Normalize the upstream DSH limit to Studio/OpenAI's portable tool limit.
158
+ description: str = Field(max_length=1024)
159
+ input_schema: dict[str, Any]
160
+
161
+ @field_validator("name")
162
+ @classmethod
163
+ def validate_name(cls, value: str) -> str:
164
+ if not _TOOL_NAME.fullmatch(value):
165
+ raise ValueError("tool name contains unsupported characters")
166
+ return value
167
+
168
+ @model_validator(mode="after")
169
+ def validate_schema(self) -> "DshCapabilityTool":
170
+ payload = self.model_dump(by_alias=True, mode="json")
171
+ # This also rejects lone UTF-16 surrogates and non-finite/out-of-range
172
+ # numbers before they can make descriptor hashing or API serialization
173
+ # fail later in the lifecycle.
174
+ _canonical_json_value_bytes(payload)
175
+ encoded = json.dumps(
176
+ payload,
177
+ allow_nan=False,
178
+ ensure_ascii=False,
179
+ separators=(",", ":"),
180
+ ).encode("utf-8")
181
+ if len(encoded) > _MAX_READY_BYTES:
182
+ raise ValueError("tool schema exceeds the capability descriptor limit")
183
+ return self
184
+
185
+
186
+ def _inventory_digest(tools: Sequence[DshCapabilityTool]) -> str:
187
+ payload = [tool.model_dump(by_alias=True, mode="json") for tool in tools]
188
+ return f"sha256:{hashlib.sha256(_canonical_json_value_bytes(payload)).hexdigest()}"
189
+
190
+
191
+ def _tool_sort_key(tool: DshCapabilityTool) -> bytes:
192
+ return tool.name.encode("utf-8")
193
+
194
+
195
+ class DshProfileCapabilityReady(PluginContractModel):
196
+ """One-shot record written by the Cordis bridge after its listener binds."""
197
+
198
+ protocol_version: Literal["ksadk.dsh-capability-host/v1"]
199
+ host_version: str
200
+ dsh_version: str
201
+ profile: str = Field(min_length=1, max_length=64)
202
+ profile_digest: str
203
+ definition: Literal["mcp.connector/v1"]
204
+ transport: Literal["streamable-http"]
205
+ endpoint: str
206
+ inventory_digest: str
207
+ tools: tuple[DshCapabilityTool, ...] = Field(max_length=2048)
208
+ web_route_count: int = Field(ge=2)
209
+
210
+ @field_validator("host_version", "dsh_version")
211
+ @classmethod
212
+ def validate_version(cls, value: str) -> str:
213
+ if not _SEMVER.fullmatch(value):
214
+ raise ValueError("host versions must use exact semantic versioning")
215
+ return value
216
+
217
+ @field_validator("profile")
218
+ @classmethod
219
+ def validate_profile(cls, value: str) -> str:
220
+ if not _PROFILE.fullmatch(value):
221
+ raise ValueError("profile name is invalid")
222
+ return value
223
+
224
+ @field_validator("profile_digest", "inventory_digest")
225
+ @classmethod
226
+ def validate_digest(cls, value: str) -> str:
227
+ if not _DIGEST.fullmatch(value):
228
+ raise ValueError("capability digest is invalid")
229
+ return value
230
+
231
+ @field_validator("endpoint")
232
+ @classmethod
233
+ def validate_endpoint(cls, value: str) -> str:
234
+ parsed = urlsplit(value)
235
+ if (
236
+ parsed.scheme != "http"
237
+ or parsed.hostname != "127.0.0.1"
238
+ or parsed.port is None
239
+ or parsed.port < 1
240
+ or parsed.path != "/mcp"
241
+ or parsed.query
242
+ or parsed.fragment
243
+ or parsed.username is not None
244
+ or parsed.password is not None
245
+ ):
246
+ raise ValueError("capability endpoint must be an unadorned loopback /mcp URL")
247
+ return value
248
+
249
+ @model_validator(mode="after")
250
+ def validate_inventory(self) -> "DshProfileCapabilityReady":
251
+ names = [tool.name for tool in self.tools]
252
+ if list(self.tools) != sorted(self.tools, key=_tool_sort_key) or len(names) != len(
253
+ set(names)
254
+ ):
255
+ raise ValueError("capability tools must be sorted and unique")
256
+ if _inventory_digest(self.tools) != self.inventory_digest:
257
+ raise ValueError("capability inventory digest does not match its tools")
258
+ return self
259
+
260
+
261
+ class DshProfileCapabilityDescriptor(PluginContractModel):
262
+ """Stable profile capability facts; excludes the ephemeral URL and token."""
263
+
264
+ descriptor_format: Literal["dsh.profile-capability-descriptor/v1"] = (
265
+ "dsh.profile-capability-descriptor/v1"
266
+ )
267
+ ecosystem: Literal["dsh"] = "dsh"
268
+ bridge_package: Literal["@kingsoftcloud/ksadk-dsh-capability-host"] = (
269
+ "@kingsoftcloud/ksadk-dsh-capability-host"
270
+ )
271
+ bridge_version: Literal["1.0.0"] = "1.0.0"
272
+ dsh_version: str
273
+ profile: str = Field(min_length=1, max_length=64)
274
+ profile_digest: str
275
+ definition: Literal["mcp.connector/v1"] = "mcp.connector/v1"
276
+ transport: Literal["streamable-http"] = "streamable-http"
277
+ mcp_protocol: Literal["2025-06-18"] = "2025-06-18"
278
+ inventory_digest: str
279
+ tools: tuple[DshCapabilityTool, ...] = Field(max_length=2048)
280
+
281
+ @field_validator("dsh_version")
282
+ @classmethod
283
+ def validate_dsh_version(cls, value: str) -> str:
284
+ if not _SEMVER.fullmatch(value):
285
+ raise ValueError("dshVersion must use exact semantic versioning")
286
+ return value
287
+
288
+ @field_validator("profile")
289
+ @classmethod
290
+ def validate_descriptor_profile(cls, value: str) -> str:
291
+ if not _PROFILE.fullmatch(value):
292
+ raise ValueError("profile name is invalid")
293
+ return value
294
+
295
+ @field_validator("profile_digest", "inventory_digest")
296
+ @classmethod
297
+ def validate_descriptor_digest(cls, value: str) -> str:
298
+ if not _DIGEST.fullmatch(value):
299
+ raise ValueError("descriptor digest field is invalid")
300
+ return value
301
+
302
+ @model_validator(mode="after")
303
+ def validate_descriptor_inventory(self) -> "DshProfileCapabilityDescriptor":
304
+ names = [tool.name for tool in self.tools]
305
+ if list(self.tools) != sorted(self.tools, key=_tool_sort_key) or len(names) != len(
306
+ set(names)
307
+ ):
308
+ raise ValueError("descriptor tools must be sorted and unique")
309
+ if _inventory_digest(self.tools) != self.inventory_digest:
310
+ raise ValueError("descriptor inventory digest does not match its tools")
311
+ return self
312
+
313
+ @property
314
+ def descriptor_digest(self) -> str:
315
+ return _canonical_digest(self.model_dump(by_alias=True, mode="json"))
316
+
317
+
318
+ @dataclass(frozen=True)
319
+ class DshMcpConnectorLease:
320
+ """Ephemeral connection material for one running ``mcp.connector/v1``."""
321
+
322
+ endpoint: str = field(repr=False)
323
+ profile: str
324
+ profile_digest: str
325
+ descriptor_digest: str
326
+ _bearer_token: str = field(repr=False)
327
+ _browser_token: str = field(repr=False, default="")
328
+ web_route_count: int = 0
329
+ definition: Literal["mcp.connector/v1"] = "mcp.connector/v1"
330
+ transport: Literal["streamable-http"] = "streamable-http"
331
+ protocol_version: Literal["2025-06-18"] = "2025-06-18"
332
+
333
+ def headers(self) -> dict[str, str]:
334
+ """Return fresh request headers without storing them in descriptors/logs."""
335
+
336
+ return {"Authorization": f"Bearer {self._bearer_token}"}
337
+
338
+ def companion_bearer_token(
339
+ self, *, plugin_id: str, generation_id: str, handle: str,
340
+ tool_name: str, call_id: str, ttl_seconds: int = 60,
341
+ ) -> str:
342
+ """Sign only an opaque Python grant and one exact native tool call."""
343
+ if (not re.fullmatch(r"[A-Za-z0-9_.-]{1,128}", plugin_id)
344
+ or not re.fullmatch(r"dshgen_[A-Za-z0-9_-]{24,96}", generation_id)
345
+ or not re.fullmatch(r"[A-Za-z0-9_-]{32,256}", handle)
346
+ or not _TOOL_NAME.fullmatch(tool_name)
347
+ or not re.fullmatch(r"[A-Za-z0-9_.:-]{1,128}", call_id)
348
+ or not 1 <= ttl_seconds <= 120):
349
+ raise ValueError("invalid companion invocation scope")
350
+ payload = {
351
+ "v": 3, "profileDigest": self.profile_digest,
352
+ "pluginId": plugin_id, "generationId": generation_id,
353
+ "handle": handle, "callId": call_id,
354
+ "aliases": {tool_name: tool_name},
355
+ "exp": int(time.time()) + ttl_seconds,
356
+ "jti": secrets.token_urlsafe(24),
357
+ }
358
+ raw = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
359
+ encoded = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
360
+ message = f"ks3.{encoded}"
361
+ signature = base64.urlsafe_b64encode(
362
+ hmac.new(self._bearer_token.encode(), message.encode(), hashlib.sha256).digest()
363
+ ).rstrip(b"=").decode()
364
+ return f"{message}.{signature}"
365
+
366
+ def resource_bearer_token(self, tool_aliases: Mapping[str, str], resource_leases) -> str:
367
+ """Mint scope v2 from host-issued, activation-bound resource leases."""
368
+ from ksadk.resource_runtime.leases import ResourceLease
369
+
370
+ leases = tuple(resource_leases)
371
+ if not leases or any(not isinstance(lease, ResourceLease) for lease in leases):
372
+ raise ValueError("Resource scope requires host-issued leases")
373
+ first = leases[0].scope
374
+ if first.profile_digest != self.profile_digest:
375
+ raise ValueError("Resource scope belongs to another profile")
376
+ aliases = dict(tool_aliases)
377
+ if not 1 <= len(aliases) <= 32 or any(
378
+ not isinstance(alias, str)
379
+ or not isinstance(source, str)
380
+ or not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", alias)
381
+ or not _TOOL_NAME.fullmatch(source)
382
+ for alias, source in aliases.items()
383
+ ):
384
+ raise ValueError("Resource scope requires explicit valid tool aliases")
385
+ resources = {}
386
+ lifetime = 600
387
+ for lease in leases:
388
+ scope = lease.scope
389
+ if (
390
+ scope.generation_id != first.generation_id
391
+ or scope.activation_id != first.activation_id
392
+ or scope.build_digest != first.build_digest
393
+ or scope.binding_snapshot_digest != first.binding_snapshot_digest
394
+ or scope.identity != first.identity
395
+ or scope.profile_digest != first.profile_digest
396
+ ):
397
+ raise ValueError("Resource scope cannot mix activations")
398
+ lifetime = min(lifetime, math.floor(lease.expires_at - time.monotonic()))
399
+ for operation in scope.allowed_operations:
400
+ if operation not in aliases.values():
401
+ continue
402
+ if operation in resources:
403
+ raise ValueError("Resource operation has ambiguous bindings")
404
+ resources[operation] = {
405
+ "handle": lease.handle,
406
+ "bindingId": scope.binding_id,
407
+ "operation": operation,
408
+ }
409
+ if lifetime < 1 or not resources:
410
+ raise ValueError("Resource scope has no live operations")
411
+ issued = int(time.time())
412
+ payload = {
413
+ "v": 2,
414
+ "profileDigest": self.profile_digest,
415
+ "generationId": first.generation_id,
416
+ "activationId": first.activation_id,
417
+ "buildDigest": first.build_digest,
418
+ "bindingSnapshotDigest": first.binding_snapshot_digest,
419
+ "identity": first.identity.model_dump(by_alias=True, mode="json"),
420
+ "aliases": aliases,
421
+ "resources": resources,
422
+ "issuedAt": issued,
423
+ "expiresAt": issued + lifetime,
424
+ "jti": secrets.token_urlsafe(24),
425
+ }
426
+ raw = json.dumps(
427
+ payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
428
+ ).encode()
429
+ encoded = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
430
+ message = f"ks2.{encoded}"
431
+ signature = (
432
+ base64.urlsafe_b64encode(
433
+ hmac.new(self._bearer_token.encode(), message.encode(), hashlib.sha256).digest()
434
+ )
435
+ .rstrip(b"=")
436
+ .decode()
437
+ )
438
+ token = f"{message}.{signature}"
439
+ if len(token) > 12 * 1024:
440
+ raise ValueError("Resource scope exceeds header size limit")
441
+ return token
442
+
443
+ def browser_url(self) -> str:
444
+ """Return the one-time full Core DSH browser handoff URL."""
445
+
446
+ if self.web_route_count < 1 or not self._browser_token:
447
+ raise PluginHostError(
448
+ "dsh_core_web_unavailable",
449
+ "The selected DSH Profile does not expose the Core Web application",
450
+ )
451
+ parsed = urlsplit(self.endpoint)
452
+ return f"http://127.0.0.1:{parsed.port}/?token={self._browser_token}"
453
+
454
+ def bearer_token_for_runtime(self, tool_aliases: Mapping[str, str]) -> str:
455
+ """Mint a generation-bound token limited to explicit tool names."""
456
+
457
+ aliases = {
458
+ str(alias).strip(): str(source).strip() for alias, source in tool_aliases.items()
459
+ }
460
+ if (
461
+ not aliases
462
+ or len(aliases) != len(tool_aliases)
463
+ or len(aliases) > 32
464
+ or any(
465
+ not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", alias) or not _TOOL_NAME.fullmatch(source)
466
+ for alias, source in aliases.items()
467
+ )
468
+ ):
469
+ raise PluginHostError(
470
+ "dsh_capability_scope_invalid",
471
+ "DSH capability tokens require an explicit valid tool scope",
472
+ )
473
+ payload = json.dumps(
474
+ {
475
+ "v": 1,
476
+ "profileDigest": self.profile_digest,
477
+ "exp": int(time.time()) + _SCOPED_TOKEN_LIFETIME_SECONDS,
478
+ "jti": secrets.token_urlsafe(16),
479
+ "aliases": dict(sorted(aliases.items())),
480
+ },
481
+ allow_nan=False,
482
+ ensure_ascii=False,
483
+ sort_keys=True,
484
+ separators=(",", ":"),
485
+ ).encode("utf-8")
486
+ encoded = base64.urlsafe_b64encode(payload).rstrip(b"=")
487
+ signature = hmac.new(self._bearer_token.encode("utf-8"), encoded, hashlib.sha256).digest()
488
+ encoded_signature = base64.urlsafe_b64encode(signature).rstrip(b"=")
489
+ token = f"ks1.{encoded.decode('ascii')}.{encoded_signature.decode('ascii')}"
490
+ if len(token) > 12 * 1024:
491
+ raise PluginHostError(
492
+ "dsh_capability_scope_too_large",
493
+ "DSH capability token scope exceeds the HTTP header budget",
494
+ )
495
+ return token
496
+
497
+
498
+ class DshProfileCapabilityInventory(PluginContractModel):
499
+ ecosystem: Literal["dsh"] = "dsh"
500
+ profile: str
501
+ profile_digest: str
502
+ descriptor_digest: str | None = None
503
+ inventory_digest: str | None = None
504
+ state: Literal["stopped", "starting", "ready", "failed", "disposed"]
505
+ pid: int | None = Field(default=None, ge=1)
506
+ tool_count: int = Field(ge=0)
507
+ circuit_state: Literal["closed", "open", "half-open"]
508
+ consecutive_failures: int = Field(ge=0)
509
+ retry_after_seconds: float = Field(ge=0)
510
+
511
+
512
+ @dataclass(frozen=True)
513
+ class DshCapabilityBundle:
514
+ package_name: str
515
+ version: str
516
+ root: Path
517
+ entrypoint: Path
518
+ patch: Path
519
+ digest: str
520
+
521
+
522
+ def load_dsh_capability_bundle() -> DshCapabilityBundle:
523
+ """Locate and validate the immutable bridge assets shipped in the wheel."""
524
+
525
+ root = Path(__file__).with_name("bundles") / "ksadk-dsh-capability-host"
526
+ manifest_path = root / "package.json"
527
+ try:
528
+ payload = json.loads(manifest_path.read_text(encoding="utf-8"))
529
+ except (OSError, UnicodeError, json.JSONDecodeError) as error:
530
+ raise PluginHostError(
531
+ "dsh_capability_bundle_invalid", "DSH capability Bundle manifest is unavailable"
532
+ ) from error
533
+ dsh = payload.get("dsh") if isinstance(payload, dict) else None
534
+ bundle = dsh.get("bundle") if isinstance(dsh, dict) else None
535
+ patch_value = bundle.get("patch") if isinstance(bundle, dict) else None
536
+ integrity = bundle.get("integrity") if isinstance(bundle, dict) else None
537
+ if (
538
+ not isinstance(payload, dict)
539
+ or payload.get("name") != DSH_CAPABILITY_BUNDLE_PACKAGE
540
+ or payload.get("version") != DSH_CAPABILITY_HOST_VERSION
541
+ or payload.get("type") != "module"
542
+ or patch_value != "./cordis.patch.yml"
543
+ or not isinstance(integrity, str)
544
+ or not _DIGEST.fullmatch(integrity)
545
+ ):
546
+ raise PluginHostError(
547
+ "dsh_capability_bundle_invalid", "DSH capability Bundle identity is invalid"
548
+ )
549
+ entrypoint = (root / "index.mjs").resolve()
550
+ patch = (root / str(patch_value)).resolve()
551
+ resolved_root = root.resolve()
552
+ if (
553
+ not entrypoint.is_relative_to(resolved_root)
554
+ or not patch.is_relative_to(resolved_root)
555
+ or not entrypoint.is_file()
556
+ or not patch.is_file()
557
+ ):
558
+ raise PluginHostError(
559
+ "dsh_capability_bundle_invalid", "DSH capability Bundle assets are missing"
560
+ )
561
+ hasher = hashlib.sha256()
562
+ for name, path in (("index.mjs", entrypoint), ("cordis.patch.yml", patch)):
563
+ hasher.update(name.encode("utf-8"))
564
+ hasher.update(b"\0")
565
+ hasher.update(path.read_bytes())
566
+ hasher.update(b"\0")
567
+ digest = f"sha256:{hasher.hexdigest()}"
568
+ if digest != integrity:
569
+ raise PluginHostError(
570
+ "dsh_capability_bundle_invalid",
571
+ "DSH capability Bundle integrity does not match its assets",
572
+ )
573
+ return DshCapabilityBundle(
574
+ package_name=DSH_CAPABILITY_BUNDLE_PACKAGE,
575
+ version=DSH_CAPABILITY_HOST_VERSION,
576
+ root=resolved_root,
577
+ entrypoint=entrypoint,
578
+ patch=patch,
579
+ digest=digest,
580
+ )
581
+
582
+
583
+ class DshProfileCapabilityHost:
584
+ """Supervise one complete DSH Profile and its loopback MCP exporter."""
585
+
586
+ def __init__(
587
+ self,
588
+ dsh_command: Sequence[str],
589
+ *,
590
+ projection: DshProfileProjection,
591
+ dsh_home: Path,
592
+ cwd: Path | None = None,
593
+ environment: Mapping[str, str] | None = None,
594
+ node_command: str | Path | None = None,
595
+ studio_index: Path | None = None,
596
+ studio_models: Any = None,
597
+ startup_timeout: float = 15.0,
598
+ shutdown_timeout: float = 5.0,
599
+ health_timeout: float = 2.0,
600
+ call_timeout: float = 60.0,
601
+ max_argument_bytes: int = 256 * 1024,
602
+ max_result_bytes: int = 1024 * 1024,
603
+ max_request_bytes: int = 1024 * 1024,
604
+ max_in_flight: int = 64,
605
+ inventory_quiet: float = 2.0,
606
+ circuit_failure_threshold: int = 3,
607
+ circuit_recovery_timeout: float = 5.0,
608
+ ) -> None:
609
+ self._command = self._validate_command(dsh_command)
610
+ if not _PROFILE.fullmatch(projection.profile) or not _DIGEST.fullmatch(
611
+ projection.config_digest
612
+ ):
613
+ raise PluginHostError(
614
+ "dsh_capability_projection_invalid", "DSH Profile projection is invalid"
615
+ )
616
+ if projection.config_bytes < 0 or projection.config_bytes > _MAX_CONFIG_BYTES:
617
+ raise PluginHostError(
618
+ "dsh_capability_projection_invalid", "DSH Profile projection is too large"
619
+ )
620
+ if not _SEMVER.fullmatch(projection.host_version):
621
+ raise PluginHostError(
622
+ "dsh_capability_projection_invalid", "DSH Profile host version is invalid"
623
+ )
624
+ self._projection = projection
625
+ self._dsh_home = dsh_home.expanduser().resolve()
626
+ self._cwd = (cwd or Path.cwd()).expanduser().resolve()
627
+ self._explicit_environment = self._validate_environment(environment or {})
628
+ self._node_command = str(node_command).strip() if node_command is not None else None
629
+ self._startup_timeout = self._positive_float(startup_timeout, "startup_timeout")
630
+ self._shutdown_timeout = self._positive_float(shutdown_timeout, "shutdown_timeout")
631
+ self._health_timeout = self._positive_float(health_timeout, "health_timeout")
632
+ self._call_timeout_ms = self._milliseconds(call_timeout, "call_timeout", 3_600_000)
633
+ self._max_argument_bytes = self._positive_int(
634
+ max_argument_bytes, "max_argument_bytes", 8 * 1024 * 1024
635
+ )
636
+ self._max_result_bytes = self._positive_int(
637
+ max_result_bytes, "max_result_bytes", 8 * 1024 * 1024
638
+ )
639
+ self._max_request_bytes = self._positive_int(
640
+ max_request_bytes, "max_request_bytes", 8 * 1024 * 1024
641
+ )
642
+ self._max_in_flight = self._positive_int(max_in_flight, "max_in_flight", 1024)
643
+ self._inventory_quiet_ms = self._milliseconds(
644
+ inventory_quiet, "inventory_quiet", 5_000
645
+ )
646
+ self._bundle = load_dsh_capability_bundle()
647
+ self._studio_index = studio_index
648
+ self._studio_models = studio_models
649
+ self._circuit = DshCircuitBreaker(
650
+ failure_threshold=circuit_failure_threshold,
651
+ recovery_timeout=circuit_recovery_timeout,
652
+ )
653
+ self._lifecycle_lock = asyncio.Lock()
654
+ self._process: asyncio.subprocess.Process | None = None
655
+ self._stdout_task: asyncio.Task[None] | None = None
656
+ self._stderr_task: asyncio.Task[None] | None = None
657
+ self._monitor_task: asyncio.Task[None] | None = None
658
+ self._ready_future: asyncio.Future[DshProfileCapabilityReady] | None = None
659
+ self._token_future: asyncio.Future[str] | None = None
660
+ self._core_token_future: asyncio.Future[str] | None = None
661
+ self._runtime_dir: Path | None = None
662
+ self._resource_socket_path: Path | None = None
663
+ self._companion_configuration: dict[str, Any] | None = None
664
+ self._lease: DshMcpConnectorLease | None = None
665
+ self._descriptor: DshProfileCapabilityDescriptor | None = None
666
+ self._stdout_tail: deque[str] = deque(maxlen=64)
667
+ self._stderr_tail: deque[str] = deque(maxlen=64)
668
+ self._state: Literal["stopped", "starting", "ready", "failed", "disposed"] = "stopped"
669
+ self._disposed = False
670
+ self._stopping = False
671
+ self._process_failure_recorded = False
672
+
673
+ @property
674
+ def pid(self) -> int | None:
675
+ process = self._process
676
+ return process.pid if process is not None and process.returncode is None else None
677
+
678
+ @property
679
+ def descriptor(self) -> DshProfileCapabilityDescriptor:
680
+ if self._descriptor is None:
681
+ raise PluginHostError(
682
+ "dsh_capability_not_ready", "DSH capability descriptor is not ready"
683
+ )
684
+ return self._descriptor
685
+
686
+ @property
687
+ def circuit_snapshot(self) -> DshCircuitSnapshot:
688
+ return self._circuit.snapshot()
689
+
690
+ @property
691
+ def stdout_tail(self) -> tuple[str, ...]:
692
+ return tuple(self._stdout_tail)
693
+
694
+ @property
695
+ def stderr_tail(self) -> tuple[str, ...]:
696
+ return tuple(self._stderr_tail)
697
+
698
+ async def start(self) -> DshMcpConnectorLease:
699
+ async with self._lifecycle_lock:
700
+ if not _PROCESS_GROUP_ISOLATION_AVAILABLE:
701
+ raise PluginHostError(
702
+ "dsh_capability_platform_unsupported",
703
+ "DSH capability host requires POSIX process-group isolation",
704
+ )
705
+ if self._disposed:
706
+ raise PluginHostError(
707
+ "dsh_capability_host_disposed", "DSH capability host is disposed"
708
+ )
709
+ if self.pid is not None and self._state == "ready" and self._lease is not None:
710
+ return self._lease
711
+ if self._process is not None:
712
+ await self._terminate()
713
+ self._require_circuit_probe()
714
+ self._state = "starting"
715
+ self._process_failure_recorded = False
716
+ try:
717
+ environment = self._base_environment()
718
+ if self._studio_models is not None:
719
+ environment.update(self._studio_models.environment)
720
+ await self._require_node_version(environment)
721
+ await self._verify_projection(environment)
722
+ runtime_dir = Path(tempfile.mkdtemp(prefix="ksadk-dsh-capability-"))
723
+ runtime_dir.chmod(0o700)
724
+ self._runtime_dir = runtime_dir
725
+ overlay = runtime_dir / "cordis.patch.yml"
726
+ overlay.write_text(self._overlay_text(), encoding="utf-8")
727
+ overlay.chmod(0o600)
728
+ environment.update(
729
+ {
730
+ "DSH_TELEMETRY_DISABLED": "1",
731
+ "KSADK_DSH_CAPABILITY_READY_STDOUT": "1",
732
+ "KSADK_DSH_PROFILE": self._projection.profile,
733
+ "KSADK_DSH_PROFILE_DIGEST": self._projection.config_digest,
734
+ "KSADK_DSH_VERSION": self._projection.host_version,
735
+ }
736
+ )
737
+ environment.pop("KSADK_DSH_COMPANION_CONFIGURATION", None)
738
+ if self._companion_configuration is not None:
739
+ environment["KSADK_DSH_COMPANION_CONFIGURATION"] = json.dumps(
740
+ self._companion_configuration, separators=(",", ":")
741
+ )
742
+ process = await asyncio.create_subprocess_exec(
743
+ *self._command,
744
+ "--profile",
745
+ self._projection.profile,
746
+ "--patch",
747
+ str(overlay),
748
+ "--host",
749
+ "127.0.0.1",
750
+ "--port",
751
+ "0",
752
+ "--no-open",
753
+ cwd=str(self._cwd),
754
+ env=environment,
755
+ stdin=asyncio.subprocess.DEVNULL,
756
+ stdout=asyncio.subprocess.PIPE,
757
+ stderr=asyncio.subprocess.PIPE,
758
+ start_new_session=True,
759
+ )
760
+ self._process = process
761
+ ready_future = asyncio.get_running_loop().create_future()
762
+ token_future = asyncio.get_running_loop().create_future()
763
+ core_token_future = asyncio.get_running_loop().create_future()
764
+ self._ready_future = ready_future
765
+ self._token_future = token_future
766
+ self._core_token_future = core_token_future
767
+ self._stdout_task = asyncio.create_task(
768
+ self._capture_stream(
769
+ process.stdout,
770
+ self._stdout_tail,
771
+ ready_future=ready_future,
772
+ token_future=token_future,
773
+ core_token_future=core_token_future,
774
+ )
775
+ )
776
+ self._stderr_task = asyncio.create_task(
777
+ self._capture_stream(process.stderr, self._stderr_tail)
778
+ )
779
+ self._monitor_task = asyncio.create_task(self._monitor_process(process))
780
+ ready, token, core_token = await self._wait_ready(
781
+ ready_future,
782
+ token_future,
783
+ core_token_future,
784
+ process,
785
+ )
786
+ self._validate_ready_fences(ready)
787
+ descriptor = DshProfileCapabilityDescriptor(
788
+ dsh_version=ready.dsh_version,
789
+ profile=ready.profile,
790
+ profile_digest=ready.profile_digest,
791
+ inventory_digest=ready.inventory_digest,
792
+ tools=ready.tools,
793
+ )
794
+ lease = DshMcpConnectorLease(
795
+ endpoint=ready.endpoint,
796
+ profile=ready.profile,
797
+ profile_digest=ready.profile_digest,
798
+ descriptor_digest=descriptor.descriptor_digest,
799
+ _bearer_token=token,
800
+ _browser_token=core_token,
801
+ web_route_count=ready.web_route_count,
802
+ )
803
+ self._descriptor = descriptor
804
+ self._lease = lease
805
+ if not await self._probe_health(lease, descriptor):
806
+ raise PluginHostError(
807
+ "dsh_capability_health_failed",
808
+ "DSH capability MCP endpoint failed its startup health check",
809
+ )
810
+ self._state = "ready"
811
+ self._circuit.succeed()
812
+ return lease
813
+ except asyncio.CancelledError:
814
+ await self._terminate()
815
+ self._state = "stopped"
816
+ raise
817
+ except PluginHostError:
818
+ self._record_process_failure()
819
+ await self._terminate()
820
+ self._state = "failed"
821
+ raise
822
+ except Exception as error:
823
+ self._record_process_failure()
824
+ await self._terminate()
825
+ self._state = "failed"
826
+ raise PluginHostError(
827
+ "dsh_capability_start_failed",
828
+ f"DSH capability host failed to start: {type(error).__name__}",
829
+ ) from error
830
+
831
+ async def lease(self) -> DshMcpConnectorLease:
832
+ return await self.start()
833
+
834
+ async def health(self) -> bool:
835
+ lease = self._lease
836
+ descriptor = self._descriptor
837
+ if self.pid is None or lease is None or descriptor is None or self._state != "ready":
838
+ return False
839
+ healthy = await self._probe_health(lease, descriptor)
840
+ if healthy:
841
+ self._circuit.succeed()
842
+ else:
843
+ self._circuit.fail()
844
+ self._state = "failed"
845
+ return healthy
846
+
847
+ async def inventory(self) -> DshProfileCapabilityInventory:
848
+ descriptor = self._descriptor
849
+ state = self._state
850
+ if state == "ready" and not await self.health():
851
+ state = "failed"
852
+ snapshot = self._circuit.snapshot()
853
+ return DshProfileCapabilityInventory(
854
+ profile=self._projection.profile,
855
+ profile_digest=self._projection.config_digest,
856
+ descriptor_digest=descriptor.descriptor_digest if descriptor is not None else None,
857
+ inventory_digest=descriptor.inventory_digest if descriptor is not None else None,
858
+ state=state,
859
+ pid=self.pid,
860
+ tool_count=len(descriptor.tools) if descriptor is not None else 0,
861
+ circuit_state=snapshot.state,
862
+ consecutive_failures=snapshot.consecutive_failures,
863
+ retry_after_seconds=snapshot.retry_after_seconds,
864
+ )
865
+
866
+ async def dispose(self) -> None:
867
+ async with self._lifecycle_lock:
868
+ if self._disposed:
869
+ return
870
+ self._disposed = True
871
+ await self._terminate()
872
+ self._state = "disposed"
873
+
874
+ async def configure_resource_socket(self, path: Path) -> None:
875
+ """Stop any old Core and bind next-start IPC after owner revokes old resources."""
876
+ import stat
877
+
878
+ path = Path(path)
879
+ if (
880
+ not path.is_absolute() or path.is_symlink()
881
+ or not stat.S_ISSOCK(path.stat().st_mode)
882
+ or path.stat().st_mode & 0o077 or path.parent.stat().st_mode & 0o077
883
+ ):
884
+ raise PluginHostError("dsh_resource_socket_invalid", "Resource IPC must be private")
885
+ async with self._lifecycle_lock:
886
+ if self._disposed:
887
+ raise PluginHostError("dsh_resource_socket_busy", "Resource host is disposed")
888
+ if self._process is not None:
889
+ await self._terminate()
890
+ self._state = "stopped"
891
+ self._resource_socket_path = path
892
+
893
+ async def configure_companions(self, configuration: Mapping[str, Any] | None) -> None:
894
+ """Bind private companion IPC to the next Core generation."""
895
+ import stat
896
+
897
+ if configuration is not None:
898
+ path = Path(str(configuration.get("socketPath", "")))
899
+ if (not path.is_absolute() or path.is_symlink()
900
+ or not stat.S_ISSOCK(path.stat().st_mode)
901
+ or path.stat().st_mode & 0o077 or path.parent.stat().st_mode & 0o077):
902
+ raise PluginHostError(
903
+ "dsh_companion_socket_invalid", "Companion IPC must be private"
904
+ )
905
+ async with self._lifecycle_lock:
906
+ if self._disposed:
907
+ raise PluginHostError("dsh_companion_host_disposed", "Companion host is disposed")
908
+ if self._process is not None:
909
+ await self._terminate()
910
+ self._state = "stopped"
911
+ self._companion_configuration = dict(configuration) if configuration else None
912
+
913
+ def _overlay_text(self) -> str:
914
+ entrypoint = json.dumps(self._bundle.entrypoint.as_uri())
915
+ overlay = (
916
+ "- insert:\n"
917
+ " - id: ksadk-dsh-capability-host\n"
918
+ f" name: {entrypoint}\n"
919
+ " config:\n"
920
+ f" callTimeoutMs: {self._call_timeout_ms}\n"
921
+ f" maxArgumentBytes: {self._max_argument_bytes}\n"
922
+ f" maxResultBytes: {self._max_result_bytes}\n"
923
+ f" maxRequestBytes: {self._max_request_bytes}\n"
924
+ f" maxInFlight: {self._max_in_flight}\n"
925
+ f" inventoryQuietMs: {self._inventory_quiet_ms}\n"
926
+ )
927
+ if self._resource_socket_path is not None:
928
+ overlay += (
929
+ "- id: dsh-platform-resources\n"
930
+ " config:\n"
931
+ f" socketPath: {json.dumps(str(self._resource_socket_path))}\n"
932
+ )
933
+ if self._companion_configuration is not None:
934
+ companion = self._bundle.root.parent / "ksadk-dsh-companion-host" / "index.mjs"
935
+ overlay += (
936
+ "- insert:\n"
937
+ " - id: ksadk-plugin-companions\n"
938
+ f" name: {json.dumps(companion.as_uri())}\n"
939
+ )
940
+ if self._studio_index is not None:
941
+ app = self._bundle.root.parent / "ksadk-dsh-studio" / "index.mjs"
942
+ overlay += (
943
+ # Studio owns the settings outlet. The stock settings shell
944
+ # declares the same child slot and must not compete for it.
945
+ # Core's settings service, settingsScope and RPC stay enabled.
946
+ "- id: ui-settings-general\n"
947
+ " disabled: true\n"
948
+ "- insert:\n"
949
+ " - id: ksadk-studio-app\n"
950
+ f" name: {json.dumps(app.as_uri())}\n"
951
+ " config:\n"
952
+ f" indexPath: {json.dumps(str(self._studio_index))}\n"
953
+ )
954
+ if self._studio_models is not None and self._studio_models.providers:
955
+ overlay += (
956
+ "- id: llm-pi-ai\n"
957
+ " config:\n"
958
+ f" providers: {json.dumps(self._studio_models.providers)}\n"
959
+ "- id: agent-default-model\n"
960
+ " config:\n"
961
+ f" provider: {json.dumps(self._studio_models.default_provider)}\n"
962
+ f" model: {json.dumps(self._studio_models.default_model)}\n"
963
+ )
964
+ return overlay
965
+
966
+ def _base_environment(self) -> dict[str, str]:
967
+ environment = cast(dict[str, str], dsh_subprocess_environment(dsh_home=self._dsh_home))
968
+ environment.update(self._explicit_environment)
969
+ # The caller cannot inject NODE_OPTIONS, but the supervised sidecar gets
970
+ # a fixed heap ceiling. This also applies to Cordis plugin child Nodes.
971
+ environment["NODE_OPTIONS"] = f"--max-old-space-size={_NODE_MEMORY_LIMIT_MB}"
972
+ return environment
973
+
974
+ async def _require_node_version(self, environment: Mapping[str, str]) -> None:
975
+ executable = self._node_command
976
+ if executable is None:
977
+ executable = shutil.which("node", path=environment.get("PATH"))
978
+ if not executable or "\x00" in executable:
979
+ raise PluginHostError(
980
+ "dsh_capability_node_unavailable", "Node.js is required for DSH capabilities"
981
+ )
982
+ candidate = Path(executable).expanduser()
983
+ if candidate.is_absolute() or len(candidate.parts) > 1:
984
+ if not candidate.is_file():
985
+ raise PluginHostError(
986
+ "dsh_capability_node_unavailable", "Node.js executable is unavailable"
987
+ )
988
+ executable = str(candidate.resolve())
989
+ try:
990
+ process = await asyncio.create_subprocess_exec(
991
+ executable,
992
+ "--version",
993
+ cwd=str(self._cwd),
994
+ env=dict(environment),
995
+ stdout=asyncio.subprocess.PIPE,
996
+ stderr=asyncio.subprocess.PIPE,
997
+ )
998
+ except OSError as error:
999
+ raise PluginHostError(
1000
+ "dsh_capability_node_unavailable", "Node.js version probe failed"
1001
+ ) from error
1002
+ try:
1003
+ stdout, _stderr = await asyncio.wait_for(
1004
+ process.communicate(), timeout=self._health_timeout
1005
+ )
1006
+ except asyncio.CancelledError:
1007
+ if process.returncode is None:
1008
+ process.kill()
1009
+ await process.wait()
1010
+ raise
1011
+ except asyncio.TimeoutError as error:
1012
+ if process.returncode is None:
1013
+ process.kill()
1014
+ await process.wait()
1015
+ raise PluginHostError(
1016
+ "dsh_capability_node_unavailable", "Node.js version probe failed"
1017
+ ) from error
1018
+ match = _NODE_VERSION.fullmatch(stdout.decode("utf-8", errors="replace").strip())
1019
+ if process.returncode != 0 or match is None:
1020
+ raise PluginHostError(
1021
+ "dsh_capability_node_unavailable", "Node.js did not report a valid version"
1022
+ )
1023
+ version = tuple(int(value) for value in match.groups())
1024
+ if version < DSH_CAPABILITY_MIN_NODE_VERSION:
1025
+ minimum = ".".join(str(value) for value in DSH_CAPABILITY_MIN_NODE_VERSION)
1026
+ raise PluginHostError(
1027
+ "dsh_capability_node_version_unsupported",
1028
+ f"DSH capabilities require Node.js >= {minimum}",
1029
+ )
1030
+
1031
+ async def _verify_projection(self, environment: Mapping[str, str]) -> None:
1032
+ try:
1033
+ process = await asyncio.create_subprocess_exec(
1034
+ *self._command,
1035
+ "--profile",
1036
+ self._projection.profile,
1037
+ "--dump-config",
1038
+ cwd=str(self._cwd),
1039
+ env=dict(environment),
1040
+ stdin=asyncio.subprocess.DEVNULL,
1041
+ stdout=asyncio.subprocess.PIPE,
1042
+ stderr=asyncio.subprocess.PIPE,
1043
+ start_new_session=os.name == "posix",
1044
+ )
1045
+ except OSError as error:
1046
+ raise PluginHostError(
1047
+ "dsh_capability_profile_unavailable", "DSH Profile preflight did not complete"
1048
+ ) from error
1049
+ try:
1050
+ stdout, stderr = await asyncio.wait_for(
1051
+ process.communicate(), timeout=self._startup_timeout
1052
+ )
1053
+ except asyncio.CancelledError:
1054
+ await self._terminate_process_tree(process)
1055
+ raise
1056
+ except asyncio.TimeoutError as error:
1057
+ await self._terminate_process_tree(process)
1058
+ raise PluginHostError(
1059
+ "dsh_capability_profile_unavailable", "DSH Profile preflight did not complete"
1060
+ ) from error
1061
+ self._append_diagnostic(stderr, self._stderr_tail)
1062
+ digest = f"sha256:{hashlib.sha256(stdout).hexdigest()}"
1063
+ if process.returncode != 0:
1064
+ raise PluginHostError(
1065
+ "dsh_capability_profile_unavailable", "DSH Profile preflight failed"
1066
+ )
1067
+ if len(stdout) != self._projection.config_bytes or digest != self._projection.config_digest:
1068
+ raise PluginHostError(
1069
+ "dsh_capability_profile_changed",
1070
+ "DSH Profile changed after its immutable projection was selected",
1071
+ )
1072
+
1073
+ async def _wait_ready(
1074
+ self,
1075
+ ready_future: asyncio.Future[DshProfileCapabilityReady],
1076
+ token_future: asyncio.Future[str],
1077
+ core_token_future: asyncio.Future[str],
1078
+ process: asyncio.subprocess.Process,
1079
+ ) -> tuple[DshProfileCapabilityReady, str, str]:
1080
+ deadline = asyncio.get_running_loop().time() + self._startup_timeout
1081
+ while asyncio.get_running_loop().time() < deadline:
1082
+ if ready_future.done() and token_future.done() and core_token_future.done():
1083
+ return (
1084
+ ready_future.result(),
1085
+ token_future.result(),
1086
+ core_token_future.result(),
1087
+ )
1088
+ if process.returncode is not None:
1089
+ raise PluginHostError(
1090
+ "dsh_capability_host_exited", "DSH capability host exited before readiness"
1091
+ )
1092
+ await asyncio.sleep(0.02)
1093
+ raise PluginHostError(
1094
+ "dsh_capability_start_timeout", "DSH capability host did not become ready in time"
1095
+ )
1096
+
1097
+ def _validate_ready_fences(self, ready: DshProfileCapabilityReady) -> None:
1098
+ if (
1099
+ ready.host_version != DSH_CAPABILITY_HOST_VERSION
1100
+ or ready.dsh_version != self._projection.host_version
1101
+ or ready.profile != self._projection.profile
1102
+ or ready.profile_digest != self._projection.config_digest
1103
+ ):
1104
+ raise PluginHostError(
1105
+ "dsh_capability_ready_mismatch",
1106
+ "DSH capability readiness crossed its Profile or version fence",
1107
+ )
1108
+
1109
+ async def _probe_health(
1110
+ self,
1111
+ lease: DshMcpConnectorLease,
1112
+ descriptor: DshProfileCapabilityDescriptor,
1113
+ ) -> bool:
1114
+ parsed = urlsplit(lease.endpoint)
1115
+ health_endpoint = f"http://127.0.0.1:{parsed.port}/health"
1116
+ try:
1117
+ async with httpx.AsyncClient(
1118
+ headers=lease.headers(), timeout=self._health_timeout, trust_env=False
1119
+ ) as client:
1120
+ response = await client.get(health_endpoint)
1121
+ if len(response.content) > 64 * 1024:
1122
+ return False
1123
+ payload = response.json()
1124
+ except (httpx.HTTPError, ValueError, TypeError):
1125
+ return False
1126
+ return bool(
1127
+ response.status_code == 200
1128
+ and isinstance(payload, dict)
1129
+ and payload.get("protocolVersion") == DSH_CAPABILITY_HOST_PROTOCOL
1130
+ and payload.get("healthy") is True
1131
+ and payload.get("profile") == descriptor.profile
1132
+ and payload.get("profileDigest") == descriptor.profile_digest
1133
+ and payload.get("inventoryDigest") == descriptor.inventory_digest
1134
+ and payload.get("toolCount") == len(descriptor.tools)
1135
+ and payload.get("drifted") is False
1136
+ and payload.get("draining") is False
1137
+ )
1138
+
1139
+ async def _capture_stream(
1140
+ self,
1141
+ stream: asyncio.StreamReader | None,
1142
+ target: deque[str],
1143
+ *,
1144
+ ready_future: asyncio.Future[DshProfileCapabilityReady] | None = None,
1145
+ token_future: asyncio.Future[str] | None = None,
1146
+ core_token_future: asyncio.Future[str] | None = None,
1147
+ ) -> None:
1148
+ if stream is None:
1149
+ return
1150
+ ready_prefix = _READY_PREFIX.encode("ascii")
1151
+ token_prefix = _TOKEN_PREFIX.encode("ascii")
1152
+ core_token_prefix = _CORE_TOKEN_PREFIX.encode("ascii")
1153
+ remainder = bytearray()
1154
+ discard_oversized_ready = False
1155
+ while True:
1156
+ chunk = await stream.read(4096)
1157
+ if not chunk:
1158
+ break
1159
+ if discard_oversized_ready:
1160
+ separator = chunk.find(b"\n")
1161
+ if separator < 0:
1162
+ continue
1163
+ chunk = chunk[separator + 1 :]
1164
+ discard_oversized_ready = False
1165
+ remainder.extend(chunk)
1166
+ while True:
1167
+ separator = remainder.find(b"\n")
1168
+ if separator < 0:
1169
+ break
1170
+ line = bytes(remainder[:separator])
1171
+ del remainder[: separator + 1]
1172
+ if line.startswith(ready_prefix):
1173
+ self._capture_ready(line, ready_future)
1174
+ elif line.startswith(token_prefix):
1175
+ self._capture_token(line, token_future)
1176
+ elif line.startswith(core_token_prefix):
1177
+ self._capture_token(
1178
+ line, core_token_future, prefix=_CORE_TOKEN_PREFIX
1179
+ )
1180
+ else:
1181
+ self._append_diagnostic(line, target)
1182
+ if remainder.startswith(ready_prefix):
1183
+ if len(remainder) > len(ready_prefix) + _MAX_READY_BYTES:
1184
+ self._capture_ready(bytes(remainder), ready_future)
1185
+ remainder.clear()
1186
+ discard_oversized_ready = True
1187
+ elif not ready_prefix.startswith(remainder) and len(remainder) > _MAX_DIAGNOSTIC_CHARS:
1188
+ self._append_diagnostic(bytes(remainder), target)
1189
+ remainder.clear()
1190
+ if remainder:
1191
+ line = bytes(remainder)
1192
+ if line.startswith(ready_prefix):
1193
+ self._capture_ready(line, ready_future)
1194
+ elif line.startswith(token_prefix):
1195
+ self._capture_token(line, token_future)
1196
+ elif line.startswith(core_token_prefix):
1197
+ self._capture_token(line, core_token_future, prefix=_CORE_TOKEN_PREFIX)
1198
+ else:
1199
+ self._append_diagnostic(line, target)
1200
+
1201
+ @staticmethod
1202
+ def _capture_ready(
1203
+ line: bytes,
1204
+ future: asyncio.Future[DshProfileCapabilityReady] | None,
1205
+ ) -> None:
1206
+ if future is None or future.done():
1207
+ return
1208
+ encoded = line[len(_READY_PREFIX.encode("ascii")) :]
1209
+ try:
1210
+ if not encoded or len(encoded) > _MAX_READY_BYTES:
1211
+ raise ValueError("readiness payload is empty or oversized")
1212
+ payload = json.loads(encoded.decode("utf-8", errors="strict"))
1213
+ ready = DshProfileCapabilityReady.model_validate(payload)
1214
+ except (UnicodeError, json.JSONDecodeError, ValueError):
1215
+ future.set_exception(
1216
+ PluginHostError(
1217
+ "dsh_capability_ready_invalid",
1218
+ "DSH capability readiness record is invalid",
1219
+ )
1220
+ )
1221
+ return
1222
+ future.set_result(ready)
1223
+
1224
+ @staticmethod
1225
+ def _capture_token(
1226
+ line: bytes,
1227
+ future: asyncio.Future[str] | None,
1228
+ *,
1229
+ prefix: str = _TOKEN_PREFIX,
1230
+ ) -> None:
1231
+ if future is None or future.done():
1232
+ return
1233
+ try:
1234
+ token = line[len(prefix.encode("ascii")) :].decode("ascii", errors="strict")
1235
+ if re.fullmatch(r"[A-Za-z0-9_-]{32,128}", token) is None:
1236
+ raise ValueError("invalid runtime token")
1237
+ except (UnicodeError, ValueError):
1238
+ future.set_exception(
1239
+ PluginHostError(
1240
+ "dsh_capability_ready_invalid",
1241
+ "DSH capability readiness record is invalid",
1242
+ )
1243
+ )
1244
+ return
1245
+ future.set_result(token)
1246
+
1247
+ async def _monitor_process(self, process: asyncio.subprocess.Process) -> None:
1248
+ await process.wait()
1249
+ async with self._lifecycle_lock:
1250
+ if process is self._process and not self._stopping and not self._disposed:
1251
+ self._state = "failed"
1252
+ self._record_process_failure()
1253
+ # The group leader may exit while Cordis/plugin children keep
1254
+ # running. Reap the process group immediately so a stale PGID
1255
+ # is neither leaked nor reused before the next lifecycle call.
1256
+ await self._terminate()
1257
+
1258
+ async def _terminate(self) -> None:
1259
+ process = self._process
1260
+ self._stopping = True
1261
+ try:
1262
+ if process is not None:
1263
+ await self._terminate_process_tree(process)
1264
+ monitor = self._monitor_task
1265
+ if monitor is not None and monitor is not asyncio.current_task() and not monitor.done():
1266
+ # Lifecycle methods hold _lifecycle_lock. A monitor that has
1267
+ # observed process exit may be waiting for that same lock, so
1268
+ # waiting for it here would deadlock until the shutdown timeout.
1269
+ monitor.cancel()
1270
+ await asyncio.gather(monitor, return_exceptions=True)
1271
+ tasks = [self._stdout_task, self._stderr_task]
1272
+ for task in tasks:
1273
+ if task is None or task is asyncio.current_task():
1274
+ continue
1275
+ try:
1276
+ await asyncio.wait_for(task, timeout=self._shutdown_timeout)
1277
+ except asyncio.TimeoutError:
1278
+ task.cancel()
1279
+ await asyncio.gather(task, return_exceptions=True)
1280
+ finally:
1281
+ self._process = None
1282
+ self._stdout_task = None
1283
+ self._stderr_task = None
1284
+ self._monitor_task = None
1285
+ ready_future = self._ready_future
1286
+ self._ready_future = None
1287
+ if ready_future is not None and not ready_future.done():
1288
+ ready_future.cancel()
1289
+ token_future = self._token_future
1290
+ self._token_future = None
1291
+ if token_future is not None and not token_future.done():
1292
+ token_future.cancel()
1293
+ core_token_future = self._core_token_future
1294
+ self._core_token_future = None
1295
+ if core_token_future is not None and not core_token_future.done():
1296
+ core_token_future.cancel()
1297
+ self._lease = None
1298
+ self._descriptor = None
1299
+ runtime_dir = self._runtime_dir
1300
+ self._runtime_dir = None
1301
+ if runtime_dir is not None and runtime_dir.name.startswith("ksadk-dsh-capability-"):
1302
+ shutil.rmtree(runtime_dir, ignore_errors=True)
1303
+ self._stopping = False
1304
+
1305
+ async def _terminate_process_tree(self, process: asyncio.subprocess.Process) -> None:
1306
+ """Stop the isolated sidecar session, including plugin child processes."""
1307
+
1308
+ deadline = asyncio.get_running_loop().time() + self._shutdown_timeout
1309
+ if os.name == "posix":
1310
+ self._signal_process_group(process.pid, signal.SIGTERM)
1311
+ elif process.returncode is None:
1312
+ process.terminate()
1313
+
1314
+ if process.returncode is None:
1315
+ try:
1316
+ remaining = max(0.001, deadline - asyncio.get_running_loop().time())
1317
+ await asyncio.wait_for(process.wait(), timeout=remaining)
1318
+ except asyncio.TimeoutError:
1319
+ pass
1320
+
1321
+ if os.name == "posix":
1322
+ while self._process_group_exists(process.pid):
1323
+ remaining = deadline - asyncio.get_running_loop().time()
1324
+ if remaining <= 0:
1325
+ break
1326
+ await asyncio.sleep(min(0.05, remaining))
1327
+ if self._process_group_exists(process.pid):
1328
+ self._signal_process_group(process.pid, signal.SIGKILL)
1329
+ elif process.returncode is None:
1330
+ process.kill()
1331
+
1332
+ if process.returncode is None:
1333
+ await process.wait()
1334
+
1335
+ @staticmethod
1336
+ def _signal_process_group(process_group: int, signum: int) -> None:
1337
+ try:
1338
+ os.killpg(process_group, signum)
1339
+ except ProcessLookupError:
1340
+ return
1341
+
1342
+ @staticmethod
1343
+ def _process_group_exists(process_group: int) -> bool:
1344
+ try:
1345
+ os.killpg(process_group, 0)
1346
+ except ProcessLookupError:
1347
+ return False
1348
+ except PermissionError:
1349
+ return True
1350
+ return True
1351
+
1352
+ def _record_process_failure(self) -> None:
1353
+ if self._process_failure_recorded:
1354
+ return
1355
+ self._process_failure_recorded = True
1356
+ self._circuit.fail()
1357
+
1358
+ def _require_circuit_probe(self) -> None:
1359
+ if self._circuit.acquire():
1360
+ return
1361
+ snapshot = self._circuit.snapshot()
1362
+ raise PluginHostError(
1363
+ "dsh_capability_circuit_open",
1364
+ "DSH capability host circuit is open; wait "
1365
+ f"{snapshot.retry_after_seconds:.1f}s before another probe",
1366
+ )
1367
+
1368
+ @staticmethod
1369
+ def _append_diagnostic(value: bytes, target: deque[str]) -> None:
1370
+ text = value.decode("utf-8", errors="replace").strip()[:_MAX_DIAGNOSTIC_CHARS]
1371
+ if text:
1372
+ target.append(_DIAGNOSTIC_SECRET.sub("[REDACTED]", text))
1373
+
1374
+ @staticmethod
1375
+ def _validate_command(command: Sequence[str]) -> tuple[str, ...]:
1376
+ if isinstance(command, (str, bytes)):
1377
+ raise PluginHostError(
1378
+ "dsh_capability_command_invalid", "DSH command must be an argv sequence"
1379
+ )
1380
+ normalized = tuple(str(value) for value in command)
1381
+ if not normalized or any(not value or "\x00" in value for value in normalized):
1382
+ raise PluginHostError(
1383
+ "dsh_capability_command_invalid", "DSH command contains an invalid argv item"
1384
+ )
1385
+ return normalized
1386
+
1387
+ @staticmethod
1388
+ def _validate_environment(environment: Mapping[str, str]) -> dict[str, str]:
1389
+ normalized: dict[str, str] = {}
1390
+ for key, value in environment.items():
1391
+ if (
1392
+ not _ENV_NAME.fullmatch(key)
1393
+ or key in _BLOCKED_ENV
1394
+ or key.startswith("DYLD_")
1395
+ or _SECRET_ENV.search(key)
1396
+ ):
1397
+ raise PluginHostError(
1398
+ "dsh_capability_environment_denied",
1399
+ f"DSH capability environment key {key!r} is not allowed",
1400
+ )
1401
+ text = str(value)
1402
+ if "\x00" in text:
1403
+ raise PluginHostError(
1404
+ "dsh_capability_environment_denied", "DSH capability environment is invalid"
1405
+ )
1406
+ normalized[key] = text
1407
+ return normalized
1408
+
1409
+ @staticmethod
1410
+ def _positive_float(value: float, field_name: str) -> float:
1411
+ normalized = float(value)
1412
+ if normalized <= 0:
1413
+ raise PluginHostError("dsh_capability_limit_invalid", f"{field_name} must be positive")
1414
+ return normalized
1415
+
1416
+ @classmethod
1417
+ def _milliseconds(cls, value: float, field_name: str, maximum: int) -> int:
1418
+ milliseconds = round(cls._positive_float(value, field_name) * 1000)
1419
+ if milliseconds < 1 or milliseconds > maximum:
1420
+ raise PluginHostError(
1421
+ "dsh_capability_limit_invalid", f"{field_name} is outside the supported range"
1422
+ )
1423
+ return milliseconds
1424
+
1425
+ @staticmethod
1426
+ def _positive_int(value: int, field_name: str, maximum: int) -> int:
1427
+ if isinstance(value, bool) or not isinstance(value, int) or value < 1 or value > maximum:
1428
+ raise PluginHostError(
1429
+ "dsh_capability_limit_invalid", f"{field_name} is outside the supported range"
1430
+ )
1431
+ return value
1432
+
1433
+
1434
+ __all__ = [
1435
+ "DSH_CAPABILITY_BUNDLE_PACKAGE",
1436
+ "DSH_CAPABILITY_DEFINITION",
1437
+ "DSH_CAPABILITY_HOST_PROTOCOL",
1438
+ "DSH_CAPABILITY_HOST_VERSION",
1439
+ "DSH_CAPABILITY_MCP_PROTOCOL",
1440
+ "DSH_CAPABILITY_MIN_NODE_VERSION",
1441
+ "DSH_CAPABILITY_TRANSPORT",
1442
+ "DshCapabilityBundle",
1443
+ "DshCapabilityTool",
1444
+ "DshMcpConnectorLease",
1445
+ "DshProfileCapabilityDescriptor",
1446
+ "DshProfileCapabilityHost",
1447
+ "DshProfileCapabilityInventory",
1448
+ "DshProfileCapabilityReady",
1449
+ "load_dsh_capability_bundle",
1450
+ ]