execweave 0.8.13__tar.gz → 0.8.15__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 (585) hide show
  1. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/provider-capability-stage-integrity.yml +9 -0
  2. {execweave-0.8.13 → execweave-0.8.15}/PKG-INFO +1 -1
  3. {execweave-0.8.13 → execweave-0.8.15}/pyproject.toml +2 -2
  4. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/__init__.py +1 -1
  5. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/collector.py +120 -15
  6. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_style.py +2 -1
  7. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_dashboard_clean.py +37 -8
  8. execweave-0.8.15/tests/test_d003_fast_reparent.py +134 -0
  9. execweave-0.8.15/tests/test_dashboard_file_activity_filter.py +30 -0
  10. execweave-0.8.15/tests/test_pypi_0813_cleanup.py +382 -0
  11. {execweave-0.8.13 → execweave-0.8.15}/tests/test_threat_model.py +2 -0
  12. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v069_dashboard_release.py +3 -3
  13. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/ci.yml +0 -0
  14. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/cleanup-workflow-history.yml +0 -0
  15. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/i18n.yml +0 -0
  16. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/package-benchmark.yml +0 -0
  17. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/package-smoke.yml +0 -0
  18. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/provider-dashboard-contract.yml +0 -0
  19. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/publish.yml +0 -0
  20. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/scalability.yml +0 -0
  21. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/viewer-agent-isolation.yml +0 -0
  22. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/wheel-smoke.yml +0 -0
  23. {execweave-0.8.13 → execweave-0.8.15}/.github/workflows/windows-launcher.yml +0 -0
  24. {execweave-0.8.13 → execweave-0.8.15}/.gitignore +0 -0
  25. {execweave-0.8.13 → execweave-0.8.15}/LICENSE +0 -0
  26. {execweave-0.8.13 → execweave-0.8.15}/README.de.md +0 -0
  27. {execweave-0.8.13 → execweave-0.8.15}/README.fr.md +0 -0
  28. {execweave-0.8.13 → execweave-0.8.15}/README.ja.md +0 -0
  29. {execweave-0.8.13 → execweave-0.8.15}/README.ko.md +0 -0
  30. {execweave-0.8.13 → execweave-0.8.15}/README.md +0 -0
  31. {execweave-0.8.13 → execweave-0.8.15}/README.ru.md +0 -0
  32. {execweave-0.8.13 → execweave-0.8.15}/README.zh-CN.md +0 -0
  33. {execweave-0.8.13 → execweave-0.8.15}/README.zh-TW.md +0 -0
  34. {execweave-0.8.13 → execweave-0.8.15}/benchmarks/phase1_overhead.py +0 -0
  35. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/acceptance_hardening_20260906.md +0 -0
  36. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/audit_eight_children.py +0 -0
  37. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/audit_python_journey.py +0 -0
  38. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/audit_real_clicks.py +0 -0
  39. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/current_state_reaudit.md +0 -0
  40. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/execweave_full_audit.md +0 -0
  41. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/gate_status.md +0 -0
  42. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/gate_status_history_through_g3.md +0 -0
  43. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/github_pr_inventory.json +0 -0
  44. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/handoff_checkpoint_20260906.md +0 -0
  45. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/next_agent_handoff.md +0 -0
  46. {execweave-0.8.13 → execweave-0.8.15}/diagnostics/pr_regression_map.md +0 -0
  47. {execweave-0.8.13 → execweave-0.8.15}/docs/antigravity-hooks.md +0 -0
  48. {execweave-0.8.13 → execweave-0.8.15}/docs/assets/codex.gif +0 -0
  49. {execweave-0.8.13 → execweave-0.8.15}/docs/benchmarks/README.md +0 -0
  50. {execweave-0.8.13 → execweave-0.8.15}/docs/benchmarks/v0.6.0-github-actions.json +0 -0
  51. {execweave-0.8.13 → execweave-0.8.15}/docs/benchmarks/v0.6.0-github-actions.svg +0 -0
  52. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.de.md +0 -0
  53. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.fr.md +0 -0
  54. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.ja.md +0 -0
  55. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.ko.md +0 -0
  56. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.md +0 -0
  57. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.ru.md +0 -0
  58. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.zh-CN.md +0 -0
  59. {execweave-0.8.13 → execweave-0.8.15}/docs/claude-code-hooks.zh-TW.md +0 -0
  60. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.de.md +0 -0
  61. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.fr.md +0 -0
  62. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.ja.md +0 -0
  63. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.ko.md +0 -0
  64. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.md +0 -0
  65. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.ru.md +0 -0
  66. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.zh-CN.md +0 -0
  67. {execweave-0.8.13 → execweave-0.8.15}/docs/codex-hooks.zh-TW.md +0 -0
  68. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.de.md +0 -0
  69. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.fr.md +0 -0
  70. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.ja.md +0 -0
  71. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.ko.md +0 -0
  72. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.md +0 -0
  73. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.ru.md +0 -0
  74. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.zh-CN.md +0 -0
  75. {execweave-0.8.13 → execweave-0.8.15}/docs/cursor-hooks.zh-TW.md +0 -0
  76. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.de.md +0 -0
  77. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.fr.md +0 -0
  78. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.ja.md +0 -0
  79. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.ko.md +0 -0
  80. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.md +0 -0
  81. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.ru.md +0 -0
  82. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.zh-CN.md +0 -0
  83. {execweave-0.8.13 → execweave-0.8.15}/docs/evidence-grades.zh-TW.md +0 -0
  84. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.de.md +0 -0
  85. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.fr.md +0 -0
  86. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.ja.md +0 -0
  87. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.ko.md +0 -0
  88. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.md +0 -0
  89. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.ru.md +0 -0
  90. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.zh-CN.md +0 -0
  91. {execweave-0.8.13 → execweave-0.8.15}/docs/inference-gateway.zh-TW.md +0 -0
  92. {execweave-0.8.13 → execweave-0.8.15}/docs/internal/provider-capability-matrix.md +0 -0
  93. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.de.md +0 -0
  94. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.fr.md +0 -0
  95. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.ja.md +0 -0
  96. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.ko.md +0 -0
  97. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.md +0 -0
  98. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.ru.md +0 -0
  99. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.zh-CN.md +0 -0
  100. {execweave-0.8.13 → execweave-0.8.15}/docs/live-graph.zh-TW.md +0 -0
  101. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.de.md +0 -0
  102. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.fr.md +0 -0
  103. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.ja.md +0 -0
  104. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.ko.md +0 -0
  105. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.md +0 -0
  106. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.ru.md +0 -0
  107. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.zh-CN.md +0 -0
  108. {execweave-0.8.13 → execweave-0.8.15}/docs/model-runtime.zh-TW.md +0 -0
  109. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.de.md +0 -0
  110. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.fr.md +0 -0
  111. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.ja.md +0 -0
  112. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.ko.md +0 -0
  113. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.md +0 -0
  114. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.ru.md +0 -0
  115. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.zh-CN.md +0 -0
  116. {execweave-0.8.13 → execweave-0.8.15}/docs/opencode-plugin.zh-TW.md +0 -0
  117. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.de.md +0 -0
  118. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.fr.md +0 -0
  119. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.ja.md +0 -0
  120. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.ko.md +0 -0
  121. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.md +0 -0
  122. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.ru.md +0 -0
  123. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.zh-CN.md +0 -0
  124. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-1-runtime-collection.zh-TW.md +0 -0
  125. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.de.md +0 -0
  126. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.fr.md +0 -0
  127. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.ja.md +0 -0
  128. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.ko.md +0 -0
  129. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.md +0 -0
  130. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.ru.md +0 -0
  131. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.zh-CN.md +0 -0
  132. {execweave-0.8.13 → execweave-0.8.15}/docs/phase-2-execution-graph.zh-TW.md +0 -0
  133. {execweave-0.8.13 → execweave-0.8.15}/docs/remaining-provider-audit-v0.8.7.md +0 -0
  134. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.de.md +0 -0
  135. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.fr.md +0 -0
  136. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.ja.md +0 -0
  137. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.ko.md +0 -0
  138. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.md +0 -0
  139. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.ru.md +0 -0
  140. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.zh-CN.md +0 -0
  141. {execweave-0.8.13 → execweave-0.8.15}/docs/rule-packs.zh-TW.md +0 -0
  142. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.de.md +0 -0
  143. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.fr.md +0 -0
  144. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.ja.md +0 -0
  145. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.ko.md +0 -0
  146. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.md +0 -0
  147. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.ru.md +0 -0
  148. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.zh-CN.md +0 -0
  149. {execweave-0.8.13 → execweave-0.8.15}/docs/run-integrity.zh-TW.md +0 -0
  150. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.de.md +0 -0
  151. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.fr.md +0 -0
  152. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.ja.md +0 -0
  153. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.ko.md +0 -0
  154. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.md +0 -0
  155. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.ru.md +0 -0
  156. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.zh-CN.md +0 -0
  157. {execweave-0.8.13 → execweave-0.8.15}/docs/runtime-threat-model.zh-TW.md +0 -0
  158. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.de.md +0 -0
  159. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.fr.md +0 -0
  160. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.ja.md +0 -0
  161. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.ko.md +0 -0
  162. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.md +0 -0
  163. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.ru.md +0 -0
  164. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.zh-CN.md +0 -0
  165. {execweave-0.8.13 → execweave-0.8.15}/docs/security-analysis.zh-TW.md +0 -0
  166. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.de.md +0 -0
  167. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.fr.md +0 -0
  168. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.ja.md +0 -0
  169. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.ko.md +0 -0
  170. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.md +0 -0
  171. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.ru.md +0 -0
  172. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.zh-CN.md +0 -0
  173. {execweave-0.8.13 → execweave-0.8.15}/docs/semantic-telemetry.zh-TW.md +0 -0
  174. {execweave-0.8.13 → execweave-0.8.15}/docs/v0.6.5-hardening-decisions.md +0 -0
  175. {execweave-0.8.13 → execweave-0.8.15}/docs/v0.8.3-graph-ergonomics.md +0 -0
  176. {execweave-0.8.13 → execweave-0.8.15}/scripts/_check_release_stage_integrity_impl.py +0 -0
  177. {execweave-0.8.13 → execweave-0.8.15}/scripts/_dashboard_acceptance_impl.py +0 -0
  178. {execweave-0.8.13 → execweave-0.8.15}/scripts/_ollama_interactive_acceptance_impl.py +0 -0
  179. {execweave-0.8.13 → execweave-0.8.15}/scripts/_ollama_visible_acceptance_impl.py +0 -0
  180. {execweave-0.8.13 → execweave-0.8.15}/scripts/_python_native_acceptance_impl.py +0 -0
  181. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/__init__.py +0 -0
  182. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/browser_diagnostics.py +0 -0
  183. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/contracts.py +0 -0
  184. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/g5_runner.py +0 -0
  185. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/g6_runner.py +0 -0
  186. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/interrupt_guard.py +0 -0
  187. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/processes.py +0 -0
  188. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/reporting.py +0 -0
  189. {execweave-0.8.13 → execweave-0.8.15}/scripts/acceptance/terminal_output.py +0 -0
  190. {execweave-0.8.13 → execweave-0.8.15}/scripts/annotate_pytest_failures.py +0 -0
  191. {execweave-0.8.13 → execweave-0.8.15}/scripts/audit_i18n_parity.py +0 -0
  192. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_claude_hook.py +0 -0
  193. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_claude_record_graph.py +0 -0
  194. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_codex_hook.py +0 -0
  195. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_conversation_records.py +0 -0
  196. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_correlation_graph.py +0 -0
  197. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_inference_gateway_cli.py +0 -0
  198. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_model_runtime_cli.py +0 -0
  199. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_openai_compatible_cli.py +0 -0
  200. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_release_stage_integrity.py +0 -0
  201. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_semantic_graph.py +0 -0
  202. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_viewer_js.py +0 -0
  203. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_wheel_install.py +0 -0
  204. {execweave-0.8.13 → execweave-0.8.15}/scripts/check_windows_launchers.py +0 -0
  205. {execweave-0.8.13 → execweave-0.8.15}/scripts/dashboard_acceptance.py +0 -0
  206. {execweave-0.8.13 → execweave-0.8.15}/scripts/emit_claude_hook_smoke.py +0 -0
  207. {execweave-0.8.13 → execweave-0.8.15}/scripts/emit_codex_hook_smoke.py +0 -0
  208. {execweave-0.8.13 → execweave-0.8.15}/scripts/emit_cursor_hook_smoke.py +0 -0
  209. {execweave-0.8.13 → execweave-0.8.15}/scripts/emit_opencode_plugin_smoke.py +0 -0
  210. {execweave-0.8.13 → execweave-0.8.15}/scripts/make_semantic_smoke.py +0 -0
  211. {execweave-0.8.13 → execweave-0.8.15}/scripts/measure_graph_metrics.py +0 -0
  212. {execweave-0.8.13 → execweave-0.8.15}/scripts/ollama_interactive_acceptance.py +0 -0
  213. {execweave-0.8.13 → execweave-0.8.15}/scripts/ollama_visible_acceptance.py +0 -0
  214. {execweave-0.8.13 → execweave-0.8.15}/scripts/owned_cleanup_acceptance.py +0 -0
  215. {execweave-0.8.13 → execweave-0.8.15}/scripts/probe_provider_capability.py +0 -0
  216. {execweave-0.8.13 → execweave-0.8.15}/scripts/python_native_acceptance.py +0 -0
  217. {execweave-0.8.13 → execweave-0.8.15}/scripts/sync_i18n_nav.py +0 -0
  218. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/__main__.py +0 -0
  219. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/_conversation_records_core.py +0 -0
  220. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/_dashboard_shell_base.py +0 -0
  221. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/_http_proxy_base.py +0 -0
  222. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/_http_proxy_bounded.py +0 -0
  223. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/_http_proxy_stage.py +0 -0
  224. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/agent_bootstrap.py +0 -0
  225. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/agent_topology.py +0 -0
  226. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/agent_trace.py +0 -0
  227. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/agy_preview_sanitize.py +0 -0
  228. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/analysis.py +0 -0
  229. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/anthropic.py +0 -0
  230. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/anthropic_cli.py +0 -0
  231. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/anthropic_full_fidelity.py +0 -0
  232. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_adapter.py +0 -0
  233. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_adapter_base.py +0 -0
  234. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_full_fidelity.py +0 -0
  235. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_full_fidelity_base.py +0 -0
  236. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
  237. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_hook_cli.py +0 -0
  238. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_record.py +0 -0
  239. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_subagent_linkage.py +0 -0
  240. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/antigravity_trace_capability.py +0 -0
  241. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/auto_specialized.py +0 -0
  242. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/backends.py +0 -0
  243. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/benchmark.py +0 -0
  244. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_adapter.py +0 -0
  245. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_child_transcript.py +0 -0
  246. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_delegation.py +0 -0
  247. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_full_fidelity.py +0 -0
  248. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_hook_cli.py +0 -0
  249. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_hook_contract.py +0 -0
  250. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_model_observer.py +0 -0
  251. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/claude_record.py +0 -0
  252. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cli.py +0 -0
  253. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_adapter.py +0 -0
  254. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_conversation.py +0 -0
  255. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_full_fidelity.py +0 -0
  256. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_hook_cli.py +0 -0
  257. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_hook_entry.py +0 -0
  258. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_hook_lifecycle.py +0 -0
  259. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_message_diagnostics.py +0 -0
  260. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_message_transport_diagnostics.py +0 -0
  261. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_record.py +0 -0
  262. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_rollout_structures.py +0 -0
  263. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_rollout_trace.py +0 -0
  264. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/codex_rollout_trace_base.py +0 -0
  265. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/command.py +0 -0
  266. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/content_evidence.py +0 -0
  267. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/content_store.py +0 -0
  268. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_archive.py +0 -0
  269. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_message_identity.py +0 -0
  270. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview.py +0 -0
  271. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_antigravity.py +0 -0
  272. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_claude.py +0 -0
  273. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_codex.py +0 -0
  274. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_common.py +0 -0
  275. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_generic.py +0 -0
  276. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_lines.py +0 -0
  277. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_preview_transcript.py +0 -0
  278. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_records.py +0 -0
  279. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_records_antigravity.py +0 -0
  280. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_records_codex.py +0 -0
  281. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_records_common.py +0 -0
  282. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/conversation_records_ollama.py +0 -0
  283. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/correlation.py +0 -0
  284. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_adapter.py +0 -0
  285. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_delegation.py +0 -0
  286. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_delegation_base.py +0 -0
  287. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_full_fidelity.py +0 -0
  288. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_hook_cli.py +0 -0
  289. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_hook_contract.py +0 -0
  290. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/cursor_record.py +0 -0
  291. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/dashboard_shell.py +0 -0
  292. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/entry.py +0 -0
  293. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/evidence_availability.py +0 -0
  294. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/evidence_grade.py +0 -0
  295. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/fidelity.py +0 -0
  296. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/filesystem.py +0 -0
  297. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/focus.py +0 -0
  298. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/graph.py +0 -0
  299. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/graph_ops.py +0 -0
  300. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/http_proxy.py +0 -0
  301. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/http_proxy_cli.py +0 -0
  302. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/inference_gateway.py +0 -0
  303. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/inference_gateway_cli.py +0 -0
  304. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/inference_gateway_full_fidelity.py +0 -0
  305. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/inference_identity.py +0 -0
  306. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/inference_identity_cli.py +0 -0
  307. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/integrity.py +0 -0
  308. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/integrity_cli.py +0 -0
  309. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/litellm_callback.py +0 -0
  310. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/litellm_callback_cli.py +0 -0
  311. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live.py +0 -0
  312. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_core.py +0 -0
  313. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view.py +0 -0
  314. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_extra_style.py +0 -0
  315. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_markup.py +0 -0
  316. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_process_layout.py +0 -0
  317. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_readability.py +0 -0
  318. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_script_a.py +0 -0
  319. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_script_b.py +0 -0
  320. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_script_c.py +0 -0
  321. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/live_view_script_d.py +0 -0
  322. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/model_runtime.py +0 -0
  323. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/model_runtime_cli.py +0 -0
  324. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/model_runtime_full_fidelity.py +0 -0
  325. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/openai_compatible.py +0 -0
  326. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/openai_compatible_cli.py +0 -0
  327. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/openai_compatible_full_fidelity.py +0 -0
  328. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_adapter.py +0 -0
  329. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_event_contract.py +0 -0
  330. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_full_fidelity.py +0 -0
  331. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_hook_cli.py +0 -0
  332. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_plugin_cli.py +0 -0
  333. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_record.py +0 -0
  334. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/opencode_task_linkage.py +0 -0
  335. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/overhead_benchmark.py +0 -0
  336. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/provider_capability.py +0 -0
  337. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/provider_lifecycle.py +0 -0
  338. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/provider_record.py +0 -0
  339. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/rule_pack.py +0 -0
  340. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/rule_pack_cli.py +0 -0
  341. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/scalability_benchmark.py +0 -0
  342. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/schema.py +0 -0
  343. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/scope.py +0 -0
  344. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/semantic.py +0 -0
  345. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/sink.py +0 -0
  346. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/strace_backend.py +0 -0
  347. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/stream_assembly.py +0 -0
  348. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/theme.py +0 -0
  349. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/top.py +0 -0
  350. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/top_cli.py +0 -0
  351. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/validate.py +0 -0
  352. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/vendor/dagre.min.js +0 -0
  353. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/view_cli.py +0 -0
  354. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer.py +0 -0
  355. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel.py +0 -0
  356. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
  357. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_claude.py +0 -0
  358. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_codex.py +0 -0
  359. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_cursor.py +0 -0
  360. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_default.py +0 -0
  361. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_ollama.py +0 -0
  362. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_opencode.py +0 -0
  363. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_agent_panel_registry.py +0 -0
  364. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_content_inspector.py +0 -0
  365. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_dashboard_focus.py +0 -0
  366. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_dashboard_hardening.py +0 -0
  367. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
  368. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_external_endpoints.py +0 -0
  369. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_limits.py +0 -0
  370. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_live_layout.py +0 -0
  371. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_projection.py +0 -0
  372. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_projection_base.py +0 -0
  373. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/viewer_semantic_projection.py +0 -0
  374. {execweave-0.8.13 → execweave-0.8.15}/src/execweave/workflow.py +0 -0
  375. {execweave-0.8.13 → execweave-0.8.15}/tests/dashboard_readability_fixture.py +0 -0
  376. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/codex_multi_agent/PROVENANCE.md +0 -0
  377. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/codex_multi_agent/hook-payloads.json +0 -0
  378. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/codex_multi_agent/rollout-main.jsonl +0 -0
  379. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/hooks/README.md +0 -0
  380. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/hooks/claude.json +0 -0
  381. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/hooks/codex.json +0 -0
  382. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/hooks/cursor.json +0 -0
  383. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/hooks/manifest.json +0 -0
  384. {execweave-0.8.13 → execweave-0.8.15}/tests/fixtures/hooks/opencode.json +0 -0
  385. {execweave-0.8.13 → execweave-0.8.15}/tests/multi_agent_run_fixture.py +0 -0
  386. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_browser_diagnostics_e2e.py +0 -0
  387. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_conpty_selection.py +0 -0
  388. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_interrupt_guard.py +0 -0
  389. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_owned_processes.py +0 -0
  390. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_required_capabilities.py +0 -0
  391. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_terminal_output.py +0 -0
  392. {execweave-0.8.13 → execweave-0.8.15}/tests/test_acceptance_visible_pipe.py +0 -0
  393. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_bootstrap.py +0 -0
  394. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_message_viewer.py +0 -0
  395. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_node_labels.py +0 -0
  396. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_said_panel.py +0 -0
  397. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_topology_evidence.py +0 -0
  398. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_trace_lifecycle.py +0 -0
  399. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agent_trace_viewer_visibility.py +0 -0
  400. {execweave-0.8.13 → execweave-0.8.15}/tests/test_agy_windows_wire_acceptance.py +0 -0
  401. {execweave-0.8.13 → execweave-0.8.15}/tests/test_analysis.py +0 -0
  402. {execweave-0.8.13 → execweave-0.8.15}/tests/test_anthropic.py +0 -0
  403. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_agent_collaboration.py +0 -0
  404. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_child_panel_policy.py +0 -0
  405. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_child_transcript_tools.py +0 -0
  406. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_cursor_launch.py +0 -0
  407. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_field_run_regressions.py +0 -0
  408. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_history_replay.py +0 -0
  409. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_identity_topology.py +0 -0
  410. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_invocation_archive.py +0 -0
  411. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_lifecycle.py +0 -0
  412. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_linkage_projection.py +0 -0
  413. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_multi_conversation_isolation.py +0 -0
  414. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_official_contract.py +0 -0
  415. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_role_path_fallback.py +0 -0
  416. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_root_child_history_v2.py +0 -0
  417. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_subagent_transcript_linkage.py +0 -0
  418. {execweave-0.8.13 → execweave-0.8.15}/tests/test_antigravity_trace_capability_cli.py +0 -0
  419. {execweave-0.8.13 → execweave-0.8.15}/tests/test_auto_specialized.py +0 -0
  420. {execweave-0.8.13 → execweave-0.8.15}/tests/test_auto_specialized_ollama_two_phase.py +0 -0
  421. {execweave-0.8.13 → execweave-0.8.15}/tests/test_backends.py +0 -0
  422. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_adapter.py +0 -0
  423. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_child_transcript_tools.py +0 -0
  424. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_delegation.py +0 -0
  425. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_full_fidelity.py +0 -0
  426. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_hook_contract.py +0 -0
  427. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_hook_metadata.py +0 -0
  428. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_hook_viewer.py +0 -0
  429. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_model_observer.py +0 -0
  430. {execweave-0.8.13 → execweave-0.8.15}/tests/test_claude_record.py +0 -0
  431. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cleanup_finalizer_identity.py +0 -0
  432. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_adapter.py +0 -0
  433. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_archive_full_fidelity.py +0 -0
  434. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_child_panel_policy.py +0 -0
  435. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_conversation_isolation.py +0 -0
  436. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_full_fidelity.py +0 -0
  437. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_hook_global_fail_open.py +0 -0
  438. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_hook_lifecycle.py +0 -0
  439. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_hook_partial_capture.py +0 -0
  440. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_interrupt_contract.py +0 -0
  441. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_message_diagnostics.py +0 -0
  442. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_message_transport_diagnostics.py +0 -0
  443. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_nickname_path_fallback.py +0 -0
  444. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_plain_routed_agent_message.py +0 -0
  445. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_real_multi_agent_conversation.py +0 -0
  446. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_record.py +0 -0
  447. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_reducer_command.py +0 -0
  448. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_rollout_structures.py +0 -0
  449. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_rollout_trace.py +0 -0
  450. {execweave-0.8.13 → execweave-0.8.15}/tests/test_codex_routing_native_identity.py +0 -0
  451. {execweave-0.8.13 → execweave-0.8.15}/tests/test_collector.py +0 -0
  452. {execweave-0.8.13 → execweave-0.8.15}/tests/test_command.py +0 -0
  453. {execweave-0.8.13 → execweave-0.8.15}/tests/test_condense.py +0 -0
  454. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_access.py +0 -0
  455. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_agent_focus.py +0 -0
  456. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_agent_isolation.py +0 -0
  457. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_identity_merge.py +0 -0
  458. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_message_observation_dedupe.py +0 -0
  459. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_provider_parity.py +0 -0
  460. {execweave-0.8.13 → execweave-0.8.15}/tests/test_conversation_root_authority.py +0 -0
  461. {execweave-0.8.13 → execweave-0.8.15}/tests/test_correlation.py +0 -0
  462. {execweave-0.8.13 → execweave-0.8.15}/tests/test_correlation_canonical_path.py +0 -0
  463. {execweave-0.8.13 → execweave-0.8.15}/tests/test_correlation_checker.py +0 -0
  464. {execweave-0.8.13 → execweave-0.8.15}/tests/test_correlation_process_exe.py +0 -0
  465. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cross_provider_agent_trace.py +0 -0
  466. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_adapter.py +0 -0
  467. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_delegation.py +0 -0
  468. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_full_fidelity.py +0 -0
  469. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_hook_contract.py +0 -0
  470. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_hook_fail_open.py +0 -0
  471. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_ollama_session_lifecycle.py +0 -0
  472. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_record.py +0 -0
  473. {execweave-0.8.13 → execweave-0.8.15}/tests/test_cursor_task_linkage.py +0 -0
  474. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dagre_semantic_pipeline.py +0 -0
  475. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_acceptance_relative_output_e2e.py +0 -0
  476. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_acceptance_reporting.py +0 -0
  477. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_camera_scheduler_e2e.py +0 -0
  478. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_favicon.py +0 -0
  479. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_gif_export_e2e.py +0 -0
  480. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_information_architecture.py +0 -0
  481. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_initial_fit_readability_e2e.py +0 -0
  482. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_node_drag_camera_e2e.py +0 -0
  483. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_readability_e2e.py +0 -0
  484. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_round_fold_state_e2e.py +0 -0
  485. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_simplification.py +0 -0
  486. {execweave-0.8.13 → execweave-0.8.15}/tests/test_dashboard_ux_and_hook_projection.py +0 -0
  487. {execweave-0.8.13 → execweave-0.8.15}/tests/test_delegation_viewer.py +0 -0
  488. {execweave-0.8.13 → execweave-0.8.15}/tests/test_evidence_grade.py +0 -0
  489. {execweave-0.8.13 → execweave-0.8.15}/tests/test_execution_viewer.py +0 -0
  490. {execweave-0.8.13 → execweave-0.8.15}/tests/test_external_endpoints_all_providers.py +0 -0
  491. {execweave-0.8.13 → execweave-0.8.15}/tests/test_fidelity.py +0 -0
  492. {execweave-0.8.13 → execweave-0.8.15}/tests/test_filesystem_resilience.py +0 -0
  493. {execweave-0.8.13 → execweave-0.8.15}/tests/test_focus.py +0 -0
  494. {execweave-0.8.13 → execweave-0.8.15}/tests/test_fold_budget_setting.py +0 -0
  495. {execweave-0.8.13 → execweave-0.8.15}/tests/test_full_fidelity.py +0 -0
  496. {execweave-0.8.13 → execweave-0.8.15}/tests/test_g5_hardening_contract.py +0 -0
  497. {execweave-0.8.13 → execweave-0.8.15}/tests/test_g6_owned_parity_contract.py +0 -0
  498. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph.py +0 -0
  499. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph_arrange_constraints_e2e.py +0 -0
  500. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph_clear_focus_e2e.py +0 -0
  501. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph_edge_routing_e2e.py +0 -0
  502. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph_lane_separation_e2e.py +0 -0
  503. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph_node_sizing_e2e.py +0 -0
  504. {execweave-0.8.13 → execweave-0.8.15}/tests/test_graph_ops.py +0 -0
  505. {execweave-0.8.13 → execweave-0.8.15}/tests/test_hook_fixture_corpus.py +0 -0
  506. {execweave-0.8.13 → execweave-0.8.15}/tests/test_http_proxy.py +0 -0
  507. {execweave-0.8.13 → execweave-0.8.15}/tests/test_http_proxy_request_phase.py +0 -0
  508. {execweave-0.8.13 → execweave-0.8.15}/tests/test_http_proxy_response_memory.py +0 -0
  509. {execweave-0.8.13 → execweave-0.8.15}/tests/test_http_proxy_streaming.py +0 -0
  510. {execweave-0.8.13 → execweave-0.8.15}/tests/test_i18n_readme_policy.py +0 -0
  511. {execweave-0.8.13 → execweave-0.8.15}/tests/test_inference_endpoint_identity.py +0 -0
  512. {execweave-0.8.13 → execweave-0.8.15}/tests/test_inference_gateway.py +0 -0
  513. {execweave-0.8.13 → execweave-0.8.15}/tests/test_inference_gateway_full_fidelity.py +0 -0
  514. {execweave-0.8.13 → execweave-0.8.15}/tests/test_inference_identity.py +0 -0
  515. {execweave-0.8.13 → execweave-0.8.15}/tests/test_integrity.py +0 -0
  516. {execweave-0.8.13 → execweave-0.8.15}/tests/test_litellm_callback.py +0 -0
  517. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live.py +0 -0
  518. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live_auth.py +0 -0
  519. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live_dashboard.py +0 -0
  520. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live_dashboard_js.py +0 -0
  521. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live_delta.py +0 -0
  522. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live_logs_export.py +0 -0
  523. {execweave-0.8.13 → execweave-0.8.15}/tests/test_live_theme.py +0 -0
  524. {execweave-0.8.13 → execweave-0.8.15}/tests/test_lmstudio_auto_specialized.py +0 -0
  525. {execweave-0.8.13 → execweave-0.8.15}/tests/test_local_runtime_live_capture.py +0 -0
  526. {execweave-0.8.13 → execweave-0.8.15}/tests/test_model_runtime.py +0 -0
  527. {execweave-0.8.13 → execweave-0.8.15}/tests/test_model_runtime_full_fidelity.py +0 -0
  528. {execweave-0.8.13 → execweave-0.8.15}/tests/test_ollama_interactive_acceptance.py +0 -0
  529. {execweave-0.8.13 → execweave-0.8.15}/tests/test_ollama_interactive_terminal_semantics.py +0 -0
  530. {execweave-0.8.13 → execweave-0.8.15}/tests/test_ollama_panel_policy.py +0 -0
  531. {execweave-0.8.13 → execweave-0.8.15}/tests/test_ollama_serve_runtime_integration.py +0 -0
  532. {execweave-0.8.13 → execweave-0.8.15}/tests/test_ollama_visible_acceptance.py +0 -0
  533. {execweave-0.8.13 → execweave-0.8.15}/tests/test_openai_compatible.py +0 -0
  534. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_adapter.py +0 -0
  535. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_event_contract.py +0 -0
  536. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_event_lifecycle.py +0 -0
  537. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_full_fidelity.py +0 -0
  538. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_identity_unification.py +0 -0
  539. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_plugin.py +0 -0
  540. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_record.py +0 -0
  541. {execweave-0.8.13 → execweave-0.8.15}/tests/test_opencode_task_session_linkage.py +0 -0
  542. {execweave-0.8.13 → execweave-0.8.15}/tests/test_overhead_benchmark.py +0 -0
  543. {execweave-0.8.13 → execweave-0.8.15}/tests/test_per_agent_conversation_default.py +0 -0
  544. {execweave-0.8.13 → execweave-0.8.15}/tests/test_pr25_premerge_hardening_contract.py +0 -0
  545. {execweave-0.8.13 → execweave-0.8.15}/tests/test_pr25_premerge_hardening_e2e.py +0 -0
  546. {execweave-0.8.13 → execweave-0.8.15}/tests/test_pro6_audit_regressions.py +0 -0
  547. {execweave-0.8.13 → execweave-0.8.15}/tests/test_process_tree_layout.py +0 -0
  548. {execweave-0.8.13 → execweave-0.8.15}/tests/test_provider_capability.py +0 -0
  549. {execweave-0.8.13 → execweave-0.8.15}/tests/test_provider_conversation_evidence.py +0 -0
  550. {execweave-0.8.13 → execweave-0.8.15}/tests/test_provider_conversation_history_integrity.py +0 -0
  551. {execweave-0.8.13 → execweave-0.8.15}/tests/test_provider_dashboard_contract.py +0 -0
  552. {execweave-0.8.13 → execweave-0.8.15}/tests/test_provider_lifecycle.py +0 -0
  553. {execweave-0.8.13 → execweave-0.8.15}/tests/test_provider_neutral_viewer_semantics.py +0 -0
  554. {execweave-0.8.13 → execweave-0.8.15}/tests/test_pypi_0812_regressions.py +0 -0
  555. {execweave-0.8.13 → execweave-0.8.15}/tests/test_python_native_acceptance.py +0 -0
  556. {execweave-0.8.13 → execweave-0.8.15}/tests/test_relay_prompt_live_e2e.py +0 -0
  557. {execweave-0.8.13 → execweave-0.8.15}/tests/test_release_stage_integrity_version_guard.py +0 -0
  558. {execweave-0.8.13 → execweave-0.8.15}/tests/test_rule_pack.py +0 -0
  559. {execweave-0.8.13 → execweave-0.8.15}/tests/test_schema.py +0 -0
  560. {execweave-0.8.13 → execweave-0.8.15}/tests/test_scope.py +0 -0
  561. {execweave-0.8.13 → execweave-0.8.15}/tests/test_semantic.py +0 -0
  562. {execweave-0.8.13 → execweave-0.8.15}/tests/test_shared_injected_context_scope.py +0 -0
  563. {execweave-0.8.13 → execweave-0.8.15}/tests/test_single_agent_barycenter.py +0 -0
  564. {execweave-0.8.13 → execweave-0.8.15}/tests/test_strace_backend.py +0 -0
  565. {execweave-0.8.13 → execweave-0.8.15}/tests/test_stream_assembly.py +0 -0
  566. {execweave-0.8.13 → execweave-0.8.15}/tests/test_tool_traffic_e2e.py +0 -0
  567. {execweave-0.8.13 → execweave-0.8.15}/tests/test_top_detached.py +0 -0
  568. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v063_features.py +0 -0
  569. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v064_live_auto_integrations.py +0 -0
  570. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v064_live_evidence.py +0 -0
  571. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v079_review_e2e.py +0 -0
  572. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v079_review_regressions.py +0 -0
  573. {execweave-0.8.13 → execweave-0.8.15}/tests/test_v083_release_gate_p2.py +0 -0
  574. {execweave-0.8.13 → execweave-0.8.15}/tests/test_validate.py +0 -0
  575. {execweave-0.8.13 → execweave-0.8.15}/tests/test_version.py +0 -0
  576. {execweave-0.8.13 → execweave-0.8.15}/tests/test_viewer.py +0 -0
  577. {execweave-0.8.13 → execweave-0.8.15}/tests/test_viewer_agent_isolation_e2e.py +0 -0
  578. {execweave-0.8.13 → execweave-0.8.15}/tests/test_viewer_child_authority.py +0 -0
  579. {execweave-0.8.13 → execweave-0.8.15}/tests/test_viewer_content_inspector.py +0 -0
  580. {execweave-0.8.13 → execweave-0.8.15}/tests/test_viewer_limits.py +0 -0
  581. {execweave-0.8.13 → execweave-0.8.15}/tests/test_viewer_projection.py +0 -0
  582. {execweave-0.8.13 → execweave-0.8.15}/tests/test_visual_ux_readability.py +0 -0
  583. {execweave-0.8.13 → execweave-0.8.15}/tests/test_windows_codex_agy_display_repair.py +0 -0
  584. {execweave-0.8.13 → execweave-0.8.15}/tests/test_workflow.py +0 -0
  585. {execweave-0.8.13 → execweave-0.8.15}/tools/verify_metrics_tool.py +0 -0
