execweave 0.8.32__tar.gz → 0.8.33__tar.gz
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.
- {execweave-0.8.32 → execweave-0.8.33}/PKG-INFO +1 -1
- {execweave-0.8.32 → execweave-0.8.33}/pyproject.toml +2 -2
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/__init__.py +1 -1
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/_dashboard_shell_base.py +1 -0
- execweave-0.8.33/src/execweave/acceptance_output.py +19 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_adapter.py +2 -1
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_hook_cli.py +26 -17
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_hook_entry.py +3 -1
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/collector.py +3 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_records.py +4 -1
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_records_common.py +1 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/dashboard_shell.py +8 -10
- execweave-0.8.33/src/execweave/finalization.py +44 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/graph.py +13 -0
- execweave-0.8.33/src/execweave/hook_input.py +16 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_core.py +51 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_script_a.py +13 -1
- execweave-0.8.33/src/execweave/session_summary.py +48 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel.py +47 -36
- execweave-0.8.33/src/execweave/viewer_background_files.py +86 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_flow_canvas.py +20 -1
- execweave-0.8.33/src/execweave/viewer_fold_state.py +31 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_semantic_projection.py +9 -2
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_session_flow.py +1 -1
- {execweave-0.8.32 → execweave-0.8.33}/.gitignore +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/LICENSE +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/README.md +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/__main__.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/_conversation_records_core.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/_http_proxy_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/_http_proxy_bounded.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/_http_proxy_stage.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/agent_bootstrap.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/agent_topology.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/agent_trace.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/agy_preview_sanitize.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/analysis.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/anthropic.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/anthropic_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/anthropic_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_adapter.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_adapter_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_full_fidelity_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_hook_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_record.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_subagent_linkage.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/antigravity_trace_capability.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/auto_specialized.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/backends.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/benchmark.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_adapter.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_child_transcript.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_delegation.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_hook_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_hook_contract.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_model_observer.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/claude_record.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_conversation.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_hook_lifecycle.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_message_diagnostics.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_message_transport_diagnostics.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_record.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_rollout_structures.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_rollout_trace.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/codex_rollout_trace_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/command.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/content_evidence.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/content_store.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_archive.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_message_identity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_antigravity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_claude.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_codex.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_common.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_generic.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_lines.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_preview_transcript.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_records_antigravity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_records_codex.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/conversation_records_ollama.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/correlation.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_adapter.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_delegation.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_delegation_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_hook_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_hook_contract.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_lifecycle.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/cursor_record.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/entry.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/evidence_availability.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/evidence_grade.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/filesystem.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/focus.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/__init__.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/autogen.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/camel.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/camel_channel.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/metagpt.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/framework_adapters/registry.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/graph_ops.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/http_proxy.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/http_proxy_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/inference_gateway.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/inference_gateway_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/inference_gateway_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/inference_identity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/inference_identity_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/integrity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/integrity_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/litellm_callback.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/litellm_callback_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_extra_style.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_markup.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_process_layout.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_readability.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_script_b.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_script_c.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_script_d.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/live_view_style.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/model_runtime.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/model_runtime_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/model_runtime_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/openai_compatible.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/openai_compatible_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/openai_compatible_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_adapter.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_event_contract.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_full_fidelity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_hook_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_plugin_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_record.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/opencode_task_linkage.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/overhead_benchmark.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/provider_capability.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/provider_lifecycle.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/provider_record.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/rule_pack.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/rule_pack_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/runtime_endpoint_availability.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/scalability_benchmark.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/schema.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/scope.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/semantic.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/sink.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/strace_backend.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/stream_assembly.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/theme.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/top.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/top_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/validate.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/vendor/dagre.min.js +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/view_cli.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_claude.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_codex.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_cursor.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_default.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_ollama.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_opencode.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_agent_panel_registry.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_content_inspector.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_dashboard_clean.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_dashboard_focus.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_dashboard_hardening.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_dashboard_pr70.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_edge_decross.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_execution_flow.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_execution_flow_provider_normalization.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_execution_flow_safety.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_external_endpoints.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_flow_layout.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_framework_flow.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_gif.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_layout_geometry.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_layout_v2.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_layout_v2_detached.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_limits.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_live_layout.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_projection.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_projection_base.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_projection_bridges.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/viewer_tool_mirror_identity.py +0 -0
- {execweave-0.8.32 → execweave-0.8.33}/src/execweave/workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execweave
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.33
|
|
4
4
|
Summary: Turn AI-agent runtime behavior into a local interactive execution graph.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Irish-kw/ExecWeave
|
|
6
6
|
Project-URL: Repository, https://github.com/Irish-kw/ExecWeave
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execweave"
|
|
7
|
-
# ExecWeave v0.8.
|
|
8
|
-
version = "0.8.
|
|
7
|
+
# ExecWeave v0.8.33 release metadata
|
|
8
|
+
version = "0.8.33"
|
|
9
9
|
description = "Turn AI-agent runtime behavior into a local interactive execution graph."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -118,6 +118,7 @@ def render_static_dashboard_html(
|
|
|
118
118
|
static_start = (
|
|
119
119
|
"applyTheme(initialTheme());applyTransform();"
|
|
120
120
|
"setSnapshot(window.__execweaveStaticGraph||{});"
|
|
121
|
+
"updateEvidence(window.__execweaveStaticGraph||{});"
|
|
121
122
|
"setStatus('FINISHED','finished');"
|
|
122
123
|
"window.__execweaveDashboard?.onFinished?.();"
|
|
123
124
|
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Safe output placement for workloads launched inside an active recording."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def prepare_acceptance_output(path: str | Path) -> Path:
|
|
9
|
+
output = Path(path).expanduser().resolve()
|
|
10
|
+
sidecar = os.environ.get("EXECWEAVE_SEMANTIC_SIDECAR")
|
|
11
|
+
shared = bool(sidecar and Path(sidecar).expanduser().resolve().parent == output
|
|
12
|
+
and os.environ.get("EXECWEAVE_SESSION_ID"))
|
|
13
|
+
if output.exists():
|
|
14
|
+
if not output.is_dir():
|
|
15
|
+
raise FileExistsError(f"Acceptance output is not a directory: {output}")
|
|
16
|
+
if (output / "summary.json").exists() or (any(output.iterdir()) and not shared):
|
|
17
|
+
raise FileExistsError(f"Acceptance output contains a previous run: {output}")
|
|
18
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
return output
|
|
@@ -9,6 +9,7 @@ from datetime import datetime, timezone
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
from .agent_topology import EVIDENCE_SUBAGENT_LIFECYCLE_HOOK, subagent_topology
|
|
12
|
+
from .hook_input import read_hook_text
|
|
12
13
|
|
|
13
14
|
_MAX_COMMAND_CHARS = 4096
|
|
14
15
|
_MAX_LABEL_CHARS = 160
|
|
@@ -443,7 +444,7 @@ def append_semantic_records(path: str | Path, records: list[dict[str, Any]]) ->
|
|
|
443
444
|
|
|
444
445
|
def read_hook_payload(stream: Any = None) -> dict[str, Any]:
|
|
445
446
|
source = stream if stream is not None else sys.stdin
|
|
446
|
-
raw = source
|
|
447
|
+
raw = read_hook_text(source)
|
|
447
448
|
if not isinstance(raw, str) or not raw.strip():
|
|
448
449
|
raise ValueError("Codex hook stdin is empty")
|
|
449
450
|
try:
|
|
@@ -246,23 +246,32 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
246
246
|
failures=failures,
|
|
247
247
|
)
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
249
|
+
# Content fields are independent evidence. A malformed input string must not
|
|
250
|
+
# prevent a valid tool result (or transcript archive) from being stored.
|
|
251
|
+
content_fields = {"prompt", "tool_input", "tool_response", "last_assistant_message"}
|
|
252
|
+
for field in sorted(content_fields & payload.keys()):
|
|
253
|
+
isolated = {key: value for key, value in payload.items()
|
|
254
|
+
if key not in content_fields or key == field}
|
|
255
|
+
try:
|
|
256
|
+
content_records = codex_hook_to_content_events(
|
|
257
|
+
isolated, store=content_store, timestamp=observed_at, include_metadata=False,
|
|
258
|
+
)
|
|
259
|
+
except _CAPTURE_ERRORS as exc:
|
|
260
|
+
failures.append("content_capture")
|
|
261
|
+
_capture_warning("content_capture:" + field, exc)
|
|
262
|
+
diagnostic = {
|
|
263
|
+
"timestamp": observed_at, "event_type": "semantic.codex.content.capture_failed",
|
|
264
|
+
"relation": "CONTENT_CAPTURE_FAILED",
|
|
265
|
+
"source": {"id": "agent:OpenAI Codex", "type": "agent", "name": "OpenAI Codex"},
|
|
266
|
+
"target": None,
|
|
267
|
+
"attributes": {"provider": "codex", "observed_field": field,
|
|
268
|
+
"error_type": type(exc).__name__, "tool_use_id": payload.get("tool_use_id"),
|
|
269
|
+
"codex_hook_event_name": payload.get("hook_event_name"),
|
|
270
|
+
"causal": False, "inferred": False},
|
|
271
|
+
}
|
|
272
|
+
_append_stage(sidecar, [diagnostic], stage="content_diagnostic_append", failures=failures)
|
|
273
|
+
else:
|
|
274
|
+
_append_stage(sidecar, content_records, stage="content_append", failures=failures)
|
|
266
275
|
|
|
267
276
|
try:
|
|
268
277
|
archive_records = codex_conversation_archive_events(
|
|
@@ -142,7 +142,9 @@ def _append_interrupt_fast(sidecar: Path, payload: dict[str, Any]) -> None:
|
|
|
142
142
|
|
|
143
143
|
def _active_auto_payload() -> tuple[str, dict[str, Any] | None]:
|
|
144
144
|
try:
|
|
145
|
-
|
|
145
|
+
from .hook_input import read_hook_text
|
|
146
|
+
|
|
147
|
+
raw = read_hook_text(sys.stdin)
|
|
146
148
|
except (OSError, ValueError):
|
|
147
149
|
# The passive entry boundary must remain fail-open even when stdin itself is
|
|
148
150
|
# unavailable. The normal capture path will receive an empty stream and its
|
|
@@ -210,6 +210,9 @@ class RuntimeCollector:
|
|
|
210
210
|
"collector_pid": os.getpid(),
|
|
211
211
|
"backend": self.backend_name,
|
|
212
212
|
"execweave_version": __version__,
|
|
213
|
+
"python_executable": sys.executable,
|
|
214
|
+
"python_version": sys.version.split()[0],
|
|
215
|
+
"package_path": str(Path(__file__).resolve().parent),
|
|
213
216
|
},
|
|
214
217
|
)
|
|
215
218
|
)
|
|
@@ -230,7 +230,7 @@ def _merge_conversation_previews(entries: list[dict[str, Any]]) -> None:
|
|
|
230
230
|
continue
|
|
231
231
|
for message in preview.get("messages") or []:
|
|
232
232
|
if isinstance(message, dict):
|
|
233
|
-
message
|
|
233
|
+
message.setdefault("content_role", role)
|
|
234
234
|
|
|
235
235
|
snapshots: list[tuple[dict[str, Any], dict[str, Any]]] = []
|
|
236
236
|
for entry in entries:
|
|
@@ -326,6 +326,9 @@ def conversation_record_entries(
|
|
|
326
326
|
preview["provider_native_id"] = root_id
|
|
327
327
|
original_source_id = entry.get("source_id")
|
|
328
328
|
if isinstance(original_source_id, str) and original_source_id != root_id:
|
|
329
|
+
for message in preview.get("messages") or []:
|
|
330
|
+
if isinstance(message, dict):
|
|
331
|
+
message["occurrence_id"] = original_source_id
|
|
329
332
|
entry["evidence_source_id"] = original_source_id
|
|
330
333
|
entry["evidence_source_type"] = entry.get("source_type")
|
|
331
334
|
entry["source_id"] = root_id
|
|
@@ -117,13 +117,10 @@ def _defer_camera_takeover_until_node_drag(html: str) -> str:
|
|
|
117
117
|
|
|
118
118
|
|
|
119
119
|
def _preserve_readable_initial_camera(html: str) -> str:
|
|
120
|
-
"""
|
|
120
|
+
"""Use the same whole-graph Fit semantics for initial and explicit fitting.
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
screen-space size even though the camera is otherwise in manual mode. Give only
|
|
125
|
-
the automatic first fit a 0.5 scale floor; an explicit Fit still defaults to the
|
|
126
|
-
established 0.07 floor so the full graph remains available as an overview.
|
|
122
|
+
Readability is addressed by evidence-preserving display clusters, not by
|
|
123
|
+
silently clipping nodes while the toolbar claims the entire graph fits.
|
|
127
124
|
"""
|
|
128
125
|
signature = "function fit(animate=true){"
|
|
129
126
|
if html.count(signature) != 1:
|
|
@@ -148,7 +145,7 @@ def _preserve_readable_initial_camera(html: str) -> str:
|
|
|
148
145
|
raise RuntimeError("initial camera fit seams changed")
|
|
149
146
|
return html.replace(
|
|
150
147
|
initial_fit,
|
|
151
|
-
"if(!hasFitted&&positions.size){fit(false,.
|
|
148
|
+
"if(!hasFitted&&positions.size){fit(false,.48);hasFitted=true}",
|
|
152
149
|
)
|
|
153
150
|
|
|
154
151
|
|
|
@@ -199,7 +196,8 @@ function execweaveFileClusterHistory(a){
|
|
|
199
196
|
add('Model',node?.name);
|
|
200
197
|
add('Provider',a.provider||a.provider_name);
|
|
201
198
|
add('Inference calls',a.viewer_inference_count);
|
|
202
|
-
add('
|
|
199
|
+
if(a.model_resource_id)add('Shared model resource',a.model_resource_id);
|
|
200
|
+
// Chronology is rendered below as persistent per-invocation accordions.
|
|
203
201
|
}else if(kind==='tool_call'){"""
|
|
204
202
|
if html.count(branch_seam) != 1:
|
|
205
203
|
raise RuntimeError("agent panel file/model branch seam changed")
|
|
@@ -224,7 +222,7 @@ def _stop_conversation_polling_after_finish(html: str) -> str:
|
|
|
224
222
|
),
|
|
225
223
|
(
|
|
226
224
|
"async function refresh(){if(window.__execweaveStaticMode||refreshing)return;refreshing=true;try{const headers={};if(window.__execweaveToken)headers['X-ExecWeave-Token']=window.__execweaveToken;const response=await fetch('/conversations.json',{cache:'no-store',headers});if(response.ok){const payload=await response.json();setEntries(payload?.entries)}}catch(_){}finally{refreshing=false}}",
|
|
227
|
-
"async function refresh({allowDuringFinish=false}={}){if(window.__execweaveStaticMode||conversationPollingFinished||(conversationFinishing&&!allowDuringFinish))return false;if(refreshing)return conversationRefreshPromise;refreshing=true;const controller=new AbortController();conversationRefreshController=controller;const task=(async()=>{try{const headers={};if(window.__execweaveToken)headers['X-ExecWeave-Token']=window.__execweaveToken;const response=await fetch('/conversations.json',{cache:'no-store',headers,signal:controller.signal});if(!response.ok)return false;const payload=await response.json();
|
|
225
|
+
"async function refresh({allowDuringFinish=false}={}){if(window.__execweaveStaticMode||(conversationPollingFinished&&!allowDuringFinish)||(conversationFinishing&&!allowDuringFinish))return false;if(refreshing)return conversationRefreshPromise;refreshing=true;const controller=new AbortController();conversationRefreshController=controller;const timeout=setTimeout(()=>controller.abort(),5000);const task=(async()=>{try{const headers={};if(window.__execweaveToken)headers['X-ExecWeave-Token']=window.__execweaveToken;const response=await fetch('/conversations.json',{cache:'no-store',headers,signal:controller.signal});if(!response.ok)return false;const payload=await response.json();if(!Array.isArray(payload?.entries))return false;setEntries(payload.entries);return true}catch(_){return false}finally{clearTimeout(timeout);if(conversationRefreshController===controller)conversationRefreshController=null;refreshing=false}})();conversationRefreshPromise=task;return await task}",
|
|
228
226
|
"agent conversation refresh seam changed",
|
|
229
227
|
),
|
|
230
228
|
(
|
|
@@ -234,7 +232,7 @@ def _stop_conversation_polling_after_finish(html: str) -> str:
|
|
|
234
232
|
),
|
|
235
233
|
(
|
|
236
234
|
"const previous=window.__execweaveDashboard||{};window.__execweaveDashboard={...previous,onPayload(data){previous.onPayload?.(data);if(selectedNode)refresh()},onFinished(){previous.onFinished?.();if(selectedNode)refresh()}};",
|
|
237
|
-
"async function finishConversationPolling(){if(conversationPollingFinished)return
|
|
235
|
+
"function reportFinalConversationSync(ok){let notice=document.getElementById('conversation-sync-status');if(ok){notice?.remove();return}if(notice)return;notice=document.createElement('div');notice.id='conversation-sync-status';notice.setAttribute('role','status');notice.style.cssText='position:fixed;right:18px;bottom:18px;z-index:100;max-width:360px;padding:12px;background:#2b2118;color:#fff;border:1px solid #e5ad63;border-radius:8px';notice.append(document.createTextNode('Conversation history may be incomplete. Final synchronization failed. '));const retry=document.createElement('button');retry.type='button';retry.textContent='Retry sync';retry.onclick=async()=>{retry.disabled=true;try{await finishConversationPolling()}finally{retry.disabled=false}};notice.append(retry);document.body.append(notice)}\nasync function finishConversationPolling(){if(conversationPollingFinished&&conversationFinishSynchronized)return true;if(conversationFinishing)return conversationFinishPromise;conversationFinishing=true;if(conversationRefreshTimer!==null){clearInterval(conversationRefreshTimer);conversationRefreshTimer=null}conversationFinishPromise=(async()=>{await conversationRefreshPromise;if(window.__execweaveStaticMode)return true;return await refresh({allowDuringFinish:true})})().then(synchronized=>{conversationFinishSynchronized=Boolean(synchronized);reportFinalConversationSync(conversationFinishSynchronized);return conversationFinishSynchronized}).finally(()=>{conversationPollingFinished=true;conversationFinishing=false;if(conversationRefreshController!==null){conversationRefreshController.abort();conversationRefreshController=null}});return await conversationFinishPromise}\nconst stopConversationPolling=finishConversationPolling;\nconst previous=window.__execweaveDashboard||{};window.__execweaveDashboard={...previous,onPayload(data){previous.onPayload?.(data);if(selectedNode&&!data?.live_finished&&!conversationFinishing&&!conversationPollingFinished)refresh()},onFinished(){previous.onFinished?.();void finishConversationPolling()}};",
|
|
238
236
|
"agent conversation lifecycle seam changed",
|
|
239
237
|
),
|
|
240
238
|
(
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Durable export status: session.finished is not proof that files were exported."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
REQUIRED_EXPORTS = ("graph.json", "viewer.html", "conversations.json")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def record_finalization(
|
|
16
|
+
run_dir: Path, *, state: str, error: BaseException | None = None,
|
|
17
|
+
) -> dict[str, Any]:
|
|
18
|
+
files: dict[str, Any] = {}
|
|
19
|
+
for name in REQUIRED_EXPORTS:
|
|
20
|
+
path = run_dir / name
|
|
21
|
+
if path.is_file():
|
|
22
|
+
digest = hashlib.sha256()
|
|
23
|
+
with path.open("rb") as handle:
|
|
24
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
25
|
+
digest.update(chunk)
|
|
26
|
+
files[name] = {"size_bytes": path.stat().st_size, "sha256": digest.hexdigest()}
|
|
27
|
+
missing = [name for name in REQUIRED_EXPORTS if not files.get(name, {}).get("size_bytes")]
|
|
28
|
+
payload = {"schema_version": "0.1", "state": state, "artifacts": files,
|
|
29
|
+
"missing": missing, "error_type": type(error).__name__ if error else None}
|
|
30
|
+
if state == "complete" and missing:
|
|
31
|
+
payload["state"] = "incomplete"
|
|
32
|
+
fd, temporary = tempfile.mkstemp(prefix=".finalization-", dir=run_dir)
|
|
33
|
+
try:
|
|
34
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
|
|
35
|
+
json.dump(payload, handle, indent=2, sort_keys=True)
|
|
36
|
+
handle.write("\n")
|
|
37
|
+
handle.flush()
|
|
38
|
+
os.fsync(handle.fileno())
|
|
39
|
+
os.replace(temporary, run_dir / "finalization.json")
|
|
40
|
+
finally:
|
|
41
|
+
Path(temporary).unlink(missing_ok=True)
|
|
42
|
+
if state == "complete" and missing and error is None:
|
|
43
|
+
raise RuntimeError("Final export is incomplete: " + ", ".join(missing))
|
|
44
|
+
return payload
|
|
@@ -7,6 +7,7 @@ from datetime import datetime, timezone
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from .session_summary import SessionSummary
|
|
10
11
|
from .fidelity import FidelityAccumulator
|
|
11
12
|
from .provider_lifecycle import ProviderLifecycleAnnotation, provider_lifecycle_annotation
|
|
12
13
|
from .validate import validate_event_stream
|
|
@@ -346,6 +347,10 @@ class ExecutionGraph:
|
|
|
346
347
|
default_factory=lambda: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
347
348
|
)
|
|
348
349
|
|
|
350
|
+
evidence_counts: dict[str, int] = field(default_factory=dict)
|
|
351
|
+
session_outcome: dict[str, Any] = field(default_factory=dict)
|
|
352
|
+
runtime_environment: dict[str, Any] = field(default_factory=dict)
|
|
353
|
+
|
|
349
354
|
def to_dict(self) -> dict[str, Any]:
|
|
350
355
|
return {
|
|
351
356
|
"graph_schema_version": GRAPH_SCHEMA_VERSION,
|
|
@@ -357,6 +362,9 @@ class ExecutionGraph:
|
|
|
357
362
|
"edge_count": len(self.edges),
|
|
358
363
|
"built_at": self.built_at,
|
|
359
364
|
"fidelity": self.fidelity,
|
|
365
|
+
"evidence_counts": dict(self.evidence_counts),
|
|
366
|
+
"session_outcome": dict(self.session_outcome),
|
|
367
|
+
"runtime_environment": dict(self.runtime_environment),
|
|
360
368
|
"nodes": [node.to_dict() for node in self.nodes],
|
|
361
369
|
"edges": [edge.to_dict() for edge in self.edges],
|
|
362
370
|
}
|
|
@@ -391,6 +399,7 @@ class GraphAccumulator:
|
|
|
391
399
|
self.nodes: dict[str, GraphNode] = {}
|
|
392
400
|
self.edges: dict[tuple[str, str, str], GraphEdge] = {}
|
|
393
401
|
self._fidelity = FidelityAccumulator()
|
|
402
|
+
self._session_summary = SessionSummary()
|
|
394
403
|
self._seen_logical_events: set[str] = set()
|
|
395
404
|
|
|
396
405
|
@property
|
|
@@ -415,6 +424,7 @@ class GraphAccumulator:
|
|
|
415
424
|
self.source_schema_versions.add(schema_version)
|
|
416
425
|
self.event_count += 1
|
|
417
426
|
self._fidelity.observe(event)
|
|
427
|
+
self._session_summary.observe(event)
|
|
418
428
|
logical_key = logical_event_key(event)
|
|
419
429
|
logical = logical_key not in self._seen_logical_events
|
|
420
430
|
if logical_key is not None:
|
|
@@ -473,6 +483,9 @@ class GraphAccumulator:
|
|
|
473
483
|
self.edges.values(), key=lambda edge: (edge.source, edge.relation, edge.target)
|
|
474
484
|
),
|
|
475
485
|
fidelity=self._fidelity.to_dict(),
|
|
486
|
+
evidence_counts=dict(self._session_summary.counts),
|
|
487
|
+
session_outcome=dict(self._session_summary.outcome),
|
|
488
|
+
runtime_environment=dict(self._session_summary.environment),
|
|
476
489
|
)
|
|
477
490
|
|
|
478
491
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Read JSON hook transport as UTF-8, independently of the terminal code page."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def read_hook_text(source: Any) -> str:
|
|
8
|
+
binary = getattr(source, "buffer", None)
|
|
9
|
+
raw = binary.read() if binary is not None else source.read()
|
|
10
|
+
if isinstance(raw, bytes):
|
|
11
|
+
# Strict decoding avoids silently replacing evidence. Invalid input is
|
|
12
|
+
# reported by the existing fail-open hook boundary, not mis-recorded.
|
|
13
|
+
return raw.decode("utf-8-sig")
|
|
14
|
+
if not isinstance(raw, str):
|
|
15
|
+
raise ValueError("hook stdin must supply UTF-8 bytes or text")
|
|
16
|
+
return raw.lstrip("\ufeff")
|
|
@@ -19,6 +19,8 @@ from urllib.parse import parse_qs, urlencode, urlsplit
|
|
|
19
19
|
from uuid import uuid4
|
|
20
20
|
|
|
21
21
|
from .backends import create_collector
|
|
22
|
+
from .conversation_records import write_conversation_records
|
|
23
|
+
from .finalization import record_finalization
|
|
22
24
|
from .graph import GRAPH_SCHEMA_VERSION, GraphAccumulator, build_execution_graph, write_execution_graph
|
|
23
25
|
from .live_view import LIVE_HTML as _LIVE_HTML
|
|
24
26
|
from .semantic import LiveSemanticNormalizer, merge_semantic_sidecar
|
|
@@ -152,6 +154,9 @@ def _compact_live_graph(graph: dict[str, object]) -> dict[str, object]:
|
|
|
152
154
|
"nodes": [],
|
|
153
155
|
"edges": [],
|
|
154
156
|
"live_payload_compact": True,
|
|
157
|
+
"evidence_counts": graph.get("evidence_counts"),
|
|
158
|
+
"session_outcome": graph.get("session_outcome"),
|
|
159
|
+
"runtime_environment": graph.get("runtime_environment"),
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
|
|
@@ -710,6 +715,7 @@ def run_live(
|
|
|
710
715
|
if artifact.exists() and artifact.stat().st_size > 0:
|
|
711
716
|
raise FileExistsError(f"ExecWeave live artifact already exists: {artifact}")
|
|
712
717
|
|
|
718
|
+
record_finalization(run_dir, state="recording")
|
|
713
719
|
sink = JsonlSink(event_path)
|
|
714
720
|
collector = create_collector(
|
|
715
721
|
backend="portable",
|
|
@@ -748,9 +754,15 @@ def run_live(
|
|
|
748
754
|
key: os.environ.get(key) for key in environment_updates
|
|
749
755
|
}
|
|
750
756
|
os.environ.update(environment_updates)
|
|
757
|
+
collection_error: Exception | None = None
|
|
758
|
+
export_complete = False
|
|
751
759
|
try:
|
|
752
760
|
try:
|
|
753
761
|
return_code = collector.run(command)
|
|
762
|
+
except Exception as exc:
|
|
763
|
+
# The collector can have written a valid terminal stream before
|
|
764
|
+
# raising. Export it rather than leaving only raw evidence behind.
|
|
765
|
+
collection_error = exc
|
|
754
766
|
finally:
|
|
755
767
|
for key, value in previous_environment.items():
|
|
756
768
|
if value is None:
|
|
@@ -758,6 +770,7 @@ def run_live(
|
|
|
758
770
|
else:
|
|
759
771
|
os.environ[key] = value
|
|
760
772
|
|
|
773
|
+
record_finalization(run_dir, state="exporting", error=collection_error)
|
|
761
774
|
validation = validate_event_stream(event_path)
|
|
762
775
|
if not validation.valid:
|
|
763
776
|
details = "; ".join(validation.errors)
|
|
@@ -771,11 +784,20 @@ def run_live(
|
|
|
771
784
|
execution_graph = build_execution_graph(materialized_event_path)
|
|
772
785
|
graph_payload = execution_graph.to_dict()
|
|
773
786
|
write_execution_graph(execution_graph, graph_path)
|
|
787
|
+
# The extracted core must materialize the conversation index itself. The
|
|
788
|
+
# projected live wrapper also writes it, but direct live_core callers (and
|
|
789
|
+
# failure-path export) must not depend on that presentation monkeypatch.
|
|
790
|
+
write_conversation_records(graph_payload, run_dir)
|
|
774
791
|
write_graph_html(graph_payload, viewer_path, open_browser=False)
|
|
792
|
+
record_finalization(run_dir, state="complete", error=collection_error)
|
|
793
|
+
export_complete = True
|
|
775
794
|
state.finish(graph_payload, final_html=viewer_path.read_text(encoding="utf-8"))
|
|
776
795
|
if linger_seconds:
|
|
777
796
|
time.sleep(linger_seconds)
|
|
778
797
|
|
|
798
|
+
if collection_error is not None:
|
|
799
|
+
raise collection_error
|
|
800
|
+
|
|
779
801
|
return LiveResult(
|
|
780
802
|
session_id=session_id,
|
|
781
803
|
return_code=return_code,
|
|
@@ -787,6 +809,35 @@ def run_live(
|
|
|
787
809
|
graph=graph_path,
|
|
788
810
|
viewer=viewer_path,
|
|
789
811
|
)
|
|
812
|
+
except Exception as exc:
|
|
813
|
+
# Export/finalization diagnostics are secondary to an already observed
|
|
814
|
+
# collector failure. Preserve the primary exception and attach later
|
|
815
|
+
# failures as notes rather than replacing the root cause.
|
|
816
|
+
primary_error = collection_error if collection_error is not None else exc
|
|
817
|
+
if collection_error is not None and exc is not collection_error:
|
|
818
|
+
try:
|
|
819
|
+
collection_error.add_note(
|
|
820
|
+
f"secondary export failure: {type(exc).__name__}: {exc}"
|
|
821
|
+
)
|
|
822
|
+
except AttributeError: # pragma: no cover - Python < 3.11 compatibility
|
|
823
|
+
pass
|
|
824
|
+
try:
|
|
825
|
+
record_finalization(
|
|
826
|
+
run_dir,
|
|
827
|
+
state="complete" if export_complete else "failed",
|
|
828
|
+
error=primary_error,
|
|
829
|
+
)
|
|
830
|
+
except Exception as finalization_error:
|
|
831
|
+
try:
|
|
832
|
+
primary_error.add_note(
|
|
833
|
+
"secondary finalization diagnostic failure: "
|
|
834
|
+
f"{type(finalization_error).__name__}: {finalization_error}"
|
|
835
|
+
)
|
|
836
|
+
except AttributeError: # pragma: no cover - Python < 3.11 compatibility
|
|
837
|
+
pass
|
|
838
|
+
if primary_error is not exc:
|
|
839
|
+
raise primary_error from exc
|
|
840
|
+
raise
|
|
790
841
|
finally:
|
|
791
842
|
server.shutdown()
|
|
792
843
|
server.server_close()
|
|
@@ -15,7 +15,19 @@ function nodeCategory(node){const value=String(node?.type||'').toLowerCase();if(
|
|
|
15
15
|
function graphCounts(data){const nodes=Number(data.node_count)||((data.nodes||[]).length),edges=Number(data.edge_count)||((data.edges||[]).length);return{nodes,edges,estimated:nodes*4+edges*3}}
|
|
16
16
|
function withinRenderBudget(data){const counts=graphCounts(data);return counts.nodes<=ACTIVE_MAX_NODES&&counts.edges<=ACTIVE_MAX_EDGES&&counts.estimated<=ACTIVE_MAX_DOM_ELEMENTS}
|
|
17
17
|
function updateStats(data){stats.innerHTML=`<strong>${Number(data.node_count)||0}</strong> nodes · <strong>${Number(data.edge_count)||0}</strong> edges · <strong>${Number(data.event_count)||0}</strong> events`}
|
|
18
|
-
function updateEvidence(data){
|
|
18
|
+
function updateEvidence(data){
|
|
19
|
+
const raw=window.__execweaveCore?.getGraph?.()||window.__execweaveStaticGraph||{},counts=data.live_evidence_counts||data.evidence_counts||raw.evidence_counts||{};
|
|
20
|
+
const count=value=>typeof value==='number'&&Number.isInteger(value)&&value>=0?String(value):'—';
|
|
21
|
+
const provisional=!!data.live_specialized_provisional,specialized=count(counts.specialized);
|
|
22
|
+
const specializedMarkup=`specialized ${specialized}`.replace(specialized,`<strong>${specialized}</strong>`);
|
|
23
|
+
evidence.innerHTML=`OS <strong>${count(counts.os_runtime)}</strong> · ${specializedMarkup}${provisional?' · provisional':''}`;
|
|
24
|
+
evidence.classList.toggle('provisional',provisional);evidence.title='Observed event counts. — means unavailable, not zero.';
|
|
25
|
+
const outcome=data.session_outcome||raw.session_outcome||{},labels={succeeded:'SUCCEEDED',failed:'FAILED',interrupted:'INTERRUPTED',collector_failed:'COLLECTOR FAILED',unknown:'UNKNOWN'};
|
|
26
|
+
let badge=document.getElementById('workload-outcome');if(!badge){badge=document.createElement('span');badge.id='workload-outcome';evidence.after(badge)}
|
|
27
|
+
const finished=outcome.recorder_finished||data.live_finished||window.__execweaveStaticMode;
|
|
28
|
+
badge.textContent=finished?` · Workload: ${labels[outcome.state]||'UNKNOWN'}${Number.isInteger(outcome.return_code)?' · exit '+outcome.return_code:''}`:'';
|
|
29
|
+
badge.dataset.state=outcome.state||'unknown';badge.title='Workload result is separate from recorder FINISHED status.';
|
|
30
|
+
}
|
|
19
31
|
function setStatus(label,kind=''){statusLabel.textContent=label;status.className=kind}
|
|
20
32
|
function enterProtectiveMode(data){const counts=graphCounts(data);edgeLayer.replaceChildren();labelLayer.replaceChildren();nodeLayer.replaceChildren();positions=new Map();nodeElements=new Map();edgeElements=new Map();svg.style.display='none';protective.hidden=false;protectiveSummary.textContent=`${counts.nodes} nodes · ${counts.edges} edges · about ${counts.estimated} SVG elements exceeds the live safety budget.`;setStatus('PROTECTED','reconnecting');protectedMode=true}
|
|
21
33
|
function leaveProtectiveMode(){protective.hidden=true;svg.style.display='block';protectedMode=false}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Portable evidence counts and workload outcome derived from observed events.
|
|
2
|
+
|
|
3
|
+
Recorder completion is deliberately separate from workload success. Missing
|
|
4
|
+
terminal evidence is unknown, never a synthesized successful exit.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SessionSummary:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.counts = {"os_runtime": 0, "specialized": 0}
|
|
14
|
+
self.outcome: dict[str, Any] = {"state": "unknown", "return_code": None}
|
|
15
|
+
self.environment: dict[str, Any] = {}
|
|
16
|
+
|
|
17
|
+
def observe(self, event: dict[str, Any]) -> None:
|
|
18
|
+
attributes = event.get("attributes")
|
|
19
|
+
attributes = attributes if isinstance(attributes, dict) else {}
|
|
20
|
+
kind = str(event.get("event_type") or "")
|
|
21
|
+
backend = attributes.get("backend")
|
|
22
|
+
semantic = (kind.startswith("semantic.")
|
|
23
|
+
or (isinstance(backend, str) and backend in {"semantic", "model_runtime"})
|
|
24
|
+
or attributes.get("attribution") == "semantic_sidecar")
|
|
25
|
+
self.counts["specialized" if semantic else "os_runtime"] += 1
|
|
26
|
+
if kind == "session.started":
|
|
27
|
+
for key in ("execweave_version", "python_executable", "python_version", "package_path", "backend"):
|
|
28
|
+
value = attributes.get(key)
|
|
29
|
+
if isinstance(value, str):
|
|
30
|
+
self.environment[key] = value
|
|
31
|
+
if kind != "session.finished":
|
|
32
|
+
return
|
|
33
|
+
code = attributes.get("return_code")
|
|
34
|
+
if not isinstance(code, int) or isinstance(code, bool):
|
|
35
|
+
code = None
|
|
36
|
+
interrupted = attributes.get("interrupted") is True
|
|
37
|
+
collector_failed = attributes.get("collector_failed") is True
|
|
38
|
+
state = (
|
|
39
|
+
"collector_failed" if collector_failed else
|
|
40
|
+
"interrupted" if interrupted else
|
|
41
|
+
"succeeded" if code == 0 else
|
|
42
|
+
"failed" if code is not None else "unknown"
|
|
43
|
+
)
|
|
44
|
+
self.outcome = {
|
|
45
|
+
"state": state, "return_code": code, "interrupted": interrupted,
|
|
46
|
+
"collector_failed": collector_failed, "event_id": event.get("event_id"),
|
|
47
|
+
"timestamp": event.get("timestamp"), "recorder_finished": True,
|
|
48
|
+
}
|