agentengine-sdk-python 0.8.4__py3-none-any.whl → 0.8.6__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 (1221) hide show
  1. {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/METADATA +17 -10
  2. agentengine_sdk_python-0.8.6.dist-info/RECORD +1731 -0
  3. ksadk/_async_iterators.py +79 -0
  4. ksadk/_build_provenance.json +1 -1
  5. ksadk/_model_anthropic.py +49 -0
  6. ksadk/_model_input.py +86 -0
  7. ksadk/_model_json.py +40 -0
  8. ksadk/_model_reasoning.py +17 -0
  9. ksadk/_model_refusal.py +20 -0
  10. ksadk/_model_sse.py +163 -0
  11. ksadk/_process.py +83 -0
  12. ksadk/_str_enum.py +17 -0
  13. ksadk/a2a/__init__.py +157 -167
  14. ksadk/a2a/_invocation_state.py +86 -0
  15. ksadk/a2a/_space_client_events.py +33 -22
  16. ksadk/a2a/adk.py +194 -0
  17. ksadk/a2a/adk_invocation_context.py +242 -0
  18. ksadk/a2a/adk_runtime.py +126 -0
  19. ksadk/a2a/adk_tools.py +319 -0
  20. ksadk/a2a/bootstrap.py +149 -23
  21. ksadk/a2a/build_validation.py +147 -0
  22. ksadk/a2a/card.py +23 -15
  23. ksadk/a2a/codex.py +160 -0
  24. ksadk/a2a/codex_checkpoint.py +111 -0
  25. ksadk/a2a/codex_recovery.py +96 -0
  26. ksadk/a2a/codex_recovery_events.py +123 -0
  27. ksadk/a2a/codex_runtime.py +198 -0
  28. ksadk/a2a/codex_session_checkpoint.py +162 -0
  29. ksadk/a2a/composition.py +88 -0
  30. ksadk/a2a/context_store.py +118 -19
  31. ksadk/a2a/control_plane.py +108 -106
  32. ksadk/a2a/event_adapter.py +92 -193
  33. ksadk/a2a/executor.py +257 -85
  34. ksadk/a2a/exposure.py +106 -0
  35. ksadk/a2a/external_transport.py +3 -4
  36. ksadk/a2a/identity.py +106 -12
  37. ksadk/a2a/ids.py +14 -3
  38. ksadk/a2a/inbound_kernel.py +133 -0
  39. ksadk/a2a/interaction_wire.py +76 -0
  40. ksadk/a2a/invocation_checkpoint.py +274 -0
  41. ksadk/a2a/invocation_policy.py +87 -0
  42. ksadk/a2a/invocation_publication.py +210 -0
  43. ksadk/a2a/invocation_service.py +972 -0
  44. ksadk/a2a/invocation_types.py +459 -0
  45. ksadk/a2a/kernel_task_adapter.py +259 -0
  46. ksadk/a2a/langgraph.py +20 -7
  47. ksadk/a2a/langgraph_invocation_context.py +136 -0
  48. ksadk/a2a/langgraph_managed.py +150 -0
  49. ksadk/a2a/langgraph_recovery.py +32 -0
  50. ksadk/a2a/langgraph_runtime.py +89 -0
  51. ksadk/a2a/langgraph_tool_runtime.py +132 -0
  52. ksadk/a2a/langgraph_tools.py +63 -0
  53. ksadk/a2a/message_digest.py +40 -0
  54. ksadk/a2a/platform.py +389 -0
  55. ksadk/a2a/policy_refs.py +13 -0
  56. ksadk/a2a/presentation.py +402 -0
  57. ksadk/a2a/product_kernel_facts.py +177 -0
  58. ksadk/a2a/product_key.py +99 -0
  59. ksadk/a2a/protocol_errors.py +116 -0
  60. ksadk/a2a/result_text.py +49 -0
  61. ksadk/a2a/resume_store.py +93 -3
  62. ksadk/a2a/routes.py +6 -1
  63. ksadk/a2a/runtime_approval.py +198 -0
  64. ksadk/a2a/runtime_input.py +151 -0
  65. ksadk/a2a/server.py +16 -7
  66. ksadk/a2a/space_client.py +101 -16
  67. ksadk/a2a/task_adapter.py +19 -5
  68. ksadk/a2a/task_event_dispatcher.py +39 -2
  69. ksadk/a2a/task_event_outbox.py +215 -0
  70. ksadk/a2a/task_event_outbox_owned.py +130 -0
  71. ksadk/a2a/task_store.py +37 -0
  72. ksadk/a2a/tool_activity.py +176 -0
  73. ksadk/a2a/workload_tokens.py +134 -0
  74. ksadk/a2ui/core.py +3 -3
  75. ksadk/api/client.py +38 -79
  76. ksadk/api/sse_stream.py +96 -0
  77. ksadk/builders/code_builder.py +121 -118
  78. ksadk/builders/container_builder.py +11 -29
  79. ksadk/builders/framework_requirements.py +22 -2
  80. ksadk/builders/managed_runtime_builder.py +12 -0
  81. ksadk/builders/source_provenance.py +87 -0
  82. ksadk/cli/__init__.py +36 -0
  83. ksadk/cli/cmd_a2a.py +43 -0
  84. ksadk/cli/cmd_build.py +9 -0
  85. ksadk/cli/cmd_config.py +4 -4
  86. ksadk/cli/cmd_create.py +12 -12
  87. ksadk/cli/cmd_deploy.py +27 -2
  88. ksadk/cli/cmd_hermes.py +24 -2
  89. ksadk/cli/cmd_invoke.py +64 -88
  90. ksadk/cli/cmd_openclaw.py +26 -1
  91. ksadk/cli/cmd_plugin.py +70 -12
  92. ksadk/cli/cmd_studio.py +69 -17
  93. ksadk/cli/cmd_web.py +2 -0
  94. ksadk/cli/hosted_invoke.py +206 -0
  95. ksadk/cli/invoke_content.py +60 -0
  96. ksadk/cli/openclaw_env.py +1 -1
  97. ksadk/cli/resource_common.py +26 -0
  98. ksadk/cli/runtime_bootstrap.py +27 -5
  99. ksadk/codex/_expert_request_reader.py +48 -0
  100. ksadk/codex/a2a_tools.py +140 -0
  101. ksadk/codex/async_approval.py +169 -0
  102. ksadk/codex/async_client.py +938 -0
  103. ksadk/codex/async_proxy.py +216 -0
  104. ksadk/codex/base_runtime.py +25 -0
  105. ksadk/codex/client.py +3 -1047
  106. ksadk/codex/client_payloads.py +85 -0
  107. ksadk/codex/rollout_checkpoint.py +264 -0
  108. ksadk/codex/runtime.py +290 -54
  109. ksadk/common/auth.py +2 -2
  110. ksadk/common/cloud_headers.py +36 -0
  111. ksadk/common/diagnostics.py +34 -0
  112. ksadk/common/kop_client.py +2 -1
  113. ksadk/configs/__init__.py +1 -1
  114. ksadk/configs/env_registry.py +216 -143
  115. ksadk/configs/env_registry_a2a.py +173 -0
  116. ksadk/configs/env_registry_studio.py +134 -0
  117. ksadk/configs/global_config.py +2 -1
  118. ksadk/configs/settings.py +9 -3
  119. ksadk/connector/__init__.py +17 -0
  120. ksadk/connector/channel.py +477 -0
  121. ksadk/context_engine/assembler.py +9 -0
  122. ksadk/context_engine/capabilities.py +52 -4
  123. ksadk/context_engine/contributors.py +80 -17
  124. ksadk/context_engine/instruction_authority.py +18 -0
  125. ksadk/conversations/attachment_storage.py +10 -3
  126. ksadk/conversations/attachments.py +69 -20
  127. ksadk/conversations/contracts.py +28 -9
  128. ksadk/conversations/message_projection.py +51 -155
  129. ksadk/conversations/message_projection_blocks.py +137 -0
  130. ksadk/conversations/model_context.py +11 -3
  131. ksadk/conversations/normalize.py +28 -10
  132. ksadk/conversations/projector.py +63 -0
  133. ksadk/conversations/runtime_baseline.py +37 -0
  134. ksadk/conversations/runtime_compaction.py +7 -2
  135. ksadk/conversations/runtime_input.py +16 -4
  136. ksadk/conversations/runtime_invocation.py +17 -4
  137. ksadk/conversations/runtime_observability.py +39 -10
  138. ksadk/conversations/runtime_payloads.py +12 -0
  139. ksadk/conversations/runtime_persistence.py +296 -32
  140. ksadk/conversations/runtime_preparation.py +98 -13
  141. ksadk/conversations/runtime_projection.py +217 -0
  142. ksadk/conversations/runtime_resume.py +141 -70
  143. ksadk/conversations/runtime_stream_events.py +29 -35
  144. ksadk/conversations/runtime_streaming.py +8 -0
  145. ksadk/conversations/tool_receipt_resume.py +387 -0
  146. ksadk/conversations/tool_receipts.py +32 -0
  147. ksadk/deployment/env_forward.py +5 -0
  148. ksadk/deployment/manager.py +15 -6
  149. ksadk/deployment/providers/serverless.py +76 -136
  150. ksadk/deployment/providers/serverless_payloads.py +122 -0
  151. ksadk/evaluation/agent_eval_client.py +2 -1
  152. ksadk/evaluation/contracts.py +78 -3
  153. ksadk/evaluation/evaluators.py +166 -1
  154. ksadk/evaluation/evidence.py +271 -4
  155. ksadk/evaluation/local_adapter.py +76 -12
  156. ksadk/evaluation/studio_build_adapter.py +73 -9
  157. ksadk/events/adapters/_a2a_checkpoint.py +151 -0
  158. ksadk/events/adapters/_a2a_snapshot.py +18 -14
  159. ksadk/events/adapters/_a2a_support.py +15 -3
  160. ksadk/events/adapters/_a2a_tool_activity.py +123 -0
  161. ksadk/events/adapters/_codex_errors.py +49 -0
  162. ksadk/events/adapters/_codex_items.py +16 -8
  163. ksadk/events/adapters/_langgraph_support.py +46 -1
  164. ksadk/events/adapters/a2a.py +54 -26
  165. ksadk/events/adapters/adk.py +188 -30
  166. ksadk/events/adapters/codex.py +33 -7
  167. ksadk/events/adapters/langgraph.py +16 -6
  168. ksadk/events/canonical.py +5 -0
  169. ksadk/events/canonical_store.py +200 -4
  170. ksadk/events/reducer.py +82 -0
  171. ksadk/events/run_authority.py +176 -0
  172. ksadk/events/session_event.py +2 -2
  173. ksadk/harness/__init__.py +18 -0
  174. ksadk/harness/app.py +11 -0
  175. ksadk/harness/artifact_adapters/__init__.py +1 -0
  176. ksadk/harness/artifact_adapters/native_page_budget.py +172 -0
  177. ksadk/harness/artifact_adapters/native_search.py +249 -0
  178. ksadk/harness/artifact_adapters/native_search_budget.py +134 -0
  179. ksadk/harness/artifact_adapters/native_store.py +115 -0
  180. ksadk/harness/artifact_adapters/native_tool.py +222 -0
  181. ksadk/harness/artifact_store.py +230 -0
  182. ksadk/harness/browser_adapters/__init__.py +6 -0
  183. ksadk/harness/browser_adapters/native_browser_tool.py +92 -0
  184. ksadk/harness/browser_adapters/native_dom_snapshot.py +187 -0
  185. ksadk/harness/browser_adapters/native_playwright.py +953 -0
  186. ksadk/harness/browser_adapters/native_vision.py +295 -0
  187. ksadk/harness/capabilities.py +115 -0
  188. ksadk/harness/capability_adapters/__init__.py +1 -0
  189. ksadk/harness/capability_adapters/native_dsh.py +289 -0
  190. ksadk/harness/capability_runtime.py +332 -0
  191. ksadk/harness/closure_contract.py +92 -0
  192. ksadk/harness/compaction_record.py +153 -0
  193. ksadk/harness/compiler.py +284 -0
  194. ksadk/harness/config.py +28 -5
  195. ksadk/harness/conformance/__init__.py +33 -0
  196. ksadk/harness/conformance/contract.py +504 -0
  197. ksadk/harness/conformance/fixtures.py +102 -0
  198. ksadk/harness/context_engine.py +388 -0
  199. ksadk/harness/context_maintenance.py +259 -0
  200. ksadk/harness/context_manifest.py +188 -0
  201. ksadk/harness/context_sections.py +397 -0
  202. ksadk/harness/context_tuning.py +168 -0
  203. ksadk/harness/diagnostic_adapters/__init__.py +1 -0
  204. ksadk/harness/diagnostic_adapters/contracts.py +70 -0
  205. ksadk/harness/diagnostic_adapters/lsp_rpc.py +210 -0
  206. ksadk/harness/diagnostic_adapters/native_lsp.py +286 -0
  207. ksadk/harness/draft_runtime.py +322 -0
  208. ksadk/harness/engine/__init__.py +40 -0
  209. ksadk/harness/engine/base.py +122 -0
  210. ksadk/harness/engine/budgets.py +108 -0
  211. ksadk/harness/engine/capability_declarations.py +59 -0
  212. ksadk/harness/engine/completion_guard.py +57 -0
  213. ksadk/harness/engine/context_pipeline.py +1351 -0
  214. ksadk/harness/engine/graph_builder.py +352 -0
  215. ksadk/harness/engine/hooked_reasoner.py +380 -0
  216. ksadk/harness/engine/langgraph.py +1049 -0
  217. ksadk/harness/engine/mcp_disclosure.py +701 -0
  218. ksadk/harness/engine/phases/__init__.py +6 -0
  219. ksadk/harness/engine/phases/context.py +48 -0
  220. ksadk/harness/engine/phases/interaction.py +31 -0
  221. ksadk/harness/engine/phases/reason.py +552 -0
  222. ksadk/harness/engine/phases/tools.py +638 -0
  223. ksadk/harness/engine/policy_runtime.py +81 -0
  224. ksadk/harness/engine/postgres_checkpointer.py +386 -0
  225. ksadk/harness/engine/run_control_bridge.py +100 -0
  226. ksadk/harness/engine/run_types.py +98 -0
  227. ksadk/harness/engine/skill_disclosure.py +275 -0
  228. ksadk/harness/engine/spans.py +41 -0
  229. ksadk/harness/engine/steering.py +177 -0
  230. ksadk/harness/engine/subagents.py +360 -0
  231. ksadk/harness/engine/thread_ids.py +112 -0
  232. ksadk/harness/engine/tool_dispatch.py +249 -0
  233. ksadk/harness/event_tree.py +212 -0
  234. ksadk/harness/events.py +498 -0
  235. ksadk/harness/execution_policy.py +104 -0
  236. ksadk/harness/hooks.py +259 -0
  237. ksadk/harness/insights.py +210 -0
  238. ksadk/harness/lifecycle.py +578 -0
  239. ksadk/harness/lifecycle_conformance.py +233 -0
  240. ksadk/harness/lifecycle_local_plane.py +104 -0
  241. ksadk/harness/loop/__init__.py +44 -0
  242. ksadk/harness/loop/reason.py +699 -0
  243. ksadk/harness/loop/tools.py +907 -0
  244. ksadk/harness/managed_runtime.py +1044 -0
  245. ksadk/harness/mcp_disclosure_contract.py +85 -0
  246. ksadk/harness/mcp_matrix.py +474 -0
  247. ksadk/harness/mcp_result_policy.py +44 -0
  248. ksadk/harness/mcp_runtime.py +504 -0
  249. ksadk/harness/memory_consolidation.py +157 -0
  250. ksadk/harness/memory_loop.py +156 -0
  251. ksadk/harness/memory_runtime.py +541 -0
  252. ksadk/harness/metrics.py +90 -0
  253. ksadk/harness/micro_compact.py +69 -0
  254. ksadk/harness/model_adapters/__init__.py +1 -0
  255. ksadk/harness/model_adapters/native_mcp_media.py +100 -0
  256. ksadk/harness/model_adapters/native_mcp_result.py +36 -0
  257. ksadk/harness/model_adapters/native_openai.py +703 -0
  258. ksadk/harness/model_capability.py +468 -0
  259. ksadk/harness/model_provider.py +430 -0
  260. ksadk/harness/multimodal.py +57 -0
  261. ksadk/harness/native/__init__.py +1 -0
  262. ksadk/harness/native/activation_contract.py +25 -0
  263. ksadk/harness/native/activation_recovery.py +29 -0
  264. ksadk/harness/native/adapters.py +291 -0
  265. ksadk/harness/native/artifact_contract.py +14 -0
  266. ksadk/harness/native/artifact_partial_read_capacity.py +171 -0
  267. ksadk/harness/native/artifact_read_capacity.py +182 -0
  268. ksadk/harness/native/artifact_read_contract.py +27 -0
  269. ksadk/harness/native/artifact_runtime.py +408 -0
  270. ksadk/harness/native/artifact_search_capacity.py +206 -0
  271. ksadk/harness/native/artifact_search_contract.py +40 -0
  272. ksadk/harness/native/artifact_single_read_capacity.py +170 -0
  273. ksadk/harness/native/browser_contract.py +229 -0
  274. ksadk/harness/native/browser_recovery.py +183 -0
  275. ksadk/harness/native/capabilities.py +204 -0
  276. ksadk/harness/native/child_approval.py +60 -0
  277. ksadk/harness/native/child_budget.py +136 -0
  278. ksadk/harness/native/child_cancellation.py +80 -0
  279. ksadk/harness/native/child_input.py +98 -0
  280. ksadk/harness/native/child_input_runtime.py +59 -0
  281. ksadk/harness/native/child_interruption.py +81 -0
  282. ksadk/harness/native/child_lifecycle.py +246 -0
  283. ksadk/harness/native/child_lifecycle_contract.py +91 -0
  284. ksadk/harness/native/child_notifications.py +75 -0
  285. ksadk/harness/native/child_promotion.py +66 -0
  286. ksadk/harness/native/command_runtime.py +99 -0
  287. ksadk/harness/native/completion.py +302 -0
  288. ksadk/harness/native/completion_text.py +116 -0
  289. ksadk/harness/native/context.py +358 -0
  290. ksadk/harness/native/context_artifact_page.py +75 -0
  291. ksadk/harness/native/context_compaction_projection.py +131 -0
  292. ksadk/harness/native/context_contributions.py +320 -0
  293. ksadk/harness/native/context_contributor_contract.py +133 -0
  294. ksadk/harness/native/context_driver.py +820 -0
  295. ksadk/harness/native/context_fresh_tools.py +49 -0
  296. ksadk/harness/native/context_history.py +272 -0
  297. ksadk/harness/native/context_microcompact.py +61 -0
  298. ksadk/harness/native/context_partial_summary.py +58 -0
  299. ksadk/harness/native/context_partition.py +492 -0
  300. ksadk/harness/native/context_pruning.py +275 -0
  301. ksadk/harness/native/context_reduction.py +60 -0
  302. ksadk/harness/native/context_replay.py +148 -0
  303. ksadk/harness/native/context_request_projection.py +526 -0
  304. ksadk/harness/native/context_runtime.py +976 -0
  305. ksadk/harness/native/context_runtime_contracts.py +409 -0
  306. ksadk/harness/native/context_search.py +112 -0
  307. ksadk/harness/native/context_source_fragments.py +188 -0
  308. ksadk/harness/native/context_source_progress.py +119 -0
  309. ksadk/harness/native/context_summary_planning.py +788 -0
  310. ksadk/harness/native/context_summary_recovery.py +105 -0
  311. ksadk/harness/native/delivery_review.py +331 -0
  312. ksadk/harness/native/delivery_review_contract.py +54 -0
  313. ksadk/harness/native/effect_admission.py +82 -0
  314. ksadk/harness/native/effect_control.py +318 -0
  315. ksadk/harness/native/effect_receipts.py +729 -0
  316. ksadk/harness/native/effect_runtime.py +303 -0
  317. ksadk/harness/native/effective_configuration.py +28 -0
  318. ksadk/harness/native/engine.py +945 -0
  319. ksadk/harness/native/final_output.py +34 -0
  320. ksadk/harness/native/final_submission.py +58 -0
  321. ksadk/harness/native/finalization_workspace.py +96 -0
  322. ksadk/harness/native/fork.py +151 -0
  323. ksadk/harness/native/guidance.py +91 -0
  324. ksadk/harness/native/history.py +381 -0
  325. ksadk/harness/native/hook_executor.py +205 -0
  326. ksadk/harness/native/hook_runtime_contract.py +282 -0
  327. ksadk/harness/native/interaction_observation.py +117 -0
  328. ksadk/harness/native/lease.py +103 -0
  329. ksadk/harness/native/machine.py +974 -0
  330. ksadk/harness/native/mcp_result_governance.py +156 -0
  331. ksadk/harness/native/model.py +570 -0
  332. ksadk/harness/native/model_feedback.py +63 -0
  333. ksadk/harness/native/model_input.py +30 -0
  334. ksadk/harness/native/model_recovery_contract.py +35 -0
  335. ksadk/harness/native/model_replay.py +128 -0
  336. ksadk/harness/native/model_request.py +133 -0
  337. ksadk/harness/native/model_runtime.py +196 -0
  338. ksadk/harness/native/model_takeover.py +419 -0
  339. ksadk/harness/native/model_tool_batch.py +40 -0
  340. ksadk/harness/native/observed_progress.py +46 -0
  341. ksadk/harness/native/operator_guidance.py +201 -0
  342. ksadk/harness/native/output_contract.py +80 -0
  343. ksadk/harness/native/output_recovery.py +24 -0
  344. ksadk/harness/native/ports.py +749 -0
  345. ksadk/harness/native/precompact_hook_runtime.py +171 -0
  346. ksadk/harness/native/replay_context.py +149 -0
  347. ksadk/harness/native/request_media.py +112 -0
  348. ksadk/harness/native/restore_contract.py +16 -0
  349. ksadk/harness/native/run_clock.py +120 -0
  350. ksadk/harness/native/state.py +436 -0
  351. ksadk/harness/native/steering.py +170 -0
  352. ksadk/harness/native/stop_hook_runtime.py +151 -0
  353. ksadk/harness/native/studio_admission.py +29 -0
  354. ksadk/harness/native/subagents.py +86 -0
  355. ksadk/harness/native/task_policy.py +99 -0
  356. ksadk/harness/native/task_progress.py +86 -0
  357. ksadk/harness/native/terminal_verification.py +43 -0
  358. ksadk/harness/native/tool_argument_feedback.py +178 -0
  359. ksadk/harness/native/tool_budget.py +253 -0
  360. ksadk/harness/native/tool_error_streak.py +89 -0
  361. ksadk/harness/native/tool_hook_runtime.py +343 -0
  362. ksadk/harness/native/tool_input_validation.py +58 -0
  363. ksadk/harness/native/tool_interruption.py +96 -0
  364. ksadk/harness/native/tool_observation.py +259 -0
  365. ksadk/harness/native/tool_progress.py +223 -0
  366. ksadk/harness/native/tool_recovery.py +25 -0
  367. ksadk/harness/native/tool_scheduling.py +33 -0
  368. ksadk/harness/native/tool_schema.py +126 -0
  369. ksadk/harness/native/transitions.py +151 -0
  370. ksadk/harness/native/turn_control.py +98 -0
  371. ksadk/harness/native/user_document.py +98 -0
  372. ksadk/harness/native/user_document_capture.py +49 -0
  373. ksadk/harness/native/user_input.py +87 -0
  374. ksadk/harness/native/verification.py +432 -0
  375. ksadk/harness/native/verification_binding.py +40 -0
  376. ksadk/harness/native/verification_runtime.py +233 -0
  377. ksadk/harness/native/verification_workspace.py +67 -0
  378. ksadk/harness/native_driver_failure.py +123 -0
  379. ksadk/harness/native_facade.py +977 -0
  380. ksadk/harness/native_factory.py +244 -0
  381. ksadk/harness/native_projection.py +503 -0
  382. ksadk/harness/native_runtime.py +621 -0
  383. ksadk/harness/native_shadow.py +250 -0
  384. ksadk/harness/native_shadow_inputs.py +146 -0
  385. ksadk/harness/observability/__init__.py +29 -0
  386. ksadk/harness/observability/inspection.py +528 -0
  387. ksadk/harness/observability/metrics.py +167 -0
  388. ksadk/harness/observability/otel_exporter.py +218 -0
  389. ksadk/harness/progress_adapters/__init__.py +5 -0
  390. ksadk/harness/progress_adapters/native_todo.py +75 -0
  391. ksadk/harness/prompt_cache.py +132 -0
  392. ksadk/harness/public_activity.py +213 -0
  393. ksadk/harness/public_output.py +30 -0
  394. ksadk/harness/public_summary.py +36 -0
  395. ksadk/harness/readiness.py +249 -0
  396. ksadk/harness/reasoner.py +779 -17
  397. ksadk/harness/release_readiness.py +140 -0
  398. ksadk/harness/resource_ref.py +69 -0
  399. ksadk/harness/rpc.py +200 -0
  400. ksadk/harness/run_control.py +773 -0
  401. ksadk/harness/runtime.py +157 -113
  402. ksadk/harness/runtime_capabilities.py +228 -0
  403. ksadk/harness/runtime_server.py +514 -0
  404. ksadk/harness/sandbox_adapters.py +739 -0
  405. ksadk/harness/sandbox_backend.py +733 -0
  406. ksadk/harness/sandbox_conformance.py +288 -0
  407. ksadk/harness/sandbox_lease.py +77 -0
  408. ksadk/harness/sandbox_lease_conformance.py +259 -0
  409. ksadk/harness/sandbox_matrix.py +170 -0
  410. ksadk/harness/sandbox_reconciliation.py +140 -0
  411. ksadk/harness/sandbox_recovery.py +586 -0
  412. ksadk/harness/sandbox_recovery_conformance.py +246 -0
  413. ksadk/harness/sdk.py +153 -0
  414. ksadk/harness/session_store.py +589 -0
  415. ksadk/harness/skill_adapters/__init__.py +1 -0
  416. ksadk/harness/skill_adapters/native_bundle.py +148 -0
  417. ksadk/harness/skill_adapters/native_disclosure.py +306 -0
  418. ksadk/harness/skill_composition.py +212 -0
  419. ksadk/harness/skill_feedback.py +123 -0
  420. ksadk/harness/skill_intent.py +47 -0
  421. ksadk/harness/skill_matrix.py +233 -0
  422. ksadk/harness/skill_runtime.py +317 -0
  423. ksadk/harness/spec.py +342 -0
  424. ksadk/harness/state.py +155 -0
  425. ksadk/harness/stores/__init__.py +14 -0
  426. ksadk/harness/stores/native_command_settlement.py +65 -0
  427. ksadk/harness/stores/native_contract.py +311 -0
  428. ksadk/harness/stores/native_memory.py +326 -0
  429. ksadk/harness/stores/native_memory_archive.py +70 -0
  430. ksadk/harness/stores/native_migration_cli.py +91 -0
  431. ksadk/harness/stores/native_outbox.py +56 -0
  432. ksadk/harness/stores/native_sqlite.py +782 -0
  433. ksadk/harness/stores/native_sqlite_audit.py +29 -0
  434. ksadk/harness/stores/native_sqlite_migration.py +252 -0
  435. ksadk/harness/strategies.py +149 -0
  436. ksadk/harness/subagent.py +263 -0
  437. ksadk/harness/subagent_adapters/checkpoint_recovery.py +120 -0
  438. ksadk/harness/subagent_adapters/native_background_child.py +803 -0
  439. ksadk/harness/subagent_adapters/native_child.py +819 -0
  440. ksadk/harness/subagent_adapters/native_child_approval.py +128 -0
  441. ksadk/harness/subagent_adapters/native_child_continuation.py +51 -0
  442. ksadk/harness/subagent_adapters/native_child_result.py +392 -0
  443. ksadk/harness/subagent_adapters/prepared_recovery.py +120 -0
  444. ksadk/harness/tool_display.py +75 -0
  445. ksadk/harness/tool_display_contract.py +134 -0
  446. ksadk/harness/tool_output.py +141 -0
  447. ksadk/harness/tool_policy.py +134 -0
  448. ksadk/harness/tool_receipts.py +96 -0
  449. ksadk/harness/tool_reliability.py +81 -0
  450. ksadk/harness/tool_result_policy.py +216 -0
  451. ksadk/harness/tools.py +52 -0
  452. ksadk/harness/user_errors.py +69 -0
  453. ksadk/harness/working_context.py +81 -0
  454. ksadk/harness/working_context_patch.py +99 -0
  455. ksadk/harness/workspace_adapters/__init__.py +6 -0
  456. ksadk/harness/workspace_adapters/native_directory.py +180 -0
  457. ksadk/harness/workspace_adapters/native_git.py +208 -0
  458. ksadk/harness/workspace_adapters/native_isolation.py +350 -0
  459. ksadk/identity/resolver.py +18 -0
  460. ksadk/interaction/__init__.py +1 -1
  461. ksadk/interaction/ledger.py +1 -1
  462. ksadk/interaction/provider.py +1 -1
  463. ksadk/interaction/providers/__init__.py +3 -1
  464. ksadk/interaction/providers/adk.py +1 -1
  465. ksadk/interaction/providers/codex.py +1 -1
  466. ksadk/interaction/providers/harness.py +50 -0
  467. ksadk/interaction/providers/langgraph.py +9 -2
  468. ksadk/kernel/__init__.py +10 -0
  469. ksadk/kernel/approval_presentation.py +91 -0
  470. ksadk/kernel/authorization.py +1 -1
  471. ksadk/kernel/bootstrap.py +220 -252
  472. ksadk/kernel/contract_fingerprints.py +45 -1
  473. ksadk/kernel/contracts.py +30 -0
  474. ksadk/kernel/control.py +180 -29
  475. ksadk/kernel/execution_grants.py +428 -0
  476. ksadk/kernel/execution_grants_memory.py +252 -0
  477. ksadk/kernel/execution_grants_postgres.py +395 -0
  478. ksadk/kernel/execution_grants_sqlite.py +435 -0
  479. ksadk/kernel/execution_host_ingress.py +1071 -0
  480. ksadk/kernel/history_projection.py +120 -0
  481. ksadk/kernel/ingress.py +139 -226
  482. ksadk/kernel/ingress_bootstrap.py +268 -0
  483. ksadk/kernel/legacy_history.py +179 -0
  484. ksadk/kernel/mapping.py +1 -1
  485. ksadk/kernel/memory_store.py +61 -58
  486. ksadk/kernel/memory_store_projection.py +63 -0
  487. ksadk/kernel/postgres_event_log.py +181 -0
  488. ksadk/kernel/postgres_store.py +230 -231
  489. ksadk/kernel/readiness.py +125 -0
  490. ksadk/kernel/recovery.py +166 -16
  491. ksadk/kernel/recovery_projection.py +124 -0
  492. ksadk/kernel/runtime_checkpoint.py +34 -0
  493. ksadk/kernel/sql/001_agent_kernel.sql +28 -0
  494. ksadk/kernel/sqlite_schema.py +100 -0
  495. ksadk/kernel/sqlite_store.py +410 -200
  496. ksadk/kernel/state.py +2 -3
  497. ksadk/kernel/store.py +104 -5
  498. ksadk/kernel/teams_artifact_tools.py +392 -0
  499. ksadk/kernel/teams_effects.py +851 -0
  500. ksadk/kernel/teams_execution_context.py +408 -0
  501. ksadk/kernel/teams_host_http.py +237 -0
  502. ksadk/kernel/teams_material_transport.py +245 -0
  503. ksadk/kernel/teams_materials.py +318 -0
  504. ksadk/kernel/teams_runtime_bootstrap.py +604 -0
  505. ksadk/kernel/teams_tool_runtime.py +273 -0
  506. ksadk/kernel/teams_workspace.py +259 -0
  507. ksadk/kernel/worker.py +369 -340
  508. ksadk/kernel/worker_controls.py +454 -0
  509. ksadk/kernel/worker_history.py +72 -0
  510. ksadk/kernel/worker_identity.py +74 -0
  511. ksadk/kernel/worker_interactions.py +197 -0
  512. ksadk/knowledge_base/client.py +15 -1
  513. ksadk/managed_runtime.py +29 -2
  514. ksadk/memory/adk/backends/http_ltm_backend.py +2 -1
  515. ksadk/memory/adk/backends/sdk_ltm_backend.py +15 -1
  516. ksadk/memory/adk/bounded_database_session_service.py +232 -0
  517. ksadk/memory/adk/resilient_session_service.py +115 -15
  518. ksadk/memory/adk/short_term_memory.py +8 -9
  519. ksadk/memory/coordinator.py +22 -6
  520. ksadk/memory/extraction.py +159 -7
  521. ksadk/memory/models.py +77 -1
  522. ksadk/memory/provider_resolver.py +5 -0
  523. ksadk/memory/providers/local_candidates.py +183 -0
  524. ksadk/memory/providers/local_postgres.py +200 -0
  525. ksadk/memory/providers/local_sqlite.py +280 -67
  526. ksadk/memory/resolved_policy.py +12 -7
  527. ksadk/memory/retrieval.py +189 -0
  528. ksadk/memory/store_adapter.py +150 -0
  529. ksadk/model_gateway/__init__.py +29 -0
  530. ksadk/model_gateway/client.py +253 -0
  531. ksadk/model_gateway/events.py +113 -0
  532. ksadk/model_gateway/matrix.py +297 -0
  533. ksadk/model_policy.py +2 -2
  534. ksadk/model_proxy/compat.py +670 -0
  535. ksadk/model_proxy/detect.py +254 -0
  536. ksadk/model_proxy/server.py +14 -9
  537. ksadk/model_proxy/transform.py +72 -6
  538. ksadk/plugins/__init__.py +1 -1
  539. ksadk/plugins/bridges/dsh.py +29 -20
  540. ksadk/plugins/bridges/dsh_profile_manifest.py +36 -0
  541. ksadk/plugins/builtin_resources.py +199 -0
  542. ksadk/plugins/builtins.py +417 -185
  543. ksadk/plugins/companion_artifacts.py +83 -0
  544. ksadk/plugins/companions.py +368 -0
  545. ksadk/plugins/context_contributor.py +15 -4
  546. ksadk/plugins/contracts.py +1 -1
  547. ksadk/plugins/dsh_channels.py +156 -0
  548. ksadk/plugins/dsh_dependency_policy.py +41 -0
  549. ksadk/plugins/dsh_home.py +140 -0
  550. ksadk/plugins/dsh_teams.py +105 -0
  551. ksadk/plugins/dsh_toolchain.py +92 -21
  552. ksadk/plugins/execution_host.py +107 -0
  553. ksadk/plugins/host.py +73 -5
  554. ksadk/plugins/kernel_adapter.py +44 -1
  555. ksadk/plugins/memory_provider.py +180 -0
  556. ksadk/plugins/providers/_process_group.py +50 -0
  557. ksadk/plugins/providers/bundles/dsh-channels-client/client.js +19 -0
  558. ksadk/plugins/providers/bundles/dsh-channels-client/cordis.patch.yml +3 -0
  559. ksadk/plugins/providers/bundles/dsh-channels-client/index.mjs +7 -0
  560. ksadk/plugins/providers/bundles/dsh-channels-client/package.json +12 -0
  561. ksadk/plugins/providers/bundles/dsh-knowledge/index.mjs +24 -2
  562. ksadk/plugins/providers/bundles/dsh-knowledge/package.json +1 -1
  563. ksadk/plugins/providers/bundles/dsh-memory/package.json +1 -1
  564. ksadk/plugins/providers/bundles/dsh-skill-center/package.json +1 -1
  565. ksadk/plugins/providers/bundles/dsh-teams-client/client.js +29 -0
  566. ksadk/plugins/providers/bundles/dsh-teams-client/cordis.patch.yml +3 -0
  567. ksadk/plugins/providers/bundles/dsh-teams-client/index.mjs +11 -0
  568. ksadk/plugins/providers/bundles/dsh-teams-client/package.json +35 -0
  569. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/cordis.patch.yml +3 -0
  570. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/index.mjs +13 -0
  571. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/package.json +27 -0
  572. ksadk/plugins/providers/bundles/dsh-teams-profile/cordis.patch.yml +3 -0
  573. ksadk/plugins/providers/bundles/dsh-teams-profile/index.mjs +12 -0
  574. ksadk/plugins/providers/bundles/dsh-teams-profile/package.json +26 -0
  575. ksadk/plugins/providers/bundles/dsh-teams-router/cordis.patch.yml +3 -0
  576. ksadk/plugins/providers/bundles/dsh-teams-router/index.mjs +16 -0
  577. ksadk/plugins/providers/bundles/dsh-teams-router/package.json +27 -0
  578. ksadk/plugins/providers/bundles/dsh-teams-tasks/cordis.patch.yml +3 -0
  579. ksadk/plugins/providers/bundles/dsh-teams-tasks/index.mjs +16 -0
  580. ksadk/plugins/providers/bundles/dsh-teams-tasks/package.json +27 -0
  581. ksadk/plugins/providers/bundles/dsh-teams-tools/cordis.patch.yml +3 -0
  582. ksadk/plugins/providers/bundles/dsh-teams-tools/index.mjs +25 -0
  583. ksadk/plugins/providers/bundles/dsh-teams-tools/package.json +29 -0
  584. ksadk/plugins/providers/bundles/dsh-teams-tools/tools.json +235 -0
  585. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +1 -1
  586. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/index.mjs +51 -1
  587. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/package.json +2 -2
  588. ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/client.mjs +133 -0
  589. ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/index.mjs +14 -0
  590. ksadk/plugins/providers/bundles/ksadk-dsh-studio/client.js +27 -4
  591. ksadk/plugins/providers/bundles/ksadk-dsh-studio/index.mjs +9 -5
  592. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +1 -1
  593. ksadk/plugins/providers/codex.py +52 -40
  594. ksadk/plugins/providers/codex_native.py +20 -0
  595. ksadk/plugins/providers/codex_skills.py +46 -0
  596. ksadk/plugins/providers/dsh_capabilities.py +49 -143
  597. ksadk/plugins/providers/dsh_connector_lease.py +225 -0
  598. ksadk/plugins/providers/dsh_mcp.py +163 -1
  599. ksadk/plugins/providers/harness.py +413 -52
  600. ksadk/plugins/providers/harness_context.py +61 -0
  601. ksadk/plugins/providers/harness_contributions.py +241 -0
  602. ksadk/plugins/providers/harness_delegation.py +670 -0
  603. ksadk/plugins/providers/harness_hooks.py +121 -0
  604. ksadk/plugins/providers/harness_managed.py +550 -0
  605. ksadk/plugins/providers/harness_tools.py +455 -0
  606. ksadk/plugins/providers/legacy_catalog.py +1 -1
  607. ksadk/plugins/providers/mcp_projection.py +100 -3
  608. ksadk/plugins/providers/platform_resources.py +16 -6
  609. ksadk/plugins/providers/shipped_dsh.py +4 -1
  610. ksadk/plugins/subagent_providers/codex.py +28 -6
  611. ksadk/plugins/teams/__init__.py +5 -0
  612. ksadk/plugins/teams/api.py +405 -0
  613. ksadk/plugins/teams/application.py +540 -0
  614. ksadk/plugins/teams/artifacts.py +60 -0
  615. ksadk/plugins/teams/build_artifacts.py +460 -0
  616. ksadk/plugins/teams/cloud_contract_fingerprints.py +22 -0
  617. ksadk/plugins/teams/cloud_contracts.py +928 -0
  618. ksadk/plugins/teams/cloud_permits.py +250 -0
  619. ksadk/plugins/teams/context.py +96 -0
  620. ksadk/plugins/teams/contracts.py +136 -0
  621. ksadk/plugins/teams/domain.py +1480 -0
  622. ksadk/plugins/teams/domain_values.py +24 -0
  623. ksadk/plugins/teams/effect_contracts.py +255 -0
  624. ksadk/plugins/teams/errors.py +13 -0
  625. ksadk/plugins/teams/execution_projection.py +255 -0
  626. ksadk/plugins/teams/leader_decisions.py +106 -0
  627. ksadk/plugins/teams/migration.py +311 -0
  628. ksadk/plugins/teams/migration_cli.py +322 -0
  629. ksadk/plugins/teams/postgres_store.py +352 -0
  630. ksadk/plugins/teams/release_contracts.py +60 -0
  631. ksadk/plugins/teams/runtime.py +853 -0
  632. ksadk/plugins/teams/source_projection.py +84 -0
  633. ksadk/plugins/teams/store.py +519 -0
  634. ksadk/plugins/teams/task_decisions.py +436 -0
  635. ksadk/plugins/teams/tool_service.py +152 -0
  636. ksadk/plugins/teams/transport.py +96 -0
  637. ksadk/plugins/teams/workspaces.py +255 -0
  638. ksadk/resource_runtime/skill_runtime.py +3 -0
  639. ksadk/resource_runtime/socket_server.py +11 -0
  640. ksadk/resource_runtime/worker.py +28 -4
  641. ksadk/runners/_adk_a2a_stream.py +122 -0
  642. ksadk/runners/_adk_canonical_stream.py +474 -0
  643. ksadk/runners/_langgraph_canonical_streams.py +527 -0
  644. ksadk/runners/_langgraph_runner_streams.py +83 -401
  645. ksadk/runners/_langgraph_state_reader.py +106 -0
  646. ksadk/runners/_langgraph_tool_projection.py +86 -0
  647. ksadk/runners/_langgraph_tools_transformer.py +27 -0
  648. ksadk/runners/_session_identity.py +285 -0
  649. ksadk/runners/adk_runner.py +220 -350
  650. ksadk/runners/base_runner.py +6 -5
  651. ksadk/runners/langgraph_runner.py +208 -195
  652. ksadk/runners/patch_langchain.py +47 -0
  653. ksadk/runners/usage_accumulator.py +11 -3
  654. ksadk/runners/utils/loader.py +28 -14
  655. ksadk/runtime/_runner_adapter/a2ui_surface.py +145 -0
  656. ksadk/runtime/_runner_adapter/gateway_approvals.py +93 -0
  657. ksadk/runtime/_runner_adapter/stream_mapping.py +134 -129
  658. ksadk/runtime/adapter.py +40 -3
  659. ksadk/runtime/conversation_execution.py +29 -3
  660. ksadk/runtime/executor.py +105 -7
  661. ksadk/runtime/factory.py +45 -3
  662. ksadk/runtime/framework_adapters.py +184 -10
  663. ksadk/runtime/hosted_finalizer.py +4 -6
  664. ksadk/runtime/launch.py +2 -0
  665. ksadk/runtime/preprocessing.py +11 -3
  666. ksadk/runtime/runner_adapter.py +160 -21
  667. ksadk/runtime/skill_eval_result.py +36 -0
  668. ksadk/runtime/timing.py +46 -0
  669. ksadk/runtime/usage.py +49 -18
  670. ksadk/runtime_context.py +105 -1
  671. ksadk/sandbox/backends/e2b.py +272 -24
  672. ksadk/sandbox/backends/local_process.py +73 -39
  673. ksadk/sandbox/base.py +62 -1
  674. ksadk/sandbox/diagnostics.py +82 -0
  675. ksadk/sandbox/e2b_connection.py +9 -1
  676. ksadk/sandbox/local_controls.py +776 -0
  677. ksadk/sandbox/local_launcher.py +124 -0
  678. ksadk/scheduler/dispatcher.py +1 -1
  679. ksadk/server/factory.py +178 -25
  680. ksadk/server/history_deadline.py +158 -0
  681. ksadk/server/history_page_cache.py +129 -0
  682. ksadk/server/invocation_identity.py +118 -0
  683. ksadk/server/routes/checkpoint_audit.py +88 -0
  684. ksadk/server/routes/checkpoint_listing.py +294 -0
  685. ksadk/server/routes/checkpoint_resolution.py +73 -20
  686. ksadk/server/routes/common.py +32 -3
  687. ksadk/server/routes/control.py +112 -9
  688. ksadk/server/routes/dependencies.py +2 -1
  689. ksadk/server/routes/display_projection.py +154 -0
  690. ksadk/server/routes/kernel_ingress.py +26 -3
  691. ksadk/server/routes/misc.py +331 -33
  692. ksadk/server/routes/models.py +28 -4
  693. ksadk/server/routes/openai_compat.py +82 -13
  694. ksadk/server/routes/projection.py +159 -216
  695. ksadk/server/routes/run.py +112 -37
  696. ksadk/server/routes/sessions.py +493 -358
  697. ksadk/server/routes/streaming.py +68 -15
  698. ksadk/server/routes/tool_receipt_claim_routes.py +107 -0
  699. ksadk/server/routes/workspace.py +135 -23
  700. ksadk/server/static/assets/{ArtifactsPanel-CGbWlMNh.js → ArtifactsPanel-CILaW-fC.js} +1 -1
  701. ksadk/server/static/assets/{CodeBlock-b4C7upmO.js → CodeBlock-BVrNVtJC.js} +10 -7
  702. ksadk/server/static/assets/MathMessageMarkdown-Dp7c4wWD.js +8 -0
  703. ksadk/server/static/assets/{MermaidBlock-BEBN4Ksz.js → MermaidBlock-Dxop8Rd7.js} +9 -9
  704. ksadk/server/static/assets/{NativeTerminalPanel-zvL-WpAn.js → NativeTerminalPanel-Cjjz8B30.js} +1 -1
  705. ksadk/server/static/assets/{abnfDiagram-VCTEODGH-BYafW7Za.js → abnfDiagram-VCTEODGH-ckWDmOzw.js} +1 -1
  706. ksadk/server/static/assets/{arc-bTohInsS.js → arc-DqxJede-.js} +1 -1
  707. ksadk/server/static/assets/architecture-7GRP2DOG-pn_DuLy6.js +1 -0
  708. ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-Ch5Lbzsi.js → architectureDiagram-5GKGNRK7-SEQA4bVi.js} +1 -1
  709. ksadk/server/static/assets/{blockDiagram-NRAW4CY4-kqF4MDOG.js → blockDiagram-NRAW4CY4-pM0B-6NJ.js} +1 -1
  710. ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-8uSRIODv.js → c4Diagram-UCG6FXSJ-m2tuCNVq.js} +1 -1
  711. ksadk/server/static/assets/channel-fySsnkft.js +1 -0
  712. ksadk/server/static/assets/chunk-2Q5K7J3B-BheGxKRQ.js +1 -0
  713. ksadk/server/static/assets/{chunk-5VM5RSS4-DkhehZDr.js → chunk-5VM5RSS4-GJHDr9Ww.js} +1 -1
  714. ksadk/server/static/assets/{chunk-F27PBJKO-BdXhtPe9.js → chunk-F27PBJKO-CbAN45AV.js} +1 -1
  715. ksadk/server/static/assets/{chunk-G27WJ6UU-DPTpvpwi.js → chunk-G27WJ6UU-D5ngbbzF.js} +1 -1
  716. ksadk/server/static/assets/{chunk-JWPE2WC7-Pqoh38el.js → chunk-JWPE2WC7-Cc5IwuYC.js} +1 -1
  717. ksadk/server/static/assets/{chunk-LCL6LL3I-DeU00qC-.js → chunk-LCL6LL3I-CprgDDdJ.js} +1 -1
  718. ksadk/server/static/assets/{chunk-POPQ4Y6H-CzxScFkr.js → chunk-POPQ4Y6H-CDZhSy82.js} +1 -1
  719. ksadk/server/static/assets/{chunk-RHFEMEQ7-DpqMx-ux.js → chunk-RHFEMEQ7-CZchUNro.js} +1 -1
  720. ksadk/server/static/assets/{chunk-SVP7TREG-C6t0SypQ.js → chunk-SVP7TREG-BVruqk6T.js} +1 -1
  721. ksadk/server/static/assets/{chunk-XXDRQBXY-DVaeafZk.js → chunk-XXDRQBXY-4ld9SxPN.js} +1 -1
  722. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DSfLtNYm.js +1 -0
  723. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DSfLtNYm.js +1 -0
  724. ksadk/server/static/assets/{cose-bilkent-JH36ORCC-Bf9D3tzJ.js → cose-bilkent-JH36ORCC-Dmyyk_Vp.js} +1 -1
  725. ksadk/server/static/assets/cynefin-OW5HDTMX-PFWIGtvA.js +1 -0
  726. ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-B9cY3iTz.js → cynefinDiagram-5FMLGOSQ-BIyq3EJb.js} +1 -1
  727. ksadk/server/static/assets/{dagre-3AP2YEHR-CSwlx3CB.js → dagre-3AP2YEHR-eBw08Qdk.js} +1 -1
  728. ksadk/server/static/assets/{dagre-UBpgon5q.js → dagre-DfvnEf45.js} +1 -1
  729. ksadk/server/static/assets/{diagram-S7CK7UJ4-C9akhN9a.js → diagram-S7CK7UJ4-CUSrE3Mg.js} +1 -1
  730. ksadk/server/static/assets/{diagram-UQ7AKVKN-CgedhTPI.js → diagram-UQ7AKVKN-Dq5Zw2gy.js} +1 -1
  731. ksadk/server/static/assets/{diagram-VSXAHHWV-Cjucz5BY.js → diagram-VSXAHHWV-DyUHfNwT.js} +1 -1
  732. ksadk/server/static/assets/{diagram-VX7I27RA-Dd4Vleyn.js → diagram-VX7I27RA-CREmM89O.js} +1 -1
  733. ksadk/server/static/assets/{diagram-Z3DM3KII-CZ2jGtYW.js → diagram-Z3DM3KII-Dw9zhHUK.js} +1 -1
  734. ksadk/server/static/assets/{dist-Bj730Ib4.js → dist-0uJ4tdx6.js} +1 -1
  735. ksadk/server/static/assets/{dist-BnaTiyzN.js → dist-AEv-vXf-.js} +1 -1
  736. ksadk/server/static/assets/{dist-BgV_owcB.js → dist-B49GzZDb.js} +1 -1
  737. ksadk/server/static/assets/{dist-D_ctSS2i.js → dist-B8Myiet9.js} +1 -1
  738. ksadk/server/static/assets/{dist-ufY3F5p6.js → dist-BBsYarrI.js} +2 -2
  739. ksadk/server/static/assets/{dist-ClYUxfeK.js → dist-BQMrUwdI.js} +1 -1
  740. ksadk/server/static/assets/{dist-CFzsn6BG.js → dist-BX_-FENW.js} +1 -1
  741. ksadk/server/static/assets/{dist-B3FR1SKK.js → dist-CD4L6ymH.js} +1 -1
  742. ksadk/server/static/assets/{dist-Cf125P_m.js → dist-CHpW1BHj.js} +1 -1
  743. ksadk/server/static/assets/{dist-DVCOWtha.js → dist-CVPau3tn.js} +1 -1
  744. ksadk/server/static/assets/{dist-DFD6Qa6T.js → dist-C_qdxcSH.js} +1 -1
  745. ksadk/server/static/assets/{dist-BIr4DSRq.js → dist-Co68Apq4.js} +1 -1
  746. ksadk/server/static/assets/{dist-DiixqL0J.js → dist-Cp2PaOMY.js} +1 -1
  747. ksadk/server/static/assets/{dist-fBO2v5Kt.js → dist-CuR5Bgme.js} +1 -1
  748. ksadk/server/static/assets/{dist-DoVSwoDb.js → dist-DDSyLEOX.js} +1 -1
  749. ksadk/server/static/assets/{dist-C5_ZAZsG.js → dist-DUxO0R99.js} +1 -1
  750. ksadk/server/static/assets/{dist-C1NMAY5a.js → dist-QzbGrED_.js} +1 -1
  751. ksadk/server/static/assets/dist-bupkyEBZ.js +1 -0
  752. ksadk/server/static/assets/{dist-DvFK7p5S.js → dist-cxTGfdqx.js} +1 -1
  753. ksadk/server/static/assets/{dist-CB9iJrvg.js → dist-hL5p1til.js} +1 -1
  754. ksadk/server/static/assets/{dist-btbmmX5O.js → dist-lOy23y43.js} +1 -1
  755. ksadk/server/static/assets/{dist-QfkEHOlb.js → dist-pq-nD33R.js} +1 -1
  756. ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Ci8NqqZu.js → ebnfDiagram-PWID7BFC-BS5LdNoR.js} +1 -1
  757. ksadk/server/static/assets/{erDiagram-SSCWMZ5O-CCIOhLZG.js → erDiagram-SSCWMZ5O-Bv1QtiY-.js} +1 -1
  758. ksadk/server/static/assets/eventmodeling-NTZA5JFV-DsM4upQX.js +1 -0
  759. ksadk/server/static/assets/flowDiagram-A5DVABFB-D5NA16k9.js +1 -0
  760. ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-Cp_r9Hb6.js → ganttDiagram-EL5Y4UJY-musi-e0G.js} +1 -1
  761. ksadk/server/static/assets/gitGraph-4MIJSDKK-DUCVz-ih.js +1 -0
  762. ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-Dlhc8Wr8.js → gitGraphDiagram-WWUBYQGX-DH37EGkG.js} +1 -1
  763. ksadk/server/static/assets/index-CZdkAvsv.js +244 -0
  764. ksadk/server/static/assets/index-CvXdHgF3.css +2 -0
  765. ksadk/server/static/assets/info-A6RAGUB7-rw0ZNHZp.js +1 -0
  766. ksadk/server/static/assets/{infoDiagram-RXCK75RN-CO0rRTRA.js → infoDiagram-RXCK75RN-C1kEtv2y.js} +1 -1
  767. ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-CHiaedSS.js → ishikawaDiagram-5VMMS53U-BCNPyMka.js} +1 -1
  768. ksadk/server/static/assets/{journeyDiagram-EYS64GPL-CHcCIB09.js → journeyDiagram-EYS64GPL--cDbKnjP.js} +1 -1
  769. ksadk/server/static/assets/{kanban-definition-3QL26DDD-CJ_ebBZf.js → kanban-definition-3QL26DDD-R1rAVw2f.js} +1 -1
  770. ksadk/server/static/assets/{katex-B4weu383.js → katex-BT-X-V7U.js} +1 -1
  771. ksadk/server/static/assets/{linear-V-MgYHW1.js → linear-DTR631iG.js} +1 -1
  772. ksadk/server/static/assets/{mermaid-parser.core-CBijEX_c.js → mermaid-parser.core-DfFzJhX8.js} +2 -2
  773. ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-Bc-pQvpp.js → mindmap-definition-FBJOCRG2-D4by_rmk.js} +1 -1
  774. ksadk/server/static/assets/packet-AYTQ26CC-Ca4Hvki9.js +1 -0
  775. ksadk/server/static/assets/{pegDiagram-XKGWAZYB-D7genGLl.js → pegDiagram-XKGWAZYB-xWXPEh0c.js} +1 -1
  776. ksadk/server/static/assets/pie-WAS4IAKB-hwiz6fbw.js +1 -0
  777. ksadk/server/static/assets/{pieDiagram-E7YTZNPT-Bxj32zwD.js → pieDiagram-E7YTZNPT-BudBDlCI.js} +1 -1
  778. ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BURv2mGh.js → quadrantDiagram-AXDQQJYC-DXZ0PCJF.js} +1 -1
  779. ksadk/server/static/assets/radar-RG4KPBEZ-BR1oZXLM.js +1 -0
  780. ksadk/server/static/assets/railroad-74A4TZTK-DfgEISfX.js +1 -0
  781. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-CWOC3rdk.js +1 -0
  782. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CiRvHCLY.js +1 -0
  783. ksadk/server/static/assets/railroad-peg-WCYAUIDC-DSDDyJ2f.js +1 -0
  784. ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-CCsh3avW.js → railroadDiagram-O6MQD6OU-BdAYRYB8.js} +1 -1
  785. ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-_FRRBKEd.js → requirementDiagram-EFPCY7ZU-CBBmaL70.js} +1 -1
  786. ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-C-gr2o9M.js → sankeyDiagram-P5KCCOFB-Doc3rdwp.js} +1 -1
  787. ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-D97NmfKg.js → sequenceDiagram-WJ2MYXX4-0J8R8hWW.js} +1 -1
  788. ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-DjrPC5FX.js → sizeCapture-X5ZJPWSS-CfFLlWsK.js} +1 -1
  789. ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DzMNg1hh.js → stateDiagram-HBIQ2CUA-jBwkjtTy.js} +1 -1
  790. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-Cw1fPb5w.js +1 -0
  791. ksadk/server/static/assets/{swimlanes-XN3QIQJK-Cssf_CsU.js → swimlanes-XN3QIQJK-B0WxTQf8.js} +1 -1
  792. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-Cc3JMBAF.js +8 -0
  793. ksadk/server/static/assets/{timeline-definition-24CTP7MA-BWTHzyS1.js → timeline-definition-24CTP7MA-BfCT_75A.js} +1 -1
  794. ksadk/server/static/assets/treeView-Q6P3EWNA-C05SJHxo.js +1 -0
  795. ksadk/server/static/assets/treemap-WGGIJYW6-BqNLAhc3.js +1 -0
  796. ksadk/server/static/assets/{vennDiagram-4TSXK5OY-BSKgJsmR.js → vennDiagram-4TSXK5OY-9gX1VMDC.js} +1 -1
  797. ksadk/server/static/assets/wardley-WFR3VGLG-DokNktqB.js +1 -0
  798. ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-eflHNoaZ.js → wardleyDiagram-VM6X3IG4-sEs_BP6O.js} +1 -1
  799. ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-eYqrQ-v4.js → xychartDiagram-S5SC5T6Z-Ll4jvUN9.js} +1 -1
  800. ksadk/server/static/index.html +2 -2
  801. ksadk/server/terminal_sessions.py +34 -2
  802. ksadk/sessions/__init__.py +8 -1
  803. ksadk/sessions/_checkpoint_resume_audit.py +44 -0
  804. ksadk/sessions/_in_memory_query.py +112 -0
  805. ksadk/sessions/_local_service_sync.py +157 -156
  806. ksadk/sessions/_local_state_sync.py +152 -0
  807. ksadk/sessions/_local_tables.py +2 -0
  808. ksadk/sessions/_postgres_checkpoint_read.py +323 -0
  809. ksadk/sessions/_postgres_checkpoint_stats.py +330 -0
  810. ksadk/sessions/_postgres_history_read.py +678 -0
  811. ksadk/sessions/_postgres_schema.py +44 -2
  812. ksadk/sessions/_postgres_tables.py +2 -0
  813. ksadk/sessions/_postgres_tool_receipts.py +169 -0
  814. ksadk/sessions/_resilient_read.py +630 -0
  815. ksadk/sessions/_tool_receipt_sync.py +119 -0
  816. ksadk/sessions/agent_identity.py +16 -0
  817. ksadk/sessions/base.py +278 -0
  818. ksadk/sessions/db_budget.py +332 -0
  819. ksadk/sessions/errors.py +12 -0
  820. ksadk/sessions/history_budget.py +36 -0
  821. ksadk/sessions/history_identity.py +27 -0
  822. ksadk/sessions/in_memory.py +376 -113
  823. ksadk/sessions/invocation_identity.py +235 -0
  824. ksadk/sessions/local_service.py +345 -0
  825. ksadk/sessions/postgres_service.py +515 -549
  826. ksadk/sessions/resilience.py +21 -14
  827. ksadk/sessions/resilient.py +611 -504
  828. ksadk/skills/__init__.py +26 -0
  829. ksadk/skills/events.py +464 -0
  830. ksadk/skills/loader.py +44 -2
  831. ksadk/skills/manifest_cache.py +263 -0
  832. ksadk/skills/mcp_server/__init__.py +0 -0
  833. ksadk/skills/mcp_server/_registry.py +33 -0
  834. ksadk/skills/mcp_server/_routing.py +26 -0
  835. ksadk/skills/mcp_server/_workspace_files.py +160 -0
  836. ksadk/skills/mcp_server/register.py +415 -0
  837. ksadk/skills/mcp_server/server.py +218 -0
  838. ksadk/skills/models.py +11 -0
  839. ksadk/skills/observability.py +132 -0
  840. ksadk/skills/package_store.py +75 -2
  841. ksadk/skills/runtime/agent.py +38 -5
  842. ksadk/skills/runtime/artifacts.py +87 -0
  843. ksadk/skills/runtime/backends/e2b.py +400 -56
  844. ksadk/skills/runtime/backends/local.py +621 -119
  845. ksadk/skills/runtime/base.py +78 -7
  846. ksadk/skills/runtime/executor.py +266 -1
  847. ksadk/skills/runtime/loader.py +108 -3
  848. ksadk/skills/runtime/request.py +18 -0
  849. ksadk/skills/service_client.py +6 -1
  850. ksadk/skills/tool_defs.py +108 -3
  851. ksadk/studio/a2a_bindings.py +407 -0
  852. ksadk/studio/a2a_orchestration.py +178 -0
  853. ksadk/studio/active_run_output.py +92 -0
  854. ksadk/studio/activity_records.py +542 -0
  855. ksadk/studio/agent_configuration.py +173 -0
  856. ksadk/studio/api.py +784 -419
  857. ksadk/studio/api_a2a_routes.py +93 -0
  858. ksadk/studio/api_catalog_routes.py +13 -0
  859. ksadk/studio/api_cloud_chat_routes.py +382 -0
  860. ksadk/studio/api_contracts.py +31 -2
  861. ksadk/studio/api_plugin_routes.py +39 -12
  862. ksadk/studio/api_resource_connections.py +79 -41
  863. ksadk/studio/attachment_store.py +14 -2
  864. ksadk/studio/authoring.py +12 -6
  865. ksadk/studio/authoring_coordinator.py +4 -2
  866. ksadk/studio/build_resolution.py +117 -0
  867. ksadk/studio/build_singleflight.py +37 -0
  868. ksadk/studio/builder.py +32 -2
  869. ksadk/studio/capabilities.py +31 -9
  870. ksadk/studio/channel_connections.py +333 -0
  871. ksadk/studio/cloud.py +113 -387
  872. ksadk/studio/cloud_gateways.py +424 -0
  873. ksadk/studio/cloud_shared_web.py +82 -2
  874. ksadk/studio/codex_agent_service.py +19 -2
  875. ksadk/studio/codex_builder.py +1 -1
  876. ksadk/studio/codex_manifest.py +5 -1
  877. ksadk/studio/codex_provider_build.py +65 -0
  878. ksadk/studio/codex_run.py +6 -1
  879. ksadk/studio/compiler.py +112 -6
  880. ksadk/studio/configuration.py +182 -0
  881. ksadk/studio/contract_base.py +19 -0
  882. ksadk/studio/contracts.py +260 -202
  883. ksadk/studio/dsh_application.py +183 -27
  884. ksadk/studio/dsh_capability_service.py +302 -189
  885. ksadk/studio/dsh_lifecycle.py +58 -0
  886. ksadk/studio/dsh_mcp_transport.py +191 -0
  887. ksadk/studio/dsh_provider_registration.py +55 -16
  888. ksadk/studio/entry.py +53 -0
  889. ksadk/studio/event_store.py +53 -5
  890. ksadk/studio/execution_host.py +598 -0
  891. ksadk/studio/framework_run.py +50 -10
  892. ksadk/studio/hosted_kernel.py +1 -1
  893. ksadk/studio/interaction_submission.py +265 -0
  894. ksadk/studio/kernel_registry.py +429 -0
  895. ksadk/studio/kernel_sqlite_migration.py +181 -0
  896. ksadk/studio/lifecycle_status.py +43 -0
  897. ksadk/studio/model_buffered_wire.py +227 -0
  898. ksadk/studio/model_chat_tools.py +143 -0
  899. ksadk/studio/model_client.py +1939 -345
  900. ksadk/studio/model_content_stream.py +129 -0
  901. ksadk/studio/model_credentials.py +196 -0
  902. ksadk/studio/model_profile_service.py +89 -40
  903. ksadk/studio/model_request_projection.py +284 -0
  904. ksadk/studio/model_responses_content.py +296 -0
  905. ksadk/studio/model_usage.py +109 -0
  906. ksadk/studio/model_wire_failure.py +110 -0
  907. ksadk/studio/model_wire_identity.py +138 -0
  908. ksadk/studio/model_wire_snapshot.py +45 -0
  909. ksadk/studio/model_wire_tools.py +283 -0
  910. ksadk/studio/native_acceptance.py +68 -0
  911. ksadk/studio/native_approval.py +111 -0
  912. ksadk/studio/native_browser_port.py +861 -0
  913. ksadk/studio/native_child_mcp.py +87 -0
  914. ksadk/studio/native_child_publication.py +68 -0
  915. ksadk/studio/native_child_skills.py +72 -0
  916. ksadk/studio/native_child_workspace.py +176 -0
  917. ksadk/studio/native_command_policy.py +53 -0
  918. ksadk/studio/native_context_port.py +326 -0
  919. ksadk/studio/native_context_registration.py +54 -0
  920. ksadk/studio/native_document_input.py +88 -0
  921. ksadk/studio/native_documents.py +172 -0
  922. ksadk/studio/native_final_shape.py +852 -0
  923. ksadk/studio/native_gateway_stream.py +69 -0
  924. ksadk/studio/native_hook_port.py +145 -0
  925. ksadk/studio/native_hook_registration.py +70 -0
  926. ksadk/studio/native_inventory_disclosure.py +223 -0
  927. ksadk/studio/native_kernel_admission.py +84 -0
  928. ksadk/studio/native_language_diagnostics.py +320 -0
  929. ksadk/studio/native_mcp_disclosure.py +198 -0
  930. ksadk/studio/native_mcp_tool_port.py +219 -0
  931. ksadk/studio/native_memory_acceptance.py +362 -0
  932. ksadk/studio/native_memory_context.py +266 -0
  933. ksadk/studio/native_memory_flush.py +174 -0
  934. ksadk/studio/native_memory_sources.py +80 -0
  935. ksadk/studio/native_memory_tool_port.py +360 -0
  936. ksadk/studio/native_memory_turn.py +183 -0
  937. ksadk/studio/native_model_port.py +765 -0
  938. ksadk/studio/native_model_recovery.py +126 -0
  939. ksadk/studio/native_options.py +75 -0
  940. ksadk/studio/native_output_fields.py +58 -0
  941. ksadk/studio/native_planning.py +156 -0
  942. ksadk/studio/native_product.py +988 -0
  943. ksadk/studio/native_read_budget.py +426 -0
  944. ksadk/studio/native_recovery.py +171 -0
  945. ksadk/studio/native_scope.py +35 -0
  946. ksadk/studio/native_session_context_port.py +141 -0
  947. ksadk/studio/native_session_engine.py +14 -0
  948. ksadk/studio/native_skill_source.py +74 -0
  949. ksadk/studio/native_subagent_port.py +700 -0
  950. ksadk/studio/native_terminal_recovery.py +95 -0
  951. ksadk/studio/native_test_action.py +610 -0
  952. ksadk/studio/native_tool_port.py +477 -0
  953. ksadk/studio/native_user_input.py +60 -0
  954. ksadk/studio/native_verification_presentation.py +65 -0
  955. ksadk/studio/native_workspace_diagnostics.py +183 -0
  956. ksadk/studio/native_workspace_port.py +43 -0
  957. ksadk/studio/operations.py +34 -9
  958. ksadk/studio/plugin_composition.py +39 -12
  959. ksadk/studio/plugin_kernel_adapter.py +89 -0
  960. ksadk/studio/plugin_lifecycle.py +80 -0
  961. ksadk/studio/plugin_native_activation.py +163 -0
  962. ksadk/studio/plugin_runtime.py +832 -99
  963. ksadk/studio/plugin_runtime_admission.py +57 -0
  964. ksadk/studio/presentation.py +92 -0
  965. ksadk/studio/provider_recovery.py +59 -0
  966. ksadk/studio/public_output_store.py +69 -0
  967. ksadk/studio/recovery_page.py +70 -0
  968. ksadk/studio/resource_authority.py +69 -34
  969. ksadk/studio/resource_catalog.py +14 -12
  970. ksadk/studio/run_activity.py +848 -0
  971. ksadk/studio/run_controls.py +185 -0
  972. ksadk/studio/run_documents.py +127 -0
  973. ksadk/studio/run_service.py +1220 -335
  974. ksadk/studio/run_submission_receipts.py +68 -0
  975. ksadk/studio/runtime_catalog.py +73 -11
  976. ksadk/studio/runtime_prewarm.py +22 -0
  977. ksadk/studio/runtime_source.py +1 -1
  978. ksadk/studio/scheduler_assistant.py +4 -1
  979. ksadk/studio/scheduler_runtime.py +32 -289
  980. ksadk/studio/service.py +615 -246
  981. ksadk/studio/service_settings.py +229 -0
  982. ksadk/studio/shared_web.py +518 -181
  983. ksadk/studio/shared_web_response_events.py +180 -0
  984. ksadk/studio/shared_web_run_state.py +133 -0
  985. ksadk/studio/skill_discovery.py +305 -3
  986. ksadk/studio/static/assets/ChannelsPage-xX5HlMId.js +8 -0
  987. ksadk/studio/static/assets/CodeBlock-DMIf4h33-CkufcPYo.js +44 -0
  988. ksadk/studio/static/assets/ExecutionTree-v3HexvPo-Bk8wzGSy.js +1 -0
  989. ksadk/studio/static/assets/MathMessageMarkdown-BesSeCYl-DYG1Wzbx.js +8 -0
  990. ksadk/studio/static/assets/MermaidBlock-H_0qzQDf-BdXtxug_.js +11 -0
  991. ksadk/studio/static/assets/MessageMarkdown-CwMTvgFj-BeyLyQHu.js +59 -0
  992. ksadk/studio/static/assets/PrismRenderer-5pK6tUdb.js +3 -0
  993. ksadk/studio/static/assets/TeamsPage-CCaaGaVp.js +3 -0
  994. ksadk/studio/static/assets/abnfDiagram-VCTEODGH-Bnb1O3tA.js +1 -0
  995. ksadk/studio/static/assets/architecture-7GRP2DOG-BMOPAkUC.js +1 -0
  996. ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-5Zn1gqJv.js +36 -0
  997. ksadk/studio/static/assets/{blockDiagram-I7D4REHJ-CFmPl1de.js → blockDiagram-I7D4REHJ-CLB4L11u.js} +6 -6
  998. ksadk/studio/static/assets/{c4Diagram-7LVT6UL2-BRrGu8qt.js → c4Diagram-7LVT6UL2-BOma_Z_F.js} +7 -7
  999. ksadk/studio/static/assets/channel-r8itPEik.js +1 -0
  1000. ksadk/studio/static/assets/chunk-2E4U76K2-BFf_huql.js +2 -0
  1001. ksadk/studio/static/assets/chunk-2ZTRR5NV-C2qZJila.js +2 -0
  1002. ksadk/studio/static/assets/chunk-3Z5EZCMW-DMniOlPd.js +1 -0
  1003. ksadk/studio/static/assets/{chunk-4HAMMTFA-D220vUtq.js → chunk-4HAMMTFA-DVtDP4rm.js} +7 -7
  1004. ksadk/studio/static/assets/chunk-5V3GS4D5-i-ZDOEnP.js +1 -0
  1005. ksadk/studio/static/assets/chunk-6AZGARVD-BB6FqMl-.js +1 -0
  1006. ksadk/studio/static/assets/chunk-6EIED4P4-DdUJkP0f.js +1 -0
  1007. ksadk/studio/static/assets/chunk-6K3QC6MW-CqVsVn-y.js +1 -0
  1008. ksadk/studio/static/assets/chunk-6TQVIW2G-DKlLbNIP.js +1 -0
  1009. ksadk/studio/static/assets/chunk-747NJXEK-BFkZPlab.js +2 -0
  1010. ksadk/studio/static/assets/chunk-75Z2AOVW-BZNhM4vB.js +2 -0
  1011. ksadk/studio/static/assets/chunk-CLGD4ZFX-D-GTtyKs.js +1 -0
  1012. ksadk/studio/static/assets/{chunk-DU6HZSFF-C4DtVpCu.js → chunk-DU6HZSFF-CpEZCUgg.js} +1 -1
  1013. ksadk/studio/static/assets/chunk-F27PBJKO-D7pvKf0C.js +1 -0
  1014. ksadk/studio/static/assets/{chunk-GMAD6QVW-DiQA-u2N.js → chunk-GMAD6QVW-DE_8H717.js} +6 -6
  1015. ksadk/studio/static/assets/chunk-GVQU2GXP-BfEB0lNv.js +1 -0
  1016. ksadk/studio/static/assets/chunk-I5DQTOEV-Hm2LfiYI.js +1 -0
  1017. ksadk/studio/static/assets/chunk-ICYGCRZG-749o-pGJ.js +1 -0
  1018. ksadk/studio/static/assets/chunk-IH6LHLGP-CC6I9F3i.js +1 -0
  1019. ksadk/studio/static/assets/{chunk-IMKFNOWR-BSlbjT_b.js → chunk-IMKFNOWR-D38axYOR.js} +5 -5
  1020. ksadk/studio/static/assets/chunk-KI3K4JFJ-Rma-0k_M.js +1 -0
  1021. ksadk/studio/static/assets/chunk-L3NEJ4N5-Dq5xn7aL.js +1 -0
  1022. ksadk/studio/static/assets/chunk-LNGE3PJU-DWpGwHrW.js +34 -0
  1023. ksadk/studio/static/assets/{chunk-OSK3NFVY-M2m6G3j4.js → chunk-OSK3NFVY-DiCQjx9b.js} +5 -5
  1024. ksadk/studio/static/assets/chunk-OUJLGHUK-D0j2JRSg.js +2 -0
  1025. ksadk/studio/static/assets/{chunk-P2QGCYS3-DezaQxiK.js → chunk-P2QGCYS3-VdhKmfYq.js} +1 -1
  1026. ksadk/studio/static/assets/chunk-POPQ4Y6H-C1_rzO5M.js +1 -0
  1027. ksadk/studio/static/assets/chunk-PWAF6VOD-B45HvflI.js +1 -0
  1028. ksadk/studio/static/assets/{chunk-SHT3W25Y-DAyIVp5H.js → chunk-SHT3W25Y-DsCFnRVE.js} +9 -9
  1029. ksadk/studio/static/assets/{chunk-SVP7TREG-DulomOrS.js → chunk-SVP7TREG-DljApgRX.js} +1 -1
  1030. ksadk/studio/static/assets/{chunk-TICWLB2K-bHJYoflj.js → chunk-TICWLB2K-DWqRtyqa.js} +6 -6
  1031. ksadk/studio/static/assets/chunk-TLUHSLCS-DUeGgnD5.js +2 -0
  1032. ksadk/studio/static/assets/chunk-UY3FDG6J-CnmaAkG5.js +1 -0
  1033. ksadk/studio/static/assets/chunk-XHIXRSVI-BFQ6B8BE.js +1 -0
  1034. ksadk/studio/static/assets/chunk-XXDRQBXY-DqAYeljN.js +1 -0
  1035. ksadk/studio/static/assets/classDiagram-ZZMXUADV-DjeMNJmu.js +1 -0
  1036. ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-DjeMNJmu.js +1 -0
  1037. ksadk/studio/static/assets/clsx-DB0hHKMi.js +1 -0
  1038. ksadk/studio/static/assets/cose-bilkent-JH36ORCC-om8AY0Mc.js +1 -0
  1039. ksadk/studio/static/assets/createLucideIcon-DR0LGTP5.js +1 -0
  1040. ksadk/studio/static/assets/cynefin-OW5HDTMX-7tP7QfM3.js +1 -0
  1041. ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-6BrmK52S.js +62 -0
  1042. ksadk/studio/static/assets/dagre-GXQ25YYZ-buZBRlwz.js +4 -0
  1043. ksadk/studio/static/assets/{diagram-S7CK7UJ4-0BZ98mya.js → diagram-S7CK7UJ4-C6aH78c4.js} +3 -3
  1044. ksadk/studio/static/assets/diagram-UQ7AKVKN-DY_tQ6C_.js +41 -0
  1045. ksadk/studio/static/assets/diagram-VSXAHHWV-DX9p5cQT.js +3 -0
  1046. ksadk/studio/static/assets/diagram-VX7I27RA-DB2WSH8X.js +24 -0
  1047. ksadk/studio/static/assets/diagram-Z3DM3KII-Ce1BJwHE.js +24 -0
  1048. ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-Daukxwqf.js +1 -0
  1049. ksadk/studio/static/assets/{erDiagram-RLTQ6QDP-DLGry2Ap.js → erDiagram-RLTQ6QDP-CGthTMmJ.js} +6 -6
  1050. ksadk/studio/static/assets/eventmodeling-NTZA5JFV-BR2FzK4R.js +1 -0
  1051. ksadk/studio/static/assets/flowDiagram-HODETNUW-CZ1tEenp.js +1 -0
  1052. ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BfoAZW9X.js +292 -0
  1053. ksadk/studio/static/assets/gitGraph-4MIJSDKK-DYlXv_yJ.js +1 -0
  1054. ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-S985OIJF.js +106 -0
  1055. ksadk/studio/static/assets/iconfont-4HN3C9Pg.woff2 +0 -0
  1056. ksadk/studio/static/assets/index-C7DNDik2.css +2 -0
  1057. ksadk/studio/static/assets/index-Cy68gTlH.js +265 -0
  1058. ksadk/studio/static/assets/index.dom-BQenBtFm.js +1 -0
  1059. ksadk/studio/static/assets/info-A6RAGUB7-BlU-8_xu.js +1 -0
  1060. ksadk/studio/static/assets/infoDiagram-27XIBGKW-CO3W8tFn.js +2 -0
  1061. ksadk/studio/static/assets/{ishikawaDiagram-5VMMS53U-CljI2wyf.js → ishikawaDiagram-5VMMS53U-BUGEl8cO.js} +2 -2
  1062. ksadk/studio/static/assets/{journeyDiagram-3NMN7TZE-D50Fo2sL.js → journeyDiagram-3NMN7TZE-Ch2ZYxvr.js} +6 -6
  1063. ksadk/studio/static/assets/{kanban-definition-UXKFOSKX-DimfOTam.js → kanban-definition-UXKFOSKX-Da-Fp4vC.js} +3 -3
  1064. ksadk/studio/static/assets/kingsoft-cloud-BOcLiQ3e.ico +0 -0
  1065. ksadk/studio/static/assets/{line-CX5BOOUP.js → line-BDaeq300.js} +1 -1
  1066. ksadk/studio/static/assets/linear-DmucvK78.js +1 -0
  1067. ksadk/studio/static/assets/mermaid-parser.core-Cj5mYdBi.js +4 -0
  1068. ksadk/studio/static/assets/{mindmap-definition-YA3MSWOX-Bizzqy5R.js → mindmap-definition-YA3MSWOX-CvlsVQ38.js} +1 -1
  1069. ksadk/studio/static/assets/packet-AYTQ26CC-BSJEH9Lb.js +1 -0
  1070. ksadk/studio/static/assets/pegDiagram-XKGWAZYB-BuHuNgT5.js +1 -0
  1071. ksadk/studio/static/assets/pie-WAS4IAKB-qxv01Y8W.js +1 -0
  1072. ksadk/studio/static/assets/{pieDiagram-E7YTZNPT-B9mrFSDF.js → pieDiagram-E7YTZNPT-DlrWiSOv.js} +1 -1
  1073. ksadk/studio/static/assets/preload-helper-CO68xz6u.js +1 -0
  1074. ksadk/studio/static/assets/presentation-yYw7xK60-BmcZriri.js +1 -0
  1075. ksadk/studio/static/assets/{quadrantDiagram-AXDQQJYC-NzUfCbMR.js → quadrantDiagram-AXDQQJYC-BA3hpUZM.js} +6 -6
  1076. ksadk/studio/static/assets/radar-RG4KPBEZ-B6h6pWOs.js +1 -0
  1077. ksadk/studio/static/assets/railroad-74A4TZTK-CGtTWdGc.js +1 -0
  1078. ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-Cq7y22IA.js +1 -0
  1079. ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-V0aQVv9m.js +1 -0
  1080. ksadk/studio/static/assets/railroad-peg-WCYAUIDC-BQiIsX_p.js +1 -0
  1081. ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-BxpAazym.js +1 -0
  1082. ksadk/studio/static/assets/{requirementDiagram-BXWQKSXE-BF3d103U.js → requirementDiagram-BXWQKSXE-E-Ge0FcV.js} +1 -1
  1083. ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-CA6YKhQ3.js +40 -0
  1084. ksadk/studio/static/assets/{sequenceDiagram-WJ2MYXX4-Dh4wZtUm.js → sequenceDiagram-WJ2MYXX4-CcElXGg4.js} +9 -9
  1085. ksadk/studio/static/assets/src-B6xuSHsQ.js +1 -0
  1086. ksadk/studio/static/assets/stateDiagram-D77RDMKH-DdXZOFqY.js +1 -0
  1087. ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-CJbOM-QK.js +1 -0
  1088. ksadk/studio/static/assets/swimlanes-42K2YHIH-NtgEEHLY.js +1 -0
  1089. ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-D-AVlW8B.js +8 -0
  1090. ksadk/studio/static/assets/{timeline-definition-24CTP7MA-DxmSSjRB.js → timeline-definition-24CTP7MA-CIuYhNy3.js} +7 -7
  1091. ksadk/studio/static/assets/treeView-Q6P3EWNA-BTTYcvrF.js +1 -0
  1092. ksadk/studio/static/assets/treemap-WGGIJYW6-Cg7tyfH8.js +1 -0
  1093. ksadk/studio/static/assets/vennDiagram-4TSXK5OY-7HbApbrv.js +34 -0
  1094. ksadk/studio/static/assets/wardley-WFR3VGLG-C6N1rULY.js +1 -0
  1095. ksadk/studio/static/assets/{wardleyDiagram-VM6X3IG4-Ba1AOWoe.js → wardleyDiagram-VM6X3IG4-CMJiaedy.js} +1 -1
  1096. ksadk/studio/static/assets/{xychartDiagram-S5SC5T6Z-Cr5pt5Y-.js → xychartDiagram-S5SC5T6Z-BdgtMvW2.js} +6 -6
  1097. ksadk/studio/static/index.html +8 -2
  1098. ksadk/studio/teams_catalog.py +166 -0
  1099. ksadk/studio/teams_installation.py +217 -0
  1100. ksadk/studio/teams_node.py +549 -0
  1101. ksadk/studio/teams_node_factory.py +558 -0
  1102. ksadk/studio/teams_node_runner.py +72 -0
  1103. ksadk/studio/teams_node_v1.py +935 -0
  1104. ksadk/studio/teams_remote.py +496 -0
  1105. ksadk/studio/templates.py +41 -19
  1106. ksadk/studio/validator.py +13 -0
  1107. ksadk/studio/workspace.py +19 -4
  1108. ksadk/studio/workspace_plugins.py +138 -0
  1109. ksadk/studio/workspace_registry.py +286 -0
  1110. ksadk/tools/a2a_policy.py +5 -0
  1111. ksadk/tools/artifact_observation.py +53 -0
  1112. ksadk/tools/captured_source_artifact.py +68 -0
  1113. ksadk/tools/schema.py +34 -0
  1114. ksadk/toolsets/__init__.py +93 -88
  1115. ksadk/toolsets/a2a.py +89 -89
  1116. ksadk/toolsets/sandbox.py +377 -6
  1117. ksadk/toolsets/skill_management.py +315 -0
  1118. ksadk/toolsets/skills.py +446 -378
  1119. ksadk/toolsets/tool_selection.py +94 -0
  1120. ksadk/toolsets/workspace.py +344 -326
  1121. ksadk/toolsets/workspace_editing.py +291 -0
  1122. ksadk/toolsets/workspace_identity.py +15 -0
  1123. ksadk/toolsets/workspace_model_observation.py +104 -0
  1124. ksadk/toolsets/workspace_mutation.py +75 -0
  1125. ksadk/toolsets/workspace_read_budget.py +216 -0
  1126. ksadk/toolsets/workspace_search.py +192 -0
  1127. ksadk/toolsets/workspace_search_process.py +129 -0
  1128. ksadk/toolsets/workspace_state.py +6 -0
  1129. ksadk/version.py +1 -1
  1130. ksadk_runtime_common/http_headers.py +18 -0
  1131. ksadk_runtime_common/memory_backend/providers/lancedb.py +56 -2
  1132. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -148
  1133. ksadk_runtime_common/workspace_files/router.py +28 -14
  1134. agentengine_sdk_python-0.8.4.dist-info/RECORD +0 -1070
  1135. ksadk/server/static/assets/MathMessageMarkdown-DbdW5A4_.js +0 -8
  1136. ksadk/server/static/assets/architecture-7GRP2DOG-BW5UtSnx.js +0 -1
  1137. ksadk/server/static/assets/channel-CF5Up3hQ.js +0 -1
  1138. ksadk/server/static/assets/chunk-2Q5K7J3B-KkXX47WI.js +0 -1
  1139. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Cz81acRQ.js +0 -1
  1140. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Cz81acRQ.js +0 -1
  1141. ksadk/server/static/assets/cynefin-OW5HDTMX-DVWs8a2t.js +0 -1
  1142. ksadk/server/static/assets/dist-B2_CVgKv.js +0 -1
  1143. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CQqR2hg3.js +0 -1
  1144. ksadk/server/static/assets/flowDiagram-A5DVABFB-DDRRbGPm.js +0 -1
  1145. ksadk/server/static/assets/gitGraph-4MIJSDKK-fgWEf7UD.js +0 -1
  1146. ksadk/server/static/assets/index-DcTML1Up.css +0 -2
  1147. ksadk/server/static/assets/index-Dh2XHqPK.js +0 -244
  1148. ksadk/server/static/assets/info-A6RAGUB7-BUecLTdt.js +0 -1
  1149. ksadk/server/static/assets/packet-AYTQ26CC-BeW-btFv.js +0 -1
  1150. ksadk/server/static/assets/pie-WAS4IAKB-X0xh4SaT.js +0 -1
  1151. ksadk/server/static/assets/radar-RG4KPBEZ-DzRMSqjd.js +0 -1
  1152. ksadk/server/static/assets/railroad-74A4TZTK-Cc44icX-.js +0 -1
  1153. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-D6S4BdV-.js +0 -1
  1154. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-vD3aVE2o.js +0 -1
  1155. ksadk/server/static/assets/railroad-peg-WCYAUIDC-CYbW_b4T.js +0 -1
  1156. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BIcjKFhF.js +0 -1
  1157. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-tq3K3Ybl.js +0 -8
  1158. ksadk/server/static/assets/treeView-Q6P3EWNA-DBXt3GdU.js +0 -1
  1159. ksadk/server/static/assets/treemap-WGGIJYW6-DrCy4E_l.js +0 -1
  1160. ksadk/server/static/assets/wardley-WFR3VGLG-CefhOliM.js +0 -1
  1161. ksadk/studio/static/assets/CodeBlock-DCUUY0PL-BLjA7kbS.js +0 -41
  1162. ksadk/studio/static/assets/MathMessageMarkdown-CBcDrAw--CUYIXwgI.js +0 -8
  1163. ksadk/studio/static/assets/MermaidBlock-DBFTztfQ-BTYKWEbD.js +0 -44
  1164. ksadk/studio/static/assets/PrismRenderer-wiXRHURw.js +0 -3
  1165. ksadk/studio/static/assets/abnfDiagram-VCTEODGH-CLETGjKq.js +0 -1
  1166. ksadk/studio/static/assets/architecture-7GRP2DOG-hXMiO_AU.js +0 -1
  1167. ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-B5EQysk6.js +0 -36
  1168. ksadk/studio/static/assets/channel-BqO-gtEw.js +0 -1
  1169. ksadk/studio/static/assets/chunk-75Z2AOVW-C8lLv_qc.js +0 -2
  1170. ksadk/studio/static/assets/chunk-F27PBJKO-Cpw14SNG.js +0 -1
  1171. ksadk/studio/static/assets/chunk-GVQU2GXP-DUdNBUCi.js +0 -1
  1172. ksadk/studio/static/assets/chunk-L3NEJ4N5-Db8Ab22W.js +0 -1
  1173. ksadk/studio/static/assets/chunk-POPQ4Y6H-DOpufJ3t.js +0 -1
  1174. ksadk/studio/static/assets/chunk-PWAF6VOD-CfLb1gSc.js +0 -1
  1175. ksadk/studio/static/assets/chunk-XXDRQBXY-7PcdR2B1.js +0 -1
  1176. ksadk/studio/static/assets/classDiagram-ZZMXUADV-D_bW8JjA.js +0 -1
  1177. ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-D_bW8JjA.js +0 -1
  1178. ksadk/studio/static/assets/cose-bilkent-JH36ORCC-snL88wkX.js +0 -1
  1179. ksadk/studio/static/assets/cynefin-OW5HDTMX-Bd1YhRo1.js +0 -1
  1180. ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-BBrIG_mI.js +0 -62
  1181. ksadk/studio/static/assets/dagre-GXQ25YYZ-DtF1nGdY.js +0 -4
  1182. ksadk/studio/static/assets/diagram-UQ7AKVKN-6mLKEo6L.js +0 -41
  1183. ksadk/studio/static/assets/diagram-VSXAHHWV-CJBIaiZ2.js +0 -3
  1184. ksadk/studio/static/assets/diagram-VX7I27RA-BohLEE1C.js +0 -24
  1185. ksadk/studio/static/assets/diagram-Z3DM3KII-D8KIGonG.js +0 -24
  1186. ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-C99HU61t.js +0 -1
  1187. ksadk/studio/static/assets/eventmodeling-NTZA5JFV-B1_5Ac8j.js +0 -1
  1188. ksadk/studio/static/assets/flowDiagram-HODETNUW-DD5hmicn.js +0 -1
  1189. ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BPJrVLSG.js +0 -292
  1190. ksadk/studio/static/assets/gitGraph-4MIJSDKK-Cy_ZQBGi.js +0 -1
  1191. ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-CZCUz4MT.js +0 -106
  1192. ksadk/studio/static/assets/index-CeHL9ARV.css +0 -2
  1193. ksadk/studio/static/assets/index-D9QEcVpL.js +0 -320
  1194. ksadk/studio/static/assets/info-A6RAGUB7-DBzDQB_I.js +0 -1
  1195. ksadk/studio/static/assets/infoDiagram-27XIBGKW-B-sq07Hi.js +0 -2
  1196. ksadk/studio/static/assets/linear-BCxN9JLv.js +0 -1
  1197. ksadk/studio/static/assets/mermaid-parser.core-BdCz8fkS.js +0 -7
  1198. ksadk/studio/static/assets/packet-AYTQ26CC-gPln-_kG.js +0 -1
  1199. ksadk/studio/static/assets/pegDiagram-XKGWAZYB-Dy3fB-gW.js +0 -1
  1200. ksadk/studio/static/assets/pie-WAS4IAKB-qZdHMr7B.js +0 -1
  1201. ksadk/studio/static/assets/radar-RG4KPBEZ-BZlnaea9.js +0 -1
  1202. ksadk/studio/static/assets/railroad-74A4TZTK-Ce3__H9c.js +0 -1
  1203. ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-DqQn7EzD.js +0 -1
  1204. ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-B55kCA8F.js +0 -1
  1205. ksadk/studio/static/assets/railroad-peg-WCYAUIDC-DfQnvkHd.js +0 -1
  1206. ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-DnhMjZCk.js +0 -1
  1207. ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-Bo-JTwt0.js +0 -40
  1208. ksadk/studio/static/assets/src-SFn9St9H.js +0 -1
  1209. ksadk/studio/static/assets/stateDiagram-D77RDMKH-DH774G6d.js +0 -1
  1210. ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-DZvVWPBo.js +0 -1
  1211. ksadk/studio/static/assets/swimlanes-42K2YHIH-Bl13OvTX.js +0 -1
  1212. ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-BnFOTALJ.js +0 -8
  1213. ksadk/studio/static/assets/treeView-Q6P3EWNA-D5Nn9Swz.js +0 -1
  1214. ksadk/studio/static/assets/treemap-WGGIJYW6-CgHgnQhE.js +0 -1
  1215. ksadk/studio/static/assets/vennDiagram-4TSXK5OY-B9QEXcJq.js +0 -34
  1216. ksadk/studio/static/assets/wardley-WFR3VGLG-DXrK2RRY.js +0 -1
  1217. {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/WHEEL +0 -0
  1218. {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/entry_points.txt +0 -0
  1219. {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/licenses/LICENSE +0 -0
  1220. {agentengine_sdk_python-0.8.4.dist-info → agentengine_sdk_python-0.8.6.dist-info}/top_level.txt +0 -0
  1221. /ksadk/studio/static/assets/{MathMessageMarkdown-CBcDrAw--BEOyPC3w.css → MathMessageMarkdown-BesSeCYl-BEOyPC3w.css} +0 -0
@@ -3,19 +3,21 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import copy
6
7
  import hashlib
7
8
  import json
9
+ import linecache
8
10
  import logging
11
+ import os
9
12
  import time
10
13
  from collections.abc import Callable, Mapping
14
+ from contextlib import suppress
11
15
  from dataclasses import asdict, dataclass, field
12
16
  from datetime import datetime, timezone
13
17
  from pathlib import Path
14
- from typing import Any
18
+ from typing import Any, Literal
15
19
  from uuid import uuid4
16
20
 
17
- from jsonschema import Draft202012Validator
18
-
19
21
  from ksadk.agui.a2ui_projection import project_a2ui_operations
20
22
  from ksadk.conversations.projector import (
21
23
  project_conversation_item,
@@ -55,7 +57,6 @@ from ksadk.events.content import (
55
57
  from ksadk.events.store import RuntimeEventStore
56
58
  from ksadk.runtime import (
57
59
  CONVERSATION_PREPROCESSING_METADATA_KEY,
58
- PauseResult,
59
60
  ResumePayload,
60
61
  ResumeTarget,
61
62
  RuntimeExecutor,
@@ -65,9 +66,12 @@ from ksadk.runtime import (
65
66
  from ksadk.runtime.executor import RuntimeStartPreparation
66
67
  from ksadk.sessions.base import BaseSessionService
67
68
  from ksadk.sessions.local_service import LocalSessionService
68
- from ksadk.studio.contracts import RunEvent, RunRecord, RunStatus, Usage
69
+ from ksadk.studio.contracts import RunEvent, RunRecord, RunStatus, RunSubmissionRef, Usage
69
70
  from ksadk.studio.errors import StudioError
70
71
  from ksadk.studio.event_store import RunEventStore
72
+ from ksadk.studio.native_verification_presentation import terminal_verification_notice
73
+ from ksadk.studio.run_activity import project_run_activities
74
+ from ksadk.studio.run_controls import StudioRunControlsMixin
71
75
  from ksadk.studio.workspace import Workspace
72
76
 
73
77
  _CANCEL_TIMEOUT_SECONDS = 2.0
@@ -77,6 +81,174 @@ _INTERACTION_SUBMIT_TIMEOUT_SECONDS = 10.0
77
81
  logger = logging.getLogger(__name__)
78
82
 
79
83
 
84
+ def _stall_seconds(name: str, default: float) -> float:
85
+ """读看门狗阈值(秒);非法值回落到默认,<=0 表示关闭。"""
86
+
87
+ raw = os.getenv(name, "")
88
+ if not raw.strip():
89
+ return default
90
+ try:
91
+ return float(raw)
92
+ except ValueError:
93
+ return default
94
+
95
+
96
+ def _format_task_stacks(limit: int = 40) -> str:
97
+ """把当前 asyncio 任务现场拼成可读文本(只用于诊断日志,不含凭据)。
98
+
99
+ 比普通 ``task.get_stack()`` 更进一步——后者只返回**当前协程**那一帧,
100
+ ``await`` 链上被等待协程的帧会整段丢掉(上一版 dump 里 run 本体只剩一帧
101
+ ``ainvoke`` 就是这个原因)。这里改为:
102
+
103
+ - 记录 ``task._fut_waiter``:任务到底在等哪个 future / event / task;
104
+ - 沿 ``coro.cr_await`` 逐层取帧,直到撞到 Future/Task/Event 边界。
105
+ """
106
+
107
+ parts: list[str] = []
108
+ for task in sorted(asyncio.all_tasks(), key=lambda t: (t.done(), t.get_name())):
109
+ if task.done():
110
+ continue
111
+ coro = task.get_coro()
112
+ parts.append(f"=== task {task.get_name()} ({_coro_qualname(coro)})")
113
+ fw = getattr(task, "_fut_waiter", None)
114
+ if fw is not None:
115
+ parts.append(f" 等待对象: {_future_desc(fw)}")
116
+ frames = _coro_await_frames(coro, limit=limit)
117
+ if not frames:
118
+ parts.append(" (无栈帧)")
119
+ continue
120
+ for filename, lineno, code in frames:
121
+ parts.append(f" {filename}:{lineno} {code}")
122
+ parts.append("")
123
+ return "\n".join(parts) or "(没有可 dump 的任务栈)"
124
+
125
+
126
+ def _coro_qualname(coro: Any) -> str:
127
+ try:
128
+ return coro.__qualname__
129
+ except Exception: # noqa: BLE001 - 诊断尽力而为
130
+ return repr(coro)[:80]
131
+
132
+
133
+ def _future_desc(fut: Any) -> str:
134
+ """描述被 await 的 future/event/task;只读属性,不触发、不等待它。"""
135
+
136
+ if isinstance(fut, asyncio.Task):
137
+ state = "done" if fut.done() else "pending"
138
+ detail = ""
139
+ if fut.done() and not fut.cancelled():
140
+ exc = fut.exception()
141
+ detail = f" exc={type(exc).__name__}" if exc is not None else ""
142
+ return f"Task({_coro_qualname(fut.get_coro())}) {state}{detail}"
143
+ if isinstance(fut, asyncio.Event):
144
+ return f"Event is_set={fut.is_set()}"
145
+ if isinstance(fut, asyncio.Future):
146
+ state = "done" if fut.done() else "pending"
147
+ if fut.done() and not fut.cancelled():
148
+ exc = fut.exception()
149
+ state += f" exc={type(exc).__name__}" if exc is not None else ""
150
+ return f"Future {state} cancelled={fut.cancelled()}"
151
+ return f"{type(fut).__name__}: {repr(fut)[:140]}"
152
+
153
+
154
+ def _coro_await_frames(
155
+ coro: Any, *, limit: int
156
+ ) -> list[tuple[str, int, str]]:
157
+ """沿 ``cr_await`` 链收集 (文件名, 行号, 代码行);撞到 Future/Task 即停。"""
158
+
159
+ frames: list[tuple[str, int, str]] = []
160
+ seen = 0
161
+ guard = 0
162
+ while coro is not None and seen < limit and guard < 256:
163
+ guard += 1
164
+ frame = getattr(coro, "cr_frame", None)
165
+ if frame is not None:
166
+ code = frame.f_code
167
+ line = linecache.getline(code.co_filename, frame.f_lineno).strip()
168
+ frames.append((code.co_filename, frame.f_lineno, line))
169
+ seen += 1
170
+ nxt = getattr(coro, "cr_await", None)
171
+ if nxt is None or nxt is coro:
172
+ break
173
+ if not hasattr(nxt, "cr_frame"):
174
+ # await 的是 Future/Task/Event:链到此为止(fut_waiter 已单独记录)。
175
+ frames.append(("<await-boundary>", 0, _future_desc(nxt)))
176
+ break
177
+ coro = nxt
178
+ return frames
179
+
180
+
181
+ def _format_loop_timers(limit: int = 12) -> str:
182
+ """列出事件循环里最近将触发的定时器(含 callback 与到期秒数)。
183
+
184
+ 用途:区分"卡在一个超长 sleep"和"在等一个永远不会就绪的 future"。
185
+ 前者会在这里看到一个很大的到期秒数;后者这里要么空、要么全是无关的
186
+ 轮询定时器,而任务却停在 await 一个 Future 上。
187
+ """
188
+
189
+ try:
190
+ loop = asyncio.get_running_loop()
191
+ except RuntimeError:
192
+ return "(没有运行中的事件循环)"
193
+ handles = []
194
+ for handle in list(getattr(loop, "_scheduled", ()) or ()):
195
+ if getattr(handle, "_cancelled", False):
196
+ continue
197
+ when = getattr(handle, "_when", None)
198
+ if when is None:
199
+ continue
200
+ callback = getattr(handle, "_callback", None)
201
+ name = getattr(callback, "__qualname__", None) or repr(callback)[:60]
202
+ handles.append((float(when), name))
203
+ if not handles:
204
+ return "(事件循环里没有待触发的定时器)"
205
+ handles.sort()
206
+ try:
207
+ now = loop.time()
208
+ except Exception: # noqa: BLE001 - 诊断尽力而为
209
+ now = handles[0][0]
210
+ lines = [
211
+ f" {max(0.0, when - now):9.2f}s 后 {name}"
212
+ for when, name in handles[:limit]
213
+ ]
214
+ if len(handles) > limit:
215
+ lines.append(f" ... 另有 {len(handles) - limit} 个定时器")
216
+ return "\n".join(lines)
217
+
218
+
219
+ def _consumed_model_output(record: Any) -> bool:
220
+ """该 run 是否真的消耗了模型输出 token(用于判定"空交付"而非"合法空输出")。
221
+
222
+ 审批/交互类流程会合法地以空输出收束;只有"跑过模型、烧了输出 token,却没有任何
223
+ 交付内容"才是事故(2026-09-18 实测:harness provider adapter 路径 COMPLETED +
224
+ output 长度 0 + 产物 0)。
225
+ """
226
+
227
+ usage = getattr(record, "usage", None)
228
+ try:
229
+ return int(getattr(usage, "output_tokens", 0) or 0) > 0
230
+ except (TypeError, ValueError):
231
+ return False
232
+
233
+
234
+ def _require_delivery(output: Any) -> None:
235
+ """空交付不得判 COMPLETED(2026-09-18 实测事故)。
236
+
237
+ 一次 run 跑了 15 次模型调用、并行 3 个子 Agent、约 17 分钟,最后以
238
+ **COMPLETED + output 长度 0 + 产物 0** 收场——用户看到"成功"却拿不到任何东西。
239
+ harness 的真正完成点是 ``_plugin_run`` / ``_kernel_run``(我第一版把守卫错放在
240
+ ``_schedule_run``,于是下一次事故照样以 COMPLETED 收场),两处都必须过这道闸。
241
+ """
242
+
243
+ if not str(output or "").strip():
244
+ raise StudioError(
245
+ "EMPTY_DELIVERY",
246
+ "本轮没有产出任何交付内容(输出为空,且没有产物),已按失败处理而不是成功。"
247
+ "请重试,或检查该 Agent 的委派/落盘策略。",
248
+ status_code=502,
249
+ )
250
+
251
+
80
252
  @dataclass(frozen=True)
81
253
  class StudioRunSpec:
82
254
  """Product metadata plus the core launch context for one Studio run."""
@@ -85,12 +257,179 @@ class StudioRunSpec:
85
257
  build_id: str
86
258
  agent_id: str
87
259
  model: str | None = None
260
+ # Immutable capability metadata copied from the selected model in the
261
+ # Build. Conversation surfaces must use this declaration instead of
262
+ # guessing modality support from model names or mutable catalog state.
263
+ model_metadata: Mapping[str, Any] = field(default_factory=dict)
88
264
  request_config: Mapping[str, Any] = field(default_factory=dict)
89
265
  manifest_sha256: str = ""
90
266
  plugin_bundle_root: Path | None = None
267
+ submission_ref: RunSubmissionRef | None = None
268
+ presentation_profile: Literal["flat-v1", "agent-block-v1"] = "flat-v1"
269
+
270
+
271
+ class _ProjectedRunEventWriter:
272
+ """Batch high-frequency presentation events before rewriting a Run JSON.
273
+
274
+ Canonical RuntimeEvents are already durable in the session store. The
275
+ RunEvent JSON is a presentation projection, so writing and retaining one
276
+ copy of the full projection envelope per token *or internal status fact*
277
+ with an independent file replace is unnecessary and becomes quadratic as
278
+ the response grows. A short batching window keeps live chat responsive,
279
+ coalesces adjacent text chunks, and commits status facts in one replace.
280
+
281
+ The status facts themselves are not discarded: the canonical session
282
+ trace remains the source of truth and the Studio projection still retains
283
+ every curated fact. Only the number of growing-file rewrites changes.
284
+ """
285
+
286
+ _STREAM_TYPES = frozenset({"message.delta", "thinking.delta"})
287
+
288
+ def __init__(
289
+ self,
290
+ event_store: RunEventStore,
291
+ run_id: str,
292
+ on_event: Callable[[RunEvent], None] | None,
293
+ *,
294
+ flush_interval: float = 0.5,
295
+ ) -> None:
296
+ self._event_store = event_store
297
+ self._run_id = run_id
298
+ self._on_event = on_event
299
+ self._flush_interval = flush_interval
300
+ self._pending: list[tuple[str, dict[str, Any]]] = []
301
+ # Publish the first chunk immediately so the UI can enter streaming
302
+ # state without waiting for the batching window. Subsequent chunks are
303
+ # coalesced into short durable writes.
304
+ self._last_flush = 0.0
305
+ self._flush_handle: asyncio.TimerHandle | None = None
306
+
307
+ def append(
308
+ self, event_type: str, data: dict[str, Any], *, record: RunRecord | None = None,
309
+ ) -> RunEvent | None:
310
+ if record is not None:
311
+ if record.id != self._run_id:
312
+ raise ValueError("projected event Run identity mismatch")
313
+ # A flush could persist a cached, already terminal record before
314
+ # its terminal fact. Publish the pending prefix in this same write.
315
+ entries = [*self._coalesce_stream_entries(self._pending), (event_type, data)]
316
+ stored = self._event_store.append_record_events(record, entries)
317
+ self._pending.clear()
318
+ if self._flush_handle is not None:
319
+ self._flush_handle.cancel()
320
+ self._flush_handle = None
321
+ self._last_flush = time.monotonic()
322
+ self._notify(stored)
323
+ return stored[-1]
324
+ if self._is_batchable(event_type, data):
325
+ self._pending.append((event_type, data))
326
+ elapsed = time.monotonic() - self._last_flush
327
+ if elapsed < self._flush_interval:
328
+ if self._flush_handle is None:
329
+ self._flush_handle = asyncio.get_running_loop().call_later(
330
+ self._flush_interval - elapsed, self.flush
331
+ )
332
+ return None
333
+ return self.flush()[-1]
334
+ self.flush()
335
+ stored = self._event_store.append(self._run_id, event_type, data)
336
+ self._notify((stored,))
337
+ return stored
338
+
339
+ @classmethod
340
+ def _is_batchable(cls, event_type: str, data: dict[str, Any]) -> bool:
341
+ if event_type in cls._STREAM_TYPES:
342
+ return True
343
+ runtime_event = data.get("runtimeEvent")
344
+ # Managed Harness publishes its execution evidence as canonical status
345
+ # items. Long runs can emit thousands of these in seconds; they drive
346
+ # the activity UI but do not require one fsync/JSON rewrite per fact.
347
+ return (
348
+ event_type == "item.completed"
349
+ and isinstance(runtime_event, dict)
350
+ and runtime_event.get("item_kind") == "status"
351
+ )
352
+
353
+ def flush(self) -> list[RunEvent]:
354
+ if self._flush_handle is not None:
355
+ self._flush_handle.cancel()
356
+ self._flush_handle = None
357
+ if not self._pending:
358
+ return []
359
+ pending, self._pending = self._pending, []
360
+ stored = self._event_store.append_many(
361
+ self._run_id,
362
+ self._coalesce_stream_entries(pending),
363
+ )
364
+ self._last_flush = time.monotonic()
365
+ self._notify(stored)
366
+ return stored
367
+
368
+ @staticmethod
369
+ def _coalesce_stream_entries(
370
+ entries: list[tuple[str, dict[str, Any]]],
371
+ ) -> list[tuple[str, dict[str, Any]]]:
372
+ """Collapse adjacent chunks without changing the rendered transcript."""
373
+
374
+ coalesced: list[tuple[str, dict[str, Any]]] = []
375
+ for event_type, data in entries:
376
+ if not coalesced:
377
+ coalesced.append((event_type, copy.deepcopy(data)))
378
+ continue
379
+ previous_type, previous = coalesced[-1]
380
+ if (
381
+ event_type not in _ProjectedRunEventWriter._STREAM_TYPES
382
+ or not _same_projected_stream(previous_type, previous, event_type, data)
383
+ ):
384
+ coalesced.append((event_type, copy.deepcopy(data)))
385
+ continue
386
+
387
+ operation = str(data.get("operation") or "append")
388
+ if operation == "append":
389
+ merged_text = str(previous.get("text") or "") + str(data.get("text") or "")
390
+ merged = copy.deepcopy(data)
391
+ _set_projected_stream_text(merged, merged_text)
392
+ coalesced[-1] = (event_type, merged)
393
+ else:
394
+ coalesced[-1] = (event_type, copy.deepcopy(data))
395
+ return coalesced
396
+
397
+ def _notify(self, events: tuple[RunEvent, ...] | list[RunEvent]) -> None:
398
+ if self._on_event is None:
399
+ return
400
+ for event in events:
401
+ self._on_event(event)
402
+
403
+
404
+ def _same_projected_stream(
405
+ previous_type: str,
406
+ previous: dict[str, Any],
407
+ event_type: str,
408
+ data: dict[str, Any],
409
+ ) -> bool:
410
+ return (
411
+ previous_type == event_type
412
+ and previous.get("itemId") == data.get("itemId")
413
+ and previous.get("partId") == data.get("partId")
414
+ and previous.get("operation") == data.get("operation")
415
+ )
416
+
91
417
 
418
+ def _set_projected_stream_text(data: dict[str, Any], text: str) -> None:
419
+ data["text"] = text
420
+ conversation_item = data.get("conversationItem")
421
+ if isinstance(conversation_item, dict):
422
+ payload = conversation_item.get("payload")
423
+ if isinstance(payload, dict):
424
+ payload["text"] = text
425
+ runtime_event = data.get("runtimeEvent")
426
+ if isinstance(runtime_event, dict):
427
+ update = runtime_event.get("update")
428
+ if isinstance(update, dict):
429
+ update["text"] = text
92
430
 
93
- class StudioRunService:
431
+
432
+ class StudioRunService(StudioRunControlsMixin):
94
433
  """Persist Studio state without selecting or wrapping a RuntimeAdapter."""
95
434
 
96
435
  def __init__(
@@ -102,6 +441,7 @@ class StudioRunService:
102
441
  session_service: BaseSessionService | None = None,
103
442
  runtime_events: RuntimeEventStore | None = None,
104
443
  plugin_runtime: Any | None = None,
444
+ native_harness_ingress: bool = False,
105
445
  ) -> None:
106
446
  self.workspace = workspace
107
447
  self.executor = executor
@@ -111,9 +451,15 @@ class StudioRunService:
111
451
  )
112
452
  self.runtime_events = runtime_events or RuntimeEventStore(self.session_service)
113
453
  self.plugin_runtime = plugin_runtime
454
+ self.native_harness_ingress = native_harness_ingress
114
455
  self.schedule_assistant: Any | None = None
115
456
  self._active_handles: dict[str, Any] = {}
116
457
  self._active_sessions: set[tuple[str, str]] = set()
458
+ # Serialize accepted next-turn prompts per Agent/Session. Direct Run
459
+ # calls retain their overlap rejection; the asynchronous Studio
460
+ # operation route may wait its turn without starting a second adapter.
461
+ self._session_turn_locks: dict[tuple[str, str], asyncio.Lock] = {}
462
+ self._session_turn_users: dict[tuple[str, str], int] = {}
117
463
  self._cancel_flags: dict[str, bool] = {}
118
464
  self._control_queues: dict[
119
465
  str,
@@ -121,22 +467,105 @@ class StudioRunService:
121
467
  ] = {}
122
468
  self._waiting_modes: dict[str, str] = {}
123
469
  self._submitted_interactions: dict[str, set[str]] = {}
470
+ # 传输活性与持久化是两条独立合同。Harness 的 reasoning/text token
471
+ # delta 出于隐私和体积原因不会写入 RunEventStore,但它们仍证明模型流
472
+ # 在前进;看门狗必须观察这条进程内单调序号,不能把“未持久化”误判成
473
+ # “没有收到任何 chunk”。
474
+ self._runtime_progress_seq = 0
124
475
  # Interaction resolution appends to one run-level event file. Serialise
125
476
  # every decision for that run, not only identical interaction ids, so
126
477
  # two concurrently visible cards cannot overwrite each other's receipt.
127
478
  self._interaction_locks: dict[str, asyncio.Lock] = {}
479
+ self._recovery_tasks: set[asyncio.Task] = set()
480
+ self._native_recovery_ready: dict[str, asyncio.Event] = {}
481
+ self._detaching_recoveries = False
482
+
483
+ def has_active_runs(self) -> bool:
484
+ # Accepted follow-ups remain live work even during the short handoff
485
+ # between two Runs. Settings/credential changes must not slip through.
486
+ return bool(self._session_turn_users or self._active_sessions or self._active_handles)
487
+
488
+ def has_pending_session_turns(self, session_id: str) -> bool:
489
+ return any(
490
+ waiting_session == session_id and count > 0
491
+ for (_, waiting_session), count in self._session_turn_users.items()
492
+ )
493
+
494
+ def is_run_executing(self, run_id: str) -> bool:
495
+ """该 run 是否仍在本进程内实际执行(内存态判定)。
496
+
497
+ 磁盘 record 停在 RUNNING 的 run 若不在这里,说明是进程崩溃/重启
498
+ 遗留的僵尸状态,允许删除会话时将其收尾为 canceled。
499
+ """
500
+ if run_id in self._active_handles:
501
+ return True
502
+ try:
503
+ record = self.event_store.get(run_id)
504
+ except StudioError:
505
+ return False
506
+ return (record.agent_id, record.session_id) in self._active_sessions
507
+
508
+ async def reap_stale_running_run(self, run_id: str) -> bool:
509
+ """把不在执行的僵尸 RUNNING run 收尾为 canceled(幂等)。"""
510
+ from ksadk.events.canonical import RunCanceled
511
+
512
+ record = self.event_store.get(run_id)
513
+ if record.status != RunStatus.RUNNING or self.is_run_executing(run_id):
514
+ return False
515
+ record.status = RunStatus.CANCELLED
516
+ record.error = {"code": "RUN_CANCELLED", "message": "会话删除时回收未完成的运行"}
517
+ record.completed_at = datetime.now(timezone.utc)
518
+ self.event_store.save(record)
519
+ await self.runtime_events.append_one(
520
+ record.session_id,
521
+ RunCanceled(
522
+ schema_version=2,
523
+ event_id=f"evt_reap_{uuid4().hex}",
524
+ seq=len(self.event_store.events(run_id)) + 1,
525
+ timestamp=time.time(),
526
+ run_id=run_id,
527
+ scope_id=run_id,
528
+ source=SourceRef(framework="ksadk", metadata={"reason": "session_deleted"}),
529
+ status="canceled",
530
+ reason="session deleted",
531
+ ),
532
+ )
533
+ return True
128
534
 
129
- async def recover_interrupted(self) -> None:
535
+ async def recover_interrupted(self, resolve_spec=None) -> None:
130
536
  """Settle local runs left active across a Studio restart.
131
537
 
132
- An in-process handle cannot be resumed safely. Keep its durable event
133
- history and record an explicit interruption; lease-backed hosted
134
- recovery remains owned by the Kernel composition root.
538
+ Reattach approval-paused Providers and opt-in local Native runs only
539
+ when their pinned Build and durable-restore capability can be verified.
540
+ Other in-process runs are explicitly interrupted; hosted takeover
541
+ remains Kernel-owned.
135
542
  """
136
- active = {RunStatus.RUNNING, RunStatus.WAITING_INPUT}
543
+ active = {RunStatus.RUNNING, RunStatus.WAITING_INPUT, RunStatus.PAUSED}
137
544
  for record in self.event_store.list_runs():
545
+ if record.status == RunStatus.COMPLETED:
546
+ from ksadk.studio.native_terminal_recovery import rebuild_native_terminal_trace
547
+
548
+ await rebuild_native_terminal_trace(self, record, resolve_spec)
138
549
  if record.status not in active:
139
550
  continue
551
+ native_ref = (
552
+ record.runtime_handle.get("native_ref", {}) if record.runtime_handle else {}
553
+ )
554
+ is_native = isinstance(native_ref, dict) and native_ref.get("engine_kind") == "native"
555
+ if (
556
+ record.status == RunStatus.WAITING_INPUT
557
+ and resolve_spec is not None
558
+ and not is_native
559
+ ):
560
+ from ksadk.studio.provider_recovery import restore_waiting_provider_run
561
+
562
+ if await restore_waiting_provider_run(self, record, resolve_spec):
563
+ continue
564
+ if resolve_spec is not None:
565
+ from ksadk.studio.native_recovery import restore_native_studio_run
566
+
567
+ if await restore_native_studio_run(self, record, resolve_spec):
568
+ continue
140
569
  record.status = RunStatus.INTERRUPTED
141
570
  record.completed_at = datetime.now(timezone.utc)
142
571
  record.error = {
@@ -151,9 +580,24 @@ class StudioRunService:
151
580
  )
152
581
  self._expire_unresolved_interactions(record)
153
582
 
154
- async def events(self, run_id: str, *, after: int = 0) -> list[RunEvent]:
583
+ async def events(
584
+ self, run_id: str, *, after: int = 0, presentation_profile: str | None = None
585
+ ) -> list[RunEvent]:
155
586
  """Read the durable Studio event timeline for API/SSE replay."""
156
- return self.event_store.events(run_id, after=after)
587
+ from ksadk.studio.presentation import StudioConversationProjection
588
+
589
+ record = self.event_store.get(run_id)
590
+ if presentation_profile is not None and presentation_profile != record.presentation_profile:
591
+ raise StudioError(
592
+ "PRESENTATION_PROFILE_CONFLICT", "重连必须沿用当前运行的展示方式", status_code=409
593
+ )
594
+ projector = StudioConversationProjection(record.session_id, record.presentation_profile)
595
+ result = []
596
+ for event in self.event_store.events(run_id, after=0):
597
+ projected = projector.apply(event)
598
+ if event.id > after:
599
+ result.append(projected)
600
+ return result
157
601
 
158
602
  async def run(
159
603
  self,
@@ -163,29 +607,164 @@ class StudioRunService:
163
607
  runtime_input: Any = None,
164
608
  session_id: str | None = None,
165
609
  on_event: Callable[[RunEvent], None] | None = None,
610
+ queue_if_active: bool = False,
611
+ on_admitted: Callable[[], None] | None = None,
166
612
  ) -> RunRecord:
167
613
  session = session_id or f"ses_{uuid4().hex}"
614
+ if spec.submission_ref is not None and (
615
+ spec.submission_ref.agent_id != spec.agent_id
616
+ or spec.submission_ref.session_id != session
617
+ ):
618
+ raise StudioError(
619
+ "RUN_SUBMISSION_SCOPE_MISMATCH", "提交来源与实际运行不一致", status_code=409
620
+ )
168
621
  key = (spec.agent_id, session)
169
- # Claim before the first await, including preparation and plugin runs.
170
- # A second turn must never attach another adapter to the live thread.
171
- if key in self._active_sessions:
622
+ # Count the admission before the first await. A direct caller cannot
623
+ # jump ahead of an already accepted follow-up during lock handoff.
624
+ if not queue_if_active and self._session_turn_users.get(key, 0):
172
625
  raise StudioError(
173
626
  "SESSION_RUN_ACTIVE",
174
627
  "上一轮仍在运行或等待回答,请先完成回答或停止运行。",
175
628
  status_code=409,
176
629
  details={"sessionId": session},
177
630
  )
178
- self._active_sessions.add(key)
631
+ # Accepted structured input belongs to this submission, not to the
632
+ # mutable composer/caller while this coroutine waits for its turn.
633
+ runtime_input = copy.deepcopy(runtime_input)
634
+ lock = self._session_turn_locks.setdefault(key, asyncio.Lock())
635
+ self._session_turn_users[key] = self._session_turn_users.get(key, 0) + 1
179
636
  try:
180
- return await self._run_owned(
181
- spec,
182
- user_input,
183
- runtime_input=runtime_input,
184
- session_id=session,
185
- on_event=on_event,
637
+ async with lock:
638
+ if asyncio.current_task() and asyncio.current_task().cancelling():
639
+ raise asyncio.CancelledError()
640
+ if on_admitted is not None:
641
+ on_admitted()
642
+ self._active_sessions.add(key)
643
+ watchdog = asyncio.create_task(self._watch_run_progress())
644
+ try:
645
+ return await self._run_owned(
646
+ spec,
647
+ user_input,
648
+ runtime_input=runtime_input,
649
+ session_id=session,
650
+ on_event=on_event,
651
+ )
652
+ finally:
653
+ watchdog.cancel()
654
+ with suppress(asyncio.CancelledError):
655
+ await watchdog
656
+ self._active_sessions.discard(key)
657
+ finally:
658
+ users = self._session_turn_users[key] - 1
659
+ if users:
660
+ self._session_turn_users[key] = users
661
+ else:
662
+ self._session_turn_users.pop(key, None)
663
+ self._session_turn_locks.pop(key, None)
664
+
665
+ async def _watch_run_progress(self) -> None:
666
+ """运行期"无进展"看门狗:只做取证,不改行为。
667
+
668
+ 依据:2026-09-18 实测一次 run 静默 437s、期间 0 事件,而进程 CPU 稳定
669
+ 在 ~20%(空转基线 0.3%),日志里没有任何一行能说明"当时在干什么";
670
+ 排查只能靠 `lsof` 反推,且 macOS 上 py-spy 需要 root、lldb attach 被拒。
671
+ 因此把"疑似卡住"时的现场(asyncio 任务栈)直接打进 studio 日志:
672
+ 下次再卡住,根因在日志里,不需要复现第二次。
673
+
674
+ 阈值:``KSADK_RUN_STALL_WARN_SECONDS``(默认 60,<=0 关闭);
675
+ 现场 dump 从 ``KSADK_RUN_STALL_DUMP_SECONDS``(默认 120)开始,
676
+ **持续卡住就每隔这么久再 dump 一次**(最多 ``KSADK_RUN_STALL_MAX_DUMPS``
677
+ 次,默认 5)。一次性 dump 区分不了"卡住"和"只是慢":2026-09-18 实测一次
678
+ run 静默 10 分钟、期间零模型请求、进程全部线程 idle,而 21:36:08 那份
679
+ 一次性 dump 早已过期,无法说明 6 分钟后的状态。
680
+ """
681
+
682
+ warn_after = _stall_seconds("KSADK_RUN_STALL_WARN_SECONDS", 60.0)
683
+ if warn_after <= 0:
684
+ return
685
+ dump_after = _stall_seconds("KSADK_RUN_STALL_DUMP_SECONDS", 120.0)
686
+ max_dumps = int(_stall_seconds("KSADK_RUN_STALL_MAX_DUMPS", 5.0))
687
+ poll_seconds = min(15.0, warn_after)
688
+ quiet_since = time.monotonic()
689
+ last_seq = self._progress_marker()
690
+ warned = False
691
+ dumps = 0
692
+ last_dump_at = 0.0
693
+ while True:
694
+ await asyncio.sleep(poll_seconds)
695
+ marker = self._progress_marker()
696
+ if marker != last_seq:
697
+ last_seq = marker
698
+ quiet_since = time.monotonic()
699
+ warned = False
700
+ dumps = 0
701
+ last_dump_at = 0.0
702
+ continue
703
+ quiet_for = time.monotonic() - quiet_since
704
+ if quiet_for >= warn_after and not warned:
705
+ warned = True
706
+ logger.warning(
707
+ "run 已 %.0fs 没有新事件(当前执行中 run=%s);疑似卡住,"
708
+ "达到 %.0fs 将 dump asyncio 任务栈",
709
+ quiet_for,
710
+ sorted(self._active_handles) or sorted(self._active_sessions),
711
+ dump_after,
712
+ )
713
+ due_for_dump = quiet_for >= dump_after and (
714
+ last_dump_at == 0.0 or quiet_for - last_dump_at >= dump_after
186
715
  )
716
+ if due_for_dump and dumps < max_dumps:
717
+ dumps += 1
718
+ last_dump_at = quiet_for
719
+ logger.warning(
720
+ "run 已 %.0fs 没有新事件(第 %d/%d 次现场);"
721
+ "asyncio 任务现场:\n%s\n事件循环最近的定时器:\n%s",
722
+ quiet_for,
723
+ dumps,
724
+ max_dumps,
725
+ _format_task_stacks(),
726
+ _format_loop_timers(),
727
+ )
728
+
729
+ def _progress_marker(self) -> tuple[int, int, int]:
730
+ """进展标记:持久事件、活跃会话与未落盘的实时流序号。"""
731
+
732
+ total = 0
733
+ for run_id in list(self._active_handles):
734
+ try:
735
+ total += len(self.event_store.events(str(run_id)))
736
+ except Exception: # noqa: BLE001 - 取证代码不得影响主流程
737
+ continue
738
+ return total, len(self._active_sessions), self._runtime_progress_seq
739
+
740
+
741
+ async def _start_public_execution(self, run_id, launch_context, request, preparation):
742
+ from ksadk.harness.public_output import public_output_scope
743
+ from ksadk.studio.public_output_store import PublicOutputStore
744
+
745
+ store = PublicOutputStore(self.workspace)
746
+ with public_output_scope(lambda text, **kwargs: store.retain(run_id, text, **kwargs)):
747
+ return await self.executor.start(launch_context, request, preparation=preparation)
748
+
749
+ async def _public_execution_stream(self, handle, run_id):
750
+ from ksadk.harness.public_output import public_output_scope
751
+ from ksadk.studio.public_output_store import PublicOutputStore
752
+
753
+ store = PublicOutputStore(self.workspace)
754
+ def sink(text, **kwargs):
755
+ return store.retain(run_id, text, **kwargs)
756
+
757
+ iterator = self.executor.stream(handle)
758
+ try:
759
+ while True:
760
+ with public_output_scope(sink):
761
+ try:
762
+ event = await anext(iterator)
763
+ except StopAsyncIteration:
764
+ return
765
+ yield event
187
766
  finally:
188
- self._active_sessions.discard(key)
767
+ await iterator.aclose()
189
768
 
190
769
  async def _run_owned(
191
770
  self,
@@ -205,11 +784,13 @@ class StudioRunService:
205
784
  agent_id=spec.agent_id,
206
785
  session_id=session,
207
786
  trace_id=uuid4().hex,
787
+ submission_ref=spec.submission_ref,
208
788
  manifest_sha256=spec.manifest_sha256,
209
789
  runtime_type=runtime_type,
210
790
  model=str(spec.model or ""),
211
791
  collaboration_mode=str(spec.request_config.get("collaboration_mode") or ""),
212
792
  goal_objective=str(spec.request_config.get("goal_objective") or ""),
793
+ presentation_profile=spec.presentation_profile,
213
794
  input=user_input,
214
795
  )
215
796
  await self.session_service.create_session(spec.agent_id, "local-user", session)
@@ -228,6 +809,7 @@ class StudioRunService:
228
809
  "model": record.model,
229
810
  "collaborationMode": record.collaboration_mode,
230
811
  "goalObjective": record.goal_objective,
812
+ "presentationProfile": record.presentation_profile,
231
813
  },
232
814
  )
233
815
  if on_event is not None:
@@ -270,13 +852,141 @@ class StudioRunService:
270
852
  on_event=on_event,
271
853
  )
272
854
 
855
+ return await self._execute_runtime_record(
856
+ spec, user_input, record=record, runtime_input=runtime_input,
857
+ on_event=on_event, prepared_turn=prepared_turn,
858
+ provider_runtime_adapter=provider_runtime_adapter,
859
+ )
860
+
861
+ def _admit_native_studio_start(
862
+ self,
863
+ spec: StudioRunSpec,
864
+ record: RunRecord,
865
+ user_input: str,
866
+ request: StartRequest,
867
+ adapter: Any,
868
+ *,
869
+ require_pin: bool,
870
+ ) -> StartRequest:
871
+ """Apply the same Build/history fence to direct and PluginHost Native starts."""
872
+ adapter.admit_studio_build(
873
+ build_id=spec.build_id,
874
+ manifest_sha256=spec.manifest_sha256,
875
+ model=request.model,
876
+ request_config=request.config,
877
+ project_dir=spec.launch_context.project_dir,
878
+ launch_config=spec.launch_context.config,
879
+ require_pin=require_pin,
880
+ )
881
+ from ksadk.studio.native_user_input import admit_native_user_input
882
+
883
+ normalized_input = admit_native_user_input(request.input, user_input)
884
+ prior_turns = [
885
+ previous
886
+ for previous in self.event_store.list_runs(session_id=record.session_id)
887
+ if previous.id != record.id
888
+ and previous.agent_id == spec.agent_id
889
+ and (
890
+ previous.status == RunStatus.COMPLETED
891
+ or (
892
+ previous.status
893
+ in {
894
+ RunStatus.FAILED,
895
+ RunStatus.CANCELLED,
896
+ RunStatus.INTERRUPTED,
897
+ RunStatus.TIMED_OUT,
898
+ }
899
+ and previous.runtime_handle.get("run_id")
900
+ )
901
+ )
902
+ ]
903
+ parent = adapter.session_parent_run_id(record.session_id)
904
+ if prior_turns:
905
+ last = prior_turns[-1]
906
+ if (
907
+ any(
908
+ (previous.runtime_handle.get("native_ref") or {}).get("engine_kind")
909
+ != "native"
910
+ or previous.build_id != spec.build_id
911
+ or previous.manifest_sha256 != spec.manifest_sha256
912
+ for previous in prior_turns
913
+ )
914
+ or parent != last.runtime_handle.get("run_id")
915
+ ):
916
+ raise ValueError("native_studio_cross_runtime_history_unavailable")
917
+ elif parent is not None:
918
+ raise ValueError("native_studio_session_parent_mismatch")
919
+ # The Native ledger is the only history owner. The fields removed here
920
+ # have already been checked against immutable host-side admissions.
921
+ from ksadk.studio.contracts import NativeRunOptions
922
+
923
+ if spec.plugin_bundle_root is not None:
924
+ # Ordinary Studio/DSH owns these normalized selections. Explicit
925
+ # injected hosts retain their separately pinned request contract;
926
+ # their opaque policy names are not Studio approval vocabulary.
927
+ record.native_runtime_options = NativeRunOptions(
928
+ tool_approval_mode=spec.request_config.get("tool_approval_mode"),
929
+ reasoning_effort=spec.request_config.get("effort") or None,
930
+ )
931
+ self.event_store.save(record)
932
+ return request.model_copy(update={
933
+ "metadata": {}, "config": {}, "model": None, "input": normalized_input,
934
+ })
935
+
936
+ async def _execute_runtime_record(
937
+ self, spec, user_input, *, record, runtime_input=None, on_event=None,
938
+ prepared_turn=None, provider_runtime_adapter=False, restored_handle=None,
939
+ ) -> RunRecord:
940
+ run_id, session = record.id, record.session_id
941
+ runtime_type = record.runtime_type
273
942
  started = time.monotonic()
274
- record.status = RunStatus.RUNNING
275
- record.started_at = datetime.now(timezone.utc)
943
+ if restored_handle is None:
944
+ record.status = RunStatus.RUNNING
945
+ record.started_at = datetime.now(timezone.utc)
276
946
  self.event_store.save(record)
947
+ projected_writer = _ProjectedRunEventWriter(self.event_store, run_id, on_event)
948
+ native_restore = (
949
+ restored_handle is not None
950
+ and restored_handle.native_ref.get("engine_kind") == "native"
951
+ )
952
+ native_restored_run_id = restored_handle.run_id if native_restore else None
953
+ existing_events = self.event_store.events(run_id) if native_restore else ()
954
+ # A cold Native replay reconstructs record state from the full ledger.
955
+ # Studio's public event file is a separate projection; its persisted
956
+ # run_seq is the safe prefix, including batches of prior text deltas.
957
+ public_replay_cursor = max(
958
+ (
959
+ fact.get("run_seq", 0)
960
+ for row in existing_events
961
+ if isinstance((fact := row.data.get("runtimeEvent")), dict)
962
+ and fact.get("run_id") == native_restored_run_id
963
+ and type(fact.get("run_seq")) is int
964
+ ),
965
+ default=0,
966
+ )
967
+ approval_surfaces = {
968
+ str(row.data.get("interactionId"))
969
+ for row in existing_events
970
+ if row.type == "a2ui.interaction" and row.data.get("interactionId")
971
+ }
277
972
 
278
- async def persist(runtime_event: RuntimeEvent) -> RunEvent:
973
+ async def persist(
974
+ runtime_event: RuntimeEvent, *, terminal_record: RunRecord | None = None,
975
+ ) -> RunEvent | None:
976
+ # 必须早于 private-delta 过滤:对齐 Codex/Pi/OpenClaw 的 transport
977
+ # liveness 语义,任意真实应用层事件都续期;是否持久化/展示由下面的
978
+ # 投影策略单独决定。
979
+ self._runtime_progress_seq += 1
980
+ if _is_private_harness_delta(runtime_event):
981
+ return None
279
982
  persisted = await self.runtime_events.append_one(record.session_id, runtime_event)
983
+ if (
984
+ native_restore
985
+ and persisted.run_id == native_restored_run_id
986
+ and persisted.run_seq is not None
987
+ and persisted.run_seq <= public_replay_cursor
988
+ ):
989
+ return None
280
990
  event_type, data = project_runtime_event(
281
991
  persisted,
282
992
  session_id=record.session_id,
@@ -289,21 +999,22 @@ class StudioRunService:
289
999
  item_payload = conversation_item.get("payload")
290
1000
  if isinstance(item_payload, dict):
291
1001
  item_payload["revision"] = 1
292
- stored = self.event_store.append(record.id, event_type, data)
293
- if on_event is not None:
294
- on_event(stored)
295
- return stored
1002
+ return projected_writer.append(event_type, data, record=terminal_record)
296
1003
 
297
1004
  handle = None
298
1005
  completed_text_by_item: dict[tuple[str, str], str] = {}
299
1006
  streamed_text_by_item: dict[tuple[str, str], str] = {}
300
1007
  terminal_output_refs: tuple[OutputRef, ...] = ()
1008
+ terminal_notice = ""
1009
+ pending_completed_event: RunCompleted | None = None
301
1010
  runtime_duration_ms: int | None = None
1011
+ native_terminal_metadata_fixed = False
302
1012
  item_phases: dict[tuple[str, str], str | None] = {}
303
1013
  control_queue: asyncio.Queue[
304
1014
  tuple[str, ResumePayload | None, asyncio.Future[None] | None]
305
1015
  ] = asyncio.Queue()
306
1016
  self._control_queues[run_id] = control_queue
1017
+ cancellation_reason = "cancelled"
307
1018
  try:
308
1019
  tool_approval_mode = str(spec.request_config.get("tool_approval_mode") or "")
309
1020
  conversation_request: dict[str, Any] = {
@@ -341,24 +1052,69 @@ class StudioRunService:
341
1052
  },
342
1053
  )
343
1054
  preparation = None
344
- if provider_runtime_adapter:
1055
+ if restored_handle is None and provider_runtime_adapter:
345
1056
  if self.plugin_runtime is None:
346
1057
  raise StudioError(
347
1058
  "PLUGIN_RUNTIME_UNAVAILABLE", "Codex Provider Runtime 不可用",
348
1059
  status_code=503,
349
1060
  )
350
1061
  adapter = self.plugin_runtime.kernel_adapter_provider(spec)()
1062
+ from ksadk.studio.plugin_kernel_adapter import StudioPluginKernelAdapter
1063
+
1064
+ if isinstance(adapter, StudioPluginKernelAdapter):
1065
+ adapter.bind_native_admission(
1066
+ lambda native, bound_request: self._admit_native_studio_start(
1067
+ spec,
1068
+ record,
1069
+ user_input,
1070
+ bound_request,
1071
+ native,
1072
+ require_pin=True,
1073
+ )
1074
+ )
351
1075
  preparation = RuntimeStartPreparation(context=spec.launch_context, adapter=adapter)
352
- handle = await self.executor.start(
353
- spec.launch_context, request, preparation=preparation,
1076
+ elif (
1077
+ restored_handle is None
1078
+ and self.native_harness_ingress
1079
+ and runtime_type == "harness"
1080
+ ):
1081
+ # Keep the selected adapter for this one start. Admission must
1082
+ # run before Native compiles or launches any effectful port.
1083
+ preparation = RuntimeStartPreparation(
1084
+ context=spec.launch_context,
1085
+ adapter=self.executor.create_adapter(spec.launch_context),
1086
+ )
1087
+ from ksadk.harness.native_runtime import NativeRuntimeAdapter
1088
+
1089
+ if isinstance(preparation.adapter, NativeRuntimeAdapter):
1090
+ request = self._admit_native_studio_start(
1091
+ spec,
1092
+ record,
1093
+ user_input,
1094
+ request,
1095
+ preparation.adapter,
1096
+ require_pin=False,
1097
+ )
1098
+ handle = restored_handle or await self._start_public_execution(
1099
+ run_id, spec.launch_context, request, preparation,
354
1100
  )
355
1101
  record.runtime_handle = handle.model_dump(mode="json")
356
1102
  self.event_store.save(record)
357
1103
  self._active_handles[run_id] = handle
1104
+ approval_timeout = spec.request_config.get("approval_timeout_seconds")
1105
+ approval_wait_seconds = (
1106
+ float(approval_timeout) if approval_timeout is not None else None
1107
+ )
1108
+ if approval_wait_seconds is not None and approval_wait_seconds <= 0:
1109
+ raise ValueError("approval_timeout_seconds must be positive")
1110
+ approval_deadline: float | None = None
358
1111
  while True:
359
1112
  terminal_seen = False
360
- should_resume = False
361
- async for event in self.executor.stream(handle):
1113
+ should_resume = restored_handle is not None
1114
+ if restored_handle is not None:
1115
+ self._waiting_modes[run_id] = "resume"
1116
+ restored_handle = None
1117
+ async for event in self._public_execution_stream(handle, run_id):
362
1118
  if (
363
1119
  isinstance(event, RunInterrupted)
364
1120
  and event.interaction_id
@@ -372,15 +1128,32 @@ class StudioRunService:
372
1128
  # the app-server pipe.
373
1129
  self._waiting_modes[run_id] = "live"
374
1130
  continue
375
- if isinstance(event, InteractionResolved) and event.source.framework == "codex":
1131
+ if isinstance(event, InteractionResolved) and (
1132
+ event.source.framework == "codex"
1133
+ or event.source.metadata.get("engine_kind") == "native"
1134
+ ):
376
1135
  submitted = self._submitted_interactions.get(run_id, set())
377
1136
  provider_call_id = str(getattr(event.response, "call_id", None) or "")
378
1137
  if event.interaction_id in submitted or provider_call_id in submitted:
379
1138
  # submit_interaction atomically persists the public
380
1139
  # terminal fact and receipt. Ignore the provider's
381
1140
  # echo so replay never shows the approval twice.
1141
+ if event.source.metadata.get("engine_kind") == "native":
1142
+ # Keep the Native canonical fact for replay and
1143
+ # reconciliation; the Studio action already
1144
+ # wrote its public approval.resolved row.
1145
+ await self.runtime_events.append_one(record.session_id, event)
382
1146
  continue
383
- await persist(event)
1147
+ # ``run.completed`` is only a provider claim until Studio has
1148
+ # resolved its output refs and applied the delivery gate. If
1149
+ # we persist it first and the gate then rejects an empty
1150
+ # delivery, replay contains both ``run.completed`` and
1151
+ # ``run.failed`` for one run. Buffer the successful terminal
1152
+ # fact; all non-success events remain live and durable.
1153
+ if isinstance(event, RunCompleted):
1154
+ pending_completed_event = event
1155
+ else:
1156
+ await persist(event)
384
1157
  if self._cancel_flags.get(run_id):
385
1158
  raise asyncio.CancelledError()
386
1159
  if isinstance(event, ItemStarted) and event.item_kind == "message":
@@ -411,23 +1184,41 @@ class StudioRunService:
411
1184
  if isinstance(part, TextContent)
412
1185
  )
413
1186
  elif isinstance(event, UsageReported):
414
- record.usage = Usage(
415
- input_tokens=event.input_tokens,
416
- output_tokens=event.output_tokens,
417
- total_tokens=event.total_tokens,
418
- cached_input_tokens=event.cached_tokens,
419
- reasoning_output_tokens=event.reasoning_tokens,
420
- reported=True,
421
- source=str(event.source.framework or runtime_type),
422
- )
1187
+ _apply_usage_event(record, event, runtime_type)
423
1188
  elif isinstance(event, InteractionRequested):
424
1189
  record.status = RunStatus.WAITING_INPUT
425
- self._waiting_modes[run_id] = "live"
1190
+ approval_deadline = (
1191
+ asyncio.get_running_loop().time() + approval_wait_seconds
1192
+ if event.interaction_kind == "approval"
1193
+ and approval_wait_seconds is not None
1194
+ else None
1195
+ )
1196
+ native_interaction = event.source.metadata.get("engine_kind") == "native"
1197
+ # Both Managed LangGraph and Native Harness suspend the
1198
+ # stream and accept approvals through resume(). A user
1199
+ # may submit as soon as the request is published,
1200
+ # before the companion RunInterrupted is projected.
1201
+ # Mark that route now instead of briefly sending an
1202
+ # early approval to RuntimeAdapter.submit() (unsupported
1203
+ # by Managed Harness). Codex keeps its live JSON-RPC
1204
+ # submit channel.
1205
+ resume_interaction = native_interaction or runtime_type == "harness"
1206
+ self._waiting_modes[run_id] = (
1207
+ "resume" if resume_interaction else "live"
1208
+ )
1209
+ # Native stream ends at the durable suspension. Its
1210
+ # continuation is a new resume call on the same run.
1211
+ should_resume = should_resume or resume_interaction
426
1212
  self.event_store.save(record)
427
- if event.interaction_kind == "approval":
1213
+ if (
1214
+ event.interaction_kind == "approval"
1215
+ and event.interaction_id not in approval_surfaces
1216
+ ):
428
1217
  self._persist_approval_surface(record, event, on_event=on_event)
1218
+ approval_surfaces.add(event.interaction_id)
429
1219
  elif isinstance(event, InteractionResolved):
430
1220
  record.status = RunStatus.RUNNING
1221
+ approval_deadline = None
431
1222
  self._waiting_modes.pop(run_id, None)
432
1223
  self.event_store.save(record)
433
1224
  elif isinstance(event, RunFailed):
@@ -445,7 +1236,7 @@ class StudioRunService:
445
1236
  "message": str(event.reason or "运行已取消"),
446
1237
  }
447
1238
  elif isinstance(event, RunInterrupted):
448
- if event.reason == "user_pause":
1239
+ if _is_pause_interruption(event):
449
1240
  record.status = RunStatus.PAUSED
450
1241
  should_resume = True
451
1242
  elif record.status == RunStatus.WAITING_INPUT:
@@ -461,7 +1252,19 @@ class StudioRunService:
461
1252
  self.event_store.save(record)
462
1253
  elif isinstance(event, RunCompleted):
463
1254
  terminal_seen = True
1255
+ if event.source.metadata.get("engine_kind") == "native":
1256
+ # A timer can flush the Store's cached record while
1257
+ # canonical persistence awaits. Keep that old record
1258
+ # active until the explicit public terminal commit.
1259
+ record = record.model_copy(deep=True)
464
1260
  record.status = RunStatus.COMPLETED
1261
+ if event.source.metadata.get("engine_kind") == "native":
1262
+ terminal_notice = terminal_verification_notice(event.source.metadata)
1263
+ native_outcome = event.source.metadata.get("outcome")
1264
+ if isinstance(native_outcome, str) and native_outcome in {
1265
+ "verified", "unverified", "partial", "blocked"
1266
+ }:
1267
+ record.verification_outcome = native_outcome
465
1268
  terminal_output_refs = event.output_refs
466
1269
  raw_duration = event.source.metadata.get("duration_ms")
467
1270
  if raw_duration is None:
@@ -476,11 +1279,32 @@ class StudioRunService:
476
1279
  record.status = RunStatus.COMPLETED
477
1280
  break
478
1281
 
479
- command, resume_payload, submit_ack = await control_queue.get()
1282
+ # Cold replay may include older, already resolved cards. Only
1283
+ # signal readiness after the full stream reaches its current
1284
+ # durable suspension and this loop can consume resume commands.
1285
+ recovery_ready = self._native_recovery_ready.pop(run_id, None)
1286
+ if recovery_ready is not None:
1287
+ recovery_ready.set()
1288
+ try:
1289
+ if record.status == RunStatus.WAITING_INPUT and approval_deadline is not None:
1290
+ command, resume_payload, submit_ack = await asyncio.wait_for(
1291
+ control_queue.get(),
1292
+ timeout=max(
1293
+ 0.0,
1294
+ approval_deadline - asyncio.get_running_loop().time(),
1295
+ ),
1296
+ )
1297
+ else:
1298
+ command, resume_payload, submit_ack = await control_queue.get()
1299
+ except TimeoutError:
1300
+ cancellation_reason = "approval_timeout"
1301
+ self._cancel_flags[run_id] = True
1302
+ raise asyncio.CancelledError() from None
480
1303
  if command == "cancel" or self._cancel_flags.get(run_id):
481
1304
  raise asyncio.CancelledError()
482
1305
  if command != "resume":
483
1306
  continue
1307
+ approval_deadline = None
484
1308
  native_thread_id = handle.native_ref.get("thread_id")
485
1309
  target = (
486
1310
  ResumeTarget(kind="thread_id", id=str(native_thread_id))
@@ -523,7 +1347,43 @@ class StudioRunService:
523
1347
  completed_parts = [part for part in completed_text_by_item.values() if part]
524
1348
  fallback_parts = [part for part in streamed_text_by_item.values() if part]
525
1349
  record.output = "\n\n".join(completed_parts or fallback_parts)
1350
+ if record.status == RunStatus.COMPLETED and (
1351
+ _consumed_model_output(record)
1352
+ or (handle is not None and handle.native_ref.get("engine_kind") == "native")
1353
+ ):
1354
+ # 空交付不得判 COMPLETED。harness 的 **provider adapter** 路径就走这里
1355
+ # (runtimeHandle 里 `_ksadk_plugin_provider_runtime_type: harness`),
1356
+ # 我前两版守卫分别错放在 `_schedule_run` 和只盖 `_plugin_run`,于是
1357
+ # run_4abd9ab7 仍然以 COMPLETED + output 长度 0 + 产物 0 收场。
1358
+ #
1359
+ # 收窄条件:只有**确实消耗了模型输出 token** 才算"空交付"。审批/交互
1360
+ # 这类合法地以空输出收束的流程不应被判失败(实测有 3 个审批用例因此挂)。
1361
+ _require_delivery(record.output)
1362
+ if record.status == RunStatus.COMPLETED and record.output and terminal_notice:
1363
+ record.output += terminal_notice
1364
+ if pending_completed_event is not None:
1365
+ native_terminal = (
1366
+ handle is not None and handle.native_ref.get("engine_kind") == "native"
1367
+ and pending_completed_event.source.metadata.get("engine_kind") == "native"
1368
+ )
1369
+ if native_terminal:
1370
+ # Seal the host's completion clock with the public terminal,
1371
+ # before cleanup can be interrupted by process termination.
1372
+ record.completed_at = datetime.now(timezone.utc)
1373
+ record.duration_ms = (
1374
+ runtime_duration_ms if runtime_duration_ms is not None
1375
+ else int((time.monotonic() - started) * 1000)
1376
+ )
1377
+ record.duration_source = (
1378
+ "runtime" if runtime_duration_ms is not None else "studio"
1379
+ )
1380
+ native_terminal_metadata_fixed = True
1381
+ await persist(
1382
+ pending_completed_event, terminal_record=record if native_terminal else None,
1383
+ )
526
1384
  except asyncio.CancelledError:
1385
+ if self._detaching_recoveries and record.status == RunStatus.WAITING_INPUT:
1386
+ raise
527
1387
  cancel_result = "task_cancelled"
528
1388
  if handle is not None and self.executor.is_attached(handle):
529
1389
  try:
@@ -537,27 +1397,51 @@ class StudioRunService:
537
1397
  except Exception: # best effort; close still owns resource cleanup
538
1398
  cancel_result = "cancel_failed"
539
1399
  record.status = RunStatus.CANCELLED
540
- record.error = {"code": "RUN_CANCELLED", "message": "运行已取消"}
1400
+ record.error = (
1401
+ {"code": "APPROVAL_TIMEOUT", "message": "审批等待超时,运行已取消"}
1402
+ if cancellation_reason == "approval_timeout"
1403
+ else {"code": "RUN_CANCELLED", "message": "运行已取消"}
1404
+ )
541
1405
  cancelled_run_id = handle.run_id if handle is not None else run_id
542
- cancelled = RunCanceled(
543
- schema_version=2,
544
- event_id=f"evt_cancel_{uuid4().hex}",
545
- seq=len(self.event_store.events(run_id)) + 1,
546
- timestamp=time.time(),
547
- run_id=cancelled_run_id,
548
- scope_id=cancelled_run_id,
549
- source=SourceRef(
550
- framework="ksadk",
551
- metadata={"cancel_result": cancel_result},
552
- ),
553
- status="canceled",
554
- reason="cancelled",
1406
+ # A provider may emit its own canceled terminal immediately before
1407
+ # the host's cancel flag raises. Preserve that canonical fact;
1408
+ # synthesizing another one would give this Run two terminals.
1409
+ already_cancelled = any(
1410
+ event.type == "run.cancelled"
1411
+ for event in self.event_store.events(run_id)
555
1412
  )
556
- await persist(cancelled)
1413
+ if not already_cancelled:
1414
+ cancelled = RunCanceled(
1415
+ schema_version=2,
1416
+ event_id=f"evt_cancel_{uuid4().hex}",
1417
+ seq=len(self.event_store.events(run_id)) + 1,
1418
+ timestamp=time.time(),
1419
+ run_id=cancelled_run_id,
1420
+ scope_id=cancelled_run_id,
1421
+ source=SourceRef(
1422
+ framework="ksadk",
1423
+ metadata={"cancel_result": cancel_result},
1424
+ ),
1425
+ status="canceled",
1426
+ reason=cancellation_reason,
1427
+ )
1428
+ await persist(cancelled)
557
1429
  raise
558
1430
  except Exception as exc: # noqa: BLE001
559
1431
  record.status = RunStatus.FAILED
560
1432
  record.error = {"code": "RUNTIME_RUN_FAILED", "message": str(exc)}
1433
+ from ksadk.harness.native_driver_failure import (
1434
+ NativeDriverFailure,
1435
+ NativeDriverFailureDiagnostic,
1436
+ )
1437
+
1438
+ diagnostic_metadata = {}
1439
+ if (type(exc) is NativeDriverFailure
1440
+ and type(exc.diagnostic) is NativeDriverFailureDiagnostic):
1441
+ diagnostic_metadata["native_driver_failure"] = exc.diagnostic.model_dump(
1442
+ mode="json"
1443
+ )
1444
+ record.error["details"] = diagnostic_metadata
561
1445
  failed_run_id = handle.run_id if handle is not None else run_id
562
1446
  failure = RunFailed(
563
1447
  schema_version=2,
@@ -566,7 +1450,7 @@ class StudioRunService:
566
1450
  timestamp=time.time(),
567
1451
  run_id=failed_run_id,
568
1452
  scope_id=failed_run_id,
569
- source=SourceRef(framework="ksadk"),
1453
+ source=SourceRef(framework="ksadk", metadata=diagnostic_metadata),
570
1454
  status="failed",
571
1455
  error=ErrorInfo(
572
1456
  code="RUNTIME_RUN_FAILED",
@@ -577,7 +1461,34 @@ class StudioRunService:
577
1461
  )
578
1462
  await persist(failure)
579
1463
  finally:
1464
+ projected_writer.flush()
580
1465
  if handle is not None and self.executor.is_attached(handle):
1466
+ if handle.native_ref.get("engine_kind") == "native":
1467
+ # Persist curated child history before the activation closes.
1468
+ # UI subscriptions are observers and cannot keep it alive.
1469
+ try:
1470
+ children = await self.execution_children(run_id, include_details=True)
1471
+ for child in children:
1472
+ if child["status"] in {"running", "paused"}:
1473
+ child["lastObservedStatus"] = child["status"]
1474
+ child["status"] = "unknown"
1475
+ for message in child.get("messages", []):
1476
+ message["streaming"] = False
1477
+ for fact in child.get("facts", []):
1478
+ if fact.get("status") == "running":
1479
+ fact["status"] = "unknown"
1480
+ for tool in fact.get("tools", []):
1481
+ if tool.get("status") == "running":
1482
+ tool["status"] = "unknown"
1483
+ if children:
1484
+ self.event_store.append(
1485
+ run_id, "child.execution_snapshot", {"children": children}
1486
+ )
1487
+ except Exception as exc:
1488
+ # Optional presentation must never skip activation cleanup.
1489
+ logger.warning(
1490
+ "Child execution snapshot unavailable: %s", type(exc).__name__
1491
+ )
581
1492
  try:
582
1493
  await asyncio.shield(self.executor.close(handle))
583
1494
  except asyncio.CancelledError:
@@ -589,14 +1500,23 @@ class StudioRunService:
589
1500
  self._control_queues.pop(run_id, None)
590
1501
  self._waiting_modes.pop(run_id, None)
591
1502
  self._submitted_interactions.pop(run_id, None)
592
- record.completed_at = datetime.now(timezone.utc)
593
- self._expire_unresolved_interactions(record, on_event=on_event)
594
- if runtime_duration_ms is not None:
595
- record.duration_ms = runtime_duration_ms
596
- record.duration_source = "runtime"
597
- else:
598
- record.duration_ms = int((time.monotonic() - started) * 1000)
599
- record.duration_source = "studio"
1503
+ preserve_waiting = (
1504
+ self._detaching_recoveries and record.status == RunStatus.WAITING_INPUT
1505
+ )
1506
+ preserve_terminal_metadata = (
1507
+ native_terminal_metadata_fixed and record.status == RunStatus.COMPLETED
1508
+ )
1509
+ if not preserve_waiting:
1510
+ if not preserve_terminal_metadata:
1511
+ record.completed_at = datetime.now(timezone.utc)
1512
+ self._expire_unresolved_interactions(record, on_event=on_event)
1513
+ if not preserve_terminal_metadata:
1514
+ if runtime_duration_ms is not None:
1515
+ record.duration_ms = runtime_duration_ms
1516
+ record.duration_source = "runtime"
1517
+ else:
1518
+ record.duration_ms = int((time.monotonic() - started) * 1000)
1519
+ record.duration_source = "studio"
600
1520
  self.event_store.save(record)
601
1521
  await self._sync_trace(record)
602
1522
  return record
@@ -728,6 +1648,17 @@ class StudioRunService:
728
1648
  output_refs=(OutputRef(scope_id=record.id, item_id=item_id, part_id=part_id),),
729
1649
  )
730
1650
  )
1651
+ if not str(output or "").strip():
1652
+ # 空交付不得判 COMPLETED(2026-09-18 实测事故):一次 run 跑了 15 次
1653
+ # 模型调用、并行 3 个子 Agent、约 17 分钟,最后以 **COMPLETED +
1654
+ # output 长度 0 + 产物 0 个** 收场——用户看到"成功"却拿不到任何东西。
1655
+ # 宁可报失败并说清原因,也不要让"什么都没交付"伪装成完成。
1656
+ raise StudioError(
1657
+ "EMPTY_DELIVERY",
1658
+ "本轮没有产出任何交付内容(输出为空,且工作区没有新产物),"
1659
+ "已按失败处理而不是成功。请重试或检查该 Agent 的委派/落盘策略。",
1660
+ status_code=502,
1661
+ )
731
1662
  record.output = output
732
1663
  record.status = RunStatus.COMPLETED
733
1664
  except asyncio.CancelledError:
@@ -802,18 +1733,61 @@ class StudioRunService:
802
1733
  "activationKey": record.session_id,
803
1734
  }
804
1735
  self.event_store.save(record)
1736
+ projected_writer = _ProjectedRunEventWriter(self.event_store, record.id, on_event)
805
1737
  rows_before = await self.session_service.get_events(record.session_id)
806
1738
  after_seq = max((int(row.seq_id or 0) for row in rows_before), default=0)
1739
+ published_seq = after_seq
807
1740
 
808
1741
  async def publish(events: list[RuntimeEvent]) -> None:
1742
+ nonlocal published_seq
809
1743
  for runtime_event in events:
1744
+ # Harness keeps token-level child output in the canonical
1745
+ # session trace for diagnostics. It is deliberately not part
1746
+ # of Studio's public run timeline: the activity projection
1747
+ # never consumes these private deltas, and persisting each one
1748
+ # rewrites the growing run JSON file. Long-running delegated
1749
+ # research can otherwise turn a few useful activity rows into
1750
+ # thousands of O(n²) file writes and make the page appear
1751
+ # frozen. Advance the cursor so final catch-up will not replay
1752
+ # an omitted delta.
1753
+ if _is_private_harness_delta(runtime_event):
1754
+ published_seq = max(published_seq, int(runtime_event.seq or 0))
1755
+ continue
810
1756
  event_type, data = project_runtime_event(
811
1757
  runtime_event,
812
1758
  session_id=record.session_id,
813
1759
  )
814
- stored = self.event_store.append(record.id, event_type, data)
815
- if on_event is not None:
816
- on_event(stored)
1760
+ projected_writer.append(event_type, data)
1761
+ published_seq = max(published_seq, int(runtime_event.seq or 0))
1762
+
1763
+ async def tail_live_events() -> None:
1764
+ # Kernel worker 会把 harness 的增量事实(TEXT_DELTA/ItemUpdated 等)
1765
+ # 在执行期间逐条写入 canonical session store;这里实时转发给 SSE
1766
+ # 订阅端,避免整轮结束后一次性爆发。终态事件会自然结束订阅。
1767
+ async for event in self.runtime_events.subscribe_run(
1768
+ record.session_id,
1769
+ record.id,
1770
+ after_seq=published_seq,
1771
+ poll_interval=0.1,
1772
+ timeout=600,
1773
+ ):
1774
+ try:
1775
+ await publish([event])
1776
+ except asyncio.CancelledError:
1777
+ raise
1778
+ except Exception:
1779
+ logger.exception(
1780
+ "failed to publish live plugin event for run %s", record.id
1781
+ )
1782
+
1783
+ tail_task: asyncio.Task[None] | None = None
1784
+
1785
+ async def stop_tail() -> None:
1786
+ nonlocal tail_task
1787
+ if tail_task is not None:
1788
+ tail_task.cancel()
1789
+ await asyncio.gather(tail_task, return_exceptions=True)
1790
+ tail_task = None
817
1791
 
818
1792
  try:
819
1793
  if self.plugin_runtime is None:
@@ -832,18 +1806,22 @@ class StudioRunService:
832
1806
  }.items()
833
1807
  if value not in {None, ""}
834
1808
  }
835
- result = await self.plugin_runtime.execute(
836
- spec,
837
- {
838
- "user_id": "local-user",
839
- "session_id": record.session_id,
840
- "invocation_id": record.id,
841
- "messages": [{"role": "user", "content": user_input}],
842
- "model": spec.model,
843
- "request_metadata": request_metadata,
844
- },
845
- session_id=record.session_id,
846
- )
1809
+ tail_task = asyncio.create_task(tail_live_events())
1810
+ try:
1811
+ result = await self.plugin_runtime.execute(
1812
+ spec,
1813
+ {
1814
+ "user_id": "local-user",
1815
+ "session_id": record.session_id,
1816
+ "invocation_id": record.id,
1817
+ "messages": [{"role": "user", "content": user_input}],
1818
+ "model": spec.model,
1819
+ "request_metadata": request_metadata,
1820
+ },
1821
+ session_id=record.session_id,
1822
+ )
1823
+ finally:
1824
+ await stop_tail()
847
1825
  if result.session_id != record.session_id:
848
1826
  raise StudioError(
849
1827
  "PLUGIN_SESSION_MISMATCH",
@@ -856,7 +1834,7 @@ class StudioRunService:
856
1834
  )
857
1835
  canonical = await self._plugin_events_after(
858
1836
  record.session_id,
859
- after_seq=after_seq,
1837
+ after_seq=published_seq,
860
1838
  run_id=record.id,
861
1839
  )
862
1840
  if not canonical:
@@ -866,10 +1844,12 @@ class StudioRunService:
866
1844
  usage=result.usage,
867
1845
  )
868
1846
  await publish(canonical)
1847
+ _require_delivery(result.output_text)
869
1848
  record.output = result.output_text
870
1849
  record.status = RunStatus.COMPLETED
871
1850
  _apply_plugin_usage(record, result.usage)
872
1851
  except asyncio.CancelledError:
1852
+ await stop_tail()
873
1853
  record.status = RunStatus.CANCELLED
874
1854
  record.error = {"code": "RUN_CANCELLED", "message": "运行已取消"}
875
1855
  cancelled = await self.runtime_events.append_one(
@@ -883,12 +1863,13 @@ class StudioRunService:
883
1863
  await publish([cancelled])
884
1864
  raise
885
1865
  except Exception as exc: # noqa: BLE001 - plugin boundary is typed below
1866
+ await stop_tail()
886
1867
  record.status = RunStatus.FAILED
887
1868
  code = str(getattr(exc, "code", "PLUGIN_RUNTIME_FAILED"))
888
1869
  record.error = {"code": code, "message": str(exc)}
889
1870
  canonical = await self._plugin_events_after(
890
1871
  record.session_id,
891
- after_seq=after_seq,
1872
+ after_seq=published_seq,
892
1873
  run_id=record.id,
893
1874
  )
894
1875
  if canonical:
@@ -909,6 +1890,7 @@ class StudioRunService:
909
1890
  )
910
1891
  await publish([failed])
911
1892
  finally:
1893
+ projected_writer.flush()
912
1894
  record.completed_at = datetime.now(timezone.utc)
913
1895
  record.duration_ms = int((time.monotonic() - started) * 1000)
914
1896
  record.duration_source = "studio"
@@ -1058,12 +2040,17 @@ class StudioRunService:
1058
2040
  continue
1059
2041
  event_type, data = projected
1060
2042
  if event_type == "message.delta":
1061
- output_text += str(data.get("delta") or "")
2043
+ # Canonical RuntimeEvent projection names the visible text
2044
+ # field ``text``; legacy Studio envelopes used ``delta``.
2045
+ # The Kernel path consumes both because it may replay a
2046
+ # mixed-version session during a rolling upgrade.
2047
+ output_text += str(data.get("text") or data.get("delta") or "")
1062
2048
  elif event_type == "run.completed":
1063
2049
  output_text = str(data.get("output_text") or output_text)
1064
2050
  stored = self.event_store.append(record.id, event_type, data)
1065
2051
  if on_event is not None:
1066
2052
  on_event(stored)
2053
+ _require_delivery(output_text)
1067
2054
  record.output = output_text
1068
2055
  record.status = RunStatus.COMPLETED
1069
2056
  record.completed_at = datetime.now(timezone.utc)
@@ -1134,6 +2121,9 @@ class StudioRunService:
1134
2121
  async def cancel_run(self, run_id: str) -> dict[str, str]:
1135
2122
  """Request cancellation; the flag and executor perform the actual stop."""
1136
2123
  self._cancel_flags[run_id] = True
2124
+ recovery_ready = self._native_recovery_ready.pop(run_id, None)
2125
+ if recovery_ready is not None:
2126
+ recovery_ready.set()
1137
2127
  queue = self._control_queues.get(run_id)
1138
2128
  if queue is not None:
1139
2129
  queue.put_nowait(("cancel", None, None))
@@ -1152,29 +2142,13 @@ class StudioRunService:
1152
2142
  pass
1153
2143
  return {"runId": run_id, "status": "cancelling"}
1154
2144
 
1155
- async def pause_run(self, run_id: str) -> dict[str, str]:
1156
- record = self.event_store.get(run_id)
1157
- if record.status == RunStatus.WAITING_INPUT:
1158
- raise StudioError(
1159
- "RUN_WAITING_INPUT",
1160
- "当前运行正在等待交互输入,请先处理卡片",
1161
- status_code=409,
1162
- )
1163
- if record.status != RunStatus.RUNNING:
1164
- raise StudioError("RUN_NOT_RUNNING", "只有运行中的 Run 可以暂停", status_code=409)
1165
- handle = self._active_handles.get(run_id)
1166
- if handle is None or not self.executor.is_attached(handle):
1167
- raise StudioError("RUN_NOT_ATTACHED", "运行句柄已不在当前进程", status_code=409)
1168
- result = await self.executor.pause(handle)
1169
- if result is PauseResult.NOT_SUPPORTED:
1170
- raise StudioError(
1171
- "RUN_PAUSE_UNSUPPORTED",
1172
- "当前 Runtime 不支持可恢复暂停",
1173
- status_code=409,
1174
- )
1175
- if result is not PauseResult.PAUSED_ACTIVE_TURN:
1176
- raise StudioError("RUN_PAUSE_FAILED", "暂停运行失败", status_code=409)
1177
- return {"runId": run_id, "status": "pausing"}
2145
+
2146
+
2147
+
2148
+
2149
+
2150
+
2151
+
1178
2152
 
1179
2153
  async def resume_run(self, run_id: str) -> dict[str, str]:
1180
2154
  record = self.event_store.get(run_id)
@@ -1187,7 +2161,9 @@ class StudioRunService:
1187
2161
  "Studio 进程已重启,当前暂停点无法恢复",
1188
2162
  status_code=409,
1189
2163
  )