@@ -78,6 +78,15 @@ jobs:
78
78
  && -n "$model_runtime_identity_changed" ]]; then
79
79
  args+=(--allow-test-change "tests/test_model_runtime.py=AUD-002 only: the historical llama.cpp request ID assertion now includes the sanitized endpoint scope; the test node ID and runtime privacy contract are unchanged")
80
80
  fi
81
+ threat_model_changed=""
82
+ if git diff --name-only "$BASELINE_REF...HEAD" \
83
+ | grep -qx 'tests/test_threat_model.py'; then
84
+ threat_model_changed=yes
85
+ fi
86
+ if [[ "$HEAD_REF" == "fix/0815-threat-model-pid-collision" \
87
+ && -n "$threat_model_changed" ]]; then
88
+ args+=(--allow-test-change "tests/test_threat_model.py=0.8.15 RC follow-up makes the existing outliving-child assertion hermetic against ambient host PID collisions; the historical test node ID and product expectation are unchanged")
89
+ fi
81
90
  if [[ "$HEAD_REF" == "fix/provider-isolation" ]]; then
82
91
  while IFS= read -r path; do
83
92
  case "$path" in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execweave
3
- Version: 0.8.13
3
+ Version: 0.8.15
4
4
  Summary: Turn AI-agent runtime behavior into a local interactive execution graph.
