execweave 0.8.33__tar.gz → 0.8.35__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.33 → execweave-0.8.35}/PKG-INFO +21 -3
- {execweave-0.8.33 → execweave-0.8.35}/README.md +17 -2
- {execweave-0.8.33 → execweave-0.8.35}/pyproject.toml +4 -2
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/__init__.py +1 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_conversation_records_core.py +50 -12
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_dashboard_shell_base.py +16 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_http_proxy_base.py +26 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_http_proxy_bounded.py +71 -30
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_http_proxy_stage.py +3 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agent_bootstrap.py +74 -11
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/anthropic.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/anthropic_cli.py +3 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_adapter_base.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_hook_cli.py +9 -2
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_adapter.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_hook_cli.py +6 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cli.py +9 -8
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_adapter.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_conversation.py +11 -5
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_hook_cli.py +5 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_hook_entry.py +5 -4
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/collector.py +101 -20
- execweave-0.8.35/src/execweave/content_integrity.py +289 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/content_store.py +16 -10
- execweave-0.8.35/src/execweave/controlled_checks.py +561 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_codex.py +1 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records.py +4 -4
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_codex.py +24 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/correlation.py +11 -19
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_adapter.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_hook_cli.py +6 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/dashboard_shell.py +16 -7
- execweave-0.8.35/src/execweave/delivery_status.py +94 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/entry.py +23 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/fidelity.py +4 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/filesystem.py +82 -2
- execweave-0.8.35/src/execweave/finalization.py +92 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/base.py +7 -5
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/graph.py +10 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/graph_ops.py +24 -10
- execweave-0.8.35/src/execweave/hook_command.py +32 -0
- execweave-0.8.35/src/execweave/hook_delivery.py +32 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_gateway.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_gateway_cli.py +3 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/integrity.py +25 -23
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/integrity_cli.py +12 -1
- execweave-0.8.35/src/execweave/investigation_index.py +444 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/litellm_callback.py +4 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live.py +17 -13
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_core.py +162 -34
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_extra_style.py +1 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_a.py +7 -5
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_c.py +16 -1
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/model_runtime.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/model_runtime_cli.py +3 -0
- execweave-0.8.35/src/execweave/observation.py +53 -0
- execweave-0.8.35/src/execweave/observation_demo.py +69 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/openai_compatible.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/openai_compatible_cli.py +3 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_adapter.py +5 -6
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_hook_cli.py +8 -2
- execweave-0.8.35/src/execweave/privacy.py +84 -0
- execweave-0.8.35/src/execweave/private_io.py +465 -0
- execweave-0.8.35/src/execweave/redacted_archive.py +906 -0
- execweave-0.8.35/src/execweave/risk.py +106 -0
- execweave-0.8.35/src/execweave/run_assessment.py +200 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/semantic.py +16 -16
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/session_summary.py +38 -0
- execweave-0.8.35/src/execweave/sink.py +53 -0
- execweave-0.8.35/src/execweave/task_artifacts.py +192 -0
- execweave-0.8.35/src/execweave/task_validation.py +317 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/top.py +30 -20
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/top_cli.py +3 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer.py +3 -2
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel.py +15 -6
- execweave-0.8.35/src/execweave/viewer_agent_panel_default.py +79 -0
- execweave-0.8.35/src/execweave/viewer_archive_verifier.py +178 -0
- execweave-0.8.35/src/execweave/viewer_content_browser.py +307 -0
- execweave-0.8.35/src/execweave/viewer_content_health.py +208 -0
- execweave-0.8.35/src/execweave/viewer_controlled_checks.py +258 -0
- execweave-0.8.35/src/execweave/viewer_conversation_sync.py +115 -0
- execweave-0.8.35/src/execweave/viewer_delivery_status.py +155 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_flow_canvas.py +525 -52
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_flow_layout.py +8 -55
- execweave-0.8.35/src/execweave/viewer_framework_model_output.py +133 -0
- execweave-0.8.35/src/execweave/viewer_history_browser.py +225 -0
- execweave-0.8.35/src/execweave/viewer_investigation.py +268 -0
- execweave-0.8.35/src/execweave/viewer_message_handoffs.py +114 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_projection.py +23 -7
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_projection_base.py +3 -2
- execweave-0.8.35/src/execweave/viewer_recorded_sources.py +146 -0
- execweave-0.8.35/src/execweave/viewer_run_assessment.py +125 -0
- execweave-0.8.35/src/execweave/viewer_run_guide.py +175 -0
- execweave-0.8.35/src/execweave/viewer_runtime_evidence.py +258 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_semantic_projection.py +152 -4
- execweave-0.8.35/src/execweave/viewer_session_flow.py +491 -0
- execweave-0.8.35/src/execweave/viewer_structure_share.py +132 -0
- execweave-0.8.35/src/execweave/viewer_task_validation.py +241 -0
- execweave-0.8.35/src/execweave/viewer_workflow_mode.py +127 -0
- execweave-0.8.33/src/execweave/finalization.py +0 -44
- execweave-0.8.33/src/execweave/sink.py +0 -33
- execweave-0.8.33/src/execweave/viewer_agent_panel_default.py +0 -36
- execweave-0.8.33/src/execweave/viewer_session_flow.py +0 -166
- {execweave-0.8.33 → execweave-0.8.35}/.gitignore +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/LICENSE +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/__main__.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/acceptance_output.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agent_topology.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agent_trace.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agy_preview_sanitize.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/analysis.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/anthropic_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_adapter.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_full_fidelity_base.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_record.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_subagent_linkage.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_trace_capability.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/auto_specialized.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/backends.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/benchmark.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_child_transcript.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_delegation.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_hook_contract.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_model_observer.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_record.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_hook_lifecycle.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_message_diagnostics.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_message_transport_diagnostics.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_record.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_rollout_structures.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_rollout_trace.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_rollout_trace_base.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/command.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/content_evidence.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_archive.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_message_identity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_antigravity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_claude.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_common.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_generic.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_lines.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_transcript.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_antigravity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_common.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_ollama.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_delegation.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_delegation_base.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_hook_contract.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_lifecycle.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_record.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/evidence_availability.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/evidence_grade.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/focus.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/__init__.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/autogen.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/camel.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/camel_channel.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/metagpt.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/registry.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/hook_input.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/http_proxy.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/http_proxy_cli.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_gateway_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_identity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_identity_cli.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/litellm_callback_cli.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_markup.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_process_layout.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_readability.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_b.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_d.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_style.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/model_runtime_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/openai_compatible_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_event_contract.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_full_fidelity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_plugin_cli.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_record.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_task_linkage.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/overhead_benchmark.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/provider_capability.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/provider_lifecycle.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/provider_record.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/rule_pack.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/rule_pack_cli.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/runtime_endpoint_availability.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/scalability_benchmark.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/schema.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/scope.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/strace_backend.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/stream_assembly.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/theme.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/validate.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/vendor/dagre.min.js +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/view_cli.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_claude.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_codex.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_cursor.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_ollama.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_opencode.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_registry.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_background_files.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_content_inspector.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_clean.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_focus.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_hardening.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_pr70.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_edge_decross.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_execution_flow.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_execution_flow_provider_normalization.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_execution_flow_safety.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_external_endpoints.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_fold_state.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_framework_flow.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_gif.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_layout_geometry.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_layout_v2.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_layout_v2_detached.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_limits.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_live_layout.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_projection_bridges.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_tool_mirror_identity.py +0 -0
- {execweave-0.8.33 → execweave-0.8.35}/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.35
|
|
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
|
|
@@ -42,6 +42,7 @@ Requires-Python: >=3.10
|
|
|
42
42
|
Requires-Dist: psutil>=5.9
|
|
43
43
|
Requires-Dist: watchdog>=4.0
|
|
44
44
|
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: cryptography>=44; extra == 'dev'
|
|
45
46
|
Requires-Dist: pillow>=10.0; extra == 'dev'
|
|
46
47
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
47
48
|
Requires-Dist: pywinpty<4,>=3.0.5; (platform_system == 'Windows') and extra == 'dev'
|
|
@@ -56,8 +57,14 @@ Provides-Extra: release
|
|
|
56
57
|
Requires-Dist: build>=1.2; extra == 'release'
|
|
57
58
|
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'release'
|
|
58
59
|
Requires-Dist: twine>=6.0; extra == 'release'
|
|
60
|
+
Provides-Extra: validation
|
|
61
|
+
Requires-Dist: cryptography>=44; extra == 'validation'
|
|
59
62
|
Description-Content-Type: text/markdown
|
|
60
63
|
|
|
64
|
+
<p align="center">
|
|
65
|
+
<img src="docs/assets/shot5_brand.jpg" alt="ExecWeave" width="100%">
|
|
66
|
+
</p>
|
|
67
|
+
|
|
61
68
|
# ExecWeave
|
|
62
69
|
|
|
63
70
|
<!-- i18n-nav:start -->
|
|
@@ -144,7 +151,15 @@ execweave top -- codex
|
|
|
144
151
|
|
|
145
152
|
### Provider integration approval
|
|
146
153
|
|
|
147
|
-
|
|
154
|
+
Since 0.8.35, `live` only checks hook configuration; it never installs or rewrites it. Explicitly run `execweave hooks install antigravity` (or `claude`, `codex`, `cursor`, `opencode`) from your user-owned environment, without sudo. `execweave hooks status antigravity` checks setup without writing. Provider approval may still be required. **Configured does not mean evidence was delivered.** Missing delivery and incomplete observation are recorded independently of process exit.
|
|
155
|
+
|
|
156
|
+
See [observation integrity and the negative example](docs/observation-integrity.md) for the three result axes and their honest limits. These changes ship in 0.8.35; earlier versions do not contain them.
|
|
157
|
+
|
|
158
|
+
**Live privacy and authentication (0.8.35)**
|
|
159
|
+
|
|
160
|
+
`execweave live` and `execweave top` record the complete provider/model content an integration exposes (prompts, responses, tool values) by default, because the run stays on your own machine for you to inspect. Add `--metadata-only` when a run must not store that plaintext; `--capture-content` remains accepted and selects the default explicitly. Invalid capture-policy values fail closed to metadata-only at automatic recorder boundaries.
|
|
161
|
+
|
|
162
|
+
The live dashboard no longer puts its API credential in `?t=...`. The announced URL contains no credential. Browser access uses a one-time pairing code submitted in a POST body and then an `HttpOnly; SameSite=Strict` cookie; non-browser API clients use the private `X-ExecWeave-Token` header.
|
|
148
163
|
|
|
149
164
|
Google Antigravity currently uses the `agy` CLI command. ExecWeave also accepts `antigravity` as a friendly alias.
|
|
150
165
|
|
|
@@ -182,6 +197,8 @@ execweave live --open -- ollama run deepseek-r1:1.5b
|
|
|
182
197
|
|
|
183
198
|
This mode does not start an Ollama server. A reachable upstream server is still required.
|
|
184
199
|
|
|
200
|
+
In both modes the relay forwards every request and, by default, records each inference exchange so the prompts and responses appear in the run and on the dashboard. Add `--metadata-only` (for example, `execweave live --metadata-only --open -- ollama serve`) when the conversation must not be stored.
|
|
201
|
+
|
|
185
202
|
## Dashboard
|
|
186
203
|
|
|
187
204
|
The dashboard is designed to keep large, multi-agent runs inspectable without changing the underlying evidence.
|
|
@@ -345,7 +362,8 @@ Raw observations remain separate from derived semantic and correlation outputs.
|
|
|
345
362
|
- **Failure ownership cleanup:** if the portable collector fails unexpectedly after launching a managed workload, it terminates the workload it owns before recording terminal session state. A filesystem observer that only partially starts is also torn down before the startup error propagates.
|
|
346
363
|
- Linux also provides a `strace` reference backend with stronger syscall-attributed evidence for supported executions.
|
|
347
364
|
- Provider semantics depend on what each integration actually exposes. Missing prompts, hidden reasoning, remote provider internals, and unexposed routing cannot be reconstructed reliably.
|
|
348
|
-
- Full-fidelity provider content may contain credentials, secrets, source code, prompts, tool values, model responses, shell output, and file contents.
|
|
365
|
+
- Full-fidelity provider content may contain credentials, secrets, source code, prompts, tool values, model responses, shell output, and file contents. `live`/`top` store it by default because the run stays on the local machine; the run directory is owner-private, and `--metadata-only` keeps that plaintext out of the run entirely.
|
|
366
|
+
- Run artifacts are written through owner-private primitives. POSIX permissions are established before sensitive bytes are published; the Windows path applies a protected owner DACL. Final Windows guarantees still depend on the release CI matrix and are not inferred from Linux tests.
|
|
349
367
|
- Conversation isolation is an attribution rule, not a redaction boundary. Explicitly routed content may legitimately appear at more than one participant.
|
|
350
368
|
- A local integrity manifest detects changes relative to the manifest; it is not an adversary-resistant trusted logging system if both evidence and manifest remain inside the same writable trust boundary.
|
|
351
369
|
- Review the complete run directory before sharing it.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/shot5_brand.jpg" alt="ExecWeave" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# ExecWeave
|
|
2
6
|
|
|
3
7
|
<!-- i18n-nav:start -->
|
|
@@ -84,7 +88,15 @@ execweave top -- codex
|
|
|
84
88
|
|
|
85
89
|
### Provider integration approval
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
Since 0.8.35, `live` only checks hook configuration; it never installs or rewrites it. Explicitly run `execweave hooks install antigravity` (or `claude`, `codex`, `cursor`, `opencode`) from your user-owned environment, without sudo. `execweave hooks status antigravity` checks setup without writing. Provider approval may still be required. **Configured does not mean evidence was delivered.** Missing delivery and incomplete observation are recorded independently of process exit.
|
|
92
|
+
|
|
93
|
+
See [observation integrity and the negative example](docs/observation-integrity.md) for the three result axes and their honest limits. These changes ship in 0.8.35; earlier versions do not contain them.
|
|
94
|
+
|
|
95
|
+
**Live privacy and authentication (0.8.35)**
|
|
96
|
+
|
|
97
|
+
`execweave live` and `execweave top` record the complete provider/model content an integration exposes (prompts, responses, tool values) by default, because the run stays on your own machine for you to inspect. Add `--metadata-only` when a run must not store that plaintext; `--capture-content` remains accepted and selects the default explicitly. Invalid capture-policy values fail closed to metadata-only at automatic recorder boundaries.
|
|
98
|
+
|
|
99
|
+
The live dashboard no longer puts its API credential in `?t=...`. The announced URL contains no credential. Browser access uses a one-time pairing code submitted in a POST body and then an `HttpOnly; SameSite=Strict` cookie; non-browser API clients use the private `X-ExecWeave-Token` header.
|
|
88
100
|
|
|
89
101
|
Google Antigravity currently uses the `agy` CLI command. ExecWeave also accepts `antigravity` as a friendly alias.
|
|
90
102
|
|
|
@@ -122,6 +134,8 @@ execweave live --open -- ollama run deepseek-r1:1.5b
|
|
|
122
134
|
|
|
123
135
|
This mode does not start an Ollama server. A reachable upstream server is still required.
|
|
124
136
|
|
|
137
|
+
In both modes the relay forwards every request and, by default, records each inference exchange so the prompts and responses appear in the run and on the dashboard. Add `--metadata-only` (for example, `execweave live --metadata-only --open -- ollama serve`) when the conversation must not be stored.
|
|
138
|
+
|
|
125
139
|
## Dashboard
|
|
126
140
|
|
|
127
141
|
The dashboard is designed to keep large, multi-agent runs inspectable without changing the underlying evidence.
|
|
@@ -285,7 +299,8 @@ Raw observations remain separate from derived semantic and correlation outputs.
|
|
|
285
299
|
- **Failure ownership cleanup:** if the portable collector fails unexpectedly after launching a managed workload, it terminates the workload it owns before recording terminal session state. A filesystem observer that only partially starts is also torn down before the startup error propagates.
|
|
286
300
|
- Linux also provides a `strace` reference backend with stronger syscall-attributed evidence for supported executions.
|
|
287
301
|
- Provider semantics depend on what each integration actually exposes. Missing prompts, hidden reasoning, remote provider internals, and unexposed routing cannot be reconstructed reliably.
|
|
288
|
-
- Full-fidelity provider content may contain credentials, secrets, source code, prompts, tool values, model responses, shell output, and file contents.
|
|
302
|
+
- Full-fidelity provider content may contain credentials, secrets, source code, prompts, tool values, model responses, shell output, and file contents. `live`/`top` store it by default because the run stays on the local machine; the run directory is owner-private, and `--metadata-only` keeps that plaintext out of the run entirely.
|
|
303
|
+
- Run artifacts are written through owner-private primitives. POSIX permissions are established before sensitive bytes are published; the Windows path applies a protected owner DACL. Final Windows guarantees still depend on the release CI matrix and are not inferred from Linux tests.
|
|
289
304
|
- Conversation isolation is an attribution rule, not a redaction boundary. Explicitly routed content may legitimately appear at more than one participant.
|
|
290
305
|
- A local integrity manifest detects changes relative to the manifest; it is not an adversary-resistant trusted logging system if both evidence and manifest remain inside the same writable trust boundary.
|
|
291
306
|
- Review the complete run directory before sharing it.
|
|
@@ -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.35 release metadata
|
|
8
|
+
version = "0.8.35"
|
|
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"
|
|
@@ -42,7 +42,9 @@ dependencies = [
|
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
[project.optional-dependencies]
|
|
45
|
+
validation = ["cryptography>=44"]
|
|
45
46
|
dev = [
|
|
47
|
+
"cryptography>=44",
|
|
46
48
|
"pytest>=8.0",
|
|
47
49
|
"PyYAML>=6.0",
|
|
48
50
|
"pillow>=10.0",
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import re
|
|
4
|
+
from collections import Counter
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
7
|
from .content_store import _filesystem_path
|
|
8
|
+
from .private_io import write_private_json, write_private_text
|
|
8
9
|
|
|
9
10
|
from .agent_topology import (
|
|
10
11
|
COMPLETENESS_ROUTING_ONLY,
|
|
@@ -491,6 +492,7 @@ def conversation_record_entries(
|
|
|
491
492
|
for node in nodes
|
|
492
493
|
if isinstance(node.get("id"), str) and node.get("id")
|
|
493
494
|
}
|
|
495
|
+
source_counts = Counter(node.get("id") for node in nodes if isinstance(node.get("id"), str))
|
|
494
496
|
entries: list[dict[str, Any]] = []
|
|
495
497
|
for edge in edges:
|
|
496
498
|
target_id = edge.get("target")
|
|
@@ -542,6 +544,36 @@ def conversation_record_entries(
|
|
|
542
544
|
derived = preview.pop("derived_agent_previews", None) if isinstance(preview, dict) else None
|
|
543
545
|
if preview is not None:
|
|
544
546
|
entry["conversation_preview"] = preview
|
|
547
|
+
# Keep each framework model response attached to its own content reference.
|
|
548
|
+
# Conversation merging publishes only one aggregate preview and can otherwise
|
|
549
|
+
# leave the actual model bodies behind a later control/status message.
|
|
550
|
+
source_attrs = source.get("attributes") if isinstance(source, dict) else None
|
|
551
|
+
if (
|
|
552
|
+
isinstance(source_attrs, dict)
|
|
553
|
+
and source.get("type") == "agent"
|
|
554
|
+
and source_counts[source_id] == 1
|
|
555
|
+
and source_attrs.get("conversation_scope") == "framework_agent"
|
|
556
|
+
and reference["content_kind"] in {
|
|
557
|
+
"metagpt.model_response", "autogen.model_response", "camel.model_response"
|
|
558
|
+
}
|
|
559
|
+
and edge.get("relation") == "HAS_MODEL_CONTENT"
|
|
560
|
+
and all(
|
|
561
|
+
record.get(flag) is None or record.get(flag) is False
|
|
562
|
+
for record in (edge, edge.get("attributes") if isinstance(edge.get("attributes"), dict) else {})
|
|
563
|
+
for flag in ("inferred", "viewer_only")
|
|
564
|
+
)
|
|
565
|
+
and isinstance(preview, dict)
|
|
566
|
+
):
|
|
567
|
+
agent_path = preview.get("agent_path")
|
|
568
|
+
entry["model_response_preview"] = {
|
|
569
|
+
"schema_version": "1",
|
|
570
|
+
"source_id": source_id,
|
|
571
|
+
"agent_path": agent_path,
|
|
572
|
+
"messages": [dict(message) for message in preview.get("messages", [])
|
|
573
|
+
if isinstance(message, dict) and agent_path
|
|
574
|
+
and message.get("sender") == agent_path],
|
|
575
|
+
"messages_truncated": bool(preview.get("messages_truncated")),
|
|
576
|
+
}
|
|
545
577
|
entries.append(entry)
|
|
546
578
|
if isinstance(derived, list):
|
|
547
579
|
entries.extend(_derived_agent_entries(entry, derived))
|
|
@@ -810,14 +842,19 @@ def _mark_shared_evidence(entries: list[dict[str, Any]]) -> None:
|
|
|
810
842
|
def conversation_index_payload(
|
|
811
843
|
graph: dict[str, Any],
|
|
812
844
|
run_root: str | Path,
|
|
845
|
+
*,
|
|
846
|
+
investigation_cache=None,
|
|
847
|
+
include_investigation: bool = False,
|
|
813
848
|
) -> dict[str, Any]:
|
|
814
|
-
"""Build the conversation
|
|
849
|
+
"""Build the shared conversation projection, with opt-in investigation.
|
|
815
850
|
|
|
816
|
-
The
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
851
|
+
The default is the historical, lightweight conversation contract used by
|
|
852
|
+
ordinary Live polling. Event-stream inspection is requested explicitly by
|
|
853
|
+
the investigation reader and offline exporters. Both modes publish exactly
|
|
854
|
+
the same conversation entries; the extended mode only adds an index.
|
|
820
855
|
"""
|
|
856
|
+
from .investigation_index import build_investigation_index
|
|
857
|
+
|
|
821
858
|
root = Path(run_root).expanduser().resolve()
|
|
822
859
|
entries = conversation_record_entries(graph, root)
|
|
823
860
|
_mark_shared_injected_context(entries)
|
|
@@ -830,6 +867,10 @@ def conversation_index_payload(
|
|
|
830
867
|
return {
|
|
831
868
|
"schema_version": "0.3",
|
|
832
869
|
"scope": "run_local_provider_neutral_conversation_projection",
|
|
870
|
+
"session_id": graph.get("session_id") if isinstance(graph.get("session_id"), str) else None,
|
|
871
|
+
"source_path": graph.get("source_path") if isinstance(graph.get("source_path"), str) else None,
|
|
872
|
+
**({"investigation": build_investigation_index(graph, root, cache=investigation_cache)}
|
|
873
|
+
if include_investigation else {}),
|
|
833
874
|
"entry_count": len(entries),
|
|
834
875
|
"visible_message_count": visible_message_count,
|
|
835
876
|
"external_provider_folder_lookup_required": False,
|
|
@@ -852,13 +893,10 @@ def write_conversation_records(
|
|
|
852
893
|
root = Path(run_root).expanduser().resolve()
|
|
853
894
|
root.mkdir(parents=True, exist_ok=True)
|
|
854
895
|
if payload is None:
|
|
855
|
-
payload = conversation_index_payload(graph, root)
|
|
896
|
+
payload = conversation_index_payload(graph, root, include_investigation=True)
|
|
856
897
|
entries = payload["entries"]
|
|
857
898
|
json_path = root / "conversations.json"
|
|
858
899
|
markdown_path = root / "conversations.md"
|
|
859
|
-
json_path.
|
|
860
|
-
|
|
861
|
-
encoding="utf-8",
|
|
862
|
-
)
|
|
863
|
-
markdown_path.write_text(_render_markdown(entries), encoding="utf-8")
|
|
900
|
+
write_private_json(json_path, payload, replace=json_path.exists())
|
|
901
|
+
write_private_text(markdown_path, _render_markdown(entries), replace=markdown_path.exists())
|
|
864
902
|
return json_path, markdown_path
|
|
@@ -4,7 +4,13 @@ import json
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from .live_view import LIVE_HTML as _BASE_LIVE_HTML
|
|
7
|
+
from .run_assessment import build_run_assessment
|
|
8
|
+
from .viewer_run_assessment import inject_run_assessment
|
|
9
|
+
from .viewer_investigation import inject_investigation
|
|
10
|
+
from .viewer_runtime_evidence import inject_runtime_evidence
|
|
7
11
|
from .viewer_agent_panel import inject_agent_panel
|
|
12
|
+
from .viewer_framework_model_output import inject_framework_model_output
|
|
13
|
+
from .viewer_content_browser import inject_content_browser
|
|
8
14
|
from .viewer_dashboard_clean import fold_budget_bootstrap, inject_live_dashboard_clean
|
|
9
15
|
from .viewer_dashboard_focus import inject_live_dashboard_focus
|
|
10
16
|
from .viewer_dashboard_pr70 import inject_pr70_dashboard_repairs
|
|
@@ -84,7 +90,9 @@ def _build_dashboard_html() -> str:
|
|
|
84
90
|
html = normalize_provider_execution_flow(
|
|
85
91
|
harden_execution_flow_projection(inject_execution_flow(html))
|
|
86
92
|
)
|
|
87
|
-
|
|
93
|
+
html = _align_agent_panel_topology(inject_agent_panel(_guard_compact_live_snapshot(html)))
|
|
94
|
+
html = inject_framework_model_output(html)
|
|
95
|
+
return inject_runtime_evidence(inject_investigation(inject_run_assessment(inject_content_browser(html))))
|
|
88
96
|
|
|
89
97
|
|
|
90
98
|
DASHBOARD_HTML = _build_dashboard_html()
|
|
@@ -103,14 +111,21 @@ def render_static_dashboard_html(
|
|
|
103
111
|
graph: dict[str, Any],
|
|
104
112
|
*,
|
|
105
113
|
conversation_entries: list[dict[str, Any]] | None = None,
|
|
114
|
+
investigation_index: dict[str, Any] | None = None,
|
|
106
115
|
) -> str:
|
|
107
116
|
"""Render the exact dashboard shell used by live, backed by embedded snapshots."""
|
|
117
|
+
# Derived presentation metadata belongs beside the raw graph, not in it.
|
|
118
|
+
assessment = graph.get("run_assessment")
|
|
119
|
+
if not isinstance(assessment, dict):
|
|
120
|
+
assessment = build_run_assessment(graph)
|
|
108
121
|
bootstrap = (
|
|
109
122
|
"<script>window.__execweaveStaticMode=true;"
|
|
110
123
|
f"{fold_budget_bootstrap()}"
|
|
111
124
|
f"{viewer_limits_bootstrap(resolve_viewer_limits())}"
|
|
125
|
+
f"window.__execweaveStaticRunAssessment={_safe_json(assessment)};"
|
|
112
126
|
f"window.__execweaveStaticGraph={_safe_json(graph)};"
|
|
113
127
|
f"window.__execweaveStaticConversations={_safe_json(conversation_entries or [])};"
|
|
128
|
+
f"window.__execweaveStaticInvestigation={_safe_json(investigation_index)};"
|
|
114
129
|
"</script>\n"
|
|
115
130
|
)
|
|
116
131
|
html = DASHBOARD_HTML.replace("<script>", bootstrap + "<script>", 1)
|
|
@@ -4,6 +4,7 @@ import http.client
|
|
|
4
4
|
import ipaddress
|
|
5
5
|
import json
|
|
6
6
|
import sys
|
|
7
|
+
import threading
|
|
7
8
|
import uuid
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
@@ -16,6 +17,7 @@ from .content_store import FullFidelityContentStore
|
|
|
16
17
|
from .model_runtime_full_fidelity import runtime_exchange_to_content_events
|
|
17
18
|
from .openai_compatible import append_openai_compatible_records
|
|
18
19
|
from .openai_compatible_full_fidelity import exchange_to_content_events
|
|
20
|
+
from .privacy import full_content_capture_enabled
|
|
19
21
|
from .stream_assembly import (
|
|
20
22
|
OLLAMA_NDJSON,
|
|
21
23
|
OPENAI_CHAT_DELTA,
|
|
@@ -208,6 +210,8 @@ def record_exchange_fail_open(
|
|
|
208
210
|
request_only: bool = False,
|
|
209
211
|
request_recorded: bool = False,
|
|
210
212
|
) -> bool:
|
|
213
|
+
if not full_content_capture_enabled():
|
|
214
|
+
return False
|
|
211
215
|
try:
|
|
212
216
|
request = _json(request_body)
|
|
213
217
|
if not isinstance(request, dict):
|
|
@@ -386,8 +390,30 @@ class ExecWeaveHTTPProxyServer(ThreadingHTTPServer):
|
|
|
386
390
|
self.proxy_config = config
|
|
387
391
|
self.upstream = _parse_upstream(config.upstream)
|
|
388
392
|
self.recorder = recorder
|
|
393
|
+
# Handler threads are daemonic, so ThreadingMixIn.server_close() does not
|
|
394
|
+
# join them. A response can reach the client before its recording finishes,
|
|
395
|
+
# so closing the server waits for those recordings explicitly instead.
|
|
396
|
+
self._recordings = 0
|
|
397
|
+
self._recordings_done = threading.Condition()
|
|
389
398
|
super().__init__(address, ExecWeaveHTTPProxyHandler)
|
|
390
399
|
|
|
400
|
+
def begin_recording(self) -> None:
|
|
401
|
+
with self._recordings_done:
|
|
402
|
+
self._recordings += 1
|
|
403
|
+
|
|
404
|
+
def end_recording(self) -> None:
|
|
405
|
+
with self._recordings_done:
|
|
406
|
+
self._recordings -= 1
|
|
407
|
+
self._recordings_done.notify_all()
|
|
408
|
+
|
|
409
|
+
def wait_for_recordings(self, timeout: float | None = None) -> bool:
|
|
410
|
+
with self._recordings_done:
|
|
411
|
+
return self._recordings_done.wait_for(lambda: self._recordings == 0, timeout)
|
|
412
|
+
|
|
413
|
+
def server_close(self) -> None:
|
|
414
|
+
super().server_close()
|
|
415
|
+
self.wait_for_recordings()
|
|
416
|
+
|
|
391
417
|
|
|
392
418
|
class ExecWeaveHTTPProxyHandler(BaseHTTPRequestHandler):
|
|
393
419
|
server: ExecWeaveHTTPProxyServer
|
|
@@ -4,7 +4,9 @@ import http.client
|
|
|
4
4
|
import itertools
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import socket
|
|
7
8
|
import tempfile
|
|
9
|
+
import threading
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any, Iterator
|
|
10
12
|
|
|
@@ -486,34 +488,73 @@ def relay_default(handler: _base.ExecWeaveHTTPProxyHandler) -> None:
|
|
|
486
488
|
return
|
|
487
489
|
finally:
|
|
488
490
|
connection.close()
|
|
491
|
+
|
|
492
|
+
def record() -> None:
|
|
493
|
+
try:
|
|
494
|
+
if capture is not None:
|
|
495
|
+
capture.finish()
|
|
496
|
+
_record_capture(
|
|
497
|
+
handler.server.proxy_config,
|
|
498
|
+
exchange_id=exchange_id,
|
|
499
|
+
request_body=body,
|
|
500
|
+
response_content_type=response_type,
|
|
501
|
+
method=handler.command,
|
|
502
|
+
request_path=target,
|
|
503
|
+
status=status,
|
|
504
|
+
capture=capture,
|
|
505
|
+
)
|
|
506
|
+
else:
|
|
507
|
+
handler.server.recorder(
|
|
508
|
+
handler.server.proxy_config,
|
|
509
|
+
exchange_id=exchange_id,
|
|
510
|
+
method=handler.command,
|
|
511
|
+
request_path=target,
|
|
512
|
+
request_body=body,
|
|
513
|
+
request_content_type=handler.headers.get("Content-Type"),
|
|
514
|
+
status=status,
|
|
515
|
+
response_body=bytes(response_body),
|
|
516
|
+
response_content_type=response_type,
|
|
517
|
+
request_recorded=request_recorded,
|
|
518
|
+
)
|
|
519
|
+
except Exception as exc:
|
|
520
|
+
print(f"ExecWeave HTTP proxy recorder warning: {exc}", file=_base.sys.stderr)
|
|
521
|
+
finally:
|
|
522
|
+
if capture is not None:
|
|
523
|
+
capture.cleanup()
|
|
524
|
+
|
|
525
|
+
_record_after_response(handler, record)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
# Recording normally finishes in well under this, so the client still sees the end
|
|
529
|
+
# of the response only once its evidence is on disk. A slow disk, lock or scanner
|
|
530
|
+
# must not hold the user's program, though: past this point the response is ended
|
|
531
|
+
# while recording finishes, and closing the server waits for it.
|
|
532
|
+
_CLIENT_RELEASE_SECONDS = 2.0
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _record_after_response(handler: _base.ExecWeaveHTTPProxyHandler, record) -> None:
|
|
536
|
+
server = handler.server
|
|
537
|
+
server.begin_recording()
|
|
538
|
+
|
|
539
|
+
def run() -> None:
|
|
540
|
+
try:
|
|
541
|
+
record()
|
|
542
|
+
finally:
|
|
543
|
+
server.end_recording()
|
|
544
|
+
|
|
545
|
+
worker = threading.Thread(target=run, name="execweave-proxy-record", daemon=True)
|
|
489
546
|
try:
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
handler.server.proxy_config,
|
|
505
|
-
exchange_id=exchange_id,
|
|
506
|
-
method=handler.command,
|
|
507
|
-
request_path=target,
|
|
508
|
-
request_body=body,
|
|
509
|
-
request_content_type=handler.headers.get("Content-Type"),
|
|
510
|
-
status=status,
|
|
511
|
-
response_body=bytes(response_body),
|
|
512
|
-
response_content_type=response_type,
|
|
513
|
-
request_recorded=request_recorded,
|
|
514
|
-
)
|
|
515
|
-
except Exception as exc:
|
|
516
|
-
print(f"ExecWeave HTTP proxy recorder warning: {exc}", file=_base.sys.stderr)
|
|
517
|
-
finally:
|
|
518
|
-
if capture is not None:
|
|
519
|
-
capture.cleanup()
|
|
547
|
+
worker.start()
|
|
548
|
+
except RuntimeError:
|
|
549
|
+
run()
|
|
550
|
+
return
|
|
551
|
+
worker.join(_CLIENT_RELEASE_SECONDS)
|
|
552
|
+
if worker.is_alive():
|
|
553
|
+
# HTTP/1.0 with Connection: close and no Content-Length, so the client
|
|
554
|
+
# treats the end of the stream as the end of the response.
|
|
555
|
+
try:
|
|
556
|
+
handler.wfile.flush()
|
|
557
|
+
handler.connection.shutdown(socket.SHUT_WR)
|
|
558
|
+
except OSError:
|
|
559
|
+
pass
|
|
560
|
+
worker.join()
|
|
@@ -6,6 +6,7 @@ from . import _http_proxy_base as _base
|
|
|
6
6
|
from . import model_runtime_full_fidelity as _runtime_ff
|
|
7
7
|
from . import openai_compatible_full_fidelity as _openai_ff
|
|
8
8
|
from ._http_proxy_base import * # noqa: F403
|
|
9
|
+
from .privacy import full_content_capture_enabled
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
# OpenAI-compatible request config is request-side evidence too. Without this relation
|
|
@@ -435,6 +436,8 @@ def record_exchange_fail_open(
|
|
|
435
436
|
request_only: bool = False,
|
|
436
437
|
request_recorded: bool = False,
|
|
437
438
|
) -> bool:
|
|
439
|
+
if not full_content_capture_enabled():
|
|
440
|
+
return False
|
|
438
441
|
try:
|
|
439
442
|
if request_only:
|
|
440
443
|
return _record_request_phase(
|
|
@@ -8,6 +8,8 @@ from dataclasses import asdict, dataclass
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any, Mapping
|
|
10
10
|
|
|
11
|
+
from .hook_command import hook_argv, hook_command, hook_markers
|
|
12
|
+
|
|
11
13
|
_AUTO_FLAG = "--auto"
|
|
12
14
|
_CONFIG_RETRIES = 3
|
|
13
15
|
_SUPPORTED_AGENTS = {
|
|
@@ -73,7 +75,7 @@ def _read_bytes(path: Path) -> bytes | None:
|
|
|
73
75
|
def _contains_execweave_command(value: object, marker: str) -> bool:
|
|
74
76
|
if isinstance(value, dict):
|
|
75
77
|
command = value.get("command")
|
|
76
|
-
if isinstance(command, str) and
|
|
78
|
+
if isinstance(command, str) and any(value in command for value in hook_markers(marker)):
|
|
77
79
|
return True
|
|
78
80
|
return any(_contains_execweave_command(child, marker) for child in value.values())
|
|
79
81
|
if isinstance(value, list):
|
|
@@ -81,6 +83,28 @@ def _contains_execweave_command(value: object, marker: str) -> bool:
|
|
|
81
83
|
return False
|
|
82
84
|
|
|
83
85
|
|
|
86
|
+
def _migrate_hook_commands(value: object, marker: str) -> bool:
|
|
87
|
+
"""Upgrade only our old canonical bare entry point, retaining user settings."""
|
|
88
|
+
changed = False
|
|
89
|
+
if isinstance(value, dict):
|
|
90
|
+
command = value.get("command")
|
|
91
|
+
if isinstance(command, str):
|
|
92
|
+
prefix = f"{marker} --auto"
|
|
93
|
+
suffix = command[len(prefix):] if command.startswith(prefix) else None
|
|
94
|
+
valid_suffixes = {"", " --event PreInvocation", " --event PostInvocation",
|
|
95
|
+
" --event PostToolUse", " --event Stop"}
|
|
96
|
+
if suffix in valid_suffixes:
|
|
97
|
+
provider = marker.removeprefix("execweave-").removesuffix("-hook")
|
|
98
|
+
value["command"] = hook_command(provider) + suffix
|
|
99
|
+
changed = True
|
|
100
|
+
for child in value.values():
|
|
101
|
+
changed = _migrate_hook_commands(child, marker) or changed
|
|
102
|
+
elif isinstance(value, list):
|
|
103
|
+
for child in value:
|
|
104
|
+
changed = _migrate_hook_commands(child, marker) or changed
|
|
105
|
+
return changed
|
|
106
|
+
|
|
107
|
+
|
|
84
108
|
def _merge_hook_fragment(
|
|
85
109
|
current: dict[str, Any],
|
|
86
110
|
fragment: dict[str, Any],
|
|
@@ -88,7 +112,7 @@ def _merge_hook_fragment(
|
|
|
88
112
|
marker: str,
|
|
89
113
|
) -> tuple[dict[str, Any], bool]:
|
|
90
114
|
merged = deepcopy(current)
|
|
91
|
-
changed =
|
|
115
|
+
changed = _migrate_hook_commands(merged, marker)
|
|
92
116
|
|
|
93
117
|
for key, value in fragment.items():
|
|
94
118
|
if key == "hooks":
|
|
@@ -135,8 +159,9 @@ def _merge_named_hook_fragment(
|
|
|
135
159
|
) -> tuple[dict[str, Any], bool]:
|
|
136
160
|
"""Merge Antigravity's named-hook ``hooks.json`` schema conservatively."""
|
|
137
161
|
merged = deepcopy(current)
|
|
162
|
+
changed = _migrate_hook_commands(merged, marker)
|
|
138
163
|
if _contains_execweave_command(merged, marker):
|
|
139
|
-
return merged,
|
|
164
|
+
return merged, changed
|
|
140
165
|
for name, definition in fragment.items():
|
|
141
166
|
if name in merged:
|
|
142
167
|
raise FileExistsError(
|
|
@@ -215,9 +240,11 @@ def _write_plugin_file(path: Path, content: str, *, marker: str) -> bool:
|
|
|
215
240
|
existing = original.decode("utf-8")
|
|
216
241
|
except UnicodeDecodeError as exc:
|
|
217
242
|
raise ValueError(f"existing OpenCode plugin is not UTF-8: {path}") from exc
|
|
218
|
-
if existing == content
|
|
243
|
+
if existing == content:
|
|
219
244
|
return False
|
|
220
|
-
|
|
245
|
+
from .opencode_plugin_cli import plugin_text
|
|
246
|
+
if existing != plugin_text(("execweave-opencode-hook", _AUTO_FLAG)):
|
|
247
|
+
raise FileExistsError(f"refusing to replace existing OpenCode plugin: {path}")
|
|
221
248
|
if _write_bytes_optimistic(path, original, encoded):
|
|
222
249
|
return True
|
|
223
250
|
raise RuntimeError(f"plugin path changed concurrently while updating {path}")
|
|
@@ -254,11 +281,11 @@ def _provider_fragment(provider: str) -> tuple[dict[str, Any], str]:
|
|
|
254
281
|
if provider == "claude":
|
|
255
282
|
from .claude_hook_cli import claude_hook_config
|
|
256
283
|
|
|
257
|
-
return claude_hook_config(
|
|
284
|
+
return claude_hook_config(hook_command("claude")), "execweave-claude-hook"
|
|
258
285
|
if provider == "codex":
|
|
259
286
|
from .codex_hook_cli import codex_hook_config
|
|
260
287
|
|
|
261
|
-
return codex_hook_config(
|
|
288
|
+
return codex_hook_config(hook_command("codex")), "execweave-codex-hook"
|
|
262
289
|
if provider == "antigravity":
|
|
263
290
|
from .antigravity_hook_cli import antigravity_hook_config
|
|
264
291
|
|
|
@@ -266,7 +293,7 @@ def _provider_fragment(provider: str) -> tuple[dict[str, Any], str]:
|
|
|
266
293
|
if provider == "cursor":
|
|
267
294
|
from .cursor_hook_cli import cursor_hook_config
|
|
268
295
|
|
|
269
|
-
return cursor_hook_config(
|
|
296
|
+
return cursor_hook_config(hook_command("cursor")), "execweave-cursor-hook"
|
|
270
297
|
raise ValueError(f"provider does not use JSON hooks: {provider}")
|
|
271
298
|
|
|
272
299
|
|
|
@@ -293,7 +320,7 @@ def bootstrap_supported_agent(
|
|
|
293
320
|
if provider == "opencode":
|
|
294
321
|
from .opencode_plugin_cli import plugin_text
|
|
295
322
|
|
|
296
|
-
content = plugin_text(("
|
|
323
|
+
content = plugin_text(tuple(hook_argv("opencode")))
|
|
297
324
|
changed = _write_plugin_file(target, content, marker="execweave-opencode-hook")
|
|
298
325
|
else:
|
|
299
326
|
fragment, marker = _provider_fragment(provider)
|
|
@@ -317,10 +344,10 @@ def bootstrap_supported_agent(
|
|
|
317
344
|
|
|
318
345
|
return AgentBootstrapResult(
|
|
319
346
|
provider=provider,
|
|
320
|
-
status="
|
|
347
|
+
status="configured_unverified",
|
|
321
348
|
path=str(target),
|
|
322
349
|
changed=changed,
|
|
323
|
-
detail="specialized hook/plugin
|
|
350
|
+
detail="specialized hook/plugin configured; delivery unverified",
|
|
324
351
|
)
|
|
325
352
|
except (FileExistsError, OSError, RuntimeError, TypeError, ValueError) as exc:
|
|
326
353
|
return AgentBootstrapResult(
|
|
@@ -330,3 +357,39 @@ def bootstrap_supported_agent(
|
|
|
330
357
|
changed=False,
|
|
331
358
|
detail=_bounded_detail(exc),
|
|
332
359
|
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def inspect_supported_agent(
|
|
363
|
+
command: list[str], *, home: str | Path | None = None,
|
|
364
|
+
environment: Mapping[str, str] | None = None,
|
|
365
|
+
) -> AgentBootstrapResult:
|
|
366
|
+
"""Read configuration only. Configuration is never proof of hook delivery."""
|
|
367
|
+
provider = supported_agent(command)
|
|
368
|
+
if provider is None:
|
|
369
|
+
return AgentBootstrapResult(provider=None, status="unavailable",
|
|
370
|
+
detail="no specialized lifecycle integration")
|
|
371
|
+
env = os.environ if environment is None else environment
|
|
372
|
+
home_path = Path.home() if home is None else Path(home).expanduser()
|
|
373
|
+
target = _provider_target(provider, home=home_path, environment=env)
|
|
374
|
+
try:
|
|
375
|
+
raw = _read_bytes(target)
|
|
376
|
+
if raw is None:
|
|
377
|
+
return AgentBootstrapResult(provider=provider, status="not_configured", path=str(target),
|
|
378
|
+
detail=f"Run execweave hooks install {provider} explicitly; never use sudo")
|
|
379
|
+
if provider == "opencode":
|
|
380
|
+
present = any(marker in raw.decode("utf-8")
|
|
381
|
+
for marker in hook_markers("execweave-opencode-hook"))
|
|
382
|
+
else:
|
|
383
|
+
config = _load_json_object(raw, path=target)
|
|
384
|
+
present = _contains_execweave_command(config, f"execweave-{provider}-hook")
|
|
385
|
+
if provider == "claude" and config.get("disableAllHooks") is True:
|
|
386
|
+
present = False
|
|
387
|
+
if provider == "antigravity":
|
|
388
|
+
group = config.get("execweave-observability", {})
|
|
389
|
+
present = present and isinstance(group, dict) and group.get("enabled") is True
|
|
390
|
+
return AgentBootstrapResult(provider=provider,
|
|
391
|
+
status="configured_unverified" if present else "not_configured",
|
|
392
|
+
path=str(target), detail="delivery must be verified from recorded evidence")
|
|
393
|
+
except (OSError, ValueError, UnicodeError) as exc:
|
|
394
|
+
return AgentBootstrapResult(provider=provider, status="bootstrap_failed", path=str(target),
|
|
395
|
+
detail=type(exc).__name__)
|