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.
Files changed (232) hide show
  1. {execweave-0.8.33 → execweave-0.8.35}/PKG-INFO +21 -3
  2. {execweave-0.8.33 → execweave-0.8.35}/README.md +17 -2
  3. {execweave-0.8.33 → execweave-0.8.35}/pyproject.toml +4 -2
  4. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/__init__.py +1 -1
  5. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_conversation_records_core.py +50 -12
  6. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_dashboard_shell_base.py +16 -1
  7. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_http_proxy_base.py +26 -0
  8. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_http_proxy_bounded.py +71 -30
  9. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/_http_proxy_stage.py +3 -0
  10. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agent_bootstrap.py +74 -11
  11. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/anthropic.py +5 -6
  12. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/anthropic_cli.py +3 -0
  13. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_adapter_base.py +5 -6
  14. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_hook_cli.py +9 -2
  15. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_adapter.py +5 -6
  16. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_hook_cli.py +6 -1
  17. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cli.py +9 -8
  18. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_adapter.py +5 -6
  19. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_conversation.py +11 -5
  20. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_hook_cli.py +5 -1
  21. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_hook_entry.py +5 -4
  22. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/collector.py +101 -20
  23. execweave-0.8.35/src/execweave/content_integrity.py +289 -0
  24. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/content_store.py +16 -10
  25. execweave-0.8.35/src/execweave/controlled_checks.py +561 -0
  26. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_codex.py +1 -1
  27. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records.py +4 -4
  28. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_codex.py +24 -0
  29. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/correlation.py +11 -19
  30. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_adapter.py +5 -6
  31. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_hook_cli.py +6 -1
  32. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/dashboard_shell.py +16 -7
  33. execweave-0.8.35/src/execweave/delivery_status.py +94 -0
  34. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/entry.py +23 -6
  35. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/fidelity.py +4 -6
  36. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/filesystem.py +82 -2
  37. execweave-0.8.35/src/execweave/finalization.py +92 -0
  38. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/base.py +7 -5
  39. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/graph.py +10 -6
  40. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/graph_ops.py +24 -10
  41. execweave-0.8.35/src/execweave/hook_command.py +32 -0
  42. execweave-0.8.35/src/execweave/hook_delivery.py +32 -0
  43. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_gateway.py +5 -6
  44. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_gateway_cli.py +3 -0
  45. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/integrity.py +25 -23
  46. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/integrity_cli.py +12 -1
  47. execweave-0.8.35/src/execweave/investigation_index.py +444 -0
  48. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/litellm_callback.py +4 -0
  49. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live.py +17 -13
  50. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_core.py +162 -34
  51. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_extra_style.py +1 -1
  52. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_a.py +7 -5
  53. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_c.py +16 -1
  54. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/model_runtime.py +5 -6
  55. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/model_runtime_cli.py +3 -0
  56. execweave-0.8.35/src/execweave/observation.py +53 -0
  57. execweave-0.8.35/src/execweave/observation_demo.py +69 -0
  58. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/openai_compatible.py +5 -6
  59. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/openai_compatible_cli.py +3 -0
  60. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_adapter.py +5 -6
  61. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_hook_cli.py +8 -2
  62. execweave-0.8.35/src/execweave/privacy.py +84 -0
  63. execweave-0.8.35/src/execweave/private_io.py +465 -0
  64. execweave-0.8.35/src/execweave/redacted_archive.py +906 -0
  65. execweave-0.8.35/src/execweave/risk.py +106 -0
  66. execweave-0.8.35/src/execweave/run_assessment.py +200 -0
  67. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/semantic.py +16 -16
  68. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/session_summary.py +38 -0
  69. execweave-0.8.35/src/execweave/sink.py +53 -0
  70. execweave-0.8.35/src/execweave/task_artifacts.py +192 -0
  71. execweave-0.8.35/src/execweave/task_validation.py +317 -0
  72. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/top.py +30 -20
  73. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/top_cli.py +3 -0
  74. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer.py +3 -2
  75. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel.py +15 -6
  76. execweave-0.8.35/src/execweave/viewer_agent_panel_default.py +79 -0
  77. execweave-0.8.35/src/execweave/viewer_archive_verifier.py +178 -0
  78. execweave-0.8.35/src/execweave/viewer_content_browser.py +307 -0
  79. execweave-0.8.35/src/execweave/viewer_content_health.py +208 -0
  80. execweave-0.8.35/src/execweave/viewer_controlled_checks.py +258 -0
  81. execweave-0.8.35/src/execweave/viewer_conversation_sync.py +115 -0
  82. execweave-0.8.35/src/execweave/viewer_delivery_status.py +155 -0
  83. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_flow_canvas.py +525 -52
  84. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_flow_layout.py +8 -55
  85. execweave-0.8.35/src/execweave/viewer_framework_model_output.py +133 -0
  86. execweave-0.8.35/src/execweave/viewer_history_browser.py +225 -0
  87. execweave-0.8.35/src/execweave/viewer_investigation.py +268 -0
  88. execweave-0.8.35/src/execweave/viewer_message_handoffs.py +114 -0
  89. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_projection.py +23 -7
  90. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_projection_base.py +3 -2
  91. execweave-0.8.35/src/execweave/viewer_recorded_sources.py +146 -0
  92. execweave-0.8.35/src/execweave/viewer_run_assessment.py +125 -0
  93. execweave-0.8.35/src/execweave/viewer_run_guide.py +175 -0
  94. execweave-0.8.35/src/execweave/viewer_runtime_evidence.py +258 -0
  95. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_semantic_projection.py +152 -4
  96. execweave-0.8.35/src/execweave/viewer_session_flow.py +491 -0
  97. execweave-0.8.35/src/execweave/viewer_structure_share.py +132 -0
  98. execweave-0.8.35/src/execweave/viewer_task_validation.py +241 -0
  99. execweave-0.8.35/src/execweave/viewer_workflow_mode.py +127 -0
  100. execweave-0.8.33/src/execweave/finalization.py +0 -44
  101. execweave-0.8.33/src/execweave/sink.py +0 -33
  102. execweave-0.8.33/src/execweave/viewer_agent_panel_default.py +0 -36
  103. execweave-0.8.33/src/execweave/viewer_session_flow.py +0 -166
  104. {execweave-0.8.33 → execweave-0.8.35}/.gitignore +0 -0
  105. {execweave-0.8.33 → execweave-0.8.35}/LICENSE +0 -0
  106. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/__main__.py +0 -0
  107. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/acceptance_output.py +0 -0
  108. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agent_topology.py +0 -0
  109. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agent_trace.py +0 -0
  110. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/agy_preview_sanitize.py +0 -0
  111. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/analysis.py +0 -0
  112. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/anthropic_full_fidelity.py +0 -0
  113. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_adapter.py +0 -0
  114. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_full_fidelity.py +0 -0
  115. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_full_fidelity_base.py +0 -0
  116. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
  117. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_record.py +0 -0
  118. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_subagent_linkage.py +0 -0
  119. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/antigravity_trace_capability.py +0 -0
  120. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/auto_specialized.py +0 -0
  121. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/backends.py +0 -0
  122. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/benchmark.py +0 -0
  123. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_child_transcript.py +0 -0
  124. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_delegation.py +0 -0
  125. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_full_fidelity.py +0 -0
  126. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_hook_contract.py +0 -0
  127. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_model_observer.py +0 -0
  128. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/claude_record.py +0 -0
  129. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_full_fidelity.py +0 -0
  130. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_hook_lifecycle.py +0 -0
  131. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_message_diagnostics.py +0 -0
  132. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_message_transport_diagnostics.py +0 -0
  133. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_record.py +0 -0
  134. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_rollout_structures.py +0 -0
  135. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_rollout_trace.py +0 -0
  136. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/codex_rollout_trace_base.py +0 -0
  137. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/command.py +0 -0
  138. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/content_evidence.py +0 -0
  139. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_archive.py +0 -0
  140. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_message_identity.py +0 -0
  141. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview.py +0 -0
  142. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_antigravity.py +0 -0
  143. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_claude.py +0 -0
  144. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_common.py +0 -0
  145. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_generic.py +0 -0
  146. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_lines.py +0 -0
  147. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_preview_transcript.py +0 -0
  148. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_antigravity.py +0 -0
  149. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_common.py +0 -0
  150. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/conversation_records_ollama.py +0 -0
  151. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_delegation.py +0 -0
  152. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_delegation_base.py +0 -0
  153. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_full_fidelity.py +0 -0
  154. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_hook_contract.py +0 -0
  155. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_lifecycle.py +0 -0
  156. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/cursor_record.py +0 -0
  157. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/evidence_availability.py +0 -0
  158. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/evidence_grade.py +0 -0
  159. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/focus.py +0 -0
  160. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/__init__.py +0 -0
  161. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/autogen.py +0 -0
  162. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/camel.py +0 -0
  163. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/camel_channel.py +0 -0
  164. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/metagpt.py +0 -0
  165. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/framework_adapters/registry.py +0 -0
  166. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/hook_input.py +0 -0
  167. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/http_proxy.py +0 -0
  168. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/http_proxy_cli.py +0 -0
  169. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_gateway_full_fidelity.py +0 -0
  170. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_identity.py +0 -0
  171. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/inference_identity_cli.py +0 -0
  172. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/litellm_callback_cli.py +0 -0
  173. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view.py +0 -0
  174. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_markup.py +0 -0
  175. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_process_layout.py +0 -0
  176. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_readability.py +0 -0
  177. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_b.py +0 -0
  178. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_script_d.py +0 -0
  179. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/live_view_style.py +0 -0
  180. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/model_runtime_full_fidelity.py +0 -0
  181. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/openai_compatible_full_fidelity.py +0 -0
  182. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_event_contract.py +0 -0
  183. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_full_fidelity.py +0 -0
  184. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_plugin_cli.py +0 -0
  185. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_record.py +0 -0
  186. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/opencode_task_linkage.py +0 -0
  187. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/overhead_benchmark.py +0 -0
  188. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/provider_capability.py +0 -0
  189. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/provider_lifecycle.py +0 -0
  190. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/provider_record.py +0 -0
  191. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/rule_pack.py +0 -0
  192. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/rule_pack_cli.py +0 -0
  193. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/runtime_endpoint_availability.py +0 -0
  194. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/scalability_benchmark.py +0 -0
  195. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/schema.py +0 -0
  196. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/scope.py +0 -0
  197. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/strace_backend.py +0 -0
  198. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/stream_assembly.py +0 -0
  199. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/theme.py +0 -0
  200. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/validate.py +0 -0
  201. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/vendor/dagre.min.js +0 -0
  202. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/view_cli.py +0 -0
  203. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
  204. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_claude.py +0 -0
  205. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_codex.py +0 -0
  206. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_cursor.py +0 -0
  207. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_ollama.py +0 -0
  208. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_opencode.py +0 -0
  209. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_agent_panel_registry.py +0 -0
  210. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_background_files.py +0 -0
  211. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_content_inspector.py +0 -0
  212. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_clean.py +0 -0
  213. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_focus.py +0 -0
  214. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_hardening.py +0 -0
  215. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
  216. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_dashboard_pr70.py +0 -0
  217. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_edge_decross.py +0 -0
  218. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_execution_flow.py +0 -0
  219. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_execution_flow_provider_normalization.py +0 -0
  220. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_execution_flow_safety.py +0 -0
  221. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_external_endpoints.py +0 -0
  222. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_fold_state.py +0 -0
  223. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_framework_flow.py +0 -0
  224. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_gif.py +0 -0
  225. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_layout_geometry.py +0 -0
  226. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_layout_v2.py +0 -0
  227. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_layout_v2_detached.py +0 -0
  228. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_limits.py +0 -0
  229. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_live_layout.py +0 -0
  230. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_projection_bridges.py +0 -0
  231. {execweave-0.8.33 → execweave-0.8.35}/src/execweave/viewer_tool_mirror_identity.py +0 -0
  232. {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.33
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
- Some agents and IDEs ask for permission before enabling a local hook or plugin. Approve the ExecWeave integration if you want provider-level prompt, response, tool, model, and conversation evidence. If you do not approve it, OS-runtime observation can still work, but semantic coverage may be reduced.
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
- Some agents and IDEs ask for permission before enabling a local hook or plugin. Approve the ExecWeave integration if you want provider-level prompt, response, tool, model, and conversation evidence. If you do not approve it, OS-runtime observation can still work, but semantic coverage may be reduced.
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.33 release metadata
8
- version = "0.8.33"
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,3 +1,3 @@
1
1
  """ExecWeave runtime graph package."""
2
2
 
3
- __version__ = "0.8.33"
3
+ __version__ = "0.8.35"
@@ -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 index exactly as ``conversations.json`` carries it.
849
+ """Build the shared conversation projection, with opt-in investigation.
815
850
 
816
- The live dashboard and the finalized viewer both read this. Keeping one
817
- builder is the point: a live run that computed its own projection would be
818
- free to disagree with the file written at finalization, and the disagreement
819
- would show up as an agent seeing conversations that are not its own.
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.write_text(
860
- json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
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
- return _align_agent_panel_topology(inject_agent_panel(_guard_compact_live_snapshot(html)))
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
- if capture is not None:
491
- capture.finish()
492
- _record_capture(
493
- handler.server.proxy_config,
494
- exchange_id=exchange_id,
495
- request_body=body,
496
- response_content_type=response_type,
497
- method=handler.command,
498
- request_path=target,
499
- status=status,
500
- capture=capture,
501
- )
502
- else:
503
- handler.server.recorder(
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 marker in command:
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 = False
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, False
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 or marker in existing:
243
+ if existing == content:
219
244
  return False
220
- raise FileExistsError(f"refusing to replace existing OpenCode plugin: {path}")
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(f"execweave-claude-hook {_AUTO_FLAG}"), "execweave-claude-hook"
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(f"execweave-codex-hook {_AUTO_FLAG}"), "execweave-codex-hook"
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(f"execweave-cursor-hook {_AUTO_FLAG}"), "execweave-cursor-hook"
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(("execweave-opencode-hook", _AUTO_FLAG))
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="active",
347
+ status="configured_unverified",
321
348
  path=str(target),
322
349
  changed=changed,
323
- detail="specialized hook/plugin bootstrap is configured",
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__)