5
5
  Project-URL: Homepage, https://github.com/Irish-kw/ExecWeave
6
6
  Project-URL: Repository, https://github.com/Irish-kw/ExecWeave
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execweave"
7
- # ExecWeave v0.8.13 release metadata
8
- version = "0.8.13"
7
+ # ExecWeave v0.8.15 release metadata
8
+ version = "0.8.15"
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"
@@ -1,3 +1,3 @@
1
1
  """ExecWeave runtime graph package."""
2
2
 
3
- __version__ = "0.8.13"
3
+ __version__ = "0.8.15"
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import ctypes
3
4
  import os
4
5
  import subprocess
6
+ import sys
5
7
  import time
6
8
  from dataclasses import dataclass
7
9
  from pathlib import Path
@@ -22,6 +24,10 @@ from .schema import Entity, RuntimeEvent
22
24
  from .sink import JsonlSink
23
25
 
24
26
 
27
+ _PR_SET_CHILD_SUBREAPER = 36
28
+ _PR_GET_CHILD_SUBREAPER = 37
29
+
30
+
25
31
  @dataclass(frozen=True)
26
32
  class ProcessSnapshot:
27
33
  pid: int
@@ -123,11 +129,13 @@ class RuntimeCollector:
123
129
  self.collect_filesystem = collect_filesystem
