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