1190
- queue.put_nowait(("resume", ResumePayload(kind="free_text", data="继续运行"), None))
2164
+ # A paused-run continuation is a control action, with no new user
2165
+ # message or approval. Keep the RuntimeAdapter's empty-payload seam.
2166
+ queue.put_nowait(("resume", None, None))
1191
2167
  return {"runId": run_id, "status": "resuming"}
1192
2168
 
1193
2169
  async def submit_interaction(
@@ -1232,221 +2208,14 @@ class StudioRunService:
1232
2208
  idempotency_key: str,
1233
2209
  request_digest: str,
1234
2210
  ) -> dict[str, Any]:
1235
- events = self.event_store.events(run_id)
1236
- prior = next(
1237
- (
1238
- event
1239
- for event in reversed(events)
1240
- if event.type == "a2ui.action"
1241
- and str(event.data.get("interactionId") or "") == interaction_id
1242
- and _interaction_revision(event.data) > 0
1243
- ),
1244
- None,
1245
- )
1246
- if prior is not None:
1247
- if str(prior.data.get("idempotencyKey") or "") != idempotency_key:
1248
- raise StudioError(
1249
- "INTERACTION_ALREADY_RESOLVED",
1250
- "该交互已由其他请求处理",
1251
- status_code=409,
1252
- )
1253
- if str(prior.data.get("requestDigest") or "") != request_digest:
1254
- raise StudioError(
1255
- "INTERACTION_IDEMPOTENCY_CONFLICT",
1256
- "同一幂等键不能提交不同内容",
1257
- status_code=409,
1258
- )
1259
- # A receipt replay is read-only. The run may already be waiting
1260
- # for a different interaction; never clear that newer wait state.
1261
- return dict(prior.data.get("receipt") or {})
2211
+ from ksadk.studio.interaction_submission import submit_interaction_locked
1262
2212
 