124
130
  self.collect_network = collect_network
125
131
  self._seen_processes: dict[int, ProcessSnapshot] = {}
132
+ self._discovered_processes: dict[int, ProcessSnapshot] = {}
126
133
  self._seen_connections: set[tuple[str, str | None, str | None, str]] = set()
127
134
  self._network_sample_attempts = 0
128
135
  self._network_sample_successes = 0
129
136
  self._network_sample_errors = 0
130
137
  self._network_access_denied = 0
138
+ self._collector_children_baseline: dict[int, float] | None = None
131
139
 
132
140
  def _filesystem_excluded_roots(self) -> list[Path]:
133
141
  """Return ExecWeave-owned paths that must never become workload evidence.
@@ -195,10 +203,12 @@ class RuntimeCollector:
195
203
  )
196
204
 
197
205
  process: subprocess.Popen[bytes] | None = None
206
+ owned: list[psutil.Process] = []
198
207
  return_code = 1
199
208
  interrupted = False
200
209
  collector_error_type: str | None = None
201
210
  workload_terminated_due_to_collector_error = False
211
+ subreaper_previous: bool | None = None
202
212
  post_command_probe = prepare_post_command_specialized_probe(command)
203
213
  try:
204
214
  if watcher is not None:
@@ -209,6 +219,12 @@ class RuntimeCollector:
209
219
  command,
210
220
  server_relay=True,
211
221
  ) as launch_environment:
222
+ subreaper_previous = self._enable_linux_child_subreaper()
223
+ self._collector_children_baseline = (
224
+ self._direct_child_lifetimes()
225
+ if subreaper_previous is not None
226
+ else None
227
+ )
212
228
  process = subprocess.Popen(
213
229
  launch_command,
214
230
  cwd=str(self.watch_root),
@@ -230,8 +246,6 @@ class RuntimeCollector:
230
246
  except KeyboardInterrupt:
231
247
  interrupted = True
232
248
  return_code = 130
233
- if process is not None:
234
- self._terminate_process_tree(process)
235
249
 
236
250
  if not interrupted:
237
251
  run_post_command_specialized_probe(
@@ -241,16 +255,26 @@ class RuntimeCollector:
241
255
  return return_code
242
256
  except BaseException as exc:
243
257
  collector_error_type = type(exc).__name__
244
- if process is not None and self._owned_live_processes(process):
245
- self._terminate_process_tree(process)
246
- workload_terminated_due_to_collector_error = True
247
258
  raise
248
259
  finally:
260
+ # A workload exception is a nonzero child exit, not an exception in
261
+ # this collector. Finalize observed descendants on every exit path,
262
+ # including successful launchers that leave reparented children.
263
+ if process is not None:
264
+ owned = self._owned_live_processes(process)
265
+ workload_terminated_due_to_collector_error = (
266
+ collector_error_type is not None and bool(owned)
267
+ )
268
+ self._terminate_process_tree(process, owned=owned)
269
+ self._restore_linux_child_subreaper(subreaper_previous)
249
270
  if watcher is not None:
250
271
  watcher.stop()
251
- workload_alive_after_cleanup = bool(
252
- process is not None and self._owned_live_processes(process)
253
- )
272
+ # A failed termination must not become a false clean result merely
273
+ # because the selected child has since reparented out of the tree.
274
+ workload_alive_after_cleanup = any(
275
+ self._process_is_live(candidate) for candidate in owned
276
+ ) or bool(process is not None and self._owned_live_processes(process))
277
+ self._collector_children_baseline = None
254
278
  self.sink.emit(
255
279
  RuntimeEvent.create(
256
280
  session_id=self.session_id,
@@ -278,6 +302,57 @@ class RuntimeCollector:
278
302
  )
279
303
  )
280
304
 
305
+ @staticmethod
306
+ def _linux_child_subreaper_state() -> bool | None:
307
+ if not sys.platform.startswith("linux"):
308
+ return None
309
+ try:
310
+ libc = ctypes.CDLL(None, use_errno=True)
311
+ state = ctypes.c_int()
312
+ if libc.prctl(_PR_GET_CHILD_SUBREAPER, ctypes.byref(state), 0, 0, 0) != 0:
313
+ return None
314
+ return bool(state.value)
315
+ except (AttributeError, OSError):
316
+ return None
317
+
318
+ @staticmethod
319
+ def _set_linux_child_subreaper(enabled: bool) -> bool:
320
+ if not sys.platform.startswith("linux"):
321
+ return False
322
+ try:
323
+ libc = ctypes.CDLL(None, use_errno=True)
324
+ return libc.prctl(_PR_SET_CHILD_SUBREAPER, int(enabled), 0, 0, 0) == 0
325
+ except (AttributeError, OSError):
326
+ return False
327
+
328
+ @classmethod
329
+ def _enable_linux_child_subreaper(cls) -> bool | None:
330
+ previous = cls._linux_child_subreaper_state()
331
+ if previous is None:
332
+ return None
333
+ if not previous and not cls._set_linux_child_subreaper(True):
334
+ return None
335
+ return previous
336
+
337
+ @classmethod
338
+ def _restore_linux_child_subreaper(cls, previous: bool | None) -> None:
339
+ if previous is False:
340
+ cls._set_linux_child_subreaper(False)
341
+
342
+ @staticmethod
343
+ def _direct_child_lifetimes() -> dict[int, float]:
344
+ try:
345
+ children = psutil.Process(os.getpid()).children(recursive=False)
346
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
347
+ return {}
348
+ lifetimes: dict[int, float] = {}
349
+ for child in children:
350
+ try:
351
+ lifetimes[child.pid] = child.create_time()
352
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
353
+ continue
354
+ return lifetimes
355
+
281
356
  @staticmethod
282
357
  def _process_is_live(proc: psutil.Process) -> bool:
283
358
  try:
@@ -293,12 +368,13 @@ class RuntimeCollector:
293
368
  def _process_for_snapshot(self, snapshot: ProcessSnapshot) -> psutil.Process | None:
294
369
  try:
295
370
  process = psutil.Process(snapshot.pid)
371
+ # Cleanup needs identity, not readable exe/name/cmdline metadata.
372
+ # Never treat a reused PID as the process we originally observed.
373
+ if process.create_time() != snapshot.create_time:
374
+ return None
375
+ return process
296
376
  except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
297
377
  return None
298
- current = _safe_process_snapshot(process)
299
- if current is None or not self._same_process_lifetime(snapshot, current):
300
- return None
301
- return process
302
378
 
303
379
  def _owned_live_processes(
304
380
  self,
@@ -331,15 +407,39 @@ class RuntimeCollector:
331
407
  except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
332
408
  pass
333
409
 
334
- for snapshot in list(self._seen_processes.values()):
410
+ baseline = self._collector_children_baseline
411
+ if baseline is not None:
412
+ try:
413
+ collector_children = psutil.Process(os.getpid()).children(recursive=False)
414
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
415
+ collector_children = []
416
+ for candidate in collector_children:
417
+ try:
418
+ create_time = candidate.create_time()
419
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
420
+ continue
421
+ if baseline.get(candidate.pid) == create_time:
422
+ continue
423
+ add_with_descendants(candidate)
424
+
425
+ snapshots = {**self._seen_processes, **self._discovered_processes}
426
+ for snapshot in snapshots.values():
335
427
  tracked = self._process_for_snapshot(snapshot)
336
428
  if tracked is not None:
337
429
  add_with_descendants(tracked)
338
430
 
339
431
  return list(owned.values())
340
432
 
341
- def _terminate_process_tree(self, process: subprocess.Popen[bytes]) -> None:
342
- owned = self._owned_live_processes(process)
433
+ def _terminate_process_tree(
434
+ self,
435
+ process: subprocess.Popen[bytes],
436
+ *,
437
+ owned: list[psutil.Process] | None = None,
438
+ ) -> None:
439
+ # A caller's selection must survive reparenting between discovery and
440
+ # termination. Re-querying here can lose children we just proved owned.
441
+ if owned is None:
442
+ owned = self._owned_live_processes(process)
343
443
  # Terminate descendants/tracked orphans before the launcher/root when it is
344
444
  # still alive. Holding psutil handles also keeps cleanup robust if a parent
345
445
  # exits while the loop is running.
@@ -392,6 +492,11 @@ class RuntimeCollector:
392
492
  current[snapshot.pid] = snapshot
393
493
  process_objects[snapshot.pid] = proc
394
494
 
495
+ # Preserve the complete discovered tree before any event/network callback
496
+ # can raise. _seen_processes only contains successfully registered starts;
497
+ # a root callback can fail after its children have already reparented.
498
+ # Retain only the latest sample here, not an unbounded lifetime history.
499
+ self._discovered_processes = current
395
500
  for snapshot in current.values():
396
501
  previous = self._seen_processes.get(snapshot.pid)
397
502
  if previous is None or not self._same_process_lifetime(previous, snapshot):
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  LIVE_STYLE = r""":root{color-scheme:dark;--bg:#0a0f16;--panel:#101722;--panel2:#151f2c;--panel3:#0d141e;--text:#edf2f7;--muted:#8fa0b5;--border:#263446;--border-strong:#34485e;--edge:#6f8298;--causal:#65c79b;--noncausal:#e5aa61;--inferred:#b18cff;--selected:#75b8ff;--latest:#77d7ff;--danger:#ff7b86;--node-agent:#1e4b7a;--node-process:#1d4e5f;--node-file:#1f5438;--node-network:#643d1f;--node-tool:#4f2f75;--node-model:#662952;--node-other:#334255;--shadow:0 14px 40px rgba(0,0,0,.18)}
4
4
  :root[data-theme="light"]{color-scheme:light;--bg:#eef3f8;--panel:#ffffff;--panel2:#f6f9fc;--panel3:#f9fbfd;--text:#172033;--muted:#66758a;--border:#d5dee8;--border-strong:#b9c7d7;--edge:#738195;--causal:#16855a;--noncausal:#b56a16;--inferred:#7b4dd8;--selected:#2563eb;--latest:#0f8fc5;--danger:#c2414f;--node-agent:#2d6391;--node-process:#2b697b;--node-file:#2c6e49;--node-network:#865328;--node-tool:#673d8e;--node-model:#7f3465;--node-other:#495c72;--shadow:0 14px 35px rgba(30,41,59,.08)}
5
5
  *{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);font:14px/1.45 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
6
- button,input{font:inherit}button{color:inherit}button:focus-visible,input:focus-visible{outline:2px solid var(--selected);outline-offset:2px}
6
+ button,input,select{font:inherit}button{color:inherit}button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--selected);outline-offset:2px}
7
7
  #app{display:grid;grid-template-columns:minmax(0,1fr) 340px;grid-template-rows:64px minmax(0,1fr) 230px;width:100%;height:100%;gap:0}
8
8
  header{grid-column:1/3;display:flex;align-items:center;gap:12px;padding:0 16px;background:color-mix(in srgb,var(--panel) 95%,transparent);border-bottom:1px solid var(--border);box-shadow:0 1px 0 rgba(255,255,255,.02);z-index:10;min-width:0}
9
9
  .brand{display:flex;align-items:center;gap:10px;min-width:max-content}.brand-mark{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;background:linear-gradient(145deg,var(--panel2),var(--panel));border:1px solid var(--border-strong);font-weight:800;letter-spacing:-.06em}.brand-copy{display:flex;flex-direction:column;line-height:1.1}.brand-copy strong{font-size:14px}.brand-copy span{font-size:10px;color:var(--muted);letter-spacing:.08em;text-transform:uppercase;margin-top:3px}
@@ -14,6 +14,7 @@ header{grid-column:1/3;display:flex;align-items:center;gap:12px;padding:0 16px;b
14
14
  .icon-btn{height:34px;min-width:34px;padding:0 10px;border:1px solid var(--border);border-radius:8px;background:var(--panel2);cursor:pointer;transition:border-color .15s ease,background .15s ease}.icon-btn:hover{border-color:var(--border-strong);background:color-mix(in srgb,var(--panel2) 76%,var(--selected) 8%)}
15
15
  #graph-panel{grid-column:1;grid-row:2;min-width:0;min-height:0;display:grid;grid-template-rows:46px minmax(0,1fr);border-right:1px solid var(--border);background:var(--panel3)}
16
16
  .panel-bar{display:flex;align-items:center;gap:10px;padding:0 12px;border-bottom:1px solid var(--border);background:var(--panel);min-width:0}.panel-title{font-size:12px;font-weight:750;text-transform:uppercase;letter-spacing:.08em}.panel-subtitle{color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-spacer{flex:1}
17
+ .graph-file-filter{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:11px;white-space:nowrap}.graph-file-filter select{height:30px;padding:0 8px;border:1px solid var(--border);border-radius:8px;background:var(--panel2);color:var(--text);cursor:pointer}.graph-file-filter select:hover{border-color:var(--border-strong)}
17
18
  .segmented{display:flex;align-items:center;padding:2px;border:1px solid var(--border);border-radius:9px;background:var(--panel2);gap:2px}.segmented button{height:28px;padding:0 10px;border:0;border-radius:7px;background:transparent;color:var(--muted);cursor:pointer;font-size:12px}.segmented button:hover{color:var(--text)}.segmented button.active{background:var(--panel);color:var(--text);box-shadow:inset 0 0 0 1px var(--border-strong)}
18
19
  .graph-actions{display:flex;align-items:center;gap:6px}.graph-actions button{height:30px;min-width:30px;padding:0 9px;border:1px solid var(--border);border-radius:8px;background:var(--panel2);cursor:pointer;color:var(--muted)}.graph-actions button:hover{color:var(--text);border-color:var(--border-strong)}
19
20
  #wrap{position:relative;overflow:hidden;min-width:0;min-height:0;background-image:radial-gradient(circle at 1px 1px,color-mix(in srgb,var(--border) 52%,transparent) 1px,transparent 0);background-size:22px 22px}svg{width:100%;height:100%;display:block;cursor:grab;user-select:none;touch-action:none}svg.panning{cursor:grabbing}
@@ -99,6 +99,24 @@ function execweaveDashboardGraph(data){
99
99
  if(!outgoing.has(edge.source))outgoing.set(edge.source,[]);
100
100
  incoming.get(edge.target).push(edge);outgoing.get(edge.source).push(edge);
101
101
  }
102
+ const fileDisplayMode=String(globalThis.__execweaveFileDisplayMode||'changed');
103
+ const fileTypes=new Set(['file','directory']);
104
+ const fileMutationEvents=new Set(['filesystem.created','filesystem.modified','filesystem.moved','filesystem.deleted']);
105
+ const fileMutationRelation=relation=>/(^|_)(WROTE|WRITE|WRITES|CREATE|CREATED|UPDATE|UPDATED|EDIT|EDITED|DELETE|DELETED|MOVE|MOVED|RENAME|RENAMED|PATCH|REPLACE|REPLACED|SAVE|SAVED|MODIFY|MODIFIED)($|_)/.test(String(relation||'').toUpperCase());
106
+ const fileReadRelation=relation=>/(^|_)(READ|OPEN|SCAN|LIST|STAT|INSPECT|OBSERVE|OBSERVED)($|_)/.test(String(relation||'').toUpperCase());
107
+ const fileActivity=node=>{
108
+ if(!fileTypes.has(String(node?.type||'')))return'other';
109
+ const eventTypes=Array.isArray(node?.event_types)?node.event_types:[];
110
+ const related=[...(incoming.get(node.id)||[]),...(outgoing.get(node.id)||[])];
111
+ const relatedEventTypes=related.flatMap(edge=>Array.isArray(edge?.event_types)?edge.event_types:[]);
112
+ const allEventTypes=[...eventTypes,...relatedEventTypes].map(String);
113
+ if(allEventTypes.some(value=>fileMutationEvents.has(value)))return'changed';
114
+ if(related.some(edge=>fileMutationRelation(edge?.relation)))return'changed';
115
+ const observedEvents=allEventTypes.some(value=>value.startsWith('filesystem.'));
116
+ if(observedEvents||related.some(edge=>fileReadRelation(edge?.relation)))return'observed';
117
+ return'unknown';
118
+ };
119
+ const fileVisible=node=>{const activity=fileActivity(node);if(activity==='other')return true;if(fileDisplayMode==='hide')return false;if(fileDisplayMode==='all')return true;return activity!=='observed'};
102
120
  const provider=node=>String(node?.attributes?.provider||'unknown').toLowerCase();
103
121
  const toolName=node=>String(node?.attributes?.tool_name||node?.attributes?.native_name||node?.name||'tool');
104
122
  const toolKey=node=>`${provider(node)}\u0000${toolName(node).trim().toLowerCase()}`;
@@ -205,7 +223,10 @@ function execweaveDashboardGraph(data){
205
223
  };
206
224
  for(const group of groups.values())group.occurrences.sort(occurrenceOrder);
207
225
  for(const list of toolOccurrences.values())list.sort(occurrenceOrder);
208
- let visibleNodes=allNodes.filter(node=>node&&node.id&&!hiddenIds.has(node.id)).filter(node=>node.type!=='tool'||toolCanonicalId.get(node.id)===node.id).map(node=>{
226
+ const candidateNodes=allNodes.filter(node=>node&&node.id&&!hiddenIds.has(node.id)).filter(node=>node.type!=='tool'||toolCanonicalId.get(node.id)===node.id);
227
+ const hiddenFileObservationCount=candidateNodes.filter(node=>fileTypes.has(String(node?.type||''))&&!fileVisible(node)).length;
228
+ let visibleNodes=candidateNodes.filter(node=>fileVisible(node)).map(node=>{
229
+ if(fileTypes.has(String(node?.type||'')))return{...node,attributes:{...attrsOf(node),viewer_file_activity:fileActivity(node)}};
209
230
  if(node.type!=='tool')return node;
210
231
  const occurrences=toolOccurrences.get(node.id)||[],members=toolMembersByRepresentative.get(node.id)||[node];
211
232
  if(!occurrences.length&&members.length===1)return node;
@@ -214,7 +235,8 @@ function execweaveDashboardGraph(data){
214
235
  });
215
236
 
216
237
  const canonicalTypes=new Set(['process','file']);
217
- const foldableTypes=new Set(['process','file','network_endpoint']);
238
+ const foldableTypes=new Set(['process','file','directory','network_endpoint']);
239
+ const foldType=node=>{const type=String(node?.type||'');return type==='file'||type==='directory'?'filesystem':type};
218
240
  const declaredBudget=Number(globalThis.__execweaveFoldBudget);
219
241
  const FOLD_BUDGET=Number.isFinite(declaredBudget)&&declaredBudget>=1?Math.floor(declaredBudget):12;
220
242
  const canonicalGroups=new Map();
@@ -246,19 +268,21 @@ function execweaveDashboardGraph(data){
246
268
 
247
269
  let foldedNodeCount=0;
248
270
  const byType=new Map();
249
- for(const node of nodes){const type=String(node?.type||'');if(!foldableTypes.has(type))continue;if(!byType.has(type))byType.set(type,[]);byType.get(type).push(node)}
271
+ for(const node of nodes){const rawType=String(node?.type||'');if(!foldableTypes.has(rawType))continue;const type=foldType(node);if(!byType.has(type))byType.set(type,[]);byType.get(type).push(node)}
250
272
  const folds=[];
251
273
  for(const[type,members]of byType){
252
274
  if(members.length<=FOLD_BUDGET)continue;
253
275
  const recency=node=>String(node?.last_seen||node?.first_seen||'');
254
276
  const ordered=[...members].sort((a,b)=>recency(b).localeCompare(recency(a)));
255
277
  const older=ordered.slice(FOLD_BUDGET);if(!older.length)continue;
256
- const keep=new Set(ordered.slice(0,FOLD_BUDGET).map(node=>node.id));
278
+ const removeIds=new Set(older.map(node=>node.id));
257
279
  const foldId=`viewer:folded:${type}`;
258
280
  for(const node of older)canonicalId.set(node.id,foldId);
259
281
  foldedNodeCount+=older.length;
260
- folds.push({id:foldId,type,name:`${older.length} earlier ${type.replace(/_/g,' ')}${older.length===1?'':'s'}`,first_seen:older.reduce((value,node)=>earlier(value,node.first_seen),null),last_seen:older.reduce((value,node)=>later(value,node.last_seen),null),attributes:{viewer_folded:true,viewer_folded_type:type,viewer_folded_count:older.length,viewer_folded_members:older.map(node=>({id:node.id,name:node.name||node.id,first_seen:node.first_seen||null,last_seen:node.last_seen||null}))}});
261
- for(let index=nodes.length-1;index>=0;index--)if(!keep.has(nodes[index].id)&&String(nodes[index].type||'')===type)nodes.splice(index,1);
282
+ const foldedType=type==='filesystem'?'file_cluster':type,foldedName=type==='filesystem'?`${older.length} earlier files / directories`:`${older.length} earlier ${type.replace(/_/g,' ')}${older.length===1?'':'s'}`;
283
+ const activities=[...new Set(older.map(node=>node?.attributes?.viewer_file_activity).filter(Boolean))];
284
+ folds.push({id:foldId,type:foldedType,name:foldedName,first_seen:older.reduce((value,node)=>earlier(value,node.first_seen),null),last_seen:older.reduce((value,node)=>later(value,node.last_seen),null),attributes:{viewer_folded:true,viewer_folded_type:type,viewer_folded_count:older.length,viewer_file_activity:type==='filesystem'?(activities.length===1?activities[0]:'mixed'):undefined,viewer_folded_members:older.map(node=>({id:node.id,type:node.type,name:node.name||node.id,activity:node?.attributes?.viewer_file_activity||null,first_seen:node.first_seen||null,last_seen:node.last_seen||null}))}});
285
+ for(let index=nodes.length-1;index>=0;index--)if(removeIds.has(nodes[index].id))nodes.splice(index,1);
262
286
  }
263
287
  nodes.push(...folds);
264
288
 
@@ -280,7 +304,7 @@ function execweaveDashboardGraph(data){
280
304
  const incident=new Set();for(const edge of edges){incident.add(edge.source);incident.add(edge.target)}
281
305
  visibleNodes=nodes.filter(node=>node.type!=='tool'||incident.has(node.id));
282
306
  nodeIds=new Set(visibleNodes.map(node=>node.id));edges=edges.filter(edge=>nodeIds.has(edge.source)&&nodeIds.has(edge.target));
283
- return{...data,nodes:visibleNodes,edges,node_count:visibleNodes.length,edge_count:edges.length,dashboard_projection:{hidden_detail_node_count:hiddenDetailIds.size,hidden_internal_staging_node_count:internalStagingIds.size,canonicalized_process_occurrence_count:canonicalizedProcessOccurrenceCount,canonicalized_tool_entity_count:[...toolMembersByRepresentative.values()].reduce((sum,members)=>sum+Math.max(0,members.length-1),0),folded_node_count:foldedNodeCount,collapsed_tool_call_count:invocationMap.size,raw_tool_call_detail_count:allNodes.filter(node=>['tool_call','tool_call_observation'].includes(String(node?.type||''))).length}};
307
+ return{...data,nodes:visibleNodes,edges,node_count:visibleNodes.length,edge_count:edges.length,dashboard_projection:{hidden_detail_node_count:hiddenDetailIds.size,hidden_internal_staging_node_count:internalStagingIds.size,canonicalized_process_occurrence_count:canonicalizedProcessOccurrenceCount,canonicalized_tool_entity_count:[...toolMembersByRepresentative.values()].reduce((sum,members)=>sum+Math.max(0,members.length-1),0),folded_node_count:foldedNodeCount,collapsed_tool_call_count:invocationMap.size,raw_tool_call_detail_count:allNodes.filter(node=>['tool_call','tool_call_observation'].includes(String(node?.type||''))).length,file_display_mode:fileDisplayMode,hidden_file_observation_node_count:hiddenFileObservationCount}};
284
308
  }
285
309
  """.strip()
286
310
 
@@ -310,7 +334,7 @@ _LIVE_SET_SNAPSHOT_CLEAN = "function setSnapshot(data){const signature=`${data.n
310
334
  _LIVE_APPLY_DELTA_CLEAN = "function applyDelta(update){if(update.live_payload_compact){updateStats(update);enterProtectiveMode(update);return}const rawNodes=new Map((graph.nodes||[]).map(n=>[n.id,n])),rawEdges=new Map((graph.edges||[]).map(e=>[edgeId(e),e]));for(const node of [...(update.nodes_added||[]),...(update.nodes_updated||[])])if(node?.id)rawNodes.set(node.id,node);for(const edge of [...(update.edges_added||[]),...(update.edges_updated||[])])rawEdges.set(edgeId(edge),edge);graph={...graph,event_count:update.event_count,node_count:update.node_count,edge_count:update.edge_count,nodes:[...rawNodes.values()],edges:[...rawEdges.values()]};const display=execweaveDashboardGraph(graph);updateStats({...update,node_count:display.node_count,edge_count:display.edge_count});if(!withinRenderBudget(display)){enterProtectiveMode(display);return}if(protectedMode)leaveProtectiveMode();nodeById=new Map((display.nodes||[]).map(n=>[n.id,n]));edgeById=new Map((display.edges||[]).map(e=>[edgeId(e),e]));rebuildAdjacency();renderSnapshot();seedActivities();const sortedEdges=[...edgeById.values()].sort((a,b)=>String(a.last_seen||'').localeCompare(String(b.last_seen||''))),sortedNodes=[...nodeById.values()].sort((a,b)=>String(a.last_seen||'').localeCompare(String(b.last_seen||''))),lastEdge=sortedEdges.length?sortedEdges[sortedEdges.length-1]:null,lastNode=sortedNodes.length?sortedNodes[sortedNodes.length-1]:null;markLatest(lastEdge?.target||lastNode?.id||null,lastEdge?edgeId(lastEdge):null);scheduleCamera(false);if(!hasFitted&&positions.size){fit(false);hasFitted=true}}"
311
335
 
312
336
  _LIVE_CORE_EXPORT = "window.__execweaveCore={getActivities:()=>activities.slice(),getGraph:()=>graph,getPositions:()=>new Map(positions),selectEdge,selectNode,focusNode,markLatest,setCameraMode};"
313
- _LIVE_CORE_EXPORT_CLEAN = "window.__execweaveCore={getActivities:()=>activities.slice(),getGraph:()=>graph,getDisplayGraph:()=>({...graph,nodes:[...nodeById.values()],edges:[...edgeById.values()],node_count:nodeById.size,edge_count:edgeById.size}),getPositions:()=>new Map(positions),selectEdge,selectNode,focusNode,markLatest,setCameraMode};"
337
+ _LIVE_CORE_EXPORT_CLEAN = "const execweaveFileGraphFilter=document.getElementById('file-graph-filter');globalThis.__execweaveFileDisplayMode=execweaveFileGraphFilter?.value||'changed';if(execweaveFileGraphFilter)execweaveFileGraphFilter.addEventListener('change',()=>{globalThis.__execweaveFileDisplayMode=execweaveFileGraphFilter.value||'changed';setSnapshot(graph)});window.__execweaveCore={getActivities:()=>activities.slice(),getGraph:()=>graph,getDisplayGraph:()=>({...graph,nodes:[...nodeById.values()],edges:[...edgeById.values()],node_count:nodeById.size,edge_count:edgeById.size}),getPositions:()=>new Map(positions),selectEdge,selectNode,focusNode,markLatest,setCameraMode};"
314
338
 
315
339
  _LIVE_NOOP_STATS = "else if(data.kind==='noop'){liveSequence=Number(data.sequence)||liveSequence;updateStats(data);}"
316
340
  _LIVE_NOOP_STATS_CLEAN = "else if(data.kind==='noop'){liveSequence=Number(data.sequence)||liveSequence;updateStats(protectedMode?data:{...data,node_count:nodeById.size,edge_count:edgeById.size});}"
@@ -338,6 +362,11 @@ def inject_standalone_dashboard_clean(html: str) -> str:
338
362
 
339
363
  def inject_live_dashboard_clean(html: str) -> str:
340
364
  """Simplify only the browser canvas; the live JSON protocol remains unchanged."""
365
+ control_seam = '<div class="panel-spacer"></div>\n <div class="segmented" aria-label="Camera mode">'
366
+ control = '<div class="panel-spacer"></div>\n <label class="graph-file-filter" title="Choose which file and directory nodes appear in the graph"><span>Files</span><select id="file-graph-filter"><option value="changed" selected>Changed only</option><option value="all">All observed</option><option value="hide">Hidden</option></select></label>\n <div class="segmented" aria-label="Camera mode">'
367
+ if html.count(control_seam) != 1:
368
+ raise RuntimeError("file graph filter control seam changed")
369
+ html = html.replace(control_seam, control, 1)
341
370
  marker = "function renderSnapshot(){"
342
371
  if "function execweaveDashboardGraph(data){" not in html and marker in html:
343
372
  html = html.replace(marker, _DASHBOARD_JS + "\n" + marker, 1)
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+
9
+ import psutil
10
+ import pytest
11
+
12
+ from execweave.collector import RuntimeCollector
13
+ from execweave.sink import JsonlSink
14
+
15
+
16
+ _SLEEP = "import time; time.sleep(60)"
17
+
18
+
19
+ def _live_pid(pid: int) -> bool:
20
+ try:
21
+ process = psutil.Process(pid)
22
+ return process.is_running() and process.status() != psutil.STATUS_ZOMBIE
23
+ except (psutil.NoSuchProcess, psutil.ZombieProcess):
24
+ return False
25
+
26
+
27
+ def _wait_for(predicate, *, timeout: float = 5.0) -> None:
28
+ deadline = time.monotonic() + timeout
29
+ while time.monotonic() < deadline:
30
+ if predicate():
31
+ return
32
+ time.sleep(0.01)
33
+ raise AssertionError("fast-reparent fixture did not reach its handshake")
34
+
35
+
36
+ def _collector(tmp_path: Path) -> RuntimeCollector:
37
+ return RuntimeCollector(
38
+ session_id="d003-fast-reparent",
39
+ sink=JsonlSink(tmp_path / "events.jsonl"),
40
+ watch_root=tmp_path,
41
+ poll_interval=0.02,
42
+ collect_filesystem=False,
43
+ collect_network=False,
44
+ )
45
+
46
+
47
+ def _detaching_command(pid_file: Path) -> list[str]:
48
+ code = (
49
+ "import subprocess,sys; "
50
+ "child=subprocess.Popen([sys.executable,'-c','import time; time.sleep(60)'], "
51
+ "start_new_session=True); "
52
+ "open(sys.argv[1],'w',encoding='utf-8').write(str(child.pid))"
53
+ )
54
+ return [sys.executable, "-c", code, str(pid_file)]
55
+
56
+
57
+ def _miss_short_lived_tree(
58
+ monkeypatch: pytest.MonkeyPatch,
59
+ collector: RuntimeCollector,
60
+ pid_file: Path,
61
+ ) -> None:
62
+ def miss_tree(root: psutil.Process) -> None:
63
+ _wait_for(pid_file.exists)
64
+ # Deliberately simulate the polling blind spot from issue #9: the launcher
65
+ # exits after spawning a detached child before a descendant sample lands.
66
+ time.sleep(0.05)
67
+
68
+ monkeypatch.setattr(collector, "_sample_process_tree", miss_tree)
69
+
70
+
71
+ def _non_linux_guard() -> bool:
72
+ if sys.platform.startswith("linux"):
73
+ return False
74
+ # Fast detached-child recovery in this regression is a Linux subreaper path.
75
+ # On other platforms the implementation must remain disabled rather than
76
+ # silently attempting Linux prctl semantics.
77
+ assert RuntimeCollector._linux_child_subreaper_state() is None
78
+ assert RuntimeCollector._set_linux_child_subreaper(True) is False
79
+ return True
80
+
81
+
82
+ def test_run_cleans_detached_child_even_when_polling_never_observed_it(
83
+ tmp_path: Path,
84
+ monkeypatch: pytest.MonkeyPatch,
85
+ ) -> None:
86
+ if _non_linux_guard():
87
+ return
88
+
89
+ collector = _collector(tmp_path)
90
+ pid_file = tmp_path / "detached.pid"
91
+ sentinel = subprocess.Popen([sys.executable, "-c", _SLEEP])
92
+ try:
93
+ _miss_short_lived_tree(monkeypatch, collector, pid_file)
94
+ assert collector.run(_detaching_command(pid_file)) == 0
95
+ child_pid = int(pid_file.read_text(encoding="utf-8"))
96
+ _wait_for(lambda: not _live_pid(child_pid))
97
+ assert _live_pid(sentinel.pid), "cleanup killed a pre-existing unrelated child"
98
+
99
+ finished = json.loads((tmp_path / "events.jsonl").read_text().splitlines()[-1])
100
+ assert finished["event_type"] == "session.finished"
101
+ assert finished["attributes"]["workload_alive_after_cleanup"] is False
102
+ finally:
103
+ if _live_pid(sentinel.pid):
104
+ sentinel.kill()
105
+ sentinel.wait(timeout=5)
106
+
107
+
108
+ def test_fast_detached_survivor_is_reported_if_cleanup_does_not_terminate_it(
109
+ tmp_path: Path,
110
+ monkeypatch: pytest.MonkeyPatch,
111
+ ) -> None:
112
+ if _non_linux_guard():
113
+ return
114
+
115
+ collector = _collector(tmp_path)
116
+ pid_file = tmp_path / "detached.pid"
117
+ _miss_short_lived_tree(monkeypatch, collector, pid_file)
118
+ monkeypatch.setattr(collector, "_terminate_process_tree", lambda process, *, owned=None: None)
119
+
120
+ child_pid: int | None = None
121
+ try:
122
+ assert collector.run(_detaching_command(pid_file)) == 0
123
+ child_pid = int(pid_file.read_text(encoding="utf-8"))
124
+ assert _live_pid(child_pid)
125
+ finished = json.loads((tmp_path / "events.jsonl").read_text().splitlines()[-1])
126
+ assert finished["attributes"]["workload_alive_after_cleanup"] is True
127
+ finally:
128
+ if child_pid is not None and _live_pid(child_pid):
129
+ process = psutil.Process(child_pid)
130
+ process.kill()
131
+ try:
132
+ process.wait(timeout=5)
133
+ except psutil.TimeoutExpired:
134
+ pass
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from execweave.dashboard_shell import DASHBOARD_HTML
4
+ from execweave.viewer_dashboard_clean import _DASHBOARD_JS
5
+
6
+
7
+ def test_dashboard_exposes_file_activity_filter_with_changed_default() -> None:
8
+ assert 'id="file-graph-filter"' in DASHBOARD_HTML
9
+ assert '<option value="changed" selected>Changed only</option>' in DASHBOARD_HTML
10
+ assert '<option value="all">All observed</option>' in DASHBOARD_HTML
11
+ assert '<option value="hide">Hidden</option>' in DASHBOARD_HTML
12
+ assert "__execweaveFileDisplayMode" in DASHBOARD_HTML
13
+
14
+
15
+ def test_dashboard_file_projection_distinguishes_changes_from_scans() -> None:
16
+ assert "fileMutationEvents=new Set(['filesystem.created','filesystem.modified','filesystem.moved','filesystem.deleted'])" in _DASHBOARD_JS
17
+ assert "fileDisplayMode==='hide'" in _DASHBOARD_JS
18
+ assert "fileDisplayMode==='all'" in _DASHBOARD_JS
19
+ assert "relatedEventTypes=related.flatMap" in _DASHBOARD_JS
20
+ assert "allEventTypes=[...eventTypes,...relatedEventTypes]" in _DASHBOARD_JS
21
+ assert "return activity!=='observed'" in _DASHBOARD_JS
22
+ assert "return'unknown'" in _DASHBOARD_JS
23
+ assert "hidden_file_observation_node_count" in _DASHBOARD_JS
24
+
25
+
26
+ def test_file_and_directory_nodes_share_one_fold_group() -> None:
27
+ assert "type==='file'||type==='directory'?'filesystem':type" in _DASHBOARD_JS
28
+ assert "viewer:folded:${type}" in _DASHBOARD_JS
29
+ assert "earlier files / directories" in _DASHBOARD_JS
30
+ assert "foldedType=type==='filesystem'?'file_cluster':type" in _DASHBOARD_JS