execweave 0.8.28__tar.gz → 0.8.31__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 (187) hide show
  1. {execweave-0.8.28 → execweave-0.8.31}/PKG-INFO +10 -2
  2. {execweave-0.8.28 → execweave-0.8.31}/README.md +9 -1
  3. {execweave-0.8.28 → execweave-0.8.31}/pyproject.toml +2 -2
  4. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/__init__.py +1 -1
  5. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/framework_adapters/autogen.py +48 -0
  6. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/framework_adapters/base.py +23 -0
  7. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/framework_adapters/camel.py +4 -1
  8. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/framework_adapters/metagpt.py +20 -2
  9. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_core.py +16 -6
  10. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/provider_record.py +1 -0
  11. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel.py +26 -3
  12. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_semantic_projection.py +150 -0
  13. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/workflow.py +60 -3
  14. {execweave-0.8.28 → execweave-0.8.31}/.gitignore +0 -0
  15. {execweave-0.8.28 → execweave-0.8.31}/LICENSE +0 -0
  16. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/__main__.py +0 -0
  17. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/_conversation_records_core.py +0 -0
  18. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/_dashboard_shell_base.py +0 -0
  19. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/_http_proxy_base.py +0 -0
  20. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/_http_proxy_bounded.py +0 -0
  21. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/_http_proxy_stage.py +0 -0
  22. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/agent_bootstrap.py +0 -0
  23. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/agent_topology.py +0 -0
  24. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/agent_trace.py +0 -0
  25. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/agy_preview_sanitize.py +0 -0
  26. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/analysis.py +0 -0
  27. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/anthropic.py +0 -0
  28. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/anthropic_cli.py +0 -0
  29. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/anthropic_full_fidelity.py +0 -0
  30. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_adapter.py +0 -0
  31. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_adapter_base.py +0 -0
  32. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_full_fidelity.py +0 -0
  33. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_full_fidelity_base.py +0 -0
  34. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
  35. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_hook_cli.py +0 -0
  36. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_record.py +0 -0
  37. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_subagent_linkage.py +0 -0
  38. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/antigravity_trace_capability.py +0 -0
  39. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/auto_specialized.py +0 -0
  40. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/backends.py +0 -0
  41. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/benchmark.py +0 -0
  42. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_adapter.py +0 -0
  43. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_child_transcript.py +0 -0
  44. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_delegation.py +0 -0
  45. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_full_fidelity.py +0 -0
  46. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_hook_cli.py +0 -0
  47. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_hook_contract.py +0 -0
  48. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_model_observer.py +0 -0
  49. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/claude_record.py +0 -0
  50. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cli.py +0 -0
  51. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_adapter.py +0 -0
  52. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_conversation.py +0 -0
  53. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_full_fidelity.py +0 -0
  54. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_hook_cli.py +0 -0
  55. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_hook_entry.py +0 -0
  56. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_hook_lifecycle.py +0 -0
  57. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_message_diagnostics.py +0 -0
  58. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_message_transport_diagnostics.py +0 -0
  59. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_record.py +0 -0
  60. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_rollout_structures.py +0 -0
  61. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_rollout_trace.py +0 -0
  62. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/codex_rollout_trace_base.py +0 -0
  63. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/collector.py +0 -0
  64. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/command.py +0 -0
  65. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/content_evidence.py +0 -0
  66. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/content_store.py +0 -0
  67. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_archive.py +0 -0
  68. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_message_identity.py +0 -0
  69. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview.py +0 -0
  70. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_antigravity.py +0 -0
  71. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_claude.py +0 -0
  72. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_codex.py +0 -0
  73. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_common.py +0 -0
  74. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_generic.py +0 -0
  75. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_lines.py +0 -0
  76. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_preview_transcript.py +0 -0
  77. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_records.py +0 -0
  78. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_records_antigravity.py +0 -0
  79. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_records_codex.py +0 -0
  80. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_records_common.py +0 -0
  81. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/conversation_records_ollama.py +0 -0
  82. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/correlation.py +0 -0
  83. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_adapter.py +0 -0
  84. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_delegation.py +0 -0
  85. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_delegation_base.py +0 -0
  86. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_full_fidelity.py +0 -0
  87. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_hook_cli.py +0 -0
  88. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_hook_contract.py +0 -0
  89. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_lifecycle.py +0 -0
  90. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/cursor_record.py +0 -0
  91. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/dashboard_shell.py +0 -0
  92. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/entry.py +0 -0
  93. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/evidence_availability.py +0 -0
  94. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/evidence_grade.py +0 -0
  95. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/fidelity.py +0 -0
  96. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/filesystem.py +0 -0
  97. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/focus.py +0 -0
  98. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/framework_adapters/__init__.py +0 -0
  99. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/framework_adapters/registry.py +0 -0
  100. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/graph.py +0 -0
  101. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/graph_ops.py +0 -0
  102. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/http_proxy.py +0 -0
  103. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/http_proxy_cli.py +0 -0
  104. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/inference_gateway.py +0 -0
  105. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/inference_gateway_cli.py +0 -0
  106. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/inference_gateway_full_fidelity.py +0 -0
  107. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/inference_identity.py +0 -0
  108. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/inference_identity_cli.py +0 -0
  109. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/integrity.py +0 -0
  110. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/integrity_cli.py +0 -0
  111. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/litellm_callback.py +0 -0
  112. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/litellm_callback_cli.py +0 -0
  113. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live.py +0 -0
  114. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view.py +0 -0
  115. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_extra_style.py +0 -0
  116. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_markup.py +0 -0
  117. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_process_layout.py +0 -0
  118. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_readability.py +0 -0
  119. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_script_a.py +0 -0
  120. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_script_b.py +0 -0
  121. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_script_c.py +0 -0
  122. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_script_d.py +0 -0
  123. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/live_view_style.py +0 -0
  124. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/model_runtime.py +0 -0
  125. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/model_runtime_cli.py +0 -0
  126. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/model_runtime_full_fidelity.py +0 -0
  127. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/openai_compatible.py +0 -0
  128. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/openai_compatible_cli.py +0 -0
  129. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/openai_compatible_full_fidelity.py +0 -0
  130. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_adapter.py +0 -0
  131. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_event_contract.py +0 -0
  132. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_full_fidelity.py +0 -0
  133. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_hook_cli.py +0 -0
  134. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_plugin_cli.py +0 -0
  135. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_record.py +0 -0
  136. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/opencode_task_linkage.py +0 -0
  137. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/overhead_benchmark.py +0 -0
  138. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/provider_capability.py +0 -0
  139. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/provider_lifecycle.py +0 -0
  140. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/rule_pack.py +0 -0
  141. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/rule_pack_cli.py +0 -0
  142. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/runtime_endpoint_availability.py +0 -0
  143. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/scalability_benchmark.py +0 -0
  144. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/schema.py +0 -0
  145. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/scope.py +0 -0
  146. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/semantic.py +0 -0
  147. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/sink.py +0 -0
  148. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/strace_backend.py +0 -0
  149. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/stream_assembly.py +0 -0
  150. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/theme.py +0 -0
  151. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/top.py +0 -0
  152. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/top_cli.py +0 -0
  153. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/validate.py +0 -0
  154. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/vendor/dagre.min.js +0 -0
  155. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/view_cli.py +0 -0
  156. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer.py +0 -0
  157. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
  158. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_claude.py +0 -0
  159. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_codex.py +0 -0
  160. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_cursor.py +0 -0
  161. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_default.py +0 -0
  162. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_ollama.py +0 -0
  163. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_opencode.py +0 -0
  164. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_agent_panel_registry.py +0 -0
  165. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_content_inspector.py +0 -0
  166. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_dashboard_clean.py +0 -0
  167. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_dashboard_focus.py +0 -0
  168. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_dashboard_hardening.py +0 -0
  169. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
  170. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_dashboard_pr70.py +0 -0
  171. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_edge_decross.py +0 -0
  172. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_execution_flow.py +0 -0
  173. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_execution_flow_provider_normalization.py +0 -0
  174. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_execution_flow_safety.py +0 -0
  175. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_external_endpoints.py +0 -0
  176. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_flow_canvas.py +0 -0
  177. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_flow_layout.py +0 -0
  178. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_gif.py +0 -0
  179. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_layout_geometry.py +0 -0
  180. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_layout_v2.py +0 -0
  181. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_layout_v2_detached.py +0 -0
  182. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_limits.py +0 -0
  183. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_live_layout.py +0 -0
  184. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_projection.py +0 -0
  185. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_projection_base.py +0 -0
  186. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_projection_bridges.py +0 -0
  187. {execweave-0.8.28 → execweave-0.8.31}/src/execweave/viewer_tool_mirror_identity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execweave
3
- Version: 0.8.28
3
+ Version: 0.8.31
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
@@ -388,5 +388,13 @@ Paper context: Li et al., *CAMEL: Communicative Agents for "Mind" Exploration of
388
388
 
389
389
  ```python
390
390
  from execweave.framework_adapters import AdapterContext, CAMELAdapter
391
- adapter = CAMELAdapter(AdapterContext.from_environment("camel"))
391
+ context = AdapterContext.from_environment("camel", capture_mode="prompt_and_response")
392
+ adapter = CAMELAdapter(context)
393
+ task = adapter.task_created("task-1", content="Inspect the endpoint and report completion.")
392
394
  ```
395
+
396
+ When the application is launched by `execweave record` or `execweave live`,
397
+ the adapter automatically joins that run and the finished Dashboard is built
398
+ from the merged runtime plus framework evidence. Task prompts and routed
399
+ messages remain local content references and are visible from their task and
400
+ agent nodes.
@@ -328,5 +328,13 @@ Paper context: Li et al., *CAMEL: Communicative Agents for "Mind" Exploration of
328
328
 
329
329
  ```python
330
330
  from execweave.framework_adapters import AdapterContext, CAMELAdapter
331
- adapter = CAMELAdapter(AdapterContext.from_environment("camel"))
331
+ context = AdapterContext.from_environment("camel", capture_mode="prompt_and_response")
332
+ adapter = CAMELAdapter(context)
333
+ task = adapter.task_created("task-1", content="Inspect the endpoint and report completion.")
332
334
  ```
335
+
336
+ When the application is launched by `execweave record` or `execweave live`,
337
+ the adapter automatically joins that run and the finished Dashboard is built
338
+ from the merged runtime plus framework evidence. Task prompts and routed
339
+ messages remain local content references and are visible from their task and
340
+ agent nodes.
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execweave"
7
- # ExecWeave v0.8.28 release metadata
8
- version = "0.8.28"
7
+ # ExecWeave v0.8.31 release metadata
8
+ version = "0.8.31"
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.28"
3
+ __version__ = "0.8.31"
@@ -15,6 +15,7 @@ from .base import (
15
15
  FrameworkCompatibility,
16
16
  MessageRecord,
17
17
  ModelCallRecord,
18
+ TaskRecord,
18
19
  ToolCallRecord,
19
20
  )
20
21
 
@@ -26,6 +27,13 @@ class AutoGenAdapter(FrameworkAdapter):
26
27
  "AGENT_CREATED",
27
28
  "AGENT_STARTED",
28
29
  "AGENT_STOPPED",
30
+ "TASK_CREATED",
31
+ "TASK_ASSIGNED",
32
+ "TASK_STARTED",
33
+ "TASK_UPDATED",
34
+ "TASK_COMPLETED",
35
+ "TASK_FAILED",
36
+ "TASK_CONTENT_RECORDED",
29
37
  "MESSAGE_SENT",
30
38
  "MESSAGE_RECEIVED",
31
39
  "MODEL_REQUEST",
@@ -86,6 +94,46 @@ class AutoGenAdapter(FrameworkAdapter):
86
94
  self.context.record_agent(AgentRecord(agent, role=role, process=process, attributes=attributes))
87
95
  return agent
88
96
 
97
+ def observe_task(
98
+ self,
99
+ task_id: str | int,
100
+ *,
101
+ owner: EntityRef | None = None,
102
+ name: str | None = None,
103
+ content: str | None = None,
104
+ content_kind: str = "task_prompt",
105
+ status: str = "created",
106
+ **attributes: Any,
107
+ ) -> EntityRef:
108
+ """Record an authoritative AutoGen task and its visible task prompt."""
109
+ entity_attributes = dict(attributes)
110
+ if content is not None and self.context.capture_policy.mode in {
111
+ "prompt_only",
112
+ "prompt_and_response",
113
+ }:
114
+ entity_attributes.setdefault("task_prompt", content)
115
+ task = self.task(task_id, name=name, provider="autogen", **entity_attributes)
116
+ event = {
117
+ "created": "TASK_CREATED",
118
+ "started": "TASK_STARTED",
119
+ "updated": "TASK_UPDATED",
120
+ "completed": "TASK_COMPLETED",
121
+ "failed": "TASK_FAILED",
122
+ }.get(status)
123
+ if event is None:
124
+ raise ValueError(f"unsupported AutoGen task status: {status}")
125
+ self.context.record_task(
126
+ TaskRecord(
127
+ task,
128
+ owner=owner,
129
+ content=content,
130
+ content_kind=content_kind,
131
+ attributes=attributes,
132
+ ),
133
+ event_type=event,
134
+ )
135
+ return task
136
+
89
137
  def observe_message(
90
138
  self,
91
139
  message_id: str | int,
@@ -11,6 +11,7 @@ import hashlib
11
11
  import json
12
12
  import os
13
13
  import re
14
+ import sys
14
15
  import threading
15
16
  import time
16
17
  from dataclasses import asdict, dataclass, field
@@ -19,6 +20,8 @@ from pathlib import Path
19
20
  from typing import Any, Literal, Mapping
20
21
  from uuid import uuid4
21
22
 
23
+ import psutil
24
+
22
25
  from ..content_store import ContentReference, FullFidelityContentStore
23
26
  from ..schema import SCHEMA_VERSION
24
27
 
@@ -137,6 +140,20 @@ class ProcessRef:
137
140
  return EntityRef("process_reference", f"process-reference:{identity}", self.executable or f"pid {self.pid}", attributes)
138
141
 
139
142
 
143
+ def _current_process_ref() -> ProcessRef:
144
+ """Return the authoritative identity of the adapter's current process."""
145
+ process = psutil.Process(os.getpid())
146
+ try:
147
+ create_time = process.create_time()
148
+ except (psutil.AccessDenied, psutil.NoSuchProcess, psutil.ZombieProcess):
149
+ create_time = None
150
+ try:
151
+ executable = process.exe()
152
+ except (psutil.AccessDenied, psutil.NoSuchProcess, psutil.ZombieProcess):
153
+ executable = sys.executable
154
+ return ProcessRef(os.getpid(), create_time, executable or sys.executable)
155
+
156
+
140
157
  @dataclass(frozen=True)
141
158
  class AdapterCapabilities:
142
159
  events: tuple[str, ...] = ()
@@ -362,6 +379,12 @@ class AdapterContext:
362
379
 
363
380
  @classmethod
364
381
  def from_environment(cls, framework: str, *, capture_mode: CaptureMode = "metadata_only", content_root: str | Path | None = None, run_id: str | None = None, session_id: str | None = None, conversation_id: str | None = None, process: ProcessRef | None = None) -> "AdapterContext":
382
+ if (
383
+ process is None
384
+ and os.environ.get("EXECWEAVE_SEMANTIC_SIDECAR")
385
+ and os.environ.get("EXECWEAVE_SESSION_ID")
386
+ ):
387
+ process = _current_process_ref()
365
388
  return cls(framework=framework, run_id=run_id, session_id=session_id, conversation_id=conversation_id, content_root=content_root, capture_policy=ContentCapturePolicy(capture_mode), process=process)
366
389
 
367
390
  def entity(self, entity_type: str, native_id: str | int | None, *, name: str | None = None, attributes: Mapping[str, Any] | None = None) -> EntityRef:
@@ -51,7 +51,10 @@ class CAMELAdapter(FrameworkAdapter):
51
51
  task = existing
52
52
  else:
53
53
  entity_attributes = dict(attributes)
54
- if content is not None:
54
+ if content is not None and self.context.capture_policy.mode in {
55
+ "prompt_only",
56
+ "prompt_and_response",
57
+ }:
55
58
  entity_attributes.setdefault("task_prompt", content)
56
59
  task = self.task(native_id, name=name, provider="camel", **entity_attributes)
57
60
  self._tasks[str(native_id)] = task
@@ -30,6 +30,7 @@ class MetaGPTAdapter(FrameworkAdapter):
30
30
  "TASK_COMPLETED",
31
31
  "TASK_FAILED",
32
32
  "TASK_UPDATED",
33
+ "TASK_CONTENT_RECORDED",
33
34
  "MESSAGE_SENT",
34
35
  "MESSAGE_RECEIVED",
35
36
  "MODEL_REQUEST",
@@ -96,10 +97,18 @@ class MetaGPTAdapter(FrameworkAdapter):
96
97
  *,
97
98
  owner: EntityRef | None = None,
98
99
  name: str | None = None,
100
+ content: str | None = None,
101
+ content_kind: str = "task_prompt",
99
102
  status: str = "created",
100
103
  **attributes: Any,
101
104
  ) -> EntityRef:
102
- task = self.task(task_id, name=name, provider="metagpt", **attributes)
105
+ entity_attributes = dict(attributes)
106
+ if content is not None and self.context.capture_policy.mode in {
107
+ "prompt_only",
108
+ "prompt_and_response",
109
+ }:
110
+ entity_attributes.setdefault("task_prompt", content)
111
+ task = self.task(task_id, name=name, provider="metagpt", **entity_attributes)
103
112
  event = {
104
113
  "created": "TASK_CREATED",
105
114
  "started": "TASK_STARTED",
@@ -108,7 +117,16 @@ class MetaGPTAdapter(FrameworkAdapter):
108
117
  }.get(status)
109
118
  if event is None:
110
119
  raise ValueError(f"unsupported MetaGPT task status: {status}")
111
- self.context.record_task(TaskRecord(task, owner=owner, attributes=attributes), event_type=event)
120
+ self.context.record_task(
121
+ TaskRecord(
122
+ task,
123
+ owner=owner,
124
+ content=content,
125
+ content_kind=content_kind,
126
+ attributes=attributes,
127
+ ),
128
+ event_type=event,
129
+ )
112
130
  return task
113
131
 
114
132
  def observe_message(
@@ -37,6 +37,8 @@ from .viewer import (
37
37
  LIVE_DELTA_HISTORY = 256
38
38
  LIVE_DELTA_HISTORY_BYTES = 8 * 1024 * 1024
39
39
  _SEMANTIC_ENV = "EXECWEAVE_SEMANTIC_SIDECAR"
40
+ _RUN_ID_ENV = "EXECWEAVE_RUN_ID"
41
+ _SESSION_ID_ENV = "EXECWEAVE_SESSION_ID"
40
42
  _LIVE_TOKEN_HEADER = "X-ExecWeave-Token"
41
43
 
42
44
  _FINAL_THEME_CSS = """
@@ -737,16 +739,24 @@ def run_live(
737
739
  webbrowser.open(authenticated_live_url)
738
740
 
739
741
  return_code = 1
740
- previous_semantic_sidecar = os.environ.get(_SEMANTIC_ENV)
741
- os.environ[_SEMANTIC_ENV] = str(semantic_path)
742
+ environment_updates = {
743
+ _SEMANTIC_ENV: str(semantic_path),
744
+ _RUN_ID_ENV: session_id,
745
+ _SESSION_ID_ENV: session_id,
746
+ }
747
+ previous_environment = {
748
+ key: os.environ.get(key) for key in environment_updates
749
+ }
750
+ os.environ.update(environment_updates)
742
751
  try:
743
752
  try:
744
753
  return_code = collector.run(command)
745
754
  finally:
746
- if previous_semantic_sidecar is None:
747
- os.environ.pop(_SEMANTIC_ENV, None)
748
- else:
749
- os.environ[_SEMANTIC_ENV] = previous_semantic_sidecar
755
+ for key, value in previous_environment.items():
756
+ if value is None:
757
+ os.environ.pop(key, None)
758
+ else:
759
+ os.environ[key] = value
750
760
 
751
761
  validation = validate_event_stream(event_path)
752
762
  if not validation.valid:
@@ -183,6 +183,7 @@ def record_provider_to_viewer(
183
183
  collect_network=collect_network,
184
184
  keep_raw_trace=keep_raw_trace,
185
185
  open_browser=False,
186
+ integrate_semantic=False,
186
187
  )
187
188
  finally:
188
189
  _restore_environment(previous_environment)
@@ -418,6 +418,28 @@ function toolOccurrenceSection(node){
418
418
  });
419
419
  return section;
420
420
  }
421
+ function execweaveAssignedTaskText(agent){
422
+ if(!agent||String(agent.type||'')!=='agent')return '';
423
+ const projected=attrs(agent),attached=projected.viewer_assigned_task_prompt||projected.viewer_assigned_task_name;
424
+ if(attached)return commandText(attached);
425
+ const graph=rawGraph(),nodes=Array.isArray(graph.nodes)?graph.nodes:[],edges=Array.isArray(graph.edges)?graph.edges:[];
426
+ const nodeMap=new Map(nodes.filter(node=>node&&node.id).map(node=>[String(node.id),node]));
427
+ const exactAssignment=['ASSIGNED','AGENT','TASK'].join('_');
428
+ const direct=edges.filter(edge=>edge&&String(edge.target||'')===String(agent.id||'')&&['ASSIGNED_TO',exactAssignment].includes(String(edge.relation||''))).sort((a,b)=>(Number(a.first_sequence)||0)-(Number(b.first_sequence)||0));
429
+ const owned=edges.filter(edge=>edge&&String(edge.source||'')===String(agent.id||'')&&String(edge.relation||'')==='TASK_CREATED').sort((a,b)=>(Number(a.first_sequence)||0)-(Number(b.first_sequence)||0));
430
+ const started=edges.filter(edge=>edge&&String(edge.source||'')===String(agent.id||'')&&String(edge.relation||'')==='TASK_STARTED').sort((a,b)=>(Number(a.first_sequence)||0)-(Number(b.first_sequence)||0));
431
+ const edge=direct.at(-1)||owned.at(-1)||started.at(-1);if(!edge)return '';
432
+ const taskId=direct.length?edge.source:edge.target,task=nodeMap.get(String(taskId||''));
433
+ if(!task||String(task.type||'')!=='task')return '';
434
+ const a=attrs(task);return commandText(a.task_prompt||a.prompt||a.description||task.name);
435
+ }
436
+ function execweaveFillAssignedTask(round,node,isRoot){
437
+ const task=execweaveAssignedTaskText(node);if(!task)return round;
438
+ const value=round||{cards:[]},cards=Array.isArray(value.cards)?value.cards.map(card=>[card[0],card[1]]):[];
439
+ const label=isRoot?'Prompt':'Task',index=cards.findIndex(card=>String(card[0]||'')===label);
440
+ if(index<0)cards.unshift([label,task]);else if(!commandText(cards[index][1]))cards[index][1]=task;
441
+ return{...value,cards};
442
+ }
421
443
  function renderNode(node){
422
444
  const rows=nodeCards(node);
423
445
  if(!rows.length)return false;
@@ -436,10 +458,11 @@ function render(node){
436
458
  selectedNode=node;selectedConversationSignature=conversationSignature(node);detailsEmpty.hidden=true;details.replaceChildren();
437
459
  const record=recordFor(node),preview=record?.conversation_preview||{},path=String(preview.agent_path||nodePath(node)||'').trim(),messages=Array.isArray(preview.messages)?preview.messages:[];
438
460
  const isRoot=nodeHasRootAuthority(node)||previewUsesRootRenderer(preview);
439
- const rounds=isRoot?rootRounds(messages,path||'/root'):childRounds(messages,path);
461
+ const rounds=(isRoot?rootRounds(messages,path||'/root'):childRounds(messages,path)).map(round=>execweaveFillAssignedTask(round,node,isRoot));
440
462
  const tools=toolCallsFor(String(node.id||''));
441
- const appendTools=()=>{if(tools)details.appendChild(card('Tools',tools))};
442
- if(rounds.length<2){details.appendChild(roundView(rounds[0]||{cards:isRoot?[['Prompt',''],['Final response','']]:[['Task',''],['Thinking',''],['Response','']]}));appendTools();return true}
463
+ const frameworkCommunication=attrs(node).conversation_scope==='framework_agent'?uniqueTexts(messages.filter(message=>isObserved(message)&&!isInjected(message)&&String(message?.kind||'')==='agent_message')).join('\n\n'):'';
464
+ const appendTools=()=>{if(frameworkCommunication)details.appendChild(card('Agent communication',frameworkCommunication));if(tools)details.appendChild(card('Tools',tools))};
465
+ if(rounds.length<2){const fallback={cards:isRoot?[['Prompt',''],['Final response','']]:[['Task',''],['Thinking',''],['Response','']]};details.appendChild(roundView(rounds[0]||execweaveFillAssignedTask(fallback,node,isRoot)));appendTools();return true}
443
466
  // A subagent borrows the moment and the wording of the unique canonical root round
444
467
  // it belongs to. If root identity is ambiguous, the child keeps its own timestamp.
445
468
  const runs=isRoot?rounds:runRounds();
@@ -258,6 +258,151 @@ def orphan_audit(nodes: list[dict[str, Any]], edges: list[dict[str, Any]]) -> di
258
258
  return {"count": sum(item["count"] for item in by_type), "by_type": by_type}
259
259
 
260
260
 
261
+ _FRAMEWORK_TASK_RELATIONS = frozenset(
262
+ {"ASSIGNED_TO", "TASK_CREATED", "TASK_STARTED", "TASK_UPDATED", "TASK_COMPLETED", "TASK_FAILED"}
263
+ )
264
+
265
+
266
+ def _framework_task_edge_score(edge: dict[str, Any]) -> tuple[int, str, str]:
267
+ sequence = edge.get("last_sequence")
268
+ if not isinstance(sequence, int) or isinstance(sequence, bool):
269
+ sequence = edge.get("first_sequence")
270
+ if not isinstance(sequence, int) or isinstance(sequence, bool):
271
+ sequence = -1
272
+ return (sequence, str(edge.get("last_seen") or edge.get("first_seen") or ""), str(edge.get("id") or ""))
273
+
274
+
275
+ def _framework_task_text(task: dict[str, Any]) -> str:
276
+ attributes = _attrs(task)
277
+ for value in (
278
+ attributes.get("task_prompt"),
279
+ attributes.get("prompt"),
280
+ attributes.get("description"),
281
+ task.get("name"),
282
+ ):
283
+ if isinstance(value, str) and value.strip():
284
+ return value.strip()
285
+ return ""
286
+
287
+
288
+ def collapse_framework_tasks(
289
+ nodes: list[dict[str, Any]],
290
+ edges: list[dict[str, Any]],
291
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, int]]:
292
+ """Fold framework task presentation into the owning agent, like provider views.
293
+
294
+ Framework adapters retain first-class task entities in the raw graph because task
295
+ lifecycle and prompt evidence must remain auditable. Provider dashboards expose
296
+ that same information on the agent card, however, so the viewer projection hides
297
+ only framework task nodes and copies the latest authoritative task text onto each
298
+ assigned framework agent. Provider graphs are untouched because they do not use
299
+ the framework-agent conversation scope.
300
+ """
301
+ node_by_id = {
302
+ str(node["id"]): node
303
+ for node in nodes
304
+ if isinstance(node, dict) and isinstance(node.get("id"), str)
305
+ }
306
+ framework_agents = {
307
+ node_id
308
+ for node_id, node in node_by_id.items()
309
+ if node.get("type") == "agent" and _attrs(node).get("conversation_scope") == "framework_agent"
310
+ }
311
+ if not framework_agents:
312
+ return nodes, edges, {
313
+ "framework_task_node_count": 0,
314
+ "framework_task_content_node_count": 0,
315
+ "framework_task_agent_count": 0,
316
+ }
317
+
318
+ task_ids: set[str] = set()
319
+ tasks_by_agent: dict[str, list[tuple[tuple[int, str, str], str]]] = defaultdict(list)
320
+ for edge in edges:
321
+ if not isinstance(edge, dict) or edge.get("relation") not in _FRAMEWORK_TASK_RELATIONS:
322
+ continue
323
+ source = str(edge.get("source") or "")
324
+ target = str(edge.get("target") or "")
325
+ source_node = node_by_id.get(source)
326
+ target_node = node_by_id.get(target)
327
+ task_id = source if source_node and source_node.get("type") == "task" else target if target_node and target_node.get("type") == "task" else None
328
+ if task_id is None:
329
+ continue
330
+ agent_id = source if source in framework_agents else target if target in framework_agents else None
331
+ if agent_id is None:
332
+ continue
333
+ task_ids.add(task_id)
334
+ tasks_by_agent[agent_id].append((_framework_task_edge_score(edge), task_id))
335
+
336
+ if not task_ids:
337
+ return nodes, edges, {
338
+ "framework_task_node_count": 0,
339
+ "framework_task_content_node_count": 0,
340
+ "framework_task_agent_count": 0,
341
+ }
342
+
343
+ projected_nodes: list[dict[str, Any]] = []
344
+ for node in nodes:
345
+ node_id = str(node.get("id") or "")
346
+ if node_id in task_ids:
347
+ continue
348
+ if node.get("type") == "agent" and node_id in framework_agents:
349
+ candidates: dict[str, tuple[int, str, str]] = {}
350
+ for score, task_id in tasks_by_agent.get(node_id, []):
351
+ previous = candidates.get(task_id)
352
+ if previous is None or score > previous:
353
+ candidates[task_id] = score
354
+ ordered_ids = [
355
+ task_id
356
+ for task_id, _ in sorted(
357
+ candidates.items(),
358
+ key=lambda item: (item[1], item[0]),
359
+ reverse=True,
360
+ )
361
+ ]
362
+ latest_task = node_by_id.get(ordered_ids[0]) if ordered_ids else None
363
+ attributes = dict(_attrs(node))
364
+ attributes["viewer_assigned_task_ids"] = ordered_ids
365
+ attributes["viewer_assigned_task_count"] = len(ordered_ids)
366
+ if latest_task is not None:
367
+ attributes["viewer_assigned_task_id"] = str(latest_task.get("id") or "")
368
+ attributes["viewer_assigned_task_name"] = str(latest_task.get("name") or "")
369
+ task_text = _framework_task_text(latest_task)
370
+ if task_text:
371
+ attributes["viewer_assigned_task_prompt"] = task_text
372
+ projected_nodes.append({**node, "attributes": attributes})
373
+ continue
374
+ projected_nodes.append(node)
375
+
376
+ hidden_content_ids: set[str] = set()
377
+ for node in nodes:
378
+ node_id = str(node.get("id") or "")
379
+ if node.get("type") != "observed_content" or not node_id:
380
+ continue
381
+ incident = [
382
+ edge
383
+ for edge in edges
384
+ if edge.get("source") == node_id or edge.get("target") == node_id
385
+ ]
386
+ if incident and all(
387
+ (edge.get("source") in task_ids or edge.get("target") in task_ids)
388
+ for edge in incident
389
+ ):
390
+ hidden_content_ids.add(node_id)
391
+
392
+ hidden_ids = task_ids | hidden_content_ids
393
+ projected_nodes = [node for node in projected_nodes if str(node.get("id") or "") not in hidden_content_ids]
394
+ projected_edges = [
395
+ edge
396
+ for edge in edges
397
+ if edge.get("source") not in hidden_ids and edge.get("target") not in hidden_ids
398
+ ]
399
+ return projected_nodes, projected_edges, {
400
+ "framework_task_node_count": len(task_ids),
401
+ "framework_task_content_node_count": len(hidden_content_ids),
402
+ "framework_task_agent_count": len(tasks_by_agent),
403
+ }
404
+
405
+
261
406
  def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, Any]:
262
407
  """Provider-neutral display projection; raw graph/evidence remains unchanged."""
263
408
  from . import viewer_projection_base as base
@@ -268,6 +413,7 @@ def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, An
268
413
  nodes = [deepcopy(n) for n in filtered.get("nodes", []) if isinstance(n, dict)]
269
414
  edges = [deepcopy(e) for e in filtered.get("edges", []) if isinstance(e, dict)]
270
415
  entries = _entries(graph)
416
+ nodes, edges, framework_tasks = collapse_framework_tasks(nodes, edges)
271
417
  nodes, edges, inference = collapse_inference_requests(nodes, edges, entries)
272
418
  roots, _ = _roots(nodes, entries)
273
419
  nodes, edges, files = collapse_orphan_files(nodes, edges, roots[0] if len(roots) == 1 else None)
@@ -297,6 +443,7 @@ def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, An
297
443
  or inference["collapsed_request_count"]
298
444
  or local is not None
299
445
  or files is not None
446
+ or framework_tasks["framework_task_node_count"]
300
447
  )
301
448
  if not topology_changed:
302
449
  if audit["count"]:
@@ -313,6 +460,9 @@ def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, An
313
460
  "orphan_file_node_count": len(files.get("nodes") or []) if files else 0,
314
461
  "inference_request_count": inference["collapsed_request_count"], "logical_inference_count": inference["logical_inference_count"],
315
462
  "direct_inference_edge_count": inference["direct_inference_edge_count"], "unresolved_inference_requests": inference["unresolved"],
463
+ "framework_task_node_count": framework_tasks["framework_task_node_count"],
464
+ "framework_task_content_node_count": framework_tasks["framework_task_content_node_count"],
465
+ "framework_task_agent_count": framework_tasks["framework_task_agent_count"],
316
466
  "internal_hook_node_count": len(hook_nodes), "internal_hook_edge_count": len(hook_edges),
317
467
  "projection_topology_changed": True,
318
468
  })
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  from dataclasses import dataclass
4
5
  from pathlib import Path
5
6
  from uuid import uuid4
@@ -7,11 +8,16 @@ from uuid import uuid4
7
8
  from .backends import BackendName, create_collector, resolve_backend
8
9
  from .fidelity import write_fidelity_report
9
10
  from .graph import build_execution_graph, write_execution_graph
11
+ from .semantic import merge_semantic_sidecar
10
12
  from .sink import JsonlSink
11
13
  from .theme import ensure_viewer_theme
12
14
  from .validate import validate_event_stream
13
15
  from .viewer_projection import strip_internal_hook_execution_graph, write_graph_html
14
16
 
17
+ _SEMANTIC_ENV = "EXECWEAVE_SEMANTIC_SIDECAR"
18
+ _RUN_ID_ENV = "EXECWEAVE_RUN_ID"
19
+ _SESSION_ID_ENV = "EXECWEAVE_SESSION_ID"
20
+
15
21
 
16
22
  @dataclass(frozen=True)
17
23
  class RecordResult:
@@ -26,6 +32,9 @@ class RecordResult:
26
32
  event_count: int
27
33
  node_count: int
28
34
  edge_count: int
35
+ semantic_sidecar: Path | None = None
36
+ materialized_event_stream: Path | None = None
37
+ semantic_event_count: int = 0
29
38
 
30
39
  def to_dict(self) -> dict[str, object]:
31
40
  return {
@@ -40,6 +49,15 @@ class RecordResult:
40
49
  "event_count": self.event_count,
41
50
  "node_count": self.node_count,
42
51
  "edge_count": self.edge_count,
52
+ "semantic_sidecar": (
53
+ str(self.semantic_sidecar) if self.semantic_sidecar is not None else None
54
+ ),
55
+ "materialized_event_stream": (
56
+ str(self.materialized_event_stream)
57
+ if self.materialized_event_stream is not None
58
+ else str(self.event_stream)
59
+ ),
60
+ "semantic_event_count": self.semantic_event_count,
43
61
  }
44
62
 
45
63
 
@@ -61,6 +79,7 @@ def record_to_viewer(
61
79
  collect_network: bool = True,
62
80
  keep_raw_trace: bool = False,
63
81
  open_browser: bool = False,
82
+ integrate_semantic: bool = True,
64
83
  ) -> RecordResult:
65
84
  """Record one command and materialize a local graph/viewer after it exits."""
66
85
  if not command:
@@ -79,7 +98,12 @@ def record_to_viewer(
79
98
  fidelity_path = run_dir / "fidelity.json"
80
99
  graph_path = run_dir / "graph.json"
81
100
  viewer_path = run_dir / "viewer.html"
82
- _preflight_artifacts([event_path, fidelity_path, graph_path, viewer_path])
101
+ semantic_path = run_dir / "semantic.jsonl"
102
+ merged_event_path = run_dir / "events.semantic.jsonl"
103
+ artifacts = [event_path, fidelity_path, graph_path, viewer_path]
104
+ if integrate_semantic:
105
+ artifacts.extend([semantic_path, merged_event_path])
106
+ _preflight_artifacts(artifacts)
83
107
 
84
108
  sink = JsonlSink(event_path)
85
109
  resolved = resolve_backend(backend)
@@ -94,14 +118,44 @@ def record_to_viewer(
94
118
  keep_raw_trace=keep_raw_trace,
95
119
  )
96
120
 
97
- return_code = collector.run(command)
121
+ environment_updates = {
122
+ _RUN_ID_ENV: session_id,
123
+ _SESSION_ID_ENV: session_id,
124
+ }
125
+ if integrate_semantic:
126
+ environment_updates[_SEMANTIC_ENV] = str(semantic_path)
127
+ previous_environment = {
128
+ key: os.environ.get(key) for key in environment_updates
129
+ }
130
+ os.environ.update(environment_updates)
131
+ try:
132
+ return_code = collector.run(command)
133
+ finally:
134
+ for key, value in previous_environment.items():
135
+ if value is None:
136
+ os.environ.pop(key, None)
137
+ else:
138
+ os.environ[key] = value
98
139
 
99
140
  validation = validate_event_stream(event_path)
100
141
  if not validation.valid:
101
142
  details = "; ".join(validation.errors)
102
143
  raise RuntimeError(f"recorded event stream failed validation: {details}")
103
144
 
104
- execution_graph = strip_internal_hook_execution_graph(build_execution_graph(event_path))
145
+ materialized_event_path = event_path
146
+ semantic_event_count = 0
147
+ if integrate_semantic and semantic_path.exists() and semantic_path.stat().st_size > 0:
148
+ semantic_merge = merge_semantic_sidecar(
149
+ event_path,
150
+ semantic_path,
151
+ merged_event_path,
152
+ )
153
+ materialized_event_path = merged_event_path
154
+ semantic_event_count = semantic_merge.semantic_event_count
155
+
156
+ execution_graph = strip_internal_hook_execution_graph(
157
+ build_execution_graph(materialized_event_path)
158
+ )
105
159
  write_fidelity_report(execution_graph.fidelity, fidelity_path)
106
160
  write_execution_graph(execution_graph, graph_path)
107
161
  write_graph_html(execution_graph.to_dict(), viewer_path, open_browser=False)
@@ -123,4 +177,7 @@ def record_to_viewer(
123
177
  event_count=execution_graph.event_count,
124
178
  node_count=len(execution_graph.nodes),
125
179
  edge_count=len(execution_graph.edges),
180
+ semantic_sidecar=semantic_path.resolve() if integrate_semantic else None,
181
+ materialized_event_stream=materialized_event_path.resolve(),
182
+ semantic_event_count=semantic_event_count,
126
183
  )
File without changes
File without changes