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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (819) hide show
  1. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/METADATA +13 -9
  2. agentengine_sdk_python-0.8.5.dist-info/RECORD +1296 -0
  3. ksadk/_build_provenance.json +1 -1
  4. ksadk/_process.py +83 -0
  5. ksadk/_str_enum.py +17 -0
  6. ksadk/a2ui/core.py +3 -3
  7. ksadk/api/client.py +111 -91
  8. ksadk/builders/code_builder.py +74 -21
  9. ksadk/builders/framework_requirements.py +22 -3
  10. ksadk/builders/ks3_uploader.py +36 -7
  11. ksadk/builders/managed_runtime_builder.py +1 -0
  12. ksadk/cli/__init__.py +68 -3
  13. ksadk/cli/cmd_config.py +4 -4
  14. ksadk/cli/cmd_create.py +12 -12
  15. ksadk/cli/cmd_hermes.py +24 -2
  16. ksadk/cli/cmd_invoke.py +28 -5
  17. ksadk/cli/cmd_managed_runtime.py +13 -1
  18. ksadk/cli/cmd_openclaw.py +26 -1
  19. ksadk/cli/cmd_plugin.py +76 -13
  20. ksadk/cli/cmd_studio.py +64 -16
  21. ksadk/cli/cmd_web.py +2 -5
  22. ksadk/cli/openclaw_env.py +1 -1
  23. ksadk/cli/resource_common.py +26 -0
  24. ksadk/cli/runtime_bootstrap.py +23 -1
  25. ksadk/codex/client.py +425 -96
  26. ksadk/codex/projection.py +24 -0
  27. ksadk/codex/runtime.py +56 -298
  28. ksadk/common/auth.py +16 -10
  29. ksadk/configs/__init__.py +1 -1
  30. ksadk/configs/env_registry.py +153 -31
  31. ksadk/configs/env_registry_studio.py +86 -0
  32. ksadk/configs/global_config.py +1 -1
  33. ksadk/configs/settings.py +3 -3
  34. ksadk/connector/__init__.py +17 -0
  35. ksadk/connector/channel.py +477 -0
  36. ksadk/context_engine/capabilities.py +52 -4
  37. ksadk/conversations/attachment_storage.py +10 -3
  38. ksadk/conversations/attachments.py +16 -5
  39. ksadk/conversations/message_projection.py +133 -21
  40. ksadk/conversations/normalize.py +28 -10
  41. ksadk/conversations/projector.py +4 -6
  42. ksadk/conversations/runtime_baseline.py +37 -0
  43. ksadk/conversations/runtime_invocation.py +22 -4
  44. ksadk/conversations/runtime_observability.py +39 -10
  45. ksadk/conversations/runtime_payloads.py +9 -0
  46. ksadk/conversations/runtime_persistence.py +105 -9
  47. ksadk/conversations/runtime_preparation.py +42 -33
  48. ksadk/conversations/runtime_resume.py +50 -2
  49. ksadk/conversations/runtime_stream_events.py +34 -35
  50. ksadk/conversations/runtime_streaming.py +33 -7
  51. ksadk/deployment/env_forward.py +2 -2
  52. ksadk/deployment/providers/serverless.py +1 -1
  53. ksadk/events/adapters/_codex_items.py +16 -8
  54. ksadk/events/adapters/_codex_validators.py +18 -7
  55. ksadk/events/adapters/_langgraph_support.py +34 -0
  56. ksadk/events/adapters/langgraph.py +15 -1
  57. ksadk/events/canonical_store.py +3 -1
  58. ksadk/events/projections.py +1 -1
  59. ksadk/events/session_event.py +2 -2
  60. ksadk/harness/__init__.py +14 -0
  61. ksadk/harness/app.py +11 -0
  62. ksadk/harness/artifact_store.py +213 -0
  63. ksadk/harness/capabilities.py +115 -0
  64. ksadk/harness/capability_runtime.py +237 -0
  65. ksadk/harness/closure_contract.py +92 -0
  66. ksadk/harness/compaction_record.py +122 -0
  67. ksadk/harness/compiler.py +284 -0
  68. ksadk/harness/config.py +2 -2
  69. ksadk/harness/conformance/__init__.py +33 -0
  70. ksadk/harness/conformance/contract.py +506 -0
  71. ksadk/harness/conformance/fixtures.py +102 -0
  72. ksadk/harness/context_engine.py +403 -0
  73. ksadk/harness/context_maintenance.py +259 -0
  74. ksadk/harness/context_manifest.py +188 -0
  75. ksadk/harness/context_tuning.py +168 -0
  76. ksadk/harness/draft_runtime.py +322 -0
  77. ksadk/harness/engine/__init__.py +40 -0
  78. ksadk/harness/engine/base.py +122 -0
  79. ksadk/harness/engine/budgets.py +108 -0
  80. ksadk/harness/engine/capability_declarations.py +59 -0
  81. ksadk/harness/engine/completion_guard.py +42 -0
  82. ksadk/harness/engine/context_pipeline.py +579 -0
  83. ksadk/harness/engine/graph_builder.py +859 -0
  84. ksadk/harness/engine/langgraph.py +931 -0
  85. ksadk/harness/engine/mcp_disclosure.py +750 -0
  86. ksadk/harness/engine/policy_runtime.py +70 -0
  87. ksadk/harness/engine/postgres_checkpointer.py +63 -0
  88. ksadk/harness/engine/run_control_bridge.py +80 -0
  89. ksadk/harness/engine/run_types.py +88 -0
  90. ksadk/harness/engine/skill_disclosure.py +189 -0
  91. ksadk/harness/engine/spans.py +41 -0
  92. ksadk/harness/engine/subagents.py +343 -0
  93. ksadk/harness/engine/thread_ids.py +112 -0
  94. ksadk/harness/engine/tool_dispatch.py +144 -0
  95. ksadk/harness/event_tree.py +212 -0
  96. ksadk/harness/events.py +461 -0
  97. ksadk/harness/execution_policy.py +94 -0
  98. ksadk/harness/insights.py +210 -0
  99. ksadk/harness/lifecycle.py +578 -0
  100. ksadk/harness/lifecycle_conformance.py +233 -0
  101. ksadk/harness/lifecycle_local_plane.py +104 -0
  102. ksadk/harness/loop/__init__.py +44 -0
  103. ksadk/harness/loop/reason.py +454 -0
  104. ksadk/harness/loop/tools.py +762 -0
  105. ksadk/harness/managed_runtime.py +884 -0
  106. ksadk/harness/mcp_matrix.py +474 -0
  107. ksadk/harness/mcp_runtime.py +471 -0
  108. ksadk/harness/memory_consolidation.py +157 -0
  109. ksadk/harness/memory_loop.py +156 -0
  110. ksadk/harness/memory_runtime.py +541 -0
  111. ksadk/harness/metrics.py +90 -0
  112. ksadk/harness/model_capability.py +253 -0
  113. ksadk/harness/model_provider.py +220 -0
  114. ksadk/harness/observability/__init__.py +29 -0
  115. ksadk/harness/observability/inspection.py +528 -0
  116. ksadk/harness/observability/metrics.py +167 -0
  117. ksadk/harness/observability/otel_exporter.py +218 -0
  118. ksadk/harness/prompt_cache.py +70 -0
  119. ksadk/harness/public_activity.py +154 -0
  120. ksadk/harness/readiness.py +249 -0
  121. ksadk/harness/reasoner.py +466 -13
  122. ksadk/harness/release_readiness.py +140 -0
  123. ksadk/harness/resource_ref.py +69 -0
  124. ksadk/harness/run_control.py +668 -0
  125. ksadk/harness/runtime.py +171 -136
  126. ksadk/harness/runtime_capabilities.py +158 -0
  127. ksadk/harness/runtime_server.py +514 -0
  128. ksadk/harness/sandbox_adapters.py +739 -0
  129. ksadk/harness/sandbox_backend.py +530 -0
  130. ksadk/harness/sandbox_conformance.py +288 -0
  131. ksadk/harness/sandbox_lease.py +77 -0
  132. ksadk/harness/sandbox_lease_conformance.py +259 -0
  133. ksadk/harness/sandbox_matrix.py +170 -0
  134. ksadk/harness/sandbox_reconciliation.py +140 -0
  135. ksadk/harness/sandbox_recovery.py +586 -0
  136. ksadk/harness/sandbox_recovery_conformance.py +246 -0
  137. ksadk/harness/session_store.py +243 -0
  138. ksadk/harness/skill_composition.py +212 -0
  139. ksadk/harness/skill_feedback.py +123 -0
  140. ksadk/harness/skill_matrix.py +233 -0
  141. ksadk/harness/skill_runtime.py +259 -0
  142. ksadk/harness/spec.py +332 -0
  143. ksadk/harness/state.py +153 -0
  144. ksadk/harness/strategies.py +149 -0
  145. ksadk/harness/subagent.py +253 -0
  146. ksadk/harness/tool_policy.py +131 -0
  147. ksadk/harness/tool_receipts.py +96 -0
  148. ksadk/harness/tool_reliability.py +81 -0
  149. ksadk/harness/tools.py +38 -13
  150. ksadk/harness/user_errors.py +69 -0
  151. ksadk/harness/working_context.py +81 -0
  152. ksadk/harness/working_context_patch.py +99 -0
  153. ksadk/identity/resolver.py +18 -0
  154. ksadk/interaction/__init__.py +1 -1
  155. ksadk/interaction/ledger.py +1 -1
  156. ksadk/interaction/provider.py +2 -1
  157. ksadk/interaction/providers/__init__.py +3 -1
  158. ksadk/interaction/providers/adk.py +1 -1
  159. ksadk/interaction/providers/codex.py +1 -1
  160. ksadk/interaction/providers/harness.py +50 -0
  161. ksadk/interaction/providers/langgraph.py +2 -1
  162. ksadk/kernel/__init__.py +10 -0
  163. ksadk/kernel/approval_presentation.py +91 -0
  164. ksadk/kernel/authorization.py +1 -1
  165. ksadk/kernel/bootstrap.py +114 -106
  166. ksadk/kernel/contract_fingerprints.py +1 -1
  167. ksadk/kernel/contracts.py +5 -0
  168. ksadk/kernel/control.py +32 -1
  169. ksadk/kernel/execution_grants.py +221 -0
  170. ksadk/kernel/execution_grants_memory.py +133 -0
  171. ksadk/kernel/execution_grants_postgres.py +217 -0
  172. ksadk/kernel/execution_grants_sqlite.py +209 -0
  173. ksadk/kernel/ingress.py +42 -29
  174. ksadk/kernel/mapping.py +1 -1
  175. ksadk/kernel/memory_store.py +24 -11
  176. ksadk/kernel/postgres_store.py +63 -33
  177. ksadk/kernel/recovery.py +78 -14
  178. ksadk/kernel/sql/001_agent_kernel.sql +23 -0
  179. ksadk/kernel/sqlite_store.py +331 -25
  180. ksadk/kernel/state.py +2 -3
  181. ksadk/kernel/store.py +72 -2
  182. ksadk/kernel/worker.py +144 -51
  183. ksadk/kernel/worker_history.py +72 -0
  184. ksadk/kernel/worker_identity.py +47 -0
  185. ksadk/knowledge_base/client.py +57 -27
  186. ksadk/mcp_runtime/__init__.py +12 -4
  187. ksadk/memory/adk/backends/sdk_ltm_backend.py +161 -41
  188. ksadk/memory/adk/resilient_session_service.py +25 -17
  189. ksadk/memory/adk/short_term_memory.py +26 -9
  190. ksadk/memory/coordinator.py +22 -6
  191. ksadk/memory/extraction.py +40 -1
  192. ksadk/memory/models.py +84 -5
  193. ksadk/memory/provider_resolver.py +8 -0
  194. ksadk/memory/providers/local_postgres.py +200 -0
  195. ksadk/memory/providers/local_sqlite.py +42 -3
  196. ksadk/memory/retrieval.py +189 -0
  197. ksadk/memory/store_adapter.py +150 -0
  198. ksadk/model_policy.py +2 -2
  199. ksadk/model_proxy/detect.py +254 -0
  200. ksadk/model_proxy/server.py +14 -7
  201. ksadk/model_proxy/transform.py +40 -2
  202. ksadk/plugins/__init__.py +5 -1
  203. ksadk/plugins/artifacts.py +275 -0
  204. ksadk/plugins/bridges/__init__.py +4 -2
  205. ksadk/plugins/bridges/codex.py +120 -13
  206. ksadk/plugins/bridges/dsh.py +351 -156
  207. ksadk/plugins/builtins.py +72 -35
  208. ksadk/plugins/bundle_security.py +28 -4
  209. ksadk/plugins/codex_manifest.py +983 -0
  210. ksadk/plugins/companion_artifacts.py +83 -0
  211. ksadk/plugins/companions.py +368 -0
  212. ksadk/plugins/composition.py +8 -0
  213. ksadk/plugins/contracts.py +119 -1
  214. ksadk/plugins/delivery.py +153 -0
  215. ksadk/plugins/dsh_channels.py +156 -0
  216. ksadk/plugins/dsh_home.py +140 -0
  217. ksadk/plugins/dsh_installation_digest.py +71 -0
  218. ksadk/plugins/dsh_teams.py +105 -0
  219. ksadk/plugins/dsh_toolchain.py +203 -32
  220. ksadk/plugins/ecosystem_probe.py +6 -6
  221. ksadk/plugins/execution_host.py +99 -0
  222. ksadk/plugins/host.py +102 -39
  223. ksadk/plugins/kernel_adapter.py +205 -0
  224. ksadk/plugins/providers/__init__.py +23 -0
  225. ksadk/plugins/providers/bundles/dsh-channels-client/client.js +19 -0
  226. ksadk/plugins/providers/bundles/dsh-channels-client/cordis.patch.yml +3 -0
  227. ksadk/plugins/providers/bundles/dsh-channels-client/index.mjs +7 -0
  228. ksadk/plugins/providers/bundles/dsh-channels-client/package.json +12 -0
  229. ksadk/plugins/providers/bundles/dsh-knowledge/cordis.patch.yml +3 -0
  230. ksadk/plugins/providers/bundles/dsh-knowledge/index.mjs +53 -0
  231. ksadk/plugins/providers/bundles/dsh-knowledge/package.json +16 -0
  232. ksadk/plugins/providers/bundles/dsh-memory/cordis.patch.yml +3 -0
  233. ksadk/plugins/providers/bundles/dsh-memory/index.mjs +175 -0
  234. ksadk/plugins/providers/bundles/dsh-memory/package.json +16 -0
  235. ksadk/plugins/providers/bundles/dsh-platform-resources/client.mjs +166 -0
  236. ksadk/plugins/providers/bundles/dsh-platform-resources/cordis.patch.yml +3 -0
  237. ksadk/plugins/providers/bundles/dsh-platform-resources/index.mjs +9 -0
  238. ksadk/plugins/providers/bundles/dsh-platform-resources/package.json +12 -0
  239. ksadk/plugins/providers/bundles/dsh-skill-center/cordis.patch.yml +3 -0
  240. ksadk/plugins/providers/bundles/dsh-skill-center/index.mjs +125 -0
  241. ksadk/plugins/providers/bundles/dsh-skill-center/package.json +16 -0
  242. ksadk/plugins/providers/bundles/dsh-teams-client/client.js +29 -0
  243. ksadk/plugins/providers/bundles/dsh-teams-client/cordis.patch.yml +3 -0
  244. ksadk/plugins/providers/bundles/dsh-teams-client/index.mjs +11 -0
  245. ksadk/plugins/providers/bundles/dsh-teams-client/package.json +35 -0
  246. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/cordis.patch.yml +3 -0
  247. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/index.mjs +13 -0
  248. ksadk/plugins/providers/bundles/dsh-teams-leader-policy/package.json +27 -0
  249. ksadk/plugins/providers/bundles/dsh-teams-profile/cordis.patch.yml +3 -0
  250. ksadk/plugins/providers/bundles/dsh-teams-profile/index.mjs +12 -0
  251. ksadk/plugins/providers/bundles/dsh-teams-profile/package.json +26 -0
  252. ksadk/plugins/providers/bundles/dsh-teams-router/cordis.patch.yml +3 -0
  253. ksadk/plugins/providers/bundles/dsh-teams-router/index.mjs +16 -0
  254. ksadk/plugins/providers/bundles/dsh-teams-router/package.json +27 -0
  255. ksadk/plugins/providers/bundles/dsh-teams-tasks/cordis.patch.yml +3 -0
  256. ksadk/plugins/providers/bundles/dsh-teams-tasks/index.mjs +16 -0
  257. ksadk/plugins/providers/bundles/dsh-teams-tasks/package.json +27 -0
  258. ksadk/plugins/providers/bundles/dsh-teams-tools/cordis.patch.yml +3 -0
  259. ksadk/plugins/providers/bundles/dsh-teams-tools/index.mjs +25 -0
  260. ksadk/plugins/providers/bundles/dsh-teams-tools/package.json +29 -0
  261. ksadk/plugins/providers/bundles/dsh-teams-tools/tools.json +235 -0
  262. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +1 -1
  263. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/cordis.patch.yml +3 -0
  264. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/index.mjs +920 -0
  265. ksadk/plugins/providers/bundles/ksadk-dsh-capability-host/package.json +27 -0
  266. ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/client.mjs +133 -0
  267. ksadk/plugins/providers/bundles/ksadk-dsh-companion-host/index.mjs +14 -0
  268. ksadk/plugins/providers/bundles/ksadk-dsh-studio/client.js +115 -0
  269. ksadk/plugins/providers/bundles/ksadk-dsh-studio/index.mjs +19 -0
  270. ksadk/plugins/providers/bundles/ksadk-dsh-studio/package.json +7 -0
  271. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +1 -1
  272. ksadk/plugins/providers/codex.py +344 -70
  273. ksadk/plugins/providers/codex_native.py +368 -0
  274. ksadk/plugins/providers/codex_turn.py +301 -0
  275. ksadk/plugins/providers/dsh_capabilities.py +1450 -0
  276. ksadk/plugins/providers/dsh_mcp.py +467 -0
  277. ksadk/plugins/providers/harness.py +294 -141
  278. ksadk/plugins/providers/harness_delegation.py +593 -0
  279. ksadk/plugins/providers/harness_managed.py +456 -0
  280. ksadk/plugins/providers/harness_tools.py +326 -0
  281. ksadk/plugins/providers/mcp_projection.py +150 -0
  282. ksadk/plugins/providers/platform_resources.py +657 -0
  283. ksadk/plugins/providers/shipped_dsh.py +4 -1
  284. ksadk/plugins/resolver.py +8 -1
  285. ksadk/plugins/subagent_providers/codex.py +28 -6
  286. ksadk/plugins/teams/__init__.py +5 -0
  287. ksadk/plugins/teams/api.py +405 -0
  288. ksadk/plugins/teams/application.py +680 -0
  289. ksadk/plugins/teams/artifacts.py +60 -0
  290. ksadk/plugins/teams/context.py +96 -0
  291. ksadk/plugins/teams/contracts.py +136 -0
  292. ksadk/plugins/teams/domain.py +1427 -0
  293. ksadk/plugins/teams/domain_values.py +24 -0
  294. ksadk/plugins/teams/errors.py +13 -0
  295. ksadk/plugins/teams/migration.py +311 -0
  296. ksadk/plugins/teams/migration_cli.py +322 -0
  297. ksadk/plugins/teams/postgres_store.py +138 -0
  298. ksadk/plugins/teams/runtime.py +777 -0
  299. ksadk/plugins/teams/source_projection.py +84 -0
  300. ksadk/plugins/teams/store.py +311 -0
  301. ksadk/plugins/teams/task_decisions.py +423 -0
  302. ksadk/plugins/teams/transport.py +91 -0
  303. ksadk/plugins/teams/workspaces.py +255 -0
  304. ksadk/resource_runtime/__init__.py +4 -0
  305. ksadk/resource_runtime/broker.py +616 -0
  306. ksadk/resource_runtime/build_artifacts.py +165 -0
  307. ksadk/resource_runtime/contracts.py +138 -0
  308. ksadk/resource_runtime/discovery_receipts.py +100 -0
  309. ksadk/resource_runtime/discovery_skills.py +172 -0
  310. ksadk/resource_runtime/errors.py +6 -0
  311. ksadk/resource_runtime/ipc.py +142 -0
  312. ksadk/resource_runtime/kernel_approval.py +165 -0
  313. ksadk/resource_runtime/knowledge.py +114 -0
  314. ksadk/resource_runtime/langgraph.py +94 -0
  315. ksadk/resource_runtime/leases.py +191 -0
  316. ksadk/resource_runtime/managed_projection.py +186 -0
  317. ksadk/resource_runtime/mcp_server.py +138 -0
  318. ksadk/resource_runtime/memory_provider.py +318 -0
  319. ksadk/resource_runtime/memory_receipts.py +34 -0
  320. ksadk/resource_runtime/operation_ledger.py +211 -0
  321. ksadk/resource_runtime/plugin_config.py +48 -0
  322. ksadk/resource_runtime/policy_authorization.py +54 -0
  323. ksadk/resource_runtime/process.py +110 -0
  324. ksadk/resource_runtime/skill_artifacts.py +153 -0
  325. ksadk/resource_runtime/skill_runtime.py +42 -0
  326. ksadk/resource_runtime/skill_workspace.py +118 -0
  327. ksadk/resource_runtime/skills.py +215 -0
  328. ksadk/resource_runtime/snapshots.py +180 -0
  329. ksadk/resource_runtime/socket_server.py +103 -0
  330. ksadk/resource_runtime/supervisor.py +380 -0
  331. ksadk/resource_runtime/worker.py +504 -0
  332. ksadk/runners/_langgraph_runner_streams.py +182 -38
  333. ksadk/runners/_langgraph_state_reader.py +106 -0
  334. ksadk/runners/_session_identity.py +285 -0
  335. ksadk/runners/adk_runner.py +166 -63
  336. ksadk/runners/base_runner.py +29 -1
  337. ksadk/runners/langgraph_runner.py +542 -195
  338. ksadk/runners/patch_langchain.py +47 -0
  339. ksadk/runners/usage_accumulator.py +11 -3
  340. ksadk/runtime/_runner_adapter/stream_mapping.py +194 -20
  341. ksadk/runtime/adapter.py +2 -0
  342. ksadk/runtime/conversation_execution.py +117 -17
  343. ksadk/runtime/executor.py +87 -3
  344. ksadk/runtime/factory.py +65 -274
  345. ksadk/runtime/framework_adapters.py +23 -0
  346. ksadk/runtime/preprocessing.py +21 -5
  347. ksadk/runtime/runner_adapter.py +106 -18
  348. ksadk/runtime/skill_eval_result.py +36 -0
  349. ksadk/runtime/timing.py +46 -0
  350. ksadk/runtime/usage.py +49 -18
  351. ksadk/runtime_context.py +142 -2
  352. ksadk/sandbox/backends/e2b.py +288 -25
  353. ksadk/sandbox/backends/local_process.py +72 -39
  354. ksadk/sandbox/base.py +64 -1
  355. ksadk/sandbox/diagnostics.py +82 -0
  356. ksadk/sandbox/e2b_connection.py +66 -0
  357. ksadk/sandbox/local_controls.py +746 -0
  358. ksadk/sandbox/local_launcher.py +90 -0
  359. ksadk/scheduler/dispatcher.py +1 -1
  360. ksadk/scheduler/sqlite_store.py +18 -0
  361. ksadk/server/composition.py +2 -0
  362. ksadk/server/factory.py +5 -0
  363. ksadk/server/invocation_identity.py +118 -0
  364. ksadk/server/persistence_capability.py +516 -0
  365. ksadk/server/routes/common.py +32 -3
  366. ksadk/server/routes/control.py +271 -52
  367. ksadk/server/routes/dependencies.py +16 -0
  368. ksadk/server/routes/kernel_ingress.py +9 -1
  369. ksadk/server/routes/misc.py +233 -25
  370. ksadk/server/routes/models.py +47 -4
  371. ksadk/server/routes/openai_compat.py +76 -13
  372. ksadk/server/routes/projection.py +252 -12
  373. ksadk/server/routes/run.py +106 -35
  374. ksadk/server/routes/sessions.py +491 -138
  375. ksadk/server/routes/streaming.py +43 -9
  376. ksadk/server/routes/workspace.py +130 -21
  377. ksadk/server/static/assets/{ArtifactsPanel-10GPXIJ6.js → ArtifactsPanel-C1YoaKhx.js} +1 -1
  378. ksadk/server/static/assets/{CodeBlock-BXk1l-G1.js → CodeBlock-DHuAWfr5.js} +10 -7
  379. ksadk/server/static/assets/MathMessageMarkdown-cvdrBwYr.js +8 -0
  380. ksadk/server/static/assets/{MermaidBlock--OEYoXIJ.js → MermaidBlock-DeBkbJvI.js} +9 -9
  381. ksadk/server/static/assets/{NativeTerminalPanel-DrEe1syq.js → NativeTerminalPanel-oJj44_sV.js} +1 -1
  382. ksadk/server/static/assets/{abnfDiagram-VCTEODGH-eeTFJLvo.js → abnfDiagram-VCTEODGH-Dhfv25o2.js} +1 -1
  383. ksadk/server/static/assets/{arc-S3ZNbIAd.js → arc-C35w-pn1.js} +1 -1
  384. ksadk/server/static/assets/architecture-7GRP2DOG-BCsa26zi.js +1 -0
  385. ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-CRjgEJDX.js → architectureDiagram-5GKGNRK7-FoMY_u6D.js} +1 -1
  386. ksadk/server/static/assets/{blockDiagram-NRAW4CY4-C7bsBoNH.js → blockDiagram-NRAW4CY4-zD4Rt7oE.js} +1 -1
  387. ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-BX3c33Ob.js → c4Diagram-UCG6FXSJ-BCtGGkhN.js} +1 -1
  388. ksadk/server/static/assets/channel-_KDifNys.js +1 -0
  389. ksadk/server/static/assets/chunk-2Q5K7J3B-HdVkJ9F0.js +1 -0
  390. ksadk/server/static/assets/{chunk-5VM5RSS4-bZ7ws-8m.js → chunk-5VM5RSS4-C2Ms13_k.js} +1 -1
  391. ksadk/server/static/assets/{chunk-F27PBJKO-D2r0pvhY.js → chunk-F27PBJKO-BoTj-qgx.js} +1 -1
  392. ksadk/server/static/assets/{chunk-G27WJ6UU-CZwF3oUP.js → chunk-G27WJ6UU-DOMyyjHH.js} +1 -1
  393. ksadk/server/static/assets/{chunk-JWPE2WC7-DigFYCML.js → chunk-JWPE2WC7-DbJJqDli.js} +1 -1
  394. ksadk/server/static/assets/{chunk-LCL6LL3I-CnorGV5q.js → chunk-LCL6LL3I-CLuRCt_W.js} +1 -1
  395. ksadk/server/static/assets/{chunk-POPQ4Y6H-CexntQA-.js → chunk-POPQ4Y6H-D0Dz847e.js} +1 -1
  396. ksadk/server/static/assets/{chunk-RHFEMEQ7-CHTC7UxA.js → chunk-RHFEMEQ7-BWSRzEcd.js} +1 -1
  397. ksadk/server/static/assets/{chunk-SVP7TREG-BC1EWt_-.js → chunk-SVP7TREG-CPoHqkCY.js} +1 -1
  398. ksadk/server/static/assets/{chunk-XXDRQBXY-BTG24xN0.js → chunk-XXDRQBXY-Bx1jS-X-.js} +1 -1
  399. ksadk/server/static/assets/classDiagram-DTDB5LWJ-BVCW3ZqZ.js +1 -0
  400. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-BVCW3ZqZ.js +1 -0
  401. ksadk/server/static/assets/{cose-bilkent-JH36ORCC-CkonRdAS.js → cose-bilkent-JH36ORCC-LzyWm4m0.js} +1 -1
  402. ksadk/server/static/assets/cynefin-OW5HDTMX-CNXjFctL.js +1 -0
  403. ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-DEOCBe15.js → cynefinDiagram-5FMLGOSQ-BlW_bHED.js} +1 -1
  404. ksadk/server/static/assets/{dagre-3AP2YEHR-DBThPuLa.js → dagre-3AP2YEHR-Bu_S86_i.js} +1 -1
  405. ksadk/server/static/assets/{dagre-ClTPrTht.js → dagre-B7nCawed.js} +1 -1
  406. ksadk/server/static/assets/{diagram-S7CK7UJ4-DsZHrnpJ.js → diagram-S7CK7UJ4-W_Zow2Km.js} +1 -1
  407. ksadk/server/static/assets/{diagram-UQ7AKVKN-BJR4nhVr.js → diagram-UQ7AKVKN-CLIKAHON.js} +1 -1
  408. ksadk/server/static/assets/{diagram-VSXAHHWV-xzcERX0a.js → diagram-VSXAHHWV-Dv6_QAlN.js} +1 -1
  409. ksadk/server/static/assets/{diagram-VX7I27RA-tn2xM1xu.js → diagram-VX7I27RA-BWAWRDtZ.js} +1 -1
  410. ksadk/server/static/assets/{diagram-Z3DM3KII-BZPGBkw5.js → diagram-Z3DM3KII-XA_rWMss.js} +1 -1
  411. ksadk/server/static/assets/dist-0X_j2Rcc.js +1 -0
  412. ksadk/server/static/assets/{dist-4zN1_tq5.js → dist-1nXCdsej.js} +1 -1
  413. ksadk/server/static/assets/{dist-M67Hwu0n.js → dist-2vL2sAdF.js} +1 -1
  414. ksadk/server/static/assets/{dist-BJLUwH6W.js → dist-B3bA05-e.js} +1 -1
  415. ksadk/server/static/assets/{dist-8VWI-2bZ.js → dist-BLpRy_Yx.js} +1 -1
  416. ksadk/server/static/assets/{dist-D1V3f1B4.js → dist-BO_UwatH.js} +1 -1
  417. ksadk/server/static/assets/{dist-Cq4OL6nO.js → dist-BdNFAney.js} +1 -1
  418. ksadk/server/static/assets/{dist-DfGFrb0v.js → dist-C0J5_3qM.js} +1 -1
  419. ksadk/server/static/assets/{dist-DyqV4fL4.js → dist-CGt_UGeO.js} +1 -1
  420. ksadk/server/static/assets/{dist-DjhwGUgZ.js → dist-CIRMvUM4.js} +1 -1
  421. ksadk/server/static/assets/{dist-B1oWRmrH.js → dist-CIpWk_5f.js} +1 -1
  422. ksadk/server/static/assets/{dist-CXpH_Rdd.js → dist-CPyieXEf.js} +1 -1
  423. ksadk/server/static/assets/{dist-Btyo4vX4.js → dist-D-92S-Fd.js} +1 -1
  424. ksadk/server/static/assets/{dist-u8tVH5Ak.js → dist-DNKuMzWc.js} +1 -1
  425. ksadk/server/static/assets/{dist-DQAB0qn_.js → dist-DWfdmz9s.js} +2 -2
  426. ksadk/server/static/assets/{dist-B-46q3Hk.js → dist-DppE_r-D.js} +1 -1
  427. ksadk/server/static/assets/{dist-C71CsujO.js → dist-DyG6Ya-z.js} +1 -1
  428. ksadk/server/static/assets/{dist-DJmCPRVB.js → dist-M-SrdhhZ.js} +1 -1
  429. ksadk/server/static/assets/{dist-Bbwqa8TG.js → dist-mqD5TZup.js} +1 -1
  430. ksadk/server/static/assets/{dist-BI1qQH4n.js → dist-n3n6t_FO.js} +1 -1
  431. ksadk/server/static/assets/{dist-DUwKa_l6.js → dist-qe8xjesy.js} +1 -1
  432. ksadk/server/static/assets/{dist-B0gdk4rl.js → dist-t6MzE0jU.js} +1 -1
  433. ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-DVfMsLge.js → ebnfDiagram-PWID7BFC-BfCzF1Xi.js} +1 -1
  434. ksadk/server/static/assets/{erDiagram-SSCWMZ5O-CWEZSYaw.js → erDiagram-SSCWMZ5O-Dpz9_0zn.js} +1 -1
  435. ksadk/server/static/assets/eventmodeling-NTZA5JFV-B5-8RH9c.js +1 -0
  436. ksadk/server/static/assets/flowDiagram-A5DVABFB-BppFxEKw.js +1 -0
  437. ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-TWIxoyYL.js → ganttDiagram-EL5Y4UJY-DDaG1f4_.js} +1 -1
  438. ksadk/server/static/assets/gitGraph-4MIJSDKK-B-AhYj4C.js +1 -0
  439. ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-DDh2hd2S.js → gitGraphDiagram-WWUBYQGX-DcxzRwYC.js} +1 -1
  440. ksadk/server/static/assets/index-BMd2mucF.css +2 -0
  441. ksadk/server/static/assets/index-D5qyyhTH.js +244 -0
  442. ksadk/server/static/assets/info-A6RAGUB7-0uiKrC35.js +1 -0
  443. ksadk/server/static/assets/{infoDiagram-RXCK75RN-CgoyWiDR.js → infoDiagram-RXCK75RN-CYMdn6lQ.js} +1 -1
  444. ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-CyRIu_om.js → ishikawaDiagram-5VMMS53U-CLHFAD6c.js} +1 -1
  445. ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DN5JXHzN.js → journeyDiagram-EYS64GPL-BHSKQGAl.js} +1 -1
  446. ksadk/server/static/assets/{kanban-definition-3QL26DDD-CNP3ui66.js → kanban-definition-3QL26DDD-D5W3tjTT.js} +1 -1
  447. ksadk/server/static/assets/{katex-DWEzQy1d.js → katex-mtjOi4KM.js} +1 -1
  448. ksadk/server/static/assets/{linear-C-qHp7d2.js → linear-BETqsxE_.js} +1 -1
  449. ksadk/server/static/assets/{mermaid-parser.core-Cl-K943T.js → mermaid-parser.core-DPWrvs90.js} +2 -2
  450. ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-D5DB5mYm.js → mindmap-definition-FBJOCRG2-CJG2mfBP.js} +1 -1
  451. ksadk/server/static/assets/packet-AYTQ26CC-BVTmZ_cw.js +1 -0
  452. ksadk/server/static/assets/{pegDiagram-XKGWAZYB-BQVc9ia_.js → pegDiagram-XKGWAZYB-GjAPNaqn.js} +1 -1
  453. ksadk/server/static/assets/pie-WAS4IAKB-DyfxBU7V.js +1 -0
  454. ksadk/server/static/assets/{pieDiagram-E7YTZNPT-B6UCAvtY.js → pieDiagram-E7YTZNPT-ToIewrqr.js} +1 -1
  455. ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-mIr2d3eb.js → quadrantDiagram-AXDQQJYC-4d78KiEY.js} +1 -1
  456. ksadk/server/static/assets/radar-RG4KPBEZ-FFontjL2.js +1 -0
  457. ksadk/server/static/assets/railroad-74A4TZTK-D0Uidzen.js +1 -0
  458. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BOo_9T2h.js +1 -0
  459. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-D6-BjW7Z.js +1 -0
  460. ksadk/server/static/assets/railroad-peg-WCYAUIDC-CXx__yMm.js +1 -0
  461. ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-Dt3FFDmK.js → railroadDiagram-O6MQD6OU-DKzx4xfv.js} +1 -1
  462. ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-Cz_vQtgF.js → requirementDiagram-EFPCY7ZU-CJYljxQS.js} +1 -1
  463. ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-wlO1TsH5.js → sankeyDiagram-P5KCCOFB-DLU2fV1w.js} +1 -1
  464. ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-CHRbElYq.js → sequenceDiagram-WJ2MYXX4-CVqysrA9.js} +1 -1
  465. ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-DXX54iyO.js → sizeCapture-X5ZJPWSS-BUxfmTHc.js} +1 -1
  466. ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-Dp_lw9II.js → stateDiagram-HBIQ2CUA-CNFgYI9E.js} +1 -1
  467. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BXAFScz8.js +1 -0
  468. ksadk/server/static/assets/{swimlanes-XN3QIQJK-D8O5UlLG.js → swimlanes-XN3QIQJK-B6qCMe8N.js} +1 -1
  469. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uIQ2lJKH.js +8 -0
  470. ksadk/server/static/assets/{timeline-definition-24CTP7MA-8mfKcxvG.js → timeline-definition-24CTP7MA-CMD3x_U_.js} +1 -1
  471. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5GgucZ.js +1 -0
  472. ksadk/server/static/assets/treemap-WGGIJYW6-Bq1u1SpG.js +1 -0
  473. ksadk/server/static/assets/{vennDiagram-4TSXK5OY-EnNl0DE7.js → vennDiagram-4TSXK5OY-BqtUqvXM.js} +1 -1
  474. ksadk/server/static/assets/wardley-WFR3VGLG-QAt06uEz.js +1 -0
  475. ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DBu5fbYr.js → wardleyDiagram-VM6X3IG4-B5KfDjI-.js} +1 -1
  476. ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-BSVXs4IY.js → xychartDiagram-S5SC5T6Z-BaUlSZrn.js} +1 -1
  477. ksadk/server/static/index.html +2 -2
  478. ksadk/server/terminal_sessions.py +34 -2
  479. ksadk/session_context.py +66 -0
  480. ksadk/sessions/__init__.py +11 -21
  481. ksadk/sessions/_local_service_sync.py +263 -1
  482. ksadk/sessions/base.py +176 -0
  483. ksadk/sessions/errors.py +4 -0
  484. ksadk/sessions/in_memory.py +309 -19
  485. ksadk/sessions/invocation_identity.py +235 -0
  486. ksadk/sessions/local_service.py +83 -1
  487. ksadk/sessions/persistence.py +275 -0
  488. ksadk/sessions/postgres_service.py +358 -0
  489. ksadk/sessions/resilience.py +8 -0
  490. ksadk/sessions/resilient.py +362 -4
  491. ksadk/sessions/topology.py +135 -0
  492. ksadk/skills/__init__.py +26 -0
  493. ksadk/skills/events.py +464 -0
  494. ksadk/skills/manifest_cache.py +263 -0
  495. ksadk/skills/mcp_server/__init__.py +0 -0
  496. ksadk/skills/mcp_server/_registry.py +33 -0
  497. ksadk/skills/mcp_server/_routing.py +26 -0
  498. ksadk/skills/mcp_server/_workspace_files.py +160 -0
  499. ksadk/skills/mcp_server/register.py +415 -0
  500. ksadk/skills/mcp_server/server.py +218 -0
  501. ksadk/skills/models.py +37 -4
  502. ksadk/skills/observability.py +132 -0
  503. ksadk/skills/package_store.py +261 -40
  504. ksadk/skills/runtime/agent.py +67 -9
  505. ksadk/skills/runtime/artifact_delivery.py +139 -0
  506. ksadk/skills/runtime/artifacts.py +87 -0
  507. ksadk/skills/runtime/backends/e2b.py +460 -27
  508. ksadk/skills/runtime/backends/local.py +629 -62
  509. ksadk/skills/runtime/base.py +81 -7
  510. ksadk/skills/runtime/executor.py +268 -1
  511. ksadk/skills/runtime/loader.py +118 -5
  512. ksadk/skills/runtime/pinned.py +114 -0
  513. ksadk/skills/runtime/request.py +49 -0
  514. ksadk/skills/service_client.py +167 -28
  515. ksadk/skills/tool_defs.py +108 -3
  516. ksadk/studio/api.py +810 -93
  517. ksadk/studio/api_catalog_routes.py +18 -0
  518. ksadk/studio/api_contracts.py +8 -2
  519. ksadk/studio/api_plugin_routes.py +562 -104
  520. ksadk/studio/api_resource_connections.py +221 -0
  521. ksadk/studio/authoring.py +12 -6
  522. ksadk/studio/authoring_coordinator.py +23 -16
  523. ksadk/studio/builder.py +88 -0
  524. ksadk/studio/capabilities.py +23 -6
  525. ksadk/studio/channel_connections.py +333 -0
  526. ksadk/studio/cloud.py +370 -112
  527. ksadk/studio/cloud_shared_web.py +638 -0
  528. ksadk/studio/codex_agent_service.py +130 -28
  529. ksadk/studio/codex_builder.py +242 -3
  530. ksadk/studio/codex_credentials.py +90 -0
  531. ksadk/studio/codex_manifest.py +31 -4
  532. ksadk/studio/codex_plugin_store.py +746 -0
  533. ksadk/studio/codex_provider_build.py +231 -0
  534. ksadk/studio/codex_run.py +190 -26
  535. ksadk/studio/compatibility_report.py +12 -1
  536. ksadk/studio/compiler.py +88 -14
  537. ksadk/studio/configuration.py +182 -0
  538. ksadk/studio/contracts.py +165 -11
  539. ksadk/studio/dsh_application.py +247 -0
  540. ksadk/studio/dsh_capability_service.py +1374 -0
  541. ksadk/studio/dsh_lifecycle.py +58 -0
  542. ksadk/studio/dsh_models.py +59 -0
  543. ksadk/studio/dsh_provider_registration.py +367 -21
  544. ksadk/studio/entry.py +53 -0
  545. ksadk/studio/event_store.py +36 -5
  546. ksadk/studio/execution_host.py +525 -0
  547. ksadk/studio/framework_run.py +32 -10
  548. ksadk/studio/hosted_kernel.py +89 -14
  549. ksadk/studio/kernel_registry.py +418 -0
  550. ksadk/studio/kernel_sqlite_migration.py +181 -0
  551. ksadk/studio/mcp_runtime.py +11 -3
  552. ksadk/studio/model_client.py +1180 -66
  553. ksadk/studio/model_profile_service.py +26 -11
  554. ksadk/studio/native_documents.py +172 -0
  555. ksadk/studio/plugin_composition.py +202 -24
  556. ksadk/studio/plugin_kernel_adapter.py +21 -106
  557. ksadk/studio/plugin_lifecycle.py +80 -0
  558. ksadk/studio/plugin_runtime.py +448 -56
  559. ksadk/studio/provider_recovery.py +59 -0
  560. ksadk/studio/recovery_page.py +70 -0
  561. ksadk/studio/resource_authority.py +664 -0
  562. ksadk/studio/resource_binding_validation.py +77 -0
  563. ksadk/studio/resource_build_admission.py +196 -0
  564. ksadk/studio/resource_build_materializer.py +149 -0
  565. ksadk/studio/resource_catalog.py +149 -2
  566. ksadk/studio/resource_connections.py +220 -0
  567. ksadk/studio/run_activity.py +895 -0
  568. ksadk/studio/run_documents.py +127 -0
  569. ksadk/studio/run_service.py +789 -83
  570. ksadk/studio/runtime_catalog.py +53 -10
  571. ksadk/studio/runtime_source.py +16 -8
  572. ksadk/studio/scheduler_assistant.py +317 -0
  573. ksadk/studio/scheduler_runtime.py +32 -280
  574. ksadk/studio/scheduler_service.py +3 -0
  575. ksadk/studio/service.py +861 -120
  576. ksadk/studio/shared_web.py +579 -82
  577. ksadk/studio/static/assets/ChannelsPage-D_PGmKVq.js +8 -0
  578. ksadk/studio/static/assets/CodeBlock-DMIf4h33-UIqo-BPW.js +44 -0
  579. ksadk/studio/static/assets/ExecutionTree-v3HexvPo-Bk8wzGSy.js +1 -0
  580. ksadk/studio/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  581. ksadk/studio/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  582. ksadk/studio/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  583. ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  584. ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  585. ksadk/studio/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  586. ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  587. ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  588. ksadk/studio/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  589. ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  590. ksadk/studio/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  591. ksadk/studio/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  592. ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  593. ksadk/studio/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  594. ksadk/studio/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  595. ksadk/studio/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  596. ksadk/studio/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  597. ksadk/studio/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  598. ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  599. ksadk/studio/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  600. ksadk/studio/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  601. ksadk/studio/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  602. ksadk/studio/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  603. ksadk/studio/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  604. ksadk/studio/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  605. ksadk/studio/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  606. ksadk/studio/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  607. ksadk/studio/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  608. ksadk/studio/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  609. ksadk/studio/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  610. ksadk/studio/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  611. ksadk/studio/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  612. ksadk/studio/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  613. ksadk/studio/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  614. ksadk/studio/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  615. ksadk/studio/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  616. ksadk/studio/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  617. ksadk/studio/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  618. ksadk/studio/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  619. ksadk/studio/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  620. ksadk/studio/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  621. ksadk/studio/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  622. ksadk/studio/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  623. ksadk/studio/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  624. ksadk/studio/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  625. ksadk/studio/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  626. ksadk/studio/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  627. ksadk/studio/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  628. ksadk/studio/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  629. ksadk/studio/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  630. ksadk/studio/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  631. ksadk/studio/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  632. ksadk/studio/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  633. ksadk/studio/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  634. ksadk/studio/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  635. ksadk/studio/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  636. ksadk/studio/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  637. ksadk/studio/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  638. ksadk/studio/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  639. ksadk/studio/static/assets/MathMessageMarkdown-BesSeCYl-BEOyPC3w.css +1 -0
  640. ksadk/studio/static/assets/MathMessageMarkdown-BesSeCYl-OhNwH4K9.js +8 -0
  641. ksadk/studio/static/assets/MermaidBlock-H_0qzQDf-BdXtxug_.js +11 -0
  642. ksadk/studio/static/assets/MessageMarkdown-CwMTvgFj-g0I-I4dD.js +59 -0
  643. ksadk/studio/static/assets/PrismRenderer-5pK6tUdb.js +3 -0
  644. ksadk/studio/static/assets/TeamsPage-DqX4HWKQ.js +3 -0
  645. ksadk/studio/static/assets/abnfDiagram-VCTEODGH-Bnb1O3tA.js +1 -0
  646. ksadk/studio/static/assets/arc-Pry-7G1_.js +1 -0
  647. ksadk/studio/static/assets/architecture-7GRP2DOG-BMOPAkUC.js +1 -0
  648. ksadk/studio/static/assets/architectureDiagram-5GKGNRK7-5Zn1gqJv.js +36 -0
  649. ksadk/studio/static/assets/array-BifhSqXX.js +1 -0
  650. ksadk/studio/static/assets/blockDiagram-I7D4REHJ-CLB4L11u.js +129 -0
  651. ksadk/studio/static/assets/c4Diagram-7LVT6UL2-BOma_Z_F.js +38 -0
  652. ksadk/studio/static/assets/channel-r8itPEik.js +1 -0
  653. ksadk/studio/static/assets/chunk-2E4U76K2-BFf_huql.js +2 -0
  654. ksadk/studio/static/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  655. ksadk/studio/static/assets/chunk-2ZTRR5NV-C2qZJila.js +2 -0
  656. ksadk/studio/static/assets/chunk-3Z5EZCMW-DMniOlPd.js +1 -0
  657. ksadk/studio/static/assets/chunk-4HAMMTFA-DVtDP4rm.js +62 -0
  658. ksadk/studio/static/assets/chunk-5V3GS4D5-i-ZDOEnP.js +1 -0
  659. ksadk/studio/static/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  660. ksadk/studio/static/assets/chunk-6AZGARVD-BB6FqMl-.js +1 -0
  661. ksadk/studio/static/assets/chunk-6EIED4P4-DdUJkP0f.js +1 -0
  662. ksadk/studio/static/assets/chunk-6K3QC6MW-CqVsVn-y.js +1 -0
  663. ksadk/studio/static/assets/chunk-6TQVIW2G-DKlLbNIP.js +1 -0
  664. ksadk/studio/static/assets/chunk-747NJXEK-BFkZPlab.js +2 -0
  665. ksadk/studio/static/assets/chunk-75Z2AOVW-BZNhM4vB.js +2 -0
  666. ksadk/studio/static/assets/chunk-CLGD4ZFX-D-GTtyKs.js +1 -0
  667. ksadk/studio/static/assets/chunk-DU6HZSFF-CpEZCUgg.js +128 -0
  668. ksadk/studio/static/assets/chunk-F27PBJKO-D7pvKf0C.js +1 -0
  669. ksadk/studio/static/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
  670. ksadk/studio/static/assets/chunk-GMAD6QVW-DE_8H717.js +72 -0
  671. ksadk/studio/static/assets/chunk-GVQU2GXP-BfEB0lNv.js +1 -0
  672. ksadk/studio/static/assets/chunk-I5DQTOEV-Hm2LfiYI.js +1 -0
  673. ksadk/studio/static/assets/chunk-ICYGCRZG-749o-pGJ.js +1 -0
  674. ksadk/studio/static/assets/chunk-IH6LHLGP-CC6I9F3i.js +1 -0
  675. ksadk/studio/static/assets/chunk-IMKFNOWR-D38axYOR.js +231 -0
  676. ksadk/studio/static/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  677. ksadk/studio/static/assets/chunk-KI3K4JFJ-Rma-0k_M.js +1 -0
  678. ksadk/studio/static/assets/chunk-L3NEJ4N5-Dq5xn7aL.js +1 -0
  679. ksadk/studio/static/assets/chunk-LNGE3PJU-DWpGwHrW.js +34 -0
  680. ksadk/studio/static/assets/chunk-OSK3NFVY-DiCQjx9b.js +10 -0
  681. ksadk/studio/static/assets/chunk-OUJLGHUK-D0j2JRSg.js +2 -0
  682. ksadk/studio/static/assets/chunk-P2QGCYS3-VdhKmfYq.js +1 -0
  683. ksadk/studio/static/assets/chunk-POPQ4Y6H-C1_rzO5M.js +1 -0
  684. ksadk/studio/static/assets/chunk-PWAF6VOD-B45HvflI.js +1 -0
  685. ksadk/studio/static/assets/chunk-SHT3W25Y-DsCFnRVE.js +168 -0
  686. ksadk/studio/static/assets/chunk-SVP7TREG-DljApgRX.js +88 -0
  687. ksadk/studio/static/assets/chunk-TICWLB2K-DWqRtyqa.js +206 -0
  688. ksadk/studio/static/assets/chunk-TLUHSLCS-DUeGgnD5.js +2 -0
  689. ksadk/studio/static/assets/chunk-UY3FDG6J-CnmaAkG5.js +1 -0
  690. ksadk/studio/static/assets/chunk-XHIXRSVI-BFQ6B8BE.js +1 -0
  691. ksadk/studio/static/assets/chunk-XXDRQBXY-DqAYeljN.js +1 -0
  692. ksadk/studio/static/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  693. ksadk/studio/static/assets/classDiagram-ZZMXUADV-DjeMNJmu.js +1 -0
  694. ksadk/studio/static/assets/classDiagram-v2-VYDZK3BY-DjeMNJmu.js +1 -0
  695. ksadk/studio/static/assets/clsx-DB0hHKMi.js +1 -0
  696. ksadk/studio/static/assets/cose-bilkent-JH36ORCC-om8AY0Mc.js +1 -0
  697. ksadk/studio/static/assets/createLucideIcon-DR0LGTP5.js +1 -0
  698. ksadk/studio/static/assets/cynefin-OW5HDTMX-7tP7QfM3.js +1 -0
  699. ksadk/studio/static/assets/cynefinDiagram-5FMLGOSQ-6BrmK52S.js +62 -0
  700. ksadk/studio/static/assets/cytoscape.esm-CGd-uY3x.js +321 -0
  701. ksadk/studio/static/assets/dagre-GXQ25YYZ-buZBRlwz.js +4 -0
  702. ksadk/studio/static/assets/dagre-PrKaheQc.js +1 -0
  703. ksadk/studio/static/assets/defaultLocale-BFoDCU3G.js +1 -0
  704. ksadk/studio/static/assets/diagram-S7CK7UJ4-C6aH78c4.js +30 -0
  705. ksadk/studio/static/assets/diagram-UQ7AKVKN-DY_tQ6C_.js +41 -0
  706. ksadk/studio/static/assets/diagram-VSXAHHWV-DX9p5cQT.js +3 -0
  707. ksadk/studio/static/assets/diagram-VX7I27RA-DB2WSH8X.js +24 -0
  708. ksadk/studio/static/assets/diagram-Z3DM3KII-Ce1BJwHE.js +24 -0
  709. ksadk/studio/static/assets/dist-v5Q1xZ2K.js +1 -0
  710. ksadk/studio/static/assets/ebnfDiagram-PWID7BFC-Daukxwqf.js +1 -0
  711. ksadk/studio/static/assets/erDiagram-RLTQ6QDP-CGthTMmJ.js +99 -0
  712. ksadk/studio/static/assets/eventmodeling-NTZA5JFV-BR2FzK4R.js +1 -0
  713. ksadk/studio/static/assets/flowDiagram-HODETNUW-CZ1tEenp.js +1 -0
  714. ksadk/studio/static/assets/ganttDiagram-EL5Y4UJY-BfoAZW9X.js +292 -0
  715. ksadk/studio/static/assets/gitGraph-4MIJSDKK-DYlXv_yJ.js +1 -0
  716. ksadk/studio/static/assets/gitGraphDiagram-WWUBYQGX-S985OIJF.js +106 -0
  717. ksadk/studio/static/assets/graphlib-DS17s2tU.js +1 -0
  718. ksadk/studio/static/assets/iconfont-4HN3C9Pg.woff2 +0 -0
  719. ksadk/studio/static/assets/index-CfEnms6Y.css +2 -0
  720. ksadk/studio/static/assets/index-Dp61rOWZ.js +0 -0
  721. ksadk/studio/static/assets/index.dom-BQenBtFm.js +1 -0
  722. ksadk/studio/static/assets/info-A6RAGUB7-BlU-8_xu.js +1 -0
  723. ksadk/studio/static/assets/infoDiagram-27XIBGKW-CO3W8tFn.js +2 -0
  724. ksadk/studio/static/assets/init-C-OQMol4.js +1 -0
  725. ksadk/studio/static/assets/ishikawaDiagram-5VMMS53U-BUGEl8cO.js +70 -0
  726. ksadk/studio/static/assets/journeyDiagram-3NMN7TZE-Ch2ZYxvr.js +139 -0
  727. ksadk/studio/static/assets/kanban-definition-UXKFOSKX-Da-Fp4vC.js +89 -0
  728. ksadk/studio/static/assets/katex-DhON-x74.js +257 -0
  729. ksadk/studio/static/assets/kingsoft-cloud-BOcLiQ3e.ico +0 -0
  730. ksadk/studio/static/assets/line-BDaeq300.js +1 -0
  731. ksadk/studio/static/assets/linear-DmucvK78.js +1 -0
  732. ksadk/studio/static/assets/mermaid-parser.core-Cj5mYdBi.js +4 -0
  733. ksadk/studio/static/assets/mindmap-definition-YA3MSWOX-CvlsVQ38.js +96 -0
  734. ksadk/studio/static/assets/ordinal-BDEzSJ7C.js +1 -0
  735. ksadk/studio/static/assets/packet-AYTQ26CC-BSJEH9Lb.js +1 -0
  736. ksadk/studio/static/assets/path-fybaL0A-.js +1 -0
  737. ksadk/studio/static/assets/pegDiagram-XKGWAZYB-BuHuNgT5.js +1 -0
  738. ksadk/studio/static/assets/pie-WAS4IAKB-qxv01Y8W.js +1 -0
  739. ksadk/studio/static/assets/pieDiagram-E7YTZNPT-DlrWiSOv.js +39 -0
  740. ksadk/studio/static/assets/preload-helper-CO68xz6u.js +1 -0
  741. ksadk/studio/static/assets/presentation-yYw7xK60-BmcZriri.js +1 -0
  742. ksadk/studio/static/assets/quadrantDiagram-AXDQQJYC-BA3hpUZM.js +7 -0
  743. ksadk/studio/static/assets/radar-RG4KPBEZ-B6h6pWOs.js +1 -0
  744. ksadk/studio/static/assets/railroad-74A4TZTK-CGtTWdGc.js +1 -0
  745. ksadk/studio/static/assets/railroad-abnf-HS5TGJTU-Cq7y22IA.js +1 -0
  746. ksadk/studio/static/assets/railroad-ebnf-LZEXJU2U-V0aQVv9m.js +1 -0
  747. ksadk/studio/static/assets/railroad-peg-WCYAUIDC-BQiIsX_p.js +1 -0
  748. ksadk/studio/static/assets/railroadDiagram-O6MQD6OU-BxpAazym.js +1 -0
  749. ksadk/studio/static/assets/requirementDiagram-BXWQKSXE-E-Ge0FcV.js +84 -0
  750. ksadk/studio/static/assets/rolldown-runtime-Dy4uBu1J-DK3Fl9T5.js +1 -0
  751. ksadk/studio/static/assets/rough.esm-Dy-Kn_BL.js +1 -0
  752. ksadk/studio/static/assets/sankeyDiagram-P5KCCOFB-CA6YKhQ3.js +40 -0
  753. ksadk/studio/static/assets/sequenceDiagram-WJ2MYXX4-CcElXGg4.js +162 -0
  754. ksadk/studio/static/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
  755. ksadk/studio/static/assets/src-B6xuSHsQ.js +1 -0
  756. ksadk/studio/static/assets/stateDiagram-D77RDMKH-DdXZOFqY.js +1 -0
  757. ksadk/studio/static/assets/stateDiagram-v2-MP3YSRHH-CJbOM-QK.js +1 -0
  758. ksadk/studio/static/assets/swimlanes-42K2YHIH-NtgEEHLY.js +1 -0
  759. ksadk/studio/static/assets/swimlanesDiagram-VR7AAH4N-D-AVlW8B.js +8 -0
  760. ksadk/studio/static/assets/timeline-definition-24CTP7MA-CIuYhNy3.js +120 -0
  761. ksadk/studio/static/assets/treeView-Q6P3EWNA-BTTYcvrF.js +1 -0
  762. ksadk/studio/static/assets/treemap-WGGIJYW6-Cg7tyfH8.js +1 -0
  763. ksadk/studio/static/assets/vennDiagram-4TSXK5OY-7HbApbrv.js +34 -0
  764. ksadk/studio/static/assets/wardley-WFR3VGLG-C6N1rULY.js +1 -0
  765. ksadk/studio/static/assets/wardleyDiagram-VM6X3IG4-CMJiaedy.js +78 -0
  766. ksadk/studio/static/assets/xychartDiagram-S5SC5T6Z-BdgtMvW2.js +7 -0
  767. ksadk/studio/static/index.html +8 -2
  768. ksadk/studio/teams_catalog.py +133 -0
  769. ksadk/studio/teams_installation.py +214 -0
  770. ksadk/studio/teams_node.py +545 -0
  771. ksadk/studio/teams_node_runner.py +69 -0
  772. ksadk/studio/teams_remote.py +274 -0
  773. ksadk/studio/templates.py +40 -19
  774. ksadk/studio/validator.py +13 -0
  775. ksadk/studio/workspace.py +19 -4
  776. ksadk/studio/workspace_plugins.py +136 -0
  777. ksadk/studio/workspace_registry.py +286 -0
  778. ksadk/toolsets/__init__.py +11 -0
  779. ksadk/toolsets/skills.py +446 -378
  780. ksadk/toolsets/workspace.py +3 -3
  781. ksadk/toolsets/workspace_identity.py +15 -0
  782. ksadk/version.py +1 -1
  783. ksadk_runtime_common/memory_backend/providers/lancedb.py +56 -2
  784. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -148
  785. ksadk_runtime_common/workspace_files/router.py +26 -13
  786. agentengine_sdk_python-0.8.3.dist-info/RECORD +0 -833
  787. ksadk/server/static/assets/MathMessageMarkdown-D9uB55Oh.js +0 -8
  788. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +0 -1
  789. ksadk/server/static/assets/channel-s354Yo6o.js +0 -1
  790. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +0 -1
  791. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +0 -1
  792. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +0 -1
  793. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +0 -1
  794. ksadk/server/static/assets/dist-DdRBIceA.js +0 -1
  795. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +0 -1
  796. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +0 -1
  797. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +0 -1
  798. ksadk/server/static/assets/index-B2k_urY8.js +0 -241
  799. ksadk/server/static/assets/index-ILPt6IWF.css +0 -2
  800. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +0 -1
  801. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +0 -1
  802. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +0 -1
  803. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +0 -1
  804. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +0 -1
  805. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +0 -1
  806. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +0 -1
  807. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +0 -1
  808. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +0 -1
  809. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +0 -8
  810. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +0 -1
  811. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +0 -1
  812. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +0 -1
  813. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +0 -3
  814. ksadk/studio/static/assets/index-DXs1Eq4M.css +0 -1
  815. ksadk/studio/static/assets/index-DeDqwTsD.js +0 -283
  816. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/WHEEL +0 -0
  817. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/entry_points.txt +0 -0
  818. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/licenses/LICENSE +0 -0
  819. {agentengine_sdk_python-0.8.3.dist-info → agentengine_sdk_python-0.8.5.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import copy
6
7
  import hashlib
7
8
  import json
8
9
  import logging
@@ -14,6 +15,8 @@ from pathlib import Path
14
15
  from typing import Any
15
16
  from uuid import uuid4
16
17
 
18
+ from jsonschema import Draft202012Validator
19
+
17
20
  from ksadk.agui.a2ui_projection import project_a2ui_operations
18
21
  from ksadk.conversations.projector import (
19
22
  project_conversation_item,
@@ -60,14 +63,17 @@ from ksadk.runtime import (
60
63
  RuntimeLaunchContext,
61
64
  StartRequest,
62
65
  )
66
+ from ksadk.runtime.executor import RuntimeStartPreparation
63
67
  from ksadk.sessions.base import BaseSessionService
64
68
  from ksadk.sessions.local_service import LocalSessionService
65
69
  from ksadk.studio.contracts import RunEvent, RunRecord, RunStatus, Usage
66
70
  from ksadk.studio.errors import StudioError
67
71
  from ksadk.studio.event_store import RunEventStore
72
+ from ksadk.studio.run_activity import project_run_activities
68
73
  from ksadk.studio.workspace import Workspace
69
74
 
70
75
  _CANCEL_TIMEOUT_SECONDS = 2.0
76
+ _INTERACTION_SUBMIT_TIMEOUT_SECONDS = 10.0
71
77
 
72
78
 
73
79
  logger = logging.getLogger(__name__)
@@ -86,6 +92,129 @@ class StudioRunSpec:
86
92
  plugin_bundle_root: Path | None = None
87
93
 
88
94
 
95
+ class _ProjectedRunEventWriter:
96
+ """Batch high-frequency text deltas before rewriting a Run JSON file.
97
+
98
+ Canonical RuntimeEvents are already durable in the session store. The
99
+ RunEvent JSON is a presentation projection, so writing and retaining one
100
+ copy of the full projection envelope per token is unnecessary and becomes
101
+ quadratic as the response grows. A short batching window keeps live chat
102
+ responsive and coalesces adjacent chunks for the same projected item.
103
+ """
104
+
105
+ _STREAM_TYPES = frozenset({"message.delta", "thinking.delta"})
106
+
107
+ def __init__(
108
+ self,
109
+ event_store: RunEventStore,
110
+ run_id: str,
111
+ on_event: Callable[[RunEvent], None] | None,
112
+ *,
113
+ flush_interval: float = 0.5,
114
+ ) -> None:
115
+ self._event_store = event_store
116
+ self._run_id = run_id
117
+ self._on_event = on_event
118
+ self._flush_interval = flush_interval
119
+ self._pending: list[tuple[str, dict[str, Any]]] = []
120
+ # Publish the first chunk immediately so the UI can enter streaming
121
+ # state without waiting for the batching window. Subsequent chunks are
122
+ # coalesced into short durable writes.
123
+ self._last_flush = 0.0
124
+ self._flush_handle: asyncio.TimerHandle | None = None
125
+
126
+ def append(self, event_type: str, data: dict[str, Any]) -> RunEvent | None:
127
+ if event_type in self._STREAM_TYPES:
128
+ self._pending.append((event_type, data))
129
+ elapsed = time.monotonic() - self._last_flush
130
+ if elapsed < self._flush_interval:
131
+ if self._flush_handle is None:
132
+ self._flush_handle = asyncio.get_running_loop().call_later(
133
+ self._flush_interval - elapsed, self.flush
134
+ )
135
+ return None
136
+ return self.flush()[-1]
137
+ self.flush()
138
+ stored = self._event_store.append(self._run_id, event_type, data)
139
+ self._notify((stored,))
140
+ return stored
141
+
142
+ def flush(self) -> list[RunEvent]:
143
+ if self._flush_handle is not None:
144
+ self._flush_handle.cancel()
145
+ self._flush_handle = None
146
+ if not self._pending:
147
+ return []
148
+ pending, self._pending = self._pending, []
149
+ stored = self._event_store.append_many(
150
+ self._run_id,
151
+ self._coalesce_stream_entries(pending),
152
+ )
153
+ self._last_flush = time.monotonic()
154
+ self._notify(stored)
155
+ return stored
156
+
157
+ @staticmethod
158
+ def _coalesce_stream_entries(
159
+ entries: list[tuple[str, dict[str, Any]]],
160
+ ) -> list[tuple[str, dict[str, Any]]]:
161
+ """Collapse adjacent chunks without changing the rendered transcript."""
162
+
163
+ coalesced: list[tuple[str, dict[str, Any]]] = []
164
+ for event_type, data in entries:
165
+ if not coalesced:
166
+ coalesced.append((event_type, copy.deepcopy(data)))
167
+ continue
168
+ previous_type, previous = coalesced[-1]
169
+ if not _same_projected_stream(previous_type, previous, event_type, data):
170
+ coalesced.append((event_type, copy.deepcopy(data)))
171
+ continue
172
+
173
+ operation = str(data.get("operation") or "append")
174
+ if operation == "append":
175
+ merged_text = str(previous.get("text") or "") + str(data.get("text") or "")
176
+ merged = copy.deepcopy(data)
177
+ _set_projected_stream_text(merged, merged_text)
178
+ coalesced[-1] = (event_type, merged)
179
+ else:
180
+ coalesced[-1] = (event_type, copy.deepcopy(data))
181
+ return coalesced
182
+
183
+ def _notify(self, events: tuple[RunEvent, ...] | list[RunEvent]) -> None:
184
+ if self._on_event is None:
185
+ return
186
+ for event in events:
187
+ self._on_event(event)
188
+
189
+
190
+ def _same_projected_stream(
191
+ previous_type: str,
192
+ previous: dict[str, Any],
193
+ event_type: str,
194
+ data: dict[str, Any],
195
+ ) -> bool:
196
+ return (
197
+ previous_type == event_type
198
+ and previous.get("itemId") == data.get("itemId")
199
+ and previous.get("partId") == data.get("partId")
200
+ and previous.get("operation") == data.get("operation")
201
+ )
202
+
203
+
204
+ def _set_projected_stream_text(data: dict[str, Any], text: str) -> None:
205
+ data["text"] = text
206
+ conversation_item = data.get("conversationItem")
207
+ if isinstance(conversation_item, dict):
208
+ payload = conversation_item.get("payload")
209
+ if isinstance(payload, dict):
210
+ payload["text"] = text
211
+ runtime_event = data.get("runtimeEvent")
212
+ if isinstance(runtime_event, dict):
213
+ update = runtime_event.get("update")
214
+ if isinstance(update, dict):
215
+ update["text"] = text
216
+
217
+
89
218
  class StudioRunService:
90
219
  """Persist Studio state without selecting or wrapping a RuntimeAdapter."""
91
220
 
@@ -107,29 +236,77 @@ class StudioRunService:
107
236
  )
108
237
  self.runtime_events = runtime_events or RuntimeEventStore(self.session_service)
109
238
  self.plugin_runtime = plugin_runtime
239
+ self.schedule_assistant: Any | None = None
110
240
  self._active_handles: dict[str, Any] = {}
241
+ self._active_sessions: set[tuple[str, str]] = set()
111
242
  self._cancel_flags: dict[str, bool] = {}
112
243
  self._control_queues: dict[
113
244
  str,
114
245
  asyncio.Queue[tuple[str, ResumePayload | None, asyncio.Future[None] | None]],
115
246
  ] = {}
116
247
  self._waiting_modes: dict[str, str] = {}
248
+ self._submitted_interactions: dict[str, set[str]] = {}
117
249
  # Interaction resolution appends to one run-level event file. Serialise
118
250
  # every decision for that run, not only identical interaction ids, so
119
251
  # two concurrently visible cards cannot overwrite each other's receipt.
120
252
  self._interaction_locks: dict[str, asyncio.Lock] = {}
253
+ self._recovery_tasks: set[asyncio.Task] = set()
254
+ self._detaching_recoveries = False
255
+
256
+ def has_active_runs(self) -> bool:
257
+ return bool(self._active_sessions or self._active_handles)
258
+
259
+ def is_run_executing(self, run_id: str) -> bool:
260
+ """该 run 是否仍在本进程内实际执行(内存态判定)。
121
261
 
122
- async def recover_interrupted(self) -> None:
262
+ 磁盘 record 停在 RUNNING 的 run 若不在这里,说明是进程崩溃/重启
263
+ 遗留的僵尸状态,允许删除会话时将其收尾为 canceled。
264
+ """
265
+ return run_id in self._active_handles or run_id in self._active_sessions
266
+
267
+ async def reap_stale_running_run(self, run_id: str) -> bool:
268
+ """把不在执行的僵尸 RUNNING run 收尾为 canceled(幂等)。"""
269
+ from ksadk.events.canonical import RunCanceled
270
+
271
+ record = self.event_store.get(run_id)
272
+ if record.status != RunStatus.RUNNING or self.is_run_executing(run_id):
273
+ return False
274
+ record.status = RunStatus.CANCELLED
275
+ record.error = {"code": "RUN_CANCELLED", "message": "会话删除时回收未完成的运行"}
276
+ record.completed_at = datetime.now(timezone.utc)
277
+ self.event_store.save(record)
278
+ await self.runtime_events.append_one(
279
+ record.session_id,
280
+ RunCanceled(
281
+ schema_version=2,
282
+ event_id=f"evt_reap_{uuid4().hex}",
283
+ seq=len(self.event_store.events(run_id)) + 1,
284
+ timestamp=time.time(),
285
+ run_id=run_id,
286
+ scope_id=run_id,
287
+ source=SourceRef(framework="ksadk", metadata={"reason": "session_deleted"}),
288
+ status="canceled",
289
+ reason="session deleted",
290
+ ),
291
+ )
292
+ return True
293
+
294
+ async def recover_interrupted(self, resolve_spec=None) -> None:
123
295
  """Settle local runs left active across a Studio restart.
124
296
 
125
- An in-process handle cannot be resumed safely. Keep its durable event
126
- history and record an explicit interruption; lease-backed hosted
127
- recovery remains owned by the Kernel composition root.
297
+ Reattach approval-paused providers only when their pinned Build and
298
+ durable-restore capability can be verified. Other in-process runs are
299
+ explicitly interrupted; active execution takeover remains Kernel-owned.
128
300
  """
129
301
  active = {RunStatus.RUNNING, RunStatus.WAITING_INPUT}
130
302
  for record in self.event_store.list_runs():
131
303
  if record.status not in active:
132
304
  continue
305
+ if record.status == RunStatus.WAITING_INPUT and resolve_spec is not None:
306
+ from ksadk.studio.provider_recovery import restore_waiting_provider_run
307
+
308
+ if await restore_waiting_provider_run(self, record, resolve_spec):
309
+ continue
133
310
  record.status = RunStatus.INTERRUPTED
134
311
  record.completed_at = datetime.now(timezone.utc)
135
312
  record.error = {
@@ -142,6 +319,7 @@ class StudioRunService:
142
319
  "run.interrupted",
143
320
  {"reason": "local_studio_restarted", "recoverable": False},
144
321
  )
322
+ self._expire_unresolved_interactions(record)
145
323
 
146
324
  async def events(self, run_id: str, *, after: int = 0) -> list[RunEvent]:
147
325
  """Read the durable Studio event timeline for API/SSE replay."""
@@ -155,6 +333,38 @@ class StudioRunService:
155
333
  runtime_input: Any = None,
156
334
  session_id: str | None = None,
157
335
  on_event: Callable[[RunEvent], None] | None = None,
336
+ ) -> RunRecord:
337
+ session = session_id or f"ses_{uuid4().hex}"
338
+ key = (spec.agent_id, session)
339
+ # Claim before the first await, including preparation and plugin runs.
340
+ # A second turn must never attach another adapter to the live thread.
341
+ if key in self._active_sessions:
342
+ raise StudioError(
343
+ "SESSION_RUN_ACTIVE",
344
+ "上一轮仍在运行或等待回答,请先完成回答或停止运行。",
345
+ status_code=409,
346
+ details={"sessionId": session},
347
+ )
348
+ self._active_sessions.add(key)
349
+ try:
350
+ return await self._run_owned(
351
+ spec,
352
+ user_input,
353
+ runtime_input=runtime_input,
354
+ session_id=session,
355
+ on_event=on_event,
356
+ )
357
+ finally:
358
+ self._active_sessions.discard(key)
359
+
360
+ async def _run_owned(
361
+ self,
362
+ spec: StudioRunSpec,
363
+ user_input: str,
364
+ *,
365
+ runtime_input: Any,
366
+ session_id: str,
367
+ on_event: Callable[[RunEvent], None] | None,
158
368
  ) -> RunRecord:
159
369
  run_id = f"run_{uuid4().hex}"
160
370
  session = session_id or f"ses_{uuid4().hex}"
@@ -193,7 +403,27 @@ class StudioRunService:
193
403
  if on_event is not None:
194
404
  on_event(created)
195
405
 
196
- kernel_runtime = self._kernel_runtime_for_spec(spec)
406
+ if self.schedule_assistant is not None and (
407
+ self.schedule_assistant.matches(user_input)
408
+ or self.schedule_assistant.matches_followup(spec.agent_id, session, user_input)
409
+ ):
410
+ handled = await self._schedule_run(record, user_input, on_event=on_event)
411
+ if handled is not None:
412
+ return handled
413
+
414
+ provider_runtime_adapter = (
415
+ spec.plugin_bundle_root is not None
416
+ and (
417
+ runtime_type == "codex"
418
+ or bool(spec.request_config.get("provider_runtime_adapter"))
419
+ )
420
+ )
421
+ # A process-global Codex Kernel can share Agent/cwd with this Build
422
+ # while owning a legacy direct factory. New Builds must retain their
423
+ # exact Provider activation, including when that global Kernel exists.
424
+ kernel_runtime = (
425
+ None if provider_runtime_adapter else self._kernel_runtime_for_spec(spec)
426
+ )
197
427
  if kernel_runtime is not None:
198
428
  return await self._kernel_run(
199
429
  spec,
@@ -202,7 +432,7 @@ class StudioRunService:
202
432
  on_event=on_event,
203
433
  kernel_runtime=kernel_runtime,
204
434
  )
205
- if spec.plugin_bundle_root is not None:
435
+ if spec.plugin_bundle_root is not None and not provider_runtime_adapter:
206
436
  return await self._plugin_run(
207
437
  spec,
208
438
  user_input,
@@ -210,12 +440,28 @@ class StudioRunService:
210
440
  on_event=on_event,
211
441
  )
212
442
 
443
+ return await self._execute_runtime_record(
444
+ spec, user_input, record=record, runtime_input=runtime_input,
445
+ on_event=on_event, prepared_turn=prepared_turn,
446
+ provider_runtime_adapter=provider_runtime_adapter,
447
+ )
448
+
449
+ async def _execute_runtime_record(
450
+ self, spec, user_input, *, record, runtime_input=None, on_event=None,
451
+ prepared_turn=None, provider_runtime_adapter=False, restored_handle=None,
452
+ ) -> RunRecord:
453
+ run_id, session = record.id, record.session_id
454
+ runtime_type = record.runtime_type
213
455
  started = time.monotonic()
214
- record.status = RunStatus.RUNNING
215
- record.started_at = datetime.now(timezone.utc)
456
+ if restored_handle is None:
457
+ record.status = RunStatus.RUNNING
458
+ record.started_at = datetime.now(timezone.utc)
216
459
  self.event_store.save(record)
