execweave 0.8.24__tar.gz → 0.8.27__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.24 → execweave-0.8.27}/PKG-INFO +1 -1
  2. {execweave-0.8.24 → execweave-0.8.27}/pyproject.toml +2 -2
  3. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/__init__.py +1 -1
  4. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/_conversation_records_core.py +3 -0
  5. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/collector.py +9 -1
  6. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_common.py +25 -0
  7. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_generic.py +15 -0
  8. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_lines.py +19 -2
  9. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/framework_adapters/autogen.py +5 -0
  10. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/framework_adapters/base.py +68 -9
  11. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/framework_adapters/camel.py +49 -5
  12. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/framework_adapters/metagpt.py +57 -12
  13. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/strace_backend.py +6 -0
  14. {execweave-0.8.24 → execweave-0.8.27}/.gitignore +0 -0
  15. {execweave-0.8.24 → execweave-0.8.27}/LICENSE +0 -0
  16. {execweave-0.8.24 → execweave-0.8.27}/README.md +0 -0
  17. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/__main__.py +0 -0
  18. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/_dashboard_shell_base.py +0 -0
  19. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/_http_proxy_base.py +0 -0
  20. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/_http_proxy_bounded.py +0 -0
  21. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/_http_proxy_stage.py +0 -0
  22. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/agent_bootstrap.py +0 -0
  23. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/agent_topology.py +0 -0
  24. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/agent_trace.py +0 -0
  25. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/agy_preview_sanitize.py +0 -0
  26. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/analysis.py +0 -0
  27. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/anthropic.py +0 -0
  28. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/anthropic_cli.py +0 -0
  29. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/anthropic_full_fidelity.py +0 -0
  30. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_adapter.py +0 -0
  31. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_adapter_base.py +0 -0
  32. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_full_fidelity.py +0 -0
  33. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_full_fidelity_base.py +0 -0
  34. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
  35. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_hook_cli.py +0 -0
  36. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_record.py +0 -0
  37. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_subagent_linkage.py +0 -0
  38. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/antigravity_trace_capability.py +0 -0
  39. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/auto_specialized.py +0 -0
  40. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/backends.py +0 -0
  41. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/benchmark.py +0 -0
  42. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_adapter.py +0 -0
  43. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_child_transcript.py +0 -0
  44. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_delegation.py +0 -0
  45. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_full_fidelity.py +0 -0
  46. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_hook_cli.py +0 -0
  47. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_hook_contract.py +0 -0
  48. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_model_observer.py +0 -0
  49. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/claude_record.py +0 -0
  50. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cli.py +0 -0
  51. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_adapter.py +0 -0
  52. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_conversation.py +0 -0
  53. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_full_fidelity.py +0 -0
  54. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_hook_cli.py +0 -0
  55. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_hook_entry.py +0 -0
  56. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_hook_lifecycle.py +0 -0
  57. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_message_diagnostics.py +0 -0
  58. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_message_transport_diagnostics.py +0 -0
  59. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_record.py +0 -0
  60. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_rollout_structures.py +0 -0
  61. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_rollout_trace.py +0 -0
  62. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/codex_rollout_trace_base.py +0 -0
  63. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/command.py +0 -0
  64. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/content_evidence.py +0 -0
  65. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/content_store.py +0 -0
  66. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_archive.py +0 -0
  67. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_message_identity.py +0 -0
  68. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview.py +0 -0
  69. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_antigravity.py +0 -0
  70. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_claude.py +0 -0
  71. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_codex.py +0 -0
  72. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_preview_transcript.py +0 -0
  73. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_records.py +0 -0
  74. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_records_antigravity.py +0 -0
  75. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_records_codex.py +0 -0
  76. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_records_common.py +0 -0
  77. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/conversation_records_ollama.py +0 -0
  78. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/correlation.py +0 -0
  79. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_adapter.py +0 -0
  80. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_delegation.py +0 -0
  81. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_delegation_base.py +0 -0
  82. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_full_fidelity.py +0 -0
  83. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_hook_cli.py +0 -0
  84. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_hook_contract.py +0 -0
  85. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_lifecycle.py +0 -0
  86. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/cursor_record.py +0 -0
  87. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/dashboard_shell.py +0 -0
  88. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/entry.py +0 -0
  89. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/evidence_availability.py +0 -0
  90. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/evidence_grade.py +0 -0
  91. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/fidelity.py +0 -0
  92. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/filesystem.py +0 -0
  93. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/focus.py +0 -0
  94. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/framework_adapters/__init__.py +0 -0
  95. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/framework_adapters/registry.py +0 -0
  96. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/graph.py +0 -0
  97. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/graph_ops.py +0 -0
  98. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/http_proxy.py +0 -0
  99. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/http_proxy_cli.py +0 -0
  100. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/inference_gateway.py +0 -0
  101. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/inference_gateway_cli.py +0 -0
  102. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/inference_gateway_full_fidelity.py +0 -0
  103. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/inference_identity.py +0 -0
  104. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/inference_identity_cli.py +0 -0
  105. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/integrity.py +0 -0
  106. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/integrity_cli.py +0 -0
  107. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/litellm_callback.py +0 -0
  108. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/litellm_callback_cli.py +0 -0
  109. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live.py +0 -0
  110. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_core.py +0 -0
  111. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view.py +0 -0
  112. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_extra_style.py +0 -0
  113. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_markup.py +0 -0
  114. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_process_layout.py +0 -0
  115. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_readability.py +0 -0
  116. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_script_a.py +0 -0
  117. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_script_b.py +0 -0
  118. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_script_c.py +0 -0
  119. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_script_d.py +0 -0
  120. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/live_view_style.py +0 -0
  121. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/model_runtime.py +0 -0
  122. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/model_runtime_cli.py +0 -0
  123. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/model_runtime_full_fidelity.py +0 -0
  124. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/openai_compatible.py +0 -0
  125. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/openai_compatible_cli.py +0 -0
  126. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/openai_compatible_full_fidelity.py +0 -0
  127. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_adapter.py +0 -0
  128. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_event_contract.py +0 -0
  129. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_full_fidelity.py +0 -0
  130. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_hook_cli.py +0 -0
  131. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_plugin_cli.py +0 -0
  132. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_record.py +0 -0
  133. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/opencode_task_linkage.py +0 -0
  134. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/overhead_benchmark.py +0 -0
  135. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/provider_capability.py +0 -0
  136. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/provider_lifecycle.py +0 -0
  137. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/provider_record.py +0 -0
  138. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/rule_pack.py +0 -0
  139. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/rule_pack_cli.py +0 -0
  140. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/runtime_endpoint_availability.py +0 -0
  141. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/scalability_benchmark.py +0 -0
  142. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/schema.py +0 -0
  143. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/scope.py +0 -0
  144. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/semantic.py +0 -0
  145. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/sink.py +0 -0
  146. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/stream_assembly.py +0 -0
  147. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/theme.py +0 -0
  148. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/top.py +0 -0
  149. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/top_cli.py +0 -0
  150. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/validate.py +0 -0
  151. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/vendor/dagre.min.js +0 -0
  152. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/view_cli.py +0 -0
  153. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer.py +0 -0
  154. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel.py +0 -0
  155. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
  156. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_claude.py +0 -0
  157. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_codex.py +0 -0
  158. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_cursor.py +0 -0
  159. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_default.py +0 -0
  160. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_ollama.py +0 -0
  161. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_opencode.py +0 -0
  162. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_agent_panel_registry.py +0 -0
  163. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_content_inspector.py +0 -0
  164. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_dashboard_clean.py +0 -0
  165. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_dashboard_focus.py +0 -0
  166. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_dashboard_hardening.py +0 -0
  167. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
  168. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_dashboard_pr70.py +0 -0
  169. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_edge_decross.py +0 -0
  170. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_execution_flow.py +0 -0
  171. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_execution_flow_provider_normalization.py +0 -0
  172. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_execution_flow_safety.py +0 -0
  173. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_external_endpoints.py +0 -0
  174. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_flow_canvas.py +0 -0
  175. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_flow_layout.py +0 -0
  176. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_gif.py +0 -0
  177. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_layout_geometry.py +0 -0
  178. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_layout_v2.py +0 -0
  179. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_layout_v2_detached.py +0 -0
  180. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_limits.py +0 -0
  181. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_live_layout.py +0 -0
  182. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_projection.py +0 -0
  183. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_projection_base.py +0 -0
  184. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_projection_bridges.py +0 -0
  185. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_semantic_projection.py +0 -0
  186. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/viewer_tool_mirror_identity.py +0 -0
  187. {execweave-0.8.24 → execweave-0.8.27}/src/execweave/workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execweave