1263
- interaction = next(
1264
- (
1265
- event
1266
- for event in reversed(events)
1267
- if event.type == "a2ui.interaction"
1268
- and str(event.data.get("interactionId") or event.data.get("interaction_id") or "")
1269
- == interaction_id
1270
- ),
1271
- None,
1272
- )
1273
- if interaction is None:
1274
- raise StudioError("INTERACTION_NOT_FOUND", "交互请求不存在", status_code=404)
1275
- revision = _interaction_revision(interaction.data)
1276
- if revision < 1:
1277
- raise StudioError(
1278
- "INTERACTION_READ_ONLY",
1279
- "交互请求缺少权威 revision,只能查看不能操作",
1280
- status_code=409,
1281
- )
1282
- if expected_revision != revision:
1283
- raise StudioError(
1284
- "INTERACTION_REVISION_MISMATCH",
1285
- "交互版本已变化,请刷新后重试",
1286
- status_code=409,
1287
- details={"expectedRevision": expected_revision, "actualRevision": revision},
1288
- )
1289
- record = self.event_store.get(run_id)
1290
- if record.status != RunStatus.WAITING_INPUT:
1291
- raise StudioError(
1292
- "INTERACTION_NOT_PENDING",
1293
- "该 Run 当前没有等待中的交互",
1294
- status_code=409,
1295
- )
1296
- kind = str(interaction.data.get("kind") or "form")
1297
- if kind != "approval" and name not in {"cancel", "skip"}:
1298
- schema = interaction.data.get("inputSchema") or {}
1299
- errors = list(Draft202012Validator(schema).iter_errors(data))
1300
- if errors:
1301
- raise StudioError(
1302
- "INTERACTION_RESPONSE_INVALID",
1303
- "请填写必填问题后再提交。",
1304
- status_code=422,
1305
- )
1306
- provider_call_id = str(
1307
- interaction.data.get("callId") or interaction.data.get("call_id") or ""
1308
- )
1309
- if kind == "approval" and not provider_call_id:
1310
- approval_request = next(
1311
- (
1312
- event
1313
- for event in reversed(events)
1314
- if event.type == "approval.requested"
1315
- and str(event.data.get("approvalId") or event.data.get("interactionId") or "")
1316
- == interaction_id
1317
- ),
1318
- None,
1319
- )
1320
- if approval_request is not None:
1321
- provider_call_id = str(
1322
- approval_request.data.get("callId")
1323
- or approval_request.data.get("call_id")
1324
- or ""
1325
- )
1326
- provider_call_id = provider_call_id or interaction_id
1327
- submitted = self._submitted_interactions.setdefault(run_id, set())
1328
- submitted.update({interaction_id, provider_call_id})
1329
- decision = str(data.get("decision") or name)
1330
- if kind == "approval" and record.runtime_type == "codex":
1331
- # The shared Web action is named `reject`; Codex app-server calls
1332
- # the same decision `deny`/`decline`. Do not let a UI alias fall
1333
- # through as an unsupported native approval decision.
1334
- decision = {
1335
- "reject": "deny",
1336
- "rejected": "deny",
1337
- }.get(decision.lower(), decision)
1338
- payload_data = {**data, "decision": decision}
1339
- payload = ResumePayload(
1340
- kind="approval_decision" if kind == "approval" else "hitl_answer",
1341
- call_id=provider_call_id,
1342
- data=payload_data,
1343
- )
1344
- mode = self._waiting_modes.get(run_id)
1345
- handle = self._active_handles.get(run_id)
1346
- if mode == "live":
1347
- if handle is None or not self.executor.is_attached(handle):
1348
- raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
1349
- try:
1350
- await asyncio.wait_for(
1351
- self.executor.submit(handle, payload),
1352
- timeout=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
1353
- )
1354
- except TimeoutError as exc:
1355
- submitted.difference_update({interaction_id, provider_call_id})
1356
- raise StudioError(
1357
- "INTERACTION_SUBMIT_FAILED",
1358
- "运行时未及时确认交互提交,请重试",
1359
- status_code=409,
1360
- ) from exc
1361
- except Exception as exc: # noqa: BLE001 - provider errors are user-safe here
1362
- submitted.difference_update({interaction_id, provider_call_id})
1363
- raise StudioError(
1364
- "INTERACTION_SUBMIT_FAILED",
1365
- str(exc),
1366
- status_code=409,
1367
- ) from exc
1368
- elif mode == "resume":
1369
- queue = self._control_queues.get(run_id)
1370
- if queue is None:
1371
- raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
1372
- submit_ack = asyncio.get_running_loop().create_future()
1373
- queue.put_nowait(("resume", payload, submit_ack))
1374
- try:
1375
- await asyncio.wait_for(
1376
- submit_ack,
1377
- timeout=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
1378
- )
1379
- except TimeoutError as exc:
1380
- submitted.difference_update({interaction_id, provider_call_id})
1381
- raise StudioError(
1382
- "INTERACTION_SUBMIT_FAILED",
1383
- "运行时未及时确认交互提交,请重试",
1384
- status_code=409,
1385
- ) from exc
1386
- except Exception as exc: # noqa: BLE001 - provider errors are user-safe here
1387
- submitted.difference_update({interaction_id, provider_call_id})
1388
- raise StudioError(
1389
- "INTERACTION_SUBMIT_FAILED",
1390
- str(exc),
1391
- status_code=409,
1392
- ) from exc
1393
- else:
1394
- submitted.difference_update({interaction_id, provider_call_id})
1395
- raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
1396
-
1397
- next_revision = revision + 1
1398
- normalized_action = name.lower()
1399
- if normalized_action in {"approve", "approved", "accept", "accepted"}:
1400
- outcome = "approved"
1401
- response_summary = "已同意"
1402
- elif normalized_action in {"reject", "rejected", "deny", "denied", "decline"}:
1403
- outcome = "rejected"
1404
- response_summary = "已拒绝"
1405
- elif normalized_action in {"cancel", "cancelled", "canceled"}:
1406
- outcome = "cancelled"
1407
- response_summary = "已反馈给 Agent" if data.get("feedback") else "已取消"
1408
- else:
1409
- outcome = "submitted"
1410
- response_summary = "已提交"
1411
- resolved_data = {
1412
- "runId": run_id,
1413
- "interactionId": interaction_id,
1414
- "callId": provider_call_id,
1415
- "name": name,
1416
- "action": name,
1417
- "outcome": outcome,
1418
- "actor": "user",
1419
- "responseSummary": response_summary,
1420
- "data": data,
1421
- "revision": next_revision,
1422
- "expectedRevision": expected_revision,
1423
- "idempotencyKey": idempotency_key,
1424
- }
1425
- action_data = {
1426
- "runId": run_id,
1427
- "surfaceId": str(
1428
- interaction.data.get("surfaceId") or interaction.data.get("surface_id") or ""
1429
- ),
1430
- "interactionId": interaction_id,
1431
- "callId": provider_call_id,
1432
- "actionId": f"action-{interaction_id}",
1433
- "name": name,
1434
- "data": data,
1435
- "revision": next_revision,
1436
- "expectedRevision": expected_revision,
1437
- "idempotencyKey": idempotency_key,
1438
- "requestDigest": request_digest,
1439
- }
1440
- _, _, receipt = self.event_store.append_interaction_resolution(
1441
- run_id,
1442
- resolved_type=("approval.resolved" if kind == "approval" else "interaction.resolved"),
1443
- resolved_data=resolved_data,
1444
- action_data=action_data,
2213
+ return await submit_interaction_locked(
2214
+ self, run_id, interaction_id, name=name, data=data,
2215
+ expected_revision=expected_revision, idempotency_key=idempotency_key,
2216
+ request_digest=request_digest, revision_of=_interaction_revision,
2217
+ submit_timeout_seconds=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
1445
2218
  )
1446
- record.status = RunStatus.RUNNING
1447
- self.event_store.save(record)
1448
- self._waiting_modes.pop(run_id, None)
1449
- return receipt
1450
2219
 
1451
2220
  def _expire_unresolved_interactions(
1452
2221
  self,
@@ -1684,7 +2453,41 @@ class StudioRunService:
1684
2453
  if previous.agent_id != agent_id or previous.status != RunStatus.COMPLETED:
1685
2454
  continue
1686
2455
  messages.append({"role": "user", "content": previous.input})
1687
- messages.append({"role": "assistant", "content": previous.output})
2456
+ assistant_content = previous.output
2457
+ if previous.runtime_type == "harness":
2458
+ projection = project_run_activities(
2459
+ previous,
2460
+ self.event_store.events(previous.id),
2461
+ )
2462
+ children = [
2463
+ activity
2464
+ for activity in projection.get("activities", [])
2465
+ if activity.get("kind") == "subagent"
2466
+ ][:8]
2467
+ if children:
2468
+ status_text = {
2469
+ "completed": "已完成",
2470
+ "failed": "失败",
2471
+ "cancelled": "已取消",
2472
+ "running": "仍在运行",
2473
+ "unknown": "状态未确认",
2474
+ }
2475
+ facts = ";".join(
2476
+ f"子智能体“{child.get('label') or '未命名'}”"
2477
+ f"{status_text.get(str(child.get('status')), '状态未确认')}"
2478
+ for child in children
2479
+ )
2480
+ # Lifecycle evidence belongs to the completed assistant turn.
2481
+ # A standalone system message inserted between historical turns
2482
+ # can outweigh the next user request on some providers and make
2483
+ # a new turn answer an old child result instead (conversation
2484
+ # "cross-talk"). Keep the evidence attached to the turn it
2485
+ # describes so the newest user message remains authoritative.
2486
+ assistant_content = (
2487
+ f"{assistant_content}\n\n"
2488
+ f"[Harness 执行记录] {facts}。"
2489
+ )
2490
+ messages.append({"role": "assistant", "content": assistant_content})
1688
2491
  messages.append({"role": "user", "content": user_input})
1689
2492
  return messages
1690
2493
 
@@ -1786,6 +2589,40 @@ def _interaction_revision(data: Mapping[str, Any]) -> int:
1786
2589
  return 0
1787
2590
 
1788
2591
 
2592
+ def _apply_usage_event(
2593
+ record: RunRecord, event: UsageReported, runtime_type: str = ""
2594
+ ) -> None:
2595
+ """Apply a canonical UsageReported event to the run record by ``mode``.
2596
+
2597
+ 长任务方案 §19.2 usage_mode / 交接记录 §6 Usage 计量缺陷修复:
2598
+ - ``delta``:单次调用的增量,累加到 record.usage(引擎每模型调用一条);
2599
+ - ``cumulative`` / ``snapshot``:provider 已给出累计/快照值,直接覆盖。
2600
+ 缺省按 ``delta`` 处理,保持旧事件「逐次累加」语义不回退。
2601
+ """
2602
+ mode = getattr(event, "mode", "delta") or "delta"
2603
+ source = str(event.source.framework or runtime_type or "")
2604
+ if mode == "delta":
2605
+ record.usage = Usage(
2606
+ input_tokens=record.usage.input_tokens + event.input_tokens,
2607
+ output_tokens=record.usage.output_tokens + event.output_tokens,
2608
+ total_tokens=record.usage.total_tokens + event.total_tokens,
2609
+ cached_input_tokens=record.usage.cached_input_tokens + event.cached_tokens,
2610
+ reasoning_output_tokens=record.usage.reasoning_output_tokens + event.reasoning_tokens,
2611
+ reported=True,
2612
+ source=source or record.usage.source,
2613
+ )
2614
+ else:
2615
+ record.usage = Usage(
2616
+ input_tokens=event.input_tokens,
2617
+ output_tokens=event.output_tokens,
2618
+ total_tokens=event.total_tokens,
2619
+ cached_input_tokens=event.cached_tokens,
2620
+ reasoning_output_tokens=event.reasoning_tokens,
2621
+ reported=True,
2622
+ source=source or record.usage.source,
2623
+ )
2624
+
2625
+
1789
2626
  def _apply_plugin_usage(record: RunRecord, usage: Mapping[str, Any]) -> None:
1790
2627
  normalized = _normalized_plugin_usage(usage)
1791
2628
  if not normalized["reported"]:
@@ -1801,6 +2638,31 @@ def _apply_plugin_usage(record: RunRecord, usage: Mapping[str, Any]) -> None:
1801
2638
  )
1802
2639
 
1803
2640
 
2641
+ def _is_private_harness_delta(event: RuntimeEvent) -> bool:
2642
+ """Return whether *event* is an internal token delta, not a UI activity.
2643
+
2644
+ Managed Harness projects its rich event stream to canonical status items.
2645
+ ``text.delta`` and ``reasoning.delta`` contain private token fragments,
2646
+ are intentionally ignored by ``run_activity``, and must not be copied into
2647
+ Studio's durable session or run stores. User-visible parent answer
2648
+ streaming uses canonical ``message`` items and therefore remains
2649
+ unaffected.
2650
+ """
2651
+
2652
+ if not isinstance(event, ItemCompleted) or event.item_kind != "status":
2653
+ return False
2654
+ if event.source.framework != "ksadk":
2655
+ return False
2656
+ native_event_type = str(event.source.metadata.get("native_event_type") or "")
2657
+ return native_event_type in {"reasoning.delta", "text.delta"}
2658
+
2659
+
2660
+ def _is_pause_interruption(event: RunInterrupted) -> bool:
2661
+ return event.reason == "user_pause" or (
2662
+ event.reason == "paused" and event.source.metadata.get("engine_kind") == "native"
2663
+ )
2664
+
2665
+
1804
2666
  def project_runtime_event(
1805
2667
  event: RuntimeEvent,
1806
2668
  *,
@@ -1836,8 +2698,23 @@ def project_runtime_event(
1836
2698
  projected = "run.cancelled"
1837
2699
  payload = {"reason": event.reason or ""}
1838
2700
  elif isinstance(event, RunInterrupted):
1839
- projected = "run.paused" if event.reason == "user_pause" else "run.interrupted"
2701
+ projected = "run.paused" if _is_pause_interruption(event) else "run.interrupted"
1840
2702
  payload = {"reason": event.reason or ""}
2703
+ elif isinstance(event, ItemStarted) and event.item_kind in {"message", "reasoning"}:
2704
+ # 首段文本随 ItemStarted 到达;不投影会让 live 流丢掉第一个 chunk
2705
+ # (增量在 ItemUpdated 里才开始出现)。
2706
+ text = ""
2707
+ if event.initial:
2708
+ for part in event.initial.parts:
2709
+ if isinstance(part, TextContent):
2710
+ text += part.text
2711
+ is_thinking = event.item_kind == "reasoning"
2712
+ if text:
2713
+ projected = "thinking.delta" if is_thinking else "message.delta"
2714
+ payload = {"text": text}
2715
+ else:
2716
+ projected = "thinking.started" if is_thinking else "message.started"
2717
+ payload = {"itemId": event.item_id}
1841
2718
  elif isinstance(event, ItemUpdated) and event.item_kind in {"message", "reasoning"}:
1842
2719
  text = event.update.text if isinstance(event.update, TextContent) else ""
1843
2720
  is_thinking = event.item_kind == "reasoning"
@@ -1873,6 +2750,11 @@ def project_runtime_event(
1873
2750
  else:
1874
2751
  projected = "tool.started"
1875
2752
  payload = {}
2753
+ elif (isinstance(event, ItemUpdated) and event.item_kind == "tool_call"
2754
+ and event.source.metadata.get("tool_display")):
2755
+ projected = "tool.progress"
2756
+ payload = {"callId": event.source.metadata.get("call_id"),
2757
+ "tool": event.source.metadata.get("tool_name"), "status": "running"}
1876
2758
  elif isinstance(event, ItemCompleted) and event.item_kind == "tool_call":
1877
2759
  tool_call = _first_content(event.snapshot, ToolCallContent)
1878
2760
  tool_result = _first_content(event.snapshot, ToolResultContent)
@@ -1885,7 +2767,10 @@ def project_runtime_event(
1885
2767
  )
1886
2768
  payload = {
1887
2769
  "callId": tool_call.call_id,
1888
- "status": str(result.get("status") or ""),
2770
+ "status": (
2771
+ "failed" if tool_result is not None and tool_result.is_error
2772
+ else str(result.get("status") or "")
2773
+ ),
1889
2774
  "exitCode": result.get("exit_code"),
1890
2775
  "durationMs": result.get("duration_ms"),
1891
2776
  "output": str(result.get("output") or ""),
@@ -1939,8 +2824,8 @@ def project_runtime_event(
1939
2824
  projected = "approval.resolved"
1940
2825
  payload = {
1941
2826
  "approvalId": event.interaction_id,
1942
- "callId": "",
1943
- "decision": "",
2827
+ "callId": str(event.source.metadata.get("call_id") or ""),
2828
+ "decision": event.response.decision,
1944
2829
  }
1945
2830
  else:
1946
2831
  projected = "a2ui.action"