460
+ projected_writer = _ProjectedRunEventWriter(self.event_store, run_id, on_event)
217
461
 
218
- async def persist(runtime_event: RuntimeEvent) -> RunEvent:
462
+ async def persist(runtime_event: RuntimeEvent) -> RunEvent | None:
463
+ if _is_private_harness_delta(runtime_event):
464
+ return None
219
465
  persisted = await self.runtime_events.append_one(record.session_id, runtime_event)
220
466
  event_type, data = project_runtime_event(
221
467
  persisted,
@@ -229,10 +475,7 @@ class StudioRunService:
229
475
  item_payload = conversation_item.get("payload")
230
476
  if isinstance(item_payload, dict):
231
477
  item_payload["revision"] = 1
232
- stored = self.event_store.append(record.id, event_type, data)
233
- if on_event is not None:
234
- on_event(stored)
235
- return stored
478
+ return projected_writer.append(event_type, data)
236
479
 
237
480
  handle = None
238
481
  completed_text_by_item: dict[tuple[str, str], str] = {}
@@ -269,17 +512,60 @@ class StudioRunService:
269
512
  metadata={
270
513
  "invocation_id": run_id,
271
514
  CONVERSATION_PREPROCESSING_METADATA_KEY: conversation_request,
272
- **self._native_session_metadata(spec.agent_id, session, runtime_type),
515
+ **self._native_session_metadata(
516
+ spec.agent_id,
517
+ session,
518
+ str(
519
+ spec.request_config.get("provider_runtime_type")
520
+ or runtime_type
521
+ ),
522
+ spec.build_id,
523
+ ),
273
524
  },
274
525
  )