3
- Version: 0.8.24
3
+ Version: 0.8.27
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.24 release metadata
8
- version = "0.8.24"
7
+ # ExecWeave v0.8.27 release metadata
8
+ version = "0.8.27"
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.24"
3
+ __version__ = "0.8.27"
@@ -47,6 +47,9 @@ _CONVERSATION_TOKENS = (
47
47
  "request_prompt",
48
48
  "request_input",
49
49
  "model_context_messages",
50
+ "model_request",
51
+ "model_response",
52
+ "model_failure",
50
53
  )
51
54
 
52
55
 
@@ -75,7 +75,15 @@ def _safe_process_snapshot(proc: psutil.Process) -> ProcessSnapshot | None:
75
75
  exe=exe,
76
76
  create_time=proc.create_time(),
77
77
  )
78
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
78
+ except (
79
+ psutil.NoSuchProcess,
80
+ psutil.AccessDenied,
81
+ psutil.ZombieProcess,
82
+ # macOS can surface protected-process cmdline denial as a SystemError
83
+ # from psutil's C extension instead of AccessDenied. Treat that process
84
+ # as unobservable, just like the other permission-race cases.
85
+ SystemError,
86
+ ):
79
87
  return None
80
88
 
81
89
 
@@ -5,6 +5,9 @@ from typing import Any
5
5
 
6
6
  from .agent_topology import (
7
7
  THREAD_ID_EXECWEAVE_DERIVED,
8
+ THREAD_ID_PROVIDER_NATIVE,
9
+ PATH_PROVIDER_DECLARED,
10
+ TOPOLOGY_OBSERVED,
8
11
  resolve_agent_topology,
9
12
  )
10
13
  from .conversation_preview_generic import ( # noqa: F401
@@ -93,6 +96,28 @@ def _agent_identity(provider: str, source: dict[str, Any] | None) -> dict[str, A
93
96
  source_id_text = source_id if isinstance(source_id, str) else ""
94
97
  provider_label = _provider_label(provider)
95
98
  provider_key = provider.lower() or "provider"
99
+ if attrs.get("conversation_scope") == "framework_agent":
100
+ native_id = str(attrs.get("conversation_agent_id") or source_id_text or "agent")
101
+ agent_path = attrs.get("conversation_agent_path")
102
+ if not isinstance(agent_path, str) or not agent_path.strip():
103
+ agent_path = f"/{provider_key}/{native_id}"
104
+ label = source_name or attrs.get("conversation_agent_name") or native_id
105
+ return {
106
+ "thread_id": f"{provider_key}:agent:{native_id}",
107
+ "thread_id_source": THREAD_ID_PROVIDER_NATIVE,
108
+ "parent_thread_id": None,
109
+ "agent_label": str(label),
110
+ "provider_label": provider_label,
111
+ "agent_nickname": None,
112
+ "agent_path": agent_path,
113
+ "agent_path_source": PATH_PROVIDER_DECLARED,
114
+ "topology_state": TOPOLOGY_OBSERVED,
115
+ "topology_evidence": "framework_agent_boundary",
116
+ "parent_agent_path": None,
117
+ "parent_relation_source": None,
118
+ "provider_native_id": native_id,
119
+ "is_root": False,
120
+ }
96
121
  topology = resolve_agent_topology(source)
97
122
  agent_path = topology.agent_path
98
123
  is_root = topology.is_root
@@ -64,6 +64,12 @@ def _routed_agent_message(
64
64
  return []
65
65
  sender = value.get("sender")
66
66
  recipient = value.get("recipient")
67
+ if recipient is None and isinstance(value.get("recipients"), list):
68
+ recipient = ", ".join(
69
+ str(item).strip()
70
+ for item in value["recipients"]
71
+ if str(item).strip()
72
+ ) or None
67
73
  kind = value.get("kind")
68
74
  phase = value.get("phase")
69
75
  task_name = value.get("task_name")
@@ -101,6 +107,15 @@ def _generic_content_messages(
101
107
  routed = _routed_agent_message(value, timestamp=timestamp, ordinal=ordinal, agent_path=agent_path)
102
108
  if routed:
103
109
  return routed
110
+ if "model_request" in kind or "model_response" in kind or "model_failure" in kind:
111
+ structured = _structured_messages(value, timestamp=timestamp, ordinal=ordinal, agent_path=agent_path)
112
+ if structured:
113
+ return structured
114
+ text = _text_parts(value)
115
+ if text:
116
+ if "model_request" in kind:
117
+ return [_message(timestamp=timestamp, ordinal=ordinal, kind="model_request", sender="context", recipient=agent_path, text=text, phase="request")]
118
+ return [_message(timestamp=timestamp, ordinal=ordinal, kind="assistant_message", sender=agent_path, recipient=None, text=text, phase="response")]
104
119
  text = _text_parts(value)
105
120
  if not text:
106
121
  return []
@@ -24,6 +24,13 @@ def _structured_messages(
24
24
  if isinstance(value, dict):
25
25
  if isinstance(value.get("message"), dict):
26
26
  candidates.append(value["message"])
27
+ if isinstance(value.get("messages"), list):
28
+ for item in value["messages"]:
29
+ if isinstance(item, dict):
30
+ if isinstance(item.get("message"), dict):
31
+ candidates.append(item["message"])
32
+ elif any(key in item for key in ("role", "content", "text")):
33
+ candidates.append(item)
27
34
  elif any(key in value for key in ("role", "content", "text")):
28
35
  candidates.append(value)
29
36
  elif isinstance(value, list):
@@ -41,10 +48,20 @@ def _structured_messages(
41
48
  text = _text_parts(item.get("content")) or _text_parts(item.get("text"))
42
49
  if not text:
43
50
  continue
51
+ sender = item.get("source") or item.get("sender")
52
+ recipient = item.get("recipient") or item.get("target")
53
+ if recipient is None and isinstance(item.get("recipients"), list):
54
+ recipient = ", ".join(
55
+ str(value).strip()
56
+ for value in item["recipients"]
57
+ if str(value).strip()
58
+ ) or None
59
+ sender = sender.strip() if isinstance(sender, str) and sender.strip() else None
60
+ recipient = recipient.strip() if isinstance(recipient, str) and recipient.strip() else None
44
61
  if role in {"user", "human"}:
45
- messages.append(_message(timestamp=timestamp, ordinal=ordinal, kind="user_message", sender="user", recipient=agent_path, text=text))
62
+ messages.append(_message(timestamp=timestamp, ordinal=ordinal, kind="user_message", sender=sender or "user", recipient=recipient or agent_path, text=text))
46
63
  elif role in {"assistant", "model", "agent"}:
47
- messages.append(_message(timestamp=timestamp, ordinal=ordinal, kind="assistant_message", sender=agent_path, recipient=None, text=text, phase="response"))
64
+ messages.append(_message(timestamp=timestamp, ordinal=ordinal, kind="assistant_message", sender=sender or agent_path, recipient=recipient, text=text, phase="response"))
48
65
  return messages
49
66
 
50
67
 
@@ -185,6 +185,11 @@ class AutoGenAdapter(FrameworkAdapter):
185
185
  source = self._agents.get(str(source_name)) or self.observe_agent(
186
186
  str(source_name), name=str(source_name)
187
187
  )
188
+ target_name = getattr(event, "target", None) or getattr(event, "recipient", None)
189
+ if target is None and target_name is not None:
190
+ target = self._agents.get(str(target_name)) or self.observe_agent(
191
+ str(target_name), name=str(target_name)
192
+ )
188
193
  content = getattr(event, "content", None)
189
194
  if event_name == "ThoughtEvent":
190
195
  return self.observe_message(
@@ -34,7 +34,7 @@ CANONICAL_EVENT_TYPES = frozenset(
34
34
  "AGENT_CREATED", "AGENT_STARTED", "AGENT_STOPPED",
35
35
  "TASK_CREATED", "TASK_ASSIGNED", "TASK_STARTED", "TASK_UPDATED",
36
36
  "TASK_COMPLETED", "TASK_FAILED",
37
- "MESSAGE_SENT", "MESSAGE_RECEIVED",
37
+ "MESSAGE_SENT", "MESSAGE_RECEIVED", "MESSAGE_UNROUTED",
38
38
  "MODEL_REQUEST", "MODEL_RESPONSE", "MODEL_FAILURE",
39
39
  "TOOL_CALL", "TOOL_RESULT", "TOOL_FAILURE",
40
40
  "MESSAGE_CONTENT_RECORDED", "MODEL_CONTENT_RECORDED", "TOOL_CONTENT_RECORDED",
@@ -92,6 +92,12 @@ def _string(value: Any) -> str | None:
92
92
  return text or None
93
93
 
94
94
 
95
+ def _entity_label(entity: EntityRef | None) -> str | None:
96
+ if entity is None:
97
+ return None
98
+ return entity.name or entity.id
99
+
100
+
95
101
  @dataclass(frozen=True)
96
102
  class EntityRef:
97
103
  type: str
@@ -307,6 +313,7 @@ class MessageRecord:
307
313
  content_kind: str = "message"
308
314
  direction: Literal["sent", "received"] = "sent"
309
315
  attributes: Mapping[str, Any] = field(default_factory=dict)
316
+ content_payload: Any | None = None
310
317
 
311
318
 
312
319
  @dataclass(frozen=True)
@@ -390,28 +397,73 @@ class AdapterContext:
390
397
  return record.task
391
398
 
392
399
  def record_message(self, record: MessageRecord) -> EntityRef:
393
- reference = self.content.put_text(record.content, content_kind=record.content_kind) if record.content is not None else None
394
- attrs = {"message_id": record.message.id, "role": record.role, "task_id": record.task.id if record.task else None, "parent_message_id": record.parent_message.id if record.parent_message else None, **self.content.reference_attributes(reference), **dict(record.attributes)}
400
+ content_kind = f"{self.framework}.agent_message"
401
+ message_payload = record.content_payload if record.content_payload is not None else {
402
+ "message_id": record.message.id,
403
+ "text": record.content,
404
+ "sender": _entity_label(record.source),
405
+ "recipient": _entity_label(record.target),
406
+ "role": record.role,
407
+ "kind": "agent_message",
408
+ "phase": "received" if record.direction == "received" else "sent",
409
+ "content_state": "plaintext",
410
+ }
411
+ reference = self.content.put_json(message_payload, content_kind=content_kind) if record.content is not None else None
412
+ attrs = {"message_id": record.message.id, "role": record.role, "task_id": record.task.id if record.task else None, "parent_message_id": record.parent_message.id if record.parent_message else None, "sender_agent_id": record.source.id if record.source and record.source.type == "agent" else None, "recipient_agent_id": record.target.id if record.target and record.target.type == "agent" else None, **self.content.reference_attributes(reference), **dict(record.attributes)}
395
413
  event_type = "MESSAGE_SENT" if record.direction == "sent" else "MESSAGE_RECEIVED"
396
414
  self.emit(event_type, event_type, source=record.source, target=record.target or record.message, attributes=attrs)
397
415
  if reference is not None:
398
416
  content = EntityRef("observed_content", f"observed-content:message:sha256:{reference.sha256}", reference.content_kind, reference.to_dict())
399
- self.emit("MESSAGE_CONTENT_RECORDED", "HAS_MESSAGE_CONTENT", source=record.message, target=content, attributes={"message_id": record.message.id, **self.content.reference_attributes(reference)})
417
+ participants: list[tuple[EntityRef, str]] = []
418
+ for side, participant in (("sender", record.source), ("recipient", record.target)):
419
+ if participant is None or participant.type != "agent":
420
+ continue
421
+ if any(existing.id == participant.id for existing, _ in participants):
422
+ participants = [(existing, "sender_and_recipient" if existing.id == participant.id else existing_side) for existing, existing_side in participants]
423
+ continue
424
+ participants.append((participant, side))
425
+ if not participants:
426
+ participants = [(record.message, "message")]
427
+ for participant, side in participants:
428
+ self.emit(
429
+ "MESSAGE_CONTENT_RECORDED",
430
+ "HAS_MESSAGE_CONTENT",
431
+ source=participant,
432
+ target=content,
433
+ attributes={
434
+ "message_id": record.message.id,
435
+ "conversation_scope": "agent" if participant.type == "agent" else "message",
436
+ "conversation_side": side,
437
+ "conversation_agent_id": participant.id if participant.type == "agent" else None,
438
+ **self.content.reference_attributes(reference),
439
+ },
440
+ )
400
441
  return record.message
401
442
 
402
443
  def record_model_call(self, record: ModelCallRecord) -> EntityRef:
403
444
  if record.status == "request":
404
- event_type, relation, value, kind = "MODEL_REQUEST", "REQUESTS_MODEL_CALL", record.request, record.content_kind or "model_request"
445
+ event_type, relation, value, kind = "MODEL_REQUEST", "REQUESTS_MODEL_CALL", record.request, f"{self.framework}.model_request"
405
446
  elif record.status == "response":
406
- event_type, relation, value, kind = "MODEL_RESPONSE", "MODEL_CALL_RESPONDS", record.response, record.content_kind or "model_response"
447
+ event_type, relation, value, kind = "MODEL_RESPONSE", "MODEL_CALL_RESPONDS", record.response, f"{self.framework}.model_response"
407
448
  else:
408
- event_type, relation, value, kind = "MODEL_FAILURE", "MODEL_CALL_FAILED", record.response or record.request, record.content_kind or "model_failure"
449
+ event_type, relation, value, kind = "MODEL_FAILURE", "MODEL_CALL_FAILED", record.response or record.request, f"{self.framework}.model_failure"
409
450
  reference = self.content.put_text(value, content_kind=kind) if isinstance(value, str) else self.content.put_json(value, content_kind=kind) if value is not None else None
410
451
  attrs = {"model_call_id": record.model_call.id, "task_id": record.task.id if record.task else None, "requesting_agent_id": record.requesting_agent.id if record.requesting_agent else None, **self.content.reference_attributes(reference), **dict(record.attributes)}
411
452
  self.emit(event_type, relation, source=record.requesting_agent, target=record.model, attributes=attrs)
412
453
  if reference is not None:
413
454
  content = EntityRef("observed_content", f"observed-content:model:sha256:{reference.sha256}", reference.content_kind, reference.to_dict())
414
- self.emit("MODEL_CONTENT_RECORDED", "HAS_MODEL_CONTENT", source=record.model_call, target=content, attributes={"model_call_id": record.model_call.id, **self.content.reference_attributes(reference)})
455
+ self.emit(
456
+ "MODEL_CONTENT_RECORDED",
457
+ "HAS_MODEL_CONTENT",
458
+ source=record.requesting_agent or record.model_call,
459
+ target=content,
460
+ attributes={
461
+ "model_call_id": record.model_call.id,
462
+ "conversation_scope": "agent" if record.requesting_agent is not None else "model_call",
463
+ "conversation_agent_id": record.requesting_agent.id if record.requesting_agent else None,
464
+ **self.content.reference_attributes(reference),
465
+ },
466
+ )
415
467
  return record.model_call
416
468
 
417
469
  def record_tool_call(self, record: ToolCallRecord) -> EntityRef:
@@ -452,7 +504,14 @@ class FrameworkAdapter:
452
504
  return self.context.emit(event_type, relation, **kwargs)
453
505
 
454
506
  def agent(self, native_id: str | int, *, name: str | None = None, **attributes: Any) -> EntityRef:
455
- return self.entity("agent", native_id, name=name, attributes=attributes)
507
+ native = _ID_SAFE.sub("_", str(native_id).strip()) or "agent"
508
+ values = {
509
+ "conversation_scope": "framework_agent",
510
+ "conversation_agent_id": str(native_id),
511
+ "conversation_agent_path": f"/{self.framework_name}/{native}",
512
+ **attributes,
513
+ }
514
+ return self.entity("agent", native_id, name=name, attributes=values)
456
515
 
457
516
  def task(self, native_id: str | int, *, name: str | None = None, **attributes: Any) -> EntityRef:
458
517
  return self.entity("task", native_id, name=name, attributes=attributes)
@@ -11,16 +11,17 @@ from .base import AdapterCapabilities, AdapterContext, AgentRecord, EntityRef, F
11
11
  class CAMELAdapter(FrameworkAdapter):
12
12
  framework_name = "camel"
13
13
  capabilities = AdapterCapabilities(
14
- events=("AGENT_CREATED", "AGENT_STARTED", "AGENT_STOPPED", "TASK_CREATED", "TASK_ASSIGNED", "TASK_STARTED", "TASK_UPDATED", "TASK_COMPLETED", "TASK_FAILED", "MESSAGE_SENT", "MESSAGE_RECEIVED", "MODEL_REQUEST", "MODEL_RESPONSE", "MODEL_FAILURE"),
14
+ events=("AGENT_CREATED", "AGENT_STARTED", "AGENT_STOPPED", "TASK_CREATED", "TASK_ASSIGNED", "TASK_STARTED", "TASK_UPDATED", "TASK_COMPLETED", "TASK_FAILED", "MESSAGE_SENT", "MESSAGE_RECEIVED", "MESSAGE_UNROUTED", "MODEL_REQUEST", "MODEL_RESPONSE", "MODEL_FAILURE"),
15
15
  content_modes=("metadata_only", "content_ref_only", "prompt_only", "prompt_and_response"),
16
16
  authoritative_surfaces=("camel.societies.workforce.WorkforceCallback", "camel.models.BaseModelBackend.run", "camel.models.BaseModelBackend.arun"),
17
- known_limitations=("WorkforceCallback does not expose every message boundary; callers must use an authoritative model/message hook.", "A created worker is not treated as assigned until CAMEL reports a task assignment."),
17
+ known_limitations=("WorkforceCallback LogEvent does not expose the emitting worker or recipient; those log messages are explicitly marked MESSAGE_UNROUTED and correlated to the observed process instead of being assigned to a guessed agent.", "A created worker is not treated as assigned until CAMEL reports a task assignment."),
18
18
  )
19
19
 
20
20
  def __init__(self, context: AdapterContext) -> None:
21
21
  super().__init__(context)
22
22
  self._agents: dict[str, EntityRef] = {}
23
23
  self._tasks: dict[str, EntityRef] = {}
24
+ self._message_counter = 0
24
25
 
25
26
  @classmethod
26
27
  def compatibility(cls) -> FrameworkCompatibility:
@@ -72,6 +73,32 @@ class CAMELAdapter(FrameworkAdapter):
72
73
  self.context.record_message(MessageRecord(message, source, target, role=role, task=task, content=content, direction="received" if received else "sent", attributes=attributes))
73
74
  return message
74
75
 
76
+ def message_unrouted(self, message: EntityRef, *, callback_name: str, missing_sender: bool, missing_recipient: bool, reason: str = "callback_payload_missing_agent_boundary") -> None:
77
+ """Keep callback messages visible without inventing an agent route.
78
+
79
+ CAMEL's WorkforceCallback log surface can emit a message without a
80
+ worker or recipient. The process is the only authoritative boundary
81
+ available in that case, so record that boundary explicitly rather
82
+ than leaving the message looking like a dropped graph node.
83
+ """
84
+ process = self.context.process
85
+ if process is None:
86
+ return
87
+ self.emit(
88
+ "MESSAGE_UNROUTED",
89
+ "OBSERVED_AT_PROCESS",
90
+ source=message,
91
+ target=process.to_entity(),
92
+ attributes={
93
+ "callback_name": callback_name,
94
+ "routing_status": "unrouted",
95
+ "routing_reason": reason,
96
+ "missing_sender": missing_sender,
97
+ "missing_recipient": missing_recipient,
98
+ "routing_basis": "authoritative_process_boundary",
99
+ },
100
+ )
101
+
75
102
  def model_call(self, call_id: str | int, agent: EntityRef | None, model_id: str | int, *, model_name: str | None = None, request: Any | None = None, response: Any | None = None, status: str = "request", task: EntityRef | None = None, **attributes: Any) -> EntityRef:
76
103
  model = self.model(model_id, name=model_name, provider="camel", **attributes)
77
104
  call = self.entity("model_call", call_id, name="CAMEL model call", attributes={"provider": "camel"})
@@ -91,21 +118,38 @@ class CAMELAdapter(FrameworkAdapter):
91
118
  payload = _payload(args, kwargs)
92
119
  if name in {"log_message", "log_stream_chunk"}:
93
120
  content = _string(_value(payload, "message", "text"))
94
- message_id = _native(payload, "message_id", "id", "task_id", "worker_id")
121
+ native_message_id = _value(payload, "message_id", "id", "task_id", "worker_id")
122
+ self._message_counter += 1
123
+ message_id = (
124
+ native_message_id
125
+ if isinstance(native_message_id, (str, int)) and str(native_message_id)
126
+ else f"{self.framework_name}-message-{self._message_counter}"
127
+ )
95
128
  worker_id = _value(payload, "worker_id", "agent_id")
96
129
  source = self._agents.get(str(worker_id)) if worker_id is not None else None
97
- return self.message(
130
+ target_id = _value(payload, "target_worker_id", "recipient_worker_id", "target_agent_id", "recipient_agent_id", "receiver_id")
131
+ target = self._agents.get(str(target_id)) if target_id is not None else None
132
+ message = self.message(
98
133
  message_id,
99
134
  source,
100
- None,
135
+ target,
101
136
  content=content,
102
137
  role="stream" if name == "log_stream_chunk" else "log",
103
138
  attributes={
104
139
  "callback_name": name,
105
140
  "event_type": _value(payload, "event_type"),
106
141
  "metadata": _value(payload, "metadata"),
142
+ "message_id_source": "provider" if native_message_id is not None else "execweave_derived",
107
143
  },
108
144
  )
145
+ if source is None or target is None:
146
+ self.message_unrouted(
147
+ message,
148
+ callback_name=name,
149
+ missing_sender=source is None,
150
+ missing_recipient=target is None,
151
+ )
152
+ return message
109
153
  if name == "log_task_decomposed":
110
154
  parent_key = _native(payload, "parent_task_id", "task_id", "id")
111
155
  parent = self._tasks.get(str(parent_key)) or self.task_created(parent_key)
@@ -121,6 +121,7 @@ class MetaGPTAdapter(FrameworkAdapter):
121
121
  role: str | None = None,
122
122
  task: EntityRef | None = None,
123
123
  received: bool = False,
124
+ content_payload: Any | None = None,
124
125
  **attributes: Any,
125
126
  ) -> EntityRef:
126
127
  message = self.entity("message", message_id, name="MetaGPT message", attributes={"provider": "metagpt"})
@@ -132,6 +133,7 @@ class MetaGPTAdapter(FrameworkAdapter):
132
133
  role=role,
133
134
  task=task,
134
135
  content=content,
136
+ content_payload=content_payload,
135
137
  direction="received" if received else "sent",
136
138
  attributes=attributes,
137
139
  )
@@ -145,19 +147,62 @@ class MetaGPTAdapter(FrameworkAdapter):
145
147
  source_name = getattr(message_object, "sent_from", None)
146
148
  target_name = getattr(message_object, "send_to", None)
147
149
  source = self._roles.get(str(source_name)) if source_name else None
148
- target = self._roles.get(str(target_name)) if isinstance(target_name, str) else None
149
- return self.observe_message(
150
- message_id,
151
- source=source,
152
- target=target,
153
- content=_text(getattr(message_object, "content", None)),
154
- role=_text(getattr(message_object, "role", None)),
155
- task=task,
156
- cause_by=_text(getattr(message_object, "cause_by", None)),
157
- metadata=getattr(message_object, "metadata", None),
158
- message_type=type(message_object).__name__,
150
+ content = _text(getattr(message_object, "content", None))
151
+ role = _text(getattr(message_object, "role", None))
152
+ common_attributes = {
153
+ "cause_by": _text(getattr(message_object, "cause_by", None)),
154
+ "metadata": getattr(message_object, "metadata", None),
155
+ "message_type": type(message_object).__name__,
159
156
  **attributes,
160
- )
157
+ }
158
+ target_names = None
159
+ if isinstance(target_name, (list, tuple, set, frozenset)):
160
+ target_names = list(target_name)
161
+ if isinstance(target_name, (set, frozenset)):
162
+ target_names.sort(key=str)
163
+ targets = [self._roles.get(str(name)) for name in target_names if name]
164
+ targets = [target for target in targets if target is not None]
165
+ else:
166
+ target = self._roles.get(str(target_name)) if isinstance(target_name, str) else None
167
+ targets = [target] if target is not None else []
168
+ shared_content_payload = None
169
+ if target_names is not None and targets:
170
+ recipient_labels = [target.name or target.id for target in targets]
171
+ shared_content_payload = {
172
+ "message_id": message_id,
173
+ "text": content,
174
+ "sender": source.name or source.id if source is not None else None,
175
+ "recipient": recipient_labels[0] if len(recipient_labels) == 1 else None,
176
+ "recipients": recipient_labels,
177
+ "role": role,
178
+ "kind": "agent_message",
179
+ "phase": "sent",
180
+ "content_state": "plaintext",
181
+ }
182
+ if not targets:
183
+ return self.observe_message(
184
+ message_id,
185
+ source=source,
186
+ target=None,
187
+ content=content,
188
+ role=role,
189
+ task=task,
190
+ content_payload=shared_content_payload,
191
+ **common_attributes,
192
+ )
193
+ message = None
194
+ for target in targets:
195
+ message = self.observe_message(
196
+ message_id,
197
+ source=source,
198
+ target=target,
199
+ content=content,
200
+ role=role,
201
+ task=task,
202
+ content_payload=shared_content_payload,
203
+ **common_attributes,
204
+ )
205
+ return message
161
206
 
162
207
  def observe_action(
163
208
  self,
@@ -49,6 +49,12 @@ def _iso_timestamp(value: float) -> str:
49
49
 
50
50
 
51
51
  def _decode_quoted(value: str) -> str:
52
+ # Synthetic Windows traces carry native drive paths with single
53
+ # backslashes (for example ``C:\\tmp``). Treating those as Python string
54
+ # escapes turns ``\\t`` into a tab and corrupts the observed path. Real
55
+ # Linux strace paths still use the normal C-style decoding below.
56
+ if re.match(r"^[A-Za-z]:[\\/]", value):
57
+ return value.replace(r'\"', '"').replace(r"\\", "\\")
52
58
  try:
53
59
  return str(ast.literal_eval(f'"{value}"'))
54
60
  except (SyntaxError, ValueError):
File without changes
File without changes
File without changes