275
- handle = await self.executor.start(spec.launch_context, request)
526
+ preparation = None
527
+ if provider_runtime_adapter:
528
+ if self.plugin_runtime is None:
529
+ raise StudioError(
530
+ "PLUGIN_RUNTIME_UNAVAILABLE", "Codex Provider Runtime 不可用",
531
+ status_code=503,
532
+ )
533
+ adapter = self.plugin_runtime.kernel_adapter_provider(spec)()
534
+ preparation = RuntimeStartPreparation(context=spec.launch_context, adapter=adapter)
535
+ handle = restored_handle or await self.executor.start(
536
+ spec.launch_context, request, preparation=preparation,
537
+ )
276
538
  record.runtime_handle = handle.model_dump(mode="json")
277
539
  self.event_store.save(record)
278
540
  self._active_handles[run_id] = handle
279
541
  while True:
280
542
  terminal_seen = False
281
- should_resume = False
543
+ should_resume = restored_handle is not None
544
+ if restored_handle is not None:
545
+ self._waiting_modes[run_id] = "resume"
546
+ restored_handle = None
282
547
  async for event in self.executor.stream(handle):
548
+ if (
549
+ isinstance(event, RunInterrupted)
550
+ and event.interaction_id
551
+ and record.status == RunStatus.WAITING_INPUT
552
+ and event.source.framework == "codex"
553
+ ):
554
+ # Codex emits this companion event immediately after a
555
+ # live JSON-RPC approval request. The native turn stays
556
+ # attached and must be released through submit; trying
557
+ # to resume it deadlocks that request and later breaks
558
+ # the app-server pipe.
559
+ self._waiting_modes[run_id] = "live"
560
+ continue
561
+ if isinstance(event, InteractionResolved) and event.source.framework == "codex":
562
+ submitted = self._submitted_interactions.get(run_id, set())
563
+ provider_call_id = str(getattr(event.response, "call_id", None) or "")
564
+ if event.interaction_id in submitted or provider_call_id in submitted:
565
+ # submit_interaction atomically persists the public
566
+ # terminal fact and receipt. Ignore the provider's
567
+ # echo so replay never shows the approval twice.
568
+ continue
283
569
  await persist(event)
284
570
  if self._cancel_flags.get(run_id):
285
571
  raise asyncio.CancelledError()
@@ -288,9 +574,7 @@ class StudioRunService:
288
574
  elif (
289
575
  isinstance(event, ItemUpdated)
290
576
  and event.item_kind == "message"
291
- and item_phases.get(
292
- (event.scope_id, event.item_id), "final_answer"
293
- )
577
+ and item_phases.get((event.scope_id, event.item_id), "final_answer")
294
578
  == "final_answer"
295
579
  ):
296
580
  text = event.update.text if isinstance(event.update, TextContent) else ""
@@ -304,9 +588,7 @@ class StudioRunService:
304
588
  elif (
305
589
  isinstance(event, ItemCompleted)
306
590
  and event.item_kind == "message"
307
- and item_phases.get(
308
- (event.scope_id, event.item_id), "final_answer"
309
- )
591
+ and item_phases.get((event.scope_id, event.item_id), "final_answer")
310
592
  == "final_answer"
311
593
  ):
312
594
  completed_text_by_item[(event.scope_id, event.item_id)] = "".join(
@@ -428,6 +710,8 @@ class StudioRunService:
428
710
  fallback_parts = [part for part in streamed_text_by_item.values() if part]
429
711
  record.output = "\n\n".join(completed_parts or fallback_parts)
430
712
  except asyncio.CancelledError:
713
+ if self._detaching_recoveries and record.status == RunStatus.WAITING_INPUT:
714
+ raise
431
715
  cancel_result = "task_cancelled"
432
716
  if handle is not None and self.executor.is_attached(handle):
433
717
  try:
@@ -481,6 +765,7 @@ class StudioRunService:
481
765
  )
482
766
  await persist(failure)
483
767
  finally:
768
+ projected_writer.flush()
484
769
  if handle is not None and self.executor.is_attached(handle):
485
770
  try:
486
771
  await asyncio.shield(self.executor.close(handle))
@@ -492,7 +777,13 @@ class StudioRunService:
492
777
  self._cancel_flags.pop(run_id, None)
493
778
  self._control_queues.pop(run_id, None)
494
779
  self._waiting_modes.pop(run_id, None)
495
- record.completed_at = datetime.now(timezone.utc)
780
+ self._submitted_interactions.pop(run_id, None)
781
+ preserve_waiting = (
782
+ self._detaching_recoveries and record.status == RunStatus.WAITING_INPUT
783
+ )
784
+ if not preserve_waiting:
785
+ record.completed_at = datetime.now(timezone.utc)
786
+ self._expire_unresolved_interactions(record, on_event=on_event)
496
787
  if runtime_duration_ms is not None:
497
788
  record.duration_ms = runtime_duration_ms
498
789
  record.duration_source = "runtime"
@@ -503,6 +794,179 @@ class StudioRunService:
503
794
  await self._sync_trace(record)
504
795
  return record
505
796
 
797
+ async def _schedule_run(
798
+ self,
799
+ record: RunRecord,
800
+ user_input: str,
801
+ *,
802
+ on_event: Callable[[RunEvent], None] | None,
803
+ ) -> RunRecord | None:
804
+ """Persist real host tool receipts on the canonical conversation stream."""
805
+ started = time.monotonic()
806
+
807
+ def envelope(kind: str) -> dict[str, Any]:
808
+ result = _plugin_event_envelope(record, kind)
809
+ result["source"] = SourceRef(
810
+ framework="ksadk",
811
+ metadata={"runtime": "studio-scheduler", "agent_id": record.agent_id},
812
+ )
813
+ return result
814
+
815
+ async def publish(event: RuntimeEvent) -> None:
816
+ persisted = await self.runtime_events.append_one(record.session_id, event)
817
+ kind, data = project_runtime_event(
818
+ persisted, session_id=record.session_id, public_run_id=record.id
819
+ )
820
+ stored = self.event_store.append(record.id, kind, data)
821
+ if on_event is not None:
822
+ on_event(stored)
823
+
824
+ call_id = None
825
+ try:
826
+ intent = await self.schedule_assistant.plan(
827
+ record.agent_id,
828
+ user_input,
829
+ record.session_id,
830
+ build_id=record.build_id,
831
+ model_name=record.model,
832
+ )
833
+ if self._cancel_flags.get(record.id):
834
+ raise asyncio.CancelledError()
835
+ if intent.action == "ignore":
836
+ return None
837
+ record.status = RunStatus.RUNNING
838
+ record.started_at = datetime.now(timezone.utc)
839
+ record.runtime_handle = {"provider": "studio-scheduler"}
840
+ self.event_store.save(record)
841
+ await publish(RunStarted(**envelope("run.started"), status="running"))
842
+ call_id = f"{record.id}:schedule"
843
+ arguments = intent.model_dump(mode="json", exclude_none=True)
844
+ await publish(
845
+ ItemStarted(
846
+ **envelope("tool.started"),
847
+ item_id=call_id,
848
+ item_kind="tool_call",
849
+ initial=ContentSnapshot(
850
+ parts=(
851
+ ToolCallContent(
852
+ part_id=f"{call_id}:call",
853
+ call_id=call_id,
854
+ name="studio_schedule",
855
+ arguments=arguments,
856
+ ),
857
+ )
858
+ ),
859
+ )
860
+ )
861
+ result = await self.schedule_assistant.execute(
862
+ record.agent_id, record.session_id, intent
863
+ )
864
+ await publish(
865
+ ItemCompleted(
866
+ **envelope("tool.completed"),
867
+ item_id=call_id,
868
+ item_kind="tool_call",
869
+ snapshot=ContentSnapshot(
870
+ parts=(
871
+ ToolResultContent(
872
+ part_id=f"{call_id}:result", call_id=call_id, result=result
873
+ ),
874
+ )
875
+ ),
876
+ )
877
+ )
878
+ output = result["message"]
879
+ item_id, part_id = f"{record.id}:assistant", f"{record.id}:text"
880
+ await publish(
881
+ ItemStarted(
882
+ **envelope("item.started"),
883
+ item_id=item_id,
884
+ item_kind="message",
885
+ phase="final_answer",
886
+ )
887
+ )
888
+ await publish(
889
+ ItemUpdated(
890
+ **envelope("item.updated"),
891
+ item_id=item_id,
892
+ item_kind="message",
893
+ op="append",
894
+ update=TextContent(part_id=part_id, text=output),
895
+ )
896
+ )
897
+ await publish(
898
+ ItemCompleted(
899
+ **envelope("item.completed"),
900
+ item_id=item_id,
901
+ item_kind="message",
902
+ snapshot=ContentSnapshot(parts=(TextContent(part_id=part_id, text=output),)),
903
+ )
904
+ )
905
+ if intent._usage is not None and intent._usage.reported:
906
+ record.usage = intent._usage.model_copy(update={"source": "studio-scheduler"})
907
+ await publish(
908
+ UsageReported(
909
+ **envelope("usage.reported"),
910
+ input_tokens=record.usage.input_tokens,
911
+ output_tokens=record.usage.output_tokens,
912
+ total_tokens=record.usage.total_tokens,
913
+ cached_tokens=record.usage.cached_input_tokens,
914
+ reasoning_tokens=record.usage.reasoning_output_tokens,
915
+ )
916
+ )
917
+ await publish(
918
+ RunCompleted(
919
+ **envelope("run.completed"),
920
+ status="completed",
921
+ output_refs=(OutputRef(scope_id=record.id, item_id=item_id, part_id=part_id),),
922
+ )
923
+ )
924
+ record.output = output
925
+ record.status = RunStatus.COMPLETED
926
+ except asyncio.CancelledError:
927
+ record.status = RunStatus.CANCELLED
928
+ record.completed_at = datetime.now(timezone.utc)
929
+ self._cancel_flags.pop(record.id, None)
930
+ await publish(
931
+ RunCanceled(**envelope("run.canceled"), status="canceled", reason="cancelled")
932
+ )
933
+ self.event_store.save(record)
934
+ raise
935
+ except Exception as error:
936
+ code = str(getattr(error, "code", "SCHEDULE_REQUEST_FAILED"))
937
+ record.status = RunStatus.FAILED
938
+ record.error = {"code": code, "message": str(error)}
939
+ if call_id is not None:
940
+ await publish(
941
+ ItemFailed(
942
+ **envelope("tool.failed"),
943
+ item_id=call_id,
944
+ item_kind="tool_call",
945
+ error=ErrorInfo(
946
+ code=code,
947
+ message=str(error),
948
+ source="studio-scheduler",
949
+ scope_id=record.id,
950
+ ),
951
+ )
952
+ )
953
+ await publish(
954
+ RunFailed(
955
+ **envelope("run.failed"),
956
+ status="failed",
957
+ error=ErrorInfo(
958
+ code=code, message=str(error), source="studio-scheduler", scope_id=record.id
959
+ ),
960
+ )
961
+ )
962
+ record.completed_at = datetime.now(timezone.utc)
963
+ record.duration_ms = int((time.monotonic() - started) * 1000)
964
+ record.duration_source = "studio"
965
+ self._cancel_flags.pop(record.id, None)
966
+ self.event_store.save(record)
967
+ await self._sync_trace(record)
968
+ return record
969
+
506
970
  async def _sync_trace(self, record: RunRecord) -> None:
507
971
  self.event_store.trace_store.sync(record, await self.events(record.id))
508
972
 
@@ -531,18 +995,61 @@ class StudioRunService:
531
995
  "activationKey": record.session_id,
532
996
  }
533
997
  self.event_store.save(record)
998
+ projected_writer = _ProjectedRunEventWriter(self.event_store, record.id, on_event)
534
999
  rows_before = await self.session_service.get_events(record.session_id)
535
1000
  after_seq = max((int(row.seq_id or 0) for row in rows_before), default=0)
1001
+ published_seq = after_seq
536
1002
 
537
1003
  async def publish(events: list[RuntimeEvent]) -> None:
1004
+ nonlocal published_seq
538
1005
  for runtime_event in events:
1006
+ # Harness keeps token-level child output in the canonical
1007
+ # session trace for diagnostics. It is deliberately not part
1008
+ # of Studio's public run timeline: the activity projection
1009
+ # never consumes these private deltas, and persisting each one
1010
+ # rewrites the growing run JSON file. Long-running delegated
1011
+ # research can otherwise turn a few useful activity rows into
1012
+ # thousands of O(n²) file writes and make the page appear
1013
+ # frozen. Advance the cursor so final catch-up will not replay
1014
+ # an omitted delta.
1015
+ if _is_private_harness_delta(runtime_event):
1016
+ published_seq = max(published_seq, int(runtime_event.seq or 0))
1017
+ continue
539
1018
  event_type, data = project_runtime_event(
540
1019
  runtime_event,
541
1020
  session_id=record.session_id,
542
1021
  )
543
- stored = self.event_store.append(record.id, event_type, data)
544
- if on_event is not None:
545
- on_event(stored)
1022
+ projected_writer.append(event_type, data)
1023
+ published_seq = max(published_seq, int(runtime_event.seq or 0))
1024
+
1025
+ async def tail_live_events() -> None:
1026
+ # Kernel worker 会把 harness 的增量事实(TEXT_DELTA/ItemUpdated 等)
1027
+ # 在执行期间逐条写入 canonical session store;这里实时转发给 SSE
1028
+ # 订阅端,避免整轮结束后一次性爆发。终态事件会自然结束订阅。
1029
+ async for event in self.runtime_events.subscribe_run(
1030
+ record.session_id,
1031
+ record.id,
1032
+ after_seq=published_seq,
1033
+ poll_interval=0.1,
1034
+ timeout=600,
1035
+ ):
1036
+ try:
1037
+ await publish([event])
1038
+ except asyncio.CancelledError:
1039
+ raise
1040
+ except Exception:
1041
+ logger.exception(
1042
+ "failed to publish live plugin event for run %s", record.id
1043
+ )
1044
+
1045
+ tail_task: asyncio.Task[None] | None = None
1046
+
1047
+ async def stop_tail() -> None:
1048
+ nonlocal tail_task
1049
+ if tail_task is not None:
1050
+ tail_task.cancel()
1051
+ await asyncio.gather(tail_task, return_exceptions=True)
1052
+ tail_task = None
546
1053
 
547
1054
  try:
548
1055
  if self.plugin_runtime is None:
@@ -561,18 +1068,22 @@ class StudioRunService:
561
1068
  }.items()
562
1069
  if value not in {None, ""}
563
1070
  }
564
- result = await self.plugin_runtime.execute(
565
- spec,
566
- {
567
- "user_id": "local-user",
568
- "session_id": record.session_id,
569
- "invocation_id": record.id,
570
- "messages": [{"role": "user", "content": user_input}],
571
- "model": spec.model,
572
- "request_metadata": request_metadata,
573
- },
574
- session_id=record.session_id,
575
- )
1071
+ tail_task = asyncio.create_task(tail_live_events())
1072
+ try:
1073
+ result = await self.plugin_runtime.execute(
1074
+ spec,
1075
+ {
1076
+ "user_id": "local-user",
1077
+ "session_id": record.session_id,
1078
+ "invocation_id": record.id,
1079
+ "messages": [{"role": "user", "content": user_input}],
1080
+ "model": spec.model,
1081
+ "request_metadata": request_metadata,
1082
+ },
1083
+ session_id=record.session_id,
1084
+ )
1085
+ finally:
1086
+ await stop_tail()
576
1087
  if result.session_id != record.session_id:
577
1088
  raise StudioError(
578
1089
  "PLUGIN_SESSION_MISMATCH",
@@ -585,7 +1096,7 @@ class StudioRunService:
585
1096
  )
586
1097
  canonical = await self._plugin_events_after(
587
1098
  record.session_id,
588
- after_seq=after_seq,
1099
+ after_seq=published_seq,
589
1100
  run_id=record.id,
590
1101
  )
591
1102
  if not canonical:
@@ -599,6 +1110,7 @@ class StudioRunService:
599
1110
  record.status = RunStatus.COMPLETED
600
1111
  _apply_plugin_usage(record, result.usage)
601
1112
  except asyncio.CancelledError:
1113
+ await stop_tail()
602
1114
  record.status = RunStatus.CANCELLED
603
1115
  record.error = {"code": "RUN_CANCELLED", "message": "运行已取消"}
604
1116
  cancelled = await self.runtime_events.append_one(
@@ -612,12 +1124,13 @@ class StudioRunService:
612
1124
  await publish([cancelled])
613
1125
  raise
614
1126
  except Exception as exc: # noqa: BLE001 - plugin boundary is typed below
1127
+ await stop_tail()
615
1128
  record.status = RunStatus.FAILED
616
1129
  code = str(getattr(exc, "code", "PLUGIN_RUNTIME_FAILED"))
617
1130
  record.error = {"code": code, "message": str(exc)}
618
1131
  canonical = await self._plugin_events_after(
619
1132
  record.session_id,
620
- after_seq=after_seq,
1133
+ after_seq=published_seq,
621
1134
  run_id=record.id,
622
1135
  )
623
1136
  if canonical:
@@ -638,6 +1151,7 @@ class StudioRunService:
638
1151
  )
639
1152
  await publish([failed])
640
1153
  finally:
1154
+ projected_writer.flush()
641
1155
  record.completed_at = datetime.now(timezone.utc)
642
1156
  record.duration_ms = int((time.monotonic() - started) * 1000)
643
1157
  record.duration_source = "studio"
@@ -672,9 +1186,7 @@ class StudioRunService:
672
1186
  ) -> list[RuntimeEvent]:
673
1187
  item_id = f"{record.id}:assistant"
674
1188
  part_id = f"{record.id}:text"
675
- snapshot = ContentSnapshot(
676
- parts=(TextContent(part_id=part_id, text=output_text),)
677
- )
1189
+ snapshot = ContentSnapshot(parts=(TextContent(part_id=part_id, text=output_text),))
678
1190
  events: list[RuntimeEvent] = [
679
1191
  RunStarted(
680
1192
  **_plugin_event_envelope(record, "run.started"),
@@ -725,10 +1237,7 @@ class StudioRunService:
725
1237
  ),
726
1238
  )
727
1239
  )
728
- return [
729
- await self.runtime_events.append_one(record.session_id, event)
730
- for event in events
731
- ]
1240
+ return [await self.runtime_events.append_one(record.session_id, event) for event in events]
732
1241
 
733
1242
  async def _kernel_run(
734
1243
  self,
@@ -764,9 +1273,7 @@ class StudioRunService:
764
1273
  session_id=record.session_id,
765
1274
  operations=("enqueue",),
766
1275
  )
767
- idempotency_key = str(
768
- (spec.request_config or {}).get("idempotency_key") or record.id
769
- )
1276
+ idempotency_key = str((spec.request_config or {}).get("idempotency_key") or record.id)
770
1277
  command = _kernel_ingress.map_studio_request(
771
1278
  session_id=record.session_id,
772
1279
  idempotency_key=idempotency_key,
@@ -774,9 +1281,7 @@ class StudioRunService:
774
1281
  run_id=record.id,
775
1282
  trusted=trusted,
776
1283
  )
777
- receipt = await _kernel_ingress.submit_command(
778
- command, permit=trusted.permit
779
- )
1284
+ receipt = await _kernel_ingress.submit_command(command, permit=trusted.permit)
780
1285
  if receipt.status not in ("accepted", "duplicate"):
781
1286
  record.status = RunStatus.FAILED
782
1287
  record.error = {
@@ -859,17 +1364,13 @@ class StudioRunService:
859
1364
  if active_project != build_project:
860
1365
  return None
861
1366
  defaults = getattr(runtime.config, "start_request_defaults", {}) or {}
862
- bound_agent_id = str(
863
- defaults.get("agent_id") or runtime.config.agent_instance_id
864
- ).strip()
1367
+ bound_agent_id = str(defaults.get("agent_id") or runtime.config.agent_instance_id).strip()
865
1368
  if bound_agent_id != spec.agent_id:
866
1369
  return None
867
1370
  declared_instance = str(
868
1371
  (spec.launch_context.config or {}).get("agent_instance_id") or ""
869
1372
  ).strip()
870
- if declared_instance and declared_instance != str(
871
- runtime.config.agent_instance_id
872
- ):
1373
+ if declared_instance and declared_instance != str(runtime.config.agent_instance_id):
873
1374
  return None
874
1375
  return runtime
875
1376
 
@@ -998,11 +1499,8 @@ class StudioRunService:
998
1499
  "同一幂等键不能提交不同内容",
999
1500
  status_code=409,
1000
1501
  )
1001
- record = self.event_store.get(run_id)
1002
- if record.status == RunStatus.WAITING_INPUT:
1003
- record.status = RunStatus.RUNNING
1004
- self.event_store.save(record)
1005
- self._waiting_modes.pop(run_id, None)
1502
+ # A receipt replay is read-only. The run may already be waiting
1503
+ # for a different interaction; never clear that newer wait state.
1006
1504
  return dict(prior.data.get("receipt") or {})
1007
1505
 
1008
1506
  interaction = next(
@@ -1039,10 +1537,51 @@ class StudioRunService:
1039
1537
  status_code=409,
1040
1538
  )
1041
1539
  kind = str(interaction.data.get("kind") or "form")
1042
- payload_data = {"decision": name, **data}
1540
+ if kind != "approval" and name not in {"cancel", "skip"}:
1541
+ schema = interaction.data.get("inputSchema") or {}
1542
+ errors = list(Draft202012Validator(schema).iter_errors(data))
1543
+ if errors:
1544
+ raise StudioError(
1545
+ "INTERACTION_RESPONSE_INVALID",
1546
+ "请填写必填问题后再提交。",
1547
+ status_code=422,
1548
+ )
1549
+ provider_call_id = str(
1550
+ interaction.data.get("callId") or interaction.data.get("call_id") or ""
1551
+ )
1552
+ if kind == "approval" and not provider_call_id:
1553
+ approval_request = next(
1554
+ (
1555
+ event
1556
+ for event in reversed(events)
1557
+ if event.type == "approval.requested"
1558
+ and str(event.data.get("approvalId") or event.data.get("interactionId") or "")
1559
+ == interaction_id
1560
+ ),
1561
+ None,
1562
+ )
1563
+ if approval_request is not None:
1564
+ provider_call_id = str(
1565
+ approval_request.data.get("callId")
1566
+ or approval_request.data.get("call_id")
1567
+ or ""
1568
+ )
1569
+ provider_call_id = provider_call_id or interaction_id
1570
+ submitted = self._submitted_interactions.setdefault(run_id, set())
1571
+ submitted.update({interaction_id, provider_call_id})
1572
+ decision = str(data.get("decision") or name)
1573
+ if kind == "approval" and record.runtime_type == "codex":
1574
+ # The shared Web action is named `reject`; Codex app-server calls
1575
+ # the same decision `deny`/`decline`. Do not let a UI alias fall
1576
+ # through as an unsupported native approval decision.
1577
+ decision = {
1578
+ "reject": "deny",
1579
+ "rejected": "deny",
1580
+ }.get(decision.lower(), decision)
1581
+ payload_data = {**data, "decision": decision}
1043
1582
  payload = ResumePayload(
1044
1583
  kind="approval_decision" if kind == "approval" else "hitl_answer",
1045
- call_id=interaction_id,
1584
+ call_id=provider_call_id,
1046
1585
  data=payload_data,
1047
1586
  )
1048
1587
  mode = self._waiting_modes.get(run_id)
@@ -1051,8 +1590,19 @@ class StudioRunService:
1051
1590
  if handle is None or not self.executor.is_attached(handle):
1052
1591
  raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
1053
1592
  try:
1054
- await self.executor.submit(handle, payload)
1593
+ await asyncio.wait_for(
1594
+ self.executor.submit(handle, payload),
1595
+ timeout=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
1596
+ )
1597
+ except TimeoutError as exc:
1598
+ submitted.difference_update({interaction_id, provider_call_id})
1599
+ raise StudioError(
1600
+ "INTERACTION_SUBMIT_FAILED",
1601
+ "运行时未及时确认交互提交,请重试",
1602
+ status_code=409,
1603
+ ) from exc
1055
1604
  except Exception as exc: # noqa: BLE001 - provider errors are user-safe here
1605
+ submitted.difference_update({interaction_id, provider_call_id})
1056
1606
  raise StudioError(
1057
1607
  "INTERACTION_SUBMIT_FAILED",
1058
1608
  str(exc),
@@ -1065,22 +1615,51 @@ class StudioRunService:
1065
1615
  submit_ack = asyncio.get_running_loop().create_future()
1066
1616
  queue.put_nowait(("resume", payload, submit_ack))
1067
1617
  try:
1068
- await submit_ack
1618
+ await asyncio.wait_for(
1619
+ submit_ack,
1620
+ timeout=_INTERACTION_SUBMIT_TIMEOUT_SECONDS,
1621
+ )
1622
+ except TimeoutError as exc:
1623
+ submitted.difference_update({interaction_id, provider_call_id})
1624
+ raise StudioError(
1625
+ "INTERACTION_SUBMIT_FAILED",
1626
+ "运行时未及时确认交互提交,请重试",
1627
+ status_code=409,
1628
+ ) from exc
1069
1629
  except Exception as exc: # noqa: BLE001 - provider errors are user-safe here
1630
+ submitted.difference_update({interaction_id, provider_call_id})
1070
1631
  raise StudioError(
1071
1632
  "INTERACTION_SUBMIT_FAILED",
1072
1633
  str(exc),
1073
1634
  status_code=409,
1074
1635
  ) from exc
1075
1636
  else:
1637
+ submitted.difference_update({interaction_id, provider_call_id})
1076
1638
  raise StudioError("INTERACTION_EXPIRED", "运行时交互已失效", status_code=409)
1077
1639
 
1078
1640
  next_revision = revision + 1
1641
+ normalized_action = name.lower()
1642
+ if normalized_action in {"approve", "approved", "accept", "accepted"}:
1643
+ outcome = "approved"
1644
+ response_summary = "已同意"
1645
+ elif normalized_action in {"reject", "rejected", "deny", "denied", "decline"}:
1646
+ outcome = "rejected"
1647
+ response_summary = "已拒绝"
1648
+ elif normalized_action in {"cancel", "cancelled", "canceled"}:
1649
+ outcome = "cancelled"
1650
+ response_summary = "已反馈给 Agent" if data.get("feedback") else "已取消"
1651
+ else:
1652
+ outcome = "submitted"
1653
+ response_summary = "已提交"
1079
1654
  resolved_data = {
1080
1655
  "runId": run_id,
1081
1656
  "interactionId": interaction_id,
1082
- "callId": interaction_id,
1657
+ "callId": provider_call_id,
1083
1658
  "name": name,
1659
+ "action": name,
1660
+ "outcome": outcome,
1661
+ "actor": "user",
1662
+ "responseSummary": response_summary,
1084
1663
  "data": data,
1085
1664
  "revision": next_revision,
1086
1665
  "expectedRevision": expected_revision,
@@ -1092,6 +1671,7 @@ class StudioRunService:
1092
1671
  interaction.data.get("surfaceId") or interaction.data.get("surface_id") or ""
1093
1672
  ),
1094
1673
  "interactionId": interaction_id,
1674
+ "callId": provider_call_id,
1095
1675
  "actionId": f"action-{interaction_id}",
1096
1676
  "name": name,
1097
1677
  "data": data,
@@ -1111,6 +1691,65 @@ class StudioRunService:
1111
1691
  self._waiting_modes.pop(run_id, None)
1112
1692
  return receipt
1113
1693
 
1694
+ def _expire_unresolved_interactions(
1695
+ self,
1696
+ record: RunRecord,
1697
+ *,
1698
+ on_event: Callable[[RunEvent], None] | None = None,
1699
+ ) -> None:
1700
+ """Close orphaned cards whenever their owning run is terminal."""
1701
+
1702
+ if record.status not in {
1703
+ RunStatus.COMPLETED,
1704
+ RunStatus.FAILED,
1705
+ RunStatus.CANCELLED,
1706
+ RunStatus.INTERRUPTED,
1707
+ RunStatus.TIMED_OUT,
1708
+ }:
1709
+ return
1710
+ events = self.event_store.events(record.id)
1711
+ closed_ids = {
1712
+ str(event.data.get("interactionId") or event.data.get("approvalId") or "")
1713
+ for event in events
1714
+ if event.type
1715
+ in {
1716
+ "a2ui.action",
1717
+ "approval.resolved",
1718
+ "interaction.resolved",
1719
+ "interaction.cancelled",
1720
+ "interaction.expired",
1721
+ }
1722
+ }
1723
+ pending: dict[str, RunEvent] = {}
1724
+ for event in events:
1725
+ if event.type != "a2ui.interaction":
1726
+ continue
1727
+ interaction_id = str(
1728
+ event.data.get("interactionId") or event.data.get("interaction_id") or ""
1729
+ )
1730
+ if interaction_id and interaction_id not in closed_ids:
1731
+ pending[interaction_id] = event
1732
+ for interaction_id, requested in pending.items():
1733
+ data = requested.data
1734
+ expired = self.event_store.append(
1735
+ record.id,
1736
+ "interaction.expired",
1737
+ {
1738
+ "runId": record.id,
1739
+ "interactionId": interaction_id,
1740
+ "approvalId": interaction_id,
1741
+ "callId": str(data.get("callId") or data.get("call_id") or ""),
1742
+ "kind": str(data.get("kind") or "form"),
1743
+ "title": data.get("title"),
1744
+ "message": data.get("message"),
1745
+ "detail": data.get("detail"),
1746
+ "outcome": "expired",
1747
+ "revision": _interaction_revision(data) + 1,
1748
+ },
1749
+ )
1750
+ if on_event is not None:
1751
+ on_event(expired)
1752
+
1114
1753
  async def _capture_pcm_evidence(
1115
1754
  self,
1116
1755
  record: RunRecord,
@@ -1255,7 +1894,11 @@ class StudioRunService:
1255
1894
  "runId": record.id,
1256
1895
  "surfaceId": surface_id,
1257
1896
  "interactionId": approval_id,
1897
+ "callId": event.request.call_id or approval_id,
1258
1898
  "kind": "approval",
1899
+ "title": "需要审批",
1900
+ "message": "此次操作需要确认后才能继续。",
1901
+ "detail": detail,
1259
1902
  "revision": 1,
1260
1903
  "inputSchema": {
1261
1904
  "type": "object",
@@ -1284,7 +1927,41 @@ class StudioRunService:
1284
1927
  if previous.agent_id != agent_id or previous.status != RunStatus.COMPLETED:
1285
1928
  continue
1286
1929
  messages.append({"role": "user", "content": previous.input})
1287
- messages.append({"role": "assistant", "content": previous.output})
1930
+ assistant_content = previous.output
1931
+ if previous.runtime_type == "harness":
1932
+ projection = project_run_activities(
1933
+ previous,
1934
+ self.event_store.events(previous.id),
1935
+ )
1936
+ children = [
1937
+ activity
1938
+ for activity in projection.get("activities", [])
1939
+ if activity.get("kind") == "subagent"
1940
+ ][:8]
1941
+ if children:
1942
+ status_text = {
1943
+ "completed": "已完成",
1944
+ "failed": "失败",
1945
+ "cancelled": "已取消",
1946
+ "running": "仍在运行",
1947
+ "unknown": "状态未确认",
1948
+ }
1949
+ facts = ";".join(
1950
+ f"子智能体“{child.get('label') or '未命名'}”"
1951
+ f"{status_text.get(str(child.get('status')), '状态未确认')}"
1952
+ for child in children
1953
+ )
1954
+ # Lifecycle evidence belongs to the completed assistant turn.
1955
+ # A standalone system message inserted between historical turns
1956
+ # can outweigh the next user request on some providers and make
1957
+ # a new turn answer an old child result instead (conversation
1958
+ # "cross-talk"). Keep the evidence attached to the turn it
1959
+ # describes so the newest user message remains authoritative.
1960
+ assistant_content = (
1961
+ f"{assistant_content}\n\n"
1962
+ f"[Harness 执行记录] {facts}。"
1963
+ )
1964
+ messages.append({"role": "assistant", "content": assistant_content})
1288
1965
  messages.append({"role": "user", "content": user_input})
1289
1966
  return messages
1290
1967
 
@@ -1293,16 +1970,30 @@ class StudioRunService:
1293
1970
  agent_id: str,
1294
1971
  session_id: str,
1295
1972
  runtime_type: str,
1973
+ build_id: str,
1296
1974
  ) -> dict[str, str]:
1297
1975
  if runtime_type != "codex":
1298
1976
  return {}
1299
- for previous in self.event_store.list_runs(session_id=session_id):
1300
- if previous.agent_id != agent_id or previous.status != RunStatus.COMPLETED:
1301
- continue
1977
+ runs = self.event_store.list_runs(session_id=session_id, agent_id=agent_id)
1978
+ thread_builds: dict[str, str] = {}
1979
+ for previous in runs:
1980
+ native_ref = previous.runtime_handle.get("native_ref") or {}
1981
+ if isinstance(native_ref, dict) and native_ref.get("thread_id"):
1982
+ # Older Studio versions also saved failed cross-build resumes.
1983
+ # The first occurrence identifies the thread's actual home.
1984
+ thread_builds.setdefault(str(native_ref["thread_id"]), previous.build_id)
1985
+ for previous in reversed(runs):
1986
+ # Each immutable build has its own CODEX_HOME and plugin snapshot.
1987
+ # Starting a thread in that home applies the new capabilities; the
1988
+ # transport-neutral history above preserves the Studio conversation.
1989
+ if previous.build_id != build_id:
1990
+ return {}
1302
1991
  native_ref = previous.runtime_handle.get("native_ref")
1303
1992
  native_ref = native_ref if isinstance(native_ref, dict) else {}
1304
1993
  thread_id = str(native_ref.get("thread_id") or "")
1305
1994
  if thread_id:
1995
+ if thread_builds[thread_id] != build_id:
1996
+ return {}
1306
1997
  return {"thread_id": thread_id}
1307
1998
  return {}
1308
1999
 
@@ -1340,17 +2031,13 @@ def _normalized_plugin_usage(usage: Mapping[str, Any]) -> dict[str, Any]:
1340
2031
 
1341
2032
  normalized = {
1342
2033
  "input_tokens": number("input_tokens", "inputTokens", "prompt_tokens"),
1343
- "output_tokens": number(
1344
- "output_tokens", "outputTokens", "completion_tokens"
1345
- ),
2034
+ "output_tokens": number("output_tokens", "outputTokens", "completion_tokens"),
1346
2035
  "total_tokens": number("total_tokens", "totalTokens"),
1347
2036
  "cached_tokens": number("cached_tokens", "cachedTokens"),
1348
2037
  "reasoning_tokens": number("reasoning_tokens", "reasoningTokens"),
1349
2038
  }
1350
2039
  if not normalized["total_tokens"]:
1351
- normalized["total_tokens"] = (
1352
- normalized["input_tokens"] + normalized["output_tokens"]
1353
- )
2040
+ normalized["total_tokens"] = normalized["input_tokens"] + normalized["output_tokens"]
1354
2041
  normalized["reported"] = bool(usage) and any(
1355
2042
  key in usage
1356
2043
  for key in (
@@ -1391,6 +2078,25 @@ def _apply_plugin_usage(record: RunRecord, usage: Mapping[str, Any]) -> None:
1391
2078
  )
1392
2079
 
1393
2080
 
2081
+ def _is_private_harness_delta(event: RuntimeEvent) -> bool:
2082
+ """Return whether *event* is an internal token delta, not a UI activity.
2083
+
2084
+ Managed Harness projects its rich event stream to canonical status items.
2085
+ ``text.delta`` and ``reasoning.delta`` contain private token fragments,
2086
+ are intentionally ignored by ``run_activity``, and must not be copied into
2087
+ Studio's durable session or run stores. User-visible parent answer
2088
+ streaming uses canonical ``message`` items and therefore remains
2089
+ unaffected.
2090
+ """
2091
+
2092
+ if not isinstance(event, ItemCompleted) or event.item_kind != "status":
2093
+ return False
2094
+ if event.source.framework != "ksadk":
2095
+ return False
2096
+ native_event_type = str(event.source.metadata.get("native_event_type") or "")
2097
+ return native_event_type in {"reasoning.delta", "text.delta"}
2098
+
2099
+
1394
2100
  def project_runtime_event(
1395
2101
  event: RuntimeEvent,
1396
2102
  *,
@@ -1520,7 +2226,9 @@ def project_runtime_event(
1520
2226
  payload = {
1521
2227
  "interactionId": event.interaction_id,
1522
2228
  "kind": "form",
1523
- "inputSchema": {},
2229
+ "inputSchema": event.request.schema_,
2230
+ "message": event.request.prompt or "",
2231
+ "callId": event.source.native_event_id or event.interaction_id,
1524
2232
  }
1525
2233
  elif isinstance(event, InteractionResolved):
1526
2234
  if event.interaction_kind == "approval":
@@ -1575,9 +2283,7 @@ def project_runtime_event(
1575
2283
  return projected, payload
1576
2284
 
1577
2285
 
1578
- def _first_content(
1579
- snapshot: ContentSnapshot | None, content_type: type
1580
- ) -> Any | None:
2286
+ def _first_content(snapshot: ContentSnapshot | None, content_type: type) -> Any | None:
1581
2287
  if snapshot is None:
1582
2288
  return None
1583
2289
  for part in snapshot.parts: