execweave 0.8.18__tar.gz → 0.8.19__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 (176) hide show
  1. {execweave-0.8.18 → execweave-0.8.19}/PKG-INFO +1 -1
  2. {execweave-0.8.18 → execweave-0.8.19}/pyproject.toml +2 -2
  3. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/__init__.py +1 -1
  4. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/_dashboard_shell_base.py +3 -0
  5. execweave-0.8.19/src/execweave/viewer_execution_flow.py +433 -0
  6. execweave-0.8.19/src/execweave/viewer_execution_flow_safety.py +160 -0
  7. {execweave-0.8.18 → execweave-0.8.19}/.gitignore +0 -0
  8. {execweave-0.8.18 → execweave-0.8.19}/LICENSE +0 -0
  9. {execweave-0.8.18 → execweave-0.8.19}/README.md +0 -0
  10. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/__main__.py +0 -0
  11. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/_conversation_records_core.py +0 -0
  12. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/_http_proxy_base.py +0 -0
  13. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/_http_proxy_bounded.py +0 -0
  14. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/_http_proxy_stage.py +0 -0
  15. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/agent_bootstrap.py +0 -0
  16. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/agent_topology.py +0 -0
  17. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/agent_trace.py +0 -0
  18. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/agy_preview_sanitize.py +0 -0
  19. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/analysis.py +0 -0
  20. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/anthropic.py +0 -0
  21. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/anthropic_cli.py +0 -0
  22. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/anthropic_full_fidelity.py +0 -0
  23. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_adapter.py +0 -0
  24. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_adapter_base.py +0 -0
  25. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_full_fidelity.py +0 -0
  26. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_full_fidelity_base.py +0 -0
  27. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
  28. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_hook_cli.py +0 -0
  29. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_record.py +0 -0
  30. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_subagent_linkage.py +0 -0
  31. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/antigravity_trace_capability.py +0 -0
  32. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/auto_specialized.py +0 -0
  33. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/backends.py +0 -0
  34. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/benchmark.py +0 -0
  35. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_adapter.py +0 -0
  36. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_child_transcript.py +0 -0
  37. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_delegation.py +0 -0
  38. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_full_fidelity.py +0 -0
  39. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_hook_cli.py +0 -0
  40. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_hook_contract.py +0 -0
  41. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_model_observer.py +0 -0
  42. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/claude_record.py +0 -0
  43. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cli.py +0 -0
  44. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_adapter.py +0 -0
  45. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_conversation.py +0 -0
  46. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_full_fidelity.py +0 -0
  47. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_hook_cli.py +0 -0
  48. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_hook_entry.py +0 -0
  49. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_hook_lifecycle.py +0 -0
  50. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_message_diagnostics.py +0 -0
  51. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_message_transport_diagnostics.py +0 -0
  52. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_record.py +0 -0
  53. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_rollout_structures.py +0 -0
  54. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_rollout_trace.py +0 -0
  55. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/codex_rollout_trace_base.py +0 -0
  56. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/collector.py +0 -0
  57. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/command.py +0 -0
  58. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/content_evidence.py +0 -0
  59. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/content_store.py +0 -0
  60. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_archive.py +0 -0
  61. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_message_identity.py +0 -0
  62. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview.py +0 -0
  63. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_antigravity.py +0 -0
  64. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_claude.py +0 -0
  65. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_codex.py +0 -0
  66. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_common.py +0 -0
  67. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_generic.py +0 -0
  68. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_lines.py +0 -0
  69. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_preview_transcript.py +0 -0
  70. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_records.py +0 -0
  71. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_records_antigravity.py +0 -0
  72. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_records_codex.py +0 -0
  73. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_records_common.py +0 -0
  74. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/conversation_records_ollama.py +0 -0
  75. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/correlation.py +0 -0
  76. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_adapter.py +0 -0
  77. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_delegation.py +0 -0
  78. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_delegation_base.py +0 -0
  79. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_full_fidelity.py +0 -0
  80. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_hook_cli.py +0 -0
  81. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_hook_contract.py +0 -0
  82. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/cursor_record.py +0 -0
  83. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/dashboard_shell.py +0 -0
  84. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/entry.py +0 -0
  85. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/evidence_availability.py +0 -0
  86. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/evidence_grade.py +0 -0
  87. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/fidelity.py +0 -0
  88. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/filesystem.py +0 -0
  89. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/focus.py +0 -0
  90. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/graph.py +0 -0
  91. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/graph_ops.py +0 -0
  92. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/http_proxy.py +0 -0
  93. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/http_proxy_cli.py +0 -0
  94. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/inference_gateway.py +0 -0
  95. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/inference_gateway_cli.py +0 -0
  96. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/inference_gateway_full_fidelity.py +0 -0
  97. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/inference_identity.py +0 -0
  98. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/inference_identity_cli.py +0 -0
  99. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/integrity.py +0 -0
  100. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/integrity_cli.py +0 -0
  101. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/litellm_callback.py +0 -0
  102. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/litellm_callback_cli.py +0 -0
  103. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live.py +0 -0
  104. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_core.py +0 -0
  105. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view.py +0 -0
  106. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_extra_style.py +0 -0
  107. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_markup.py +0 -0
  108. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_process_layout.py +0 -0
  109. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_readability.py +0 -0
  110. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_script_a.py +0 -0
  111. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_script_b.py +0 -0
  112. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_script_c.py +0 -0
  113. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_script_d.py +0 -0
  114. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/live_view_style.py +0 -0
  115. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/model_runtime.py +0 -0
  116. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/model_runtime_cli.py +0 -0
  117. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/model_runtime_full_fidelity.py +0 -0
  118. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/openai_compatible.py +0 -0
  119. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/openai_compatible_cli.py +0 -0
  120. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/openai_compatible_full_fidelity.py +0 -0
  121. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_adapter.py +0 -0
  122. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_event_contract.py +0 -0
  123. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_full_fidelity.py +0 -0
  124. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_hook_cli.py +0 -0
  125. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_plugin_cli.py +0 -0
  126. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_record.py +0 -0
  127. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/opencode_task_linkage.py +0 -0
  128. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/overhead_benchmark.py +0 -0
  129. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/provider_capability.py +0 -0
  130. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/provider_lifecycle.py +0 -0
  131. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/provider_record.py +0 -0
  132. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/rule_pack.py +0 -0
  133. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/rule_pack_cli.py +0 -0
  134. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/runtime_endpoint_availability.py +0 -0
  135. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/scalability_benchmark.py +0 -0
  136. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/schema.py +0 -0
  137. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/scope.py +0 -0
  138. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/semantic.py +0 -0
  139. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/sink.py +0 -0
  140. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/strace_backend.py +0 -0
  141. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/stream_assembly.py +0 -0
  142. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/theme.py +0 -0
  143. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/top.py +0 -0
  144. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/top_cli.py +0 -0
  145. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/validate.py +0 -0
  146. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/vendor/dagre.min.js +0 -0
  147. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/view_cli.py +0 -0
  148. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer.py +0 -0
  149. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel.py +0 -0
  150. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
  151. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_claude.py +0 -0
  152. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_codex.py +0 -0
  153. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_cursor.py +0 -0
  154. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_default.py +0 -0
  155. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_ollama.py +0 -0
  156. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_opencode.py +0 -0
  157. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_agent_panel_registry.py +0 -0
  158. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_content_inspector.py +0 -0
  159. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_dashboard_clean.py +0 -0
  160. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_dashboard_focus.py +0 -0
  161. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_dashboard_hardening.py +0 -0
  162. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
  163. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_dashboard_pr70.py +0 -0
  164. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_external_endpoints.py +0 -0
  165. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_gif.py +0 -0
  166. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_layout_geometry.py +0 -0
  167. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_layout_v2.py +0 -0
  168. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_layout_v2_detached.py +0 -0
  169. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_limits.py +0 -0
  170. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_live_layout.py +0 -0
  171. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_projection.py +0 -0
  172. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_projection_base.py +0 -0
  173. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_projection_bridges.py +0 -0
  174. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_semantic_projection.py +0 -0
  175. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/viewer_tool_mirror_identity.py +0 -0
  176. {execweave-0.8.18 → execweave-0.8.19}/src/execweave/workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execweave
3
- Version: 0.8.18
3
+ Version: 0.8.19
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.18 release metadata
8
- version = "0.8.18"
7
+ # ExecWeave v0.8.19 release metadata
8
+ version = "0.8.19"
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.18"
3
+ __version__ = "0.8.19"
@@ -8,6 +8,8 @@ from .viewer_agent_panel import inject_agent_panel
8
8
  from .viewer_dashboard_clean import fold_budget_bootstrap, inject_live_dashboard_clean
9
9
  from .viewer_dashboard_focus import inject_live_dashboard_focus
10
10
  from .viewer_dashboard_pr70 import inject_pr70_dashboard_repairs
11
+ from .viewer_execution_flow import inject_execution_flow
12
+ from .viewer_execution_flow_safety import harden_execution_flow_projection
11
13
  from .viewer_layout_v2 import inject_layout_v2
12
14
  from .viewer_layout_v2_detached import inject_layout_v2_detached_cluster_policy
13
15
  from .viewer_limits import resolve_viewer_limits, viewer_limits_bootstrap
@@ -76,6 +78,7 @@ def _build_dashboard_html() -> str:
76
78
  html = inject_pr70_dashboard_repairs(html)
77
79
  html = inject_layout_v2(html)
78
80
  html = inject_layout_v2_detached_cluster_policy(html)
81
+ html = harden_execution_flow_projection(inject_execution_flow(html))
79
82
  return _align_agent_panel_topology(inject_agent_panel(_guard_compact_live_snapshot(html)))
80
83
 
81
84
 
@@ -0,0 +1,433 @@
1
+ from __future__ import annotations
2
+
3
+ _STARTUP_SEAM = "applyTheme(initialTheme());applyTransform();poll();"
4
+
5
+ EXECUTION_FLOW_SCRIPT = r"""
6
+ (function(){
7
+ const MODEL_RELATIONS=new Set([
8
+ 'USED_MODEL','INVOKED_MODEL','INVOKES_MODEL','INFERRED','SERVED_BY_MODEL',
9
+ 'SWITCHED_MODEL','MODEL_INVOCATION_COMPLETED'
10
+ ]);
11
+ const DIRECT_ACTION_RELATIONS=new Map([
12
+ ['SPAWNED_AGENT','spawn_agent'],
13
+ ['SPAWNED_SUBAGENT','spawn_agent'],
14
+ ['ASSIGNED_AGENT_TASK','assign_agent_task'],
15
+ ['REQUESTED_SUBTASK','assign_agent_task'],
16
+ ['SENT_AGENT_MESSAGE','send_input'],
17
+ ['CLOSED_AGENT','close_agent'],
18
+ ]);
19
+ const ACTION_RELATION=new Map([
20
+ ['spawn_agent','SPAWNED_AGENT'],
21
+ ['assign_agent_task','ASSIGNED_AGENT_TASK'],
22
+ ['send_input','SENT_AGENT_MESSAGE'],
23
+ ['wait_agent','WAITED_FOR'],
24
+ ['resume_agent','RESUMED_AGENT'],
25
+ ['close_agent','CLOSED_AGENT'],
26
+ ]);
27
+ const ACTION_ALIASES=new Map([
28
+ ['spawn','spawn_agent'],['spawnagent','spawn_agent'],['spawn_agent','spawn_agent'],
29
+ ['spawn_subagent','spawn_agent'],['create_agent','spawn_agent'],['create_subagent','spawn_agent'],
30
+ ['send','send_input'],['sendinput','send_input'],['send_input','send_input'],
31
+ ['sendmessage','send_input'],['send_message','send_input'],['agent_message','send_input'],
32
+ ['wait','wait_agent'],['waitagent','wait_agent'],['wait_agent','wait_agent'],
33
+ ['wait_for_agent','wait_agent'],['wait_for_agents','wait_agent'],
34
+ ['resume','resume_agent'],['resumeagent','resume_agent'],['resume_agent','resume_agent'],
35
+ ['close','close_agent'],['closeagent','close_agent'],['close_agent','close_agent'],
36
+ ['assign_agent_task','assign_agent_task'],['assign_task','assign_agent_task'],
37
+ ['request_subtask','assign_agent_task'],['subtask','assign_agent_task'],
38
+ ]);
39
+ const attrsOf=node=>node&&typeof node.attributes==='object'&&node.attributes?node.attributes:{};
40
+ const relation=edge=>String(edge?.relation||'').toUpperCase();
41
+ const actionName=value=>{
42
+ const text=String(value||'').trim().toLowerCase().replace(/[\s-]+/g,'_');
43
+ return ACTION_ALIASES.get(text)||null;
44
+ };
45
+ const moment=item=>{
46
+ if(Number.isInteger(item?.first_sequence))return{kind:0,value:item.first_sequence};
47
+ const attrs=attrsOf(item);
48
+ if(Number.isInteger(attrs.first_sequence))return{kind:0,value:attrs.first_sequence};
49
+ const stamp=item?.first_seen||attrs.first_seen||attrs.timestamp;
50
+ if(stamp)return{kind:1,value:String(stamp)};
51
+ return{kind:2,value:String(item?.id||'')};
52
+ };
53
+ const momentLE=(left,right)=>{
54
+ if(!left||!right)return false;
55
+ if(left.kind!==right.kind)return left.kind<right.kind;
56
+ return left.value<=right.value;
57
+ };
58
+ const provider=node=>String(attrsOf(node).provider||'unknown').toLowerCase();
59
+
60
+ function execweaveExecutionFlowProjection(display,raw){
61
+ if(!display||!Array.isArray(display.nodes)||!Array.isArray(display.edges))return display;
62
+ const rawNodes=Array.isArray(raw?.nodes)?raw.nodes:[],rawEdges=Array.isArray(raw?.edges)?raw.edges:[];
63
+ const rawById=new Map(rawNodes.filter(node=>node?.id).map(node=>[node.id,node]));
64
+ const agents=new Map(rawNodes.filter(node=>node?.type==='agent'&&node.id).map(node=>[node.id,node]));
65
+ if(!agents.size)return display;
66
+ const models=new Map(rawNodes.filter(node=>node?.type==='model'&&node.id).map(node=>[node.id,node]));
67
+
68
+ const incoming=new Map(),outgoing=new Map();
69
+ for(const edge of rawEdges){
70
+ if(!edge)continue;
71
+ if(!incoming.has(edge.target))incoming.set(edge.target,[]);
72
+ if(!outgoing.has(edge.source))outgoing.set(edge.source,[]);
73
+ incoming.get(edge.target).push(edge);outgoing.get(edge.source).push(edge);
74
+ }
75
+ const aliasToAgent=new Map();
76
+ for(const [id,node] of agents){
77
+ const attrs=attrsOf(node);
78
+ const aliases=[id,node.name,attrs.agent_id,attrs.thread_id,attrs.agent_path,attrs.child_agent_path,attrs.nickname];
79
+ for(const value of aliases)if(value!==undefined&&value!==null&&String(value).trim())aliasToAgent.set(String(value),id);
80
+ }
81
+ const resolveAgent=value=>{
82
+ if(value===undefined||value===null)return null;
83
+ const text=String(value);
84
+ if(agents.has(text))return text;
85
+ return aliasToAgent.get(text)||null;
86
+ };
87
+ const ownerForNode=node=>{
88
+ if(!node?.id)return null;
89
+ for(const edge of incoming.get(node.id)||[])if(agents.has(edge.source))return edge.source;
90
+ for(const edge of incoming.get(node.id)||[]){
91
+ const parent=rawById.get(edge.source);
92
+ if(parent?.type!=='agent_turn')continue;
93
+ for(const grand of incoming.get(parent.id)||[])if(agents.has(grand.source))return grand.source;
94
+ }
95
+ const attrs=attrsOf(node);
96
+ for(const key of ['owner_agent_id','agent_id','source_agent_id','thread_id','agent_path']){
97
+ const found=resolveAgent(attrs[key]);if(found)return found;
98
+ }
99
+ return null;
100
+ };
101
+ const agentForAnchor=id=>{
102
+ if(agents.has(id))return id;
103
+ const node=rawById.get(id);
104
+ if(!node)return null;
105
+ return ownerForNode(node);
106
+ };
107
+
108
+ const modelNameIndex=new Map();
109
+ for(const [id,node] of models){
110
+ const attrs=attrsOf(node);
111
+ for(const value of [id,node.name,attrs.model,attrs.model_name,attrs.native_name]){
112
+ if(value!==undefined&&value!==null&&String(value).trim())modelNameIndex.set(String(value).toLowerCase(),id);
113
+ }
114
+ }
115
+ const modelHintFor=node=>{
116
+ const attrs=attrsOf(node);
117
+ for(const key of ['model','model_name','codex_model','default_model','provider_model']){
118
+ const value=attrs[key];
119
+ if(value!==undefined&&value!==null&&String(value).trim())return String(value);
120
+ }
121
+ return null;
122
+ };
123
+ const modelIdFromHint=hint=>{
124
+ if(!hint)return null;
125
+ const lower=String(hint).toLowerCase();
126
+ if(modelNameIndex.has(lower))return modelNameIndex.get(lower);
127
+ for(const [key,id] of modelNameIndex)if(key.endsWith(lower)||lower.endsWith(key))return id;
128
+ return null;
129
+ };
130
+
131
+ const modelEventsByAgent=new Map();
132
+ const addModelEvent=(owner,modelId,item)=>{
133
+ if(!owner||!modelId||!agents.has(owner)||!models.has(modelId))return;
134
+ if(!modelEventsByAgent.has(owner))modelEventsByAgent.set(owner,[]);
135
+ modelEventsByAgent.get(owner).push({modelId,moment:moment(item||{}),edge:item});
136
+ };
137
+ for(const edge of rawEdges){
138
+ if(!agents.has(edge?.source)||!models.has(edge?.target)||!MODEL_RELATIONS.has(relation(edge)))continue;
139
+ addModelEvent(edge.source,edge.target,edge);
140
+ }
141
+ // Hooks and rollout formats often carry the active model on an agent-owned
142
+ // tool/turn/interaction object even when no direct agent -> model edge exists.
143
+ for(const node of rawNodes){
144
+ const owner=ownerForNode(node);
145
+ if(!owner)continue;
146
+ const modelId=modelIdFromHint(modelHintFor(node));
147
+ if(modelId)addModelEvent(owner,modelId,node);
148
+ }
149
+ for(const list of modelEventsByAgent.values())list.sort((a,b)=>{
150
+ if(a.moment.kind!==b.moment.kind)return a.moment.kind-b.moment.kind;
151
+ return a.moment.value<b.moment.value?-1:a.moment.value>b.moment.value?1:0;
152
+ });
153
+
154
+ const resolveModel=(owner,item)=>{
155
+ const hinted=modelIdFromHint(modelHintFor(item));
156
+ if(hinted)return hinted;
157
+ const list=modelEventsByAgent.get(owner)||[];
158
+ if(!list.length)return null;
159
+ const at=moment(item);
160
+ let chosen=null;
161
+ for(const event of list)if(momentLE(event.moment,at))chosen=event.modelId;
162
+ if(chosen)return chosen;
163
+ const unique=[...new Set(list.map(event=>event.modelId))];
164
+ return unique.length===1?unique[0]:null;
165
+ };
166
+
167
+ const occurrences=[];
168
+ const pushOccurrence=(kind,owner,targets,item,rawNodeIds=[],rawEdgeIds=[])=>{
169
+ if(!kind||!owner||!agents.has(owner))return;
170
+ occurrences.push({
171
+ kind,owner,targets:new Set((targets||[]).filter(id=>agents.has(id)&&id!==owner)),
172
+ item:item||{},modelId:resolveModel(owner,item||{}),
173
+ rawNodeIds:[...rawNodeIds],rawEdgeIds:[...rawEdgeIds],
174
+ });
175
+ };
176
+
177
+ for(const node of rawNodes){
178
+ if(node?.type!=='agent_interaction'||!node.id)continue;
179
+ const kind=actionName(attrsOf(node).kind||node.name);
180
+ if(!kind)continue;
181
+ const sources=(incoming.get(node.id)||[]).filter(edge=>relation(edge)==='STARTED_AGENT_INTERACTION');
182
+ const targets=(outgoing.get(node.id)||[]).filter(edge=>relation(edge)==='TARGETED_BY_AGENT_INTERACTION');
183
+ const owner=sources.map(edge=>agentForAnchor(edge.source)).find(Boolean);
184
+ const peerIds=targets.map(edge=>agentForAnchor(edge.target)).filter(Boolean);
185
+ if(owner)pushOccurrence(kind,owner,peerIds,node,[node.id],[...sources,...targets].map(edge=>edge.id).filter(Boolean));
186
+ }
187
+
188
+ for(const node of rawNodes){
189
+ if(node?.type!=='agent_message'||!node.id)continue;
190
+ const sent=(incoming.get(node.id)||[]).filter(edge=>relation(edge)==='SENT_AGENT_MESSAGE');
191
+ const delivered=(outgoing.get(node.id)||[]).filter(edge=>relation(edge)==='DELIVERED_AGENT_MESSAGE');
192
+ const owner=sent.map(edge=>agentForAnchor(edge.source)).find(Boolean);
193
+ const peerIds=delivered.map(edge=>agentForAnchor(edge.target)).filter(Boolean);
194
+ if(owner)pushOccurrence('send_input',owner,peerIds,node,[node.id],[...sent,...delivered].map(edge=>edge.id).filter(Boolean));
195
+ }
196
+
197
+ for(const edge of rawEdges){
198
+ const kind=DIRECT_ACTION_RELATIONS.get(relation(edge));
199
+ if(!kind)continue;
200
+ const owner=agentForAnchor(edge.source),target=agentForAnchor(edge.target);
201
+ if(owner&&target&&owner!==target)pushOccurrence(kind,owner,[target],edge,[],[edge.id].filter(Boolean));
202
+ }
203
+
204
+ for(const call of rawNodes){
205
+ if(!['tool_call','tool_call_observation'].includes(String(call?.type||''))||!call.id)continue;
206
+ let tool=null;
207
+ for(const edge of outgoing.get(call.id)||[]){
208
+ if(relation(edge)!=='USES_TOOL')continue;
209
+ const candidate=rawById.get(edge.target);
210
+ if(candidate?.type==='tool'){tool=candidate;break}
211
+ }
212
+ const kind=actionName(attrsOf(call).tool_name||call.name||attrsOf(tool).native_name||tool?.name);
213
+ if(!kind)continue;
214
+ const owner=ownerForNode(call);
215
+ if(!owner)continue;
216
+ const attrs=attrsOf(call),targets=[];
217
+ for(const key of ['target_agent_id','recipient_agent_id','recipient','agent_id']){
218
+ const found=resolveAgent(attrs[key]);if(found&&found!==owner)targets.push(found);
219
+ }
220
+ for(const key of ['target_agent_ids','recipient_agent_ids','recipients','agent_ids']){
221
+ const values=attrs[key];
222
+ if(Array.isArray(values))for(const value of values){const found=resolveAgent(value);if(found&&found!==owner)targets.push(found)}
223
+ }
224
+ pushOccurrence(kind,owner,targets,call,[call.id],[]);
225
+ }
226
+
227
+ if(!occurrences.length&&![...modelEventsByAgent.values()].some(list=>list.length))return display;
228
+
229
+ const groups=new Map();
230
+ for(const occurrence of occurrences){
231
+ const key=`${occurrence.owner}\u0000${occurrence.kind}\u0000${occurrence.modelId||''}`;
232
+ let group=groups.get(key);
233
+ if(!group){
234
+ group={owner:occurrence.owner,kind:occurrence.kind,modelId:occurrence.modelId,targets:new Set(),rawNodeIds:new Set(),rawEdgeIds:new Set(),occurrences:[]};
235
+ groups.set(key,group);
236
+ }
237
+ for(const id of occurrence.targets)group.targets.add(id);
238
+ for(const id of occurrence.rawNodeIds)group.rawNodeIds.add(id);
239
+ for(const id of occurrence.rawEdgeIds)group.rawEdgeIds.add(id);
240
+ group.occurrences.push(occurrence);
241
+ }
242
+
243
+ const roots=[...agents.values()].filter(node=>{
244
+ const attrs=attrsOf(node);
245
+ return attrs.viewer_root===true||attrs.agent_role==='root'||attrs.agent_path==='/root'||attrs.root_agent_path==='/root'||node.name==='/root';
246
+ }).map(node=>node.id);
247
+ const spawnParents=new Map();
248
+ for(const group of groups.values()){
249
+ if(group.kind!=='spawn_agent')continue;
250
+ for(const child of group.targets)if(!spawnParents.has(child))spawnParents.set(child,group.owner);
251
+ }
252
+ if(!roots.length){
253
+ const candidates=[...agents.keys()].filter(id=>!spawnParents.has(id));
254
+ if(candidates.length)roots.push(...candidates.sort());
255
+ }
256
+ const agentRank=new Map(roots.map(id=>[id,0]));
257
+ for(let pass=0;pass<agents.size+2;pass++){
258
+ let changed=false;
259
+ for(const group of groups.values()){
260
+ if(group.kind!=='spawn_agent'||!agentRank.has(group.owner))continue;
261
+ const modelOffset=group.modelId?1:0,actionRank=agentRank.get(group.owner)+modelOffset+1;
262
+ for(const child of group.targets){
263
+ const wanted=actionRank+1,current=agentRank.get(child);
264
+ if(current===undefined||wanted<current){agentRank.set(child,wanted);changed=true}
265
+ }
266
+ }
267
+ if(!changed)break;
268
+ }
269
+ for(const id of agents.keys())if(!agentRank.has(id))agentRank.set(id,0);
270
+
271
+ const addedNodes=[],addedEdges=[],contextIds=new Map(),contextifiedModels=new Set();
272
+ const contextId=(owner,modelId)=>`${owner}\u0000${modelId}`;
273
+ const ensureContext=(owner,modelId)=>{
274
+ if(!modelId||!models.has(modelId))return null;
275
+ const key=contextId(owner,modelId);
276
+ if(contextIds.has(key))return contextIds.get(key);
277
+ const model=models.get(modelId),id=`viewer:model-context:${encodeURIComponent(owner)}:${encodeURIComponent(modelId)}`;
278
+ const rank=(agentRank.get(owner)||0)+1;
279
+ const node={
280
+ id,type:'model',name:model.name||attrsOf(model).model||modelId,
281
+ attributes:{...attrsOf(model),viewer_only:true,viewer_model_context:true,owner_agent_id:owner,model_resource_id:modelId,viewer_flow_rank:rank,provider:attrsOf(model).provider||provider(agents.get(owner))}
282
+ };
283
+ addedNodes.push(node);
284
+ addedEdges.push({
285
+ id:`viewer:${owner}--MODEL_CONTEXT-->${id}`,source:owner,target:id,relation:'MODEL_CONTEXT',
286
+ count:1,viewer_only:true,causal:false,inferred:false,
287
+ attributes:{viewer_only:true,projection:'execution_flow',model_resource_id:modelId}
288
+ });
289
+ contextIds.set(key,id);contextifiedModels.add(modelId);return id;
290
+ };
291
+
292
+ for(const [owner,list] of modelEventsByAgent){
293
+ for(const modelId of new Set(list.map(item=>item.modelId)))ensureContext(owner,modelId);
294
+ }
295
+
296
+ const consumedRawNodes=new Set(),consumedRawEdges=new Set(),actionToolIds=new Set();
297
+ for(const group of groups.values()){
298
+ const context=group.modelId?ensureContext(group.owner,group.modelId):null;
299
+ const source=context||group.owner;
300
+ const baseRank=agentRank.get(group.owner)||0;
301
+ const actionRank=baseRank+(context?2:1);
302
+ const id=`viewer:orchestration:${encodeURIComponent(group.owner)}:${encodeURIComponent(group.modelId||'unresolved')}:${group.kind}`;
303
+ addedNodes.push({
304
+ id,type:'tool',name:group.kind,
305
+ attributes:{
306
+ viewer_only:true,viewer_orchestration_action:true,orchestration_kind:group.kind,
307
+ owner_agent_id:group.owner,model_context_id:context,model_resource_id:group.modelId,
308
+ viewer_flow_rank:actionRank,provider:provider(agents.get(group.owner)),
309
+ evidence_node_ids:[...group.rawNodeIds].sort(),evidence_edge_ids:[...group.rawEdgeIds].sort(),
310
+ viewer_occurrence_count:group.occurrences.length,
311
+ viewer_model_context_unresolved:!context||undefined,
312
+ }
313
+ });
314
+ addedEdges.push({
315
+ id:`viewer:${source}--PERFORMED_ORCHESTRATION-->${id}`,source,target:id,
316
+ relation:'PERFORMED_ORCHESTRATION',count:group.occurrences.length,viewer_only:true,causal:false,inferred:false,
317
+ attributes:{viewer_only:true,projection:'execution_flow',orchestration_kind:group.kind,evidence_node_ids:[...group.rawNodeIds],evidence_edge_ids:[...group.rawEdgeIds]}
318
+ });
319
+ const targetRelation=ACTION_RELATION.get(group.kind)||'TARGETED_AGENT';
320
+ for(const target of [...group.targets].sort()){
321
+ addedEdges.push({
322
+ id:`viewer:${id}--${targetRelation}-->${target}`,source:id,target,relation:targetRelation,
323
+ count:1,viewer_only:true,causal:false,inferred:false,
324
+ attributes:{viewer_only:true,projection:'execution_flow',evidence_node_ids:[...group.rawNodeIds],evidence_edge_ids:[...group.rawEdgeIds]}
325
+ });
326
+ }
327
+ for(const rawId of group.rawNodeIds)consumedRawNodes.add(rawId);
328
+ for(const rawId of group.rawEdgeIds)consumedRawEdges.add(rawId);
329
+ for(const rawId of group.rawNodeIds){
330
+ const rawNode=rawById.get(rawId);
331
+ if(!['tool_call','tool_call_observation'].includes(String(rawNode?.type||'')))continue;
332
+ for(const edge of outgoing.get(rawId)||[]){
333
+ if(relation(edge)==='USES_TOOL')actionToolIds.add(edge.target);
334
+ }
335
+ }
336
+ }
337
+
338
+ const flowNodes=display.nodes.map(node=>{
339
+ if(node?.type!=='agent'||!agentRank.has(node.id))return node;
340
+ return{...node,attributes:{...attrsOf(node),viewer_flow_rank:agentRank.get(node.id)}};
341
+ });
342
+
343
+ const modelRelation=edge=>MODEL_RELATIONS.has(relation(edge))||relation(edge)==='MODEL_CONTEXT';
344
+ const orchestrationRelation=edge=>DIRECT_ACTION_RELATIONS.has(relation(edge));
345
+ let nodes=flowNodes.filter(node=>!consumedRawNodes.has(node.id)&&!actionToolIds.has(node.id));
346
+ let edges=display.edges.filter(edge=>{
347
+ if(consumedRawEdges.has(edge.id))return false;
348
+ if(orchestrationRelation(edge)&&agents.has(edge.source)&&agents.has(edge.target))return false;
349
+ if(actionToolIds.has(edge.source)||actionToolIds.has(edge.target))return false;
350
+ if(contextifiedModels.has(edge.target)&&agents.has(edge.source)&&modelRelation(edge))return false;
351
+ return true;
352
+ });
353
+
354
+ for(const modelId of contextifiedModels){
355
+ const remaining=edges.filter(edge=>edge.source===modelId||edge.target===modelId);
356
+ if(!remaining.length)nodes=nodes.filter(node=>node.id!==modelId);
357
+ }
358
+
359
+ const known=new Set(nodes.map(node=>node.id));
360
+ for(const node of addedNodes)if(!known.has(node.id)){known.add(node.id);nodes.push(node)}
361
+ const edgeIds=new Set(edges.map(edge=>edge.id));
362
+ for(const edge of addedEdges)if(!edgeIds.has(edge.id)){edgeIds.add(edge.id);edges.push(edge)}
363
+
364
+ return{
365
+ ...display,nodes,edges,node_count:nodes.length,edge_count:edges.length,
366
+ dashboard_projection:{
367
+ ...(display.dashboard_projection||{}),
368
+ execution_flow_projection:true,
369
+ model_context_node_count:addedNodes.filter(node=>attrsOf(node).viewer_model_context).length,
370
+ orchestration_action_node_count:addedNodes.filter(node=>attrsOf(node).viewer_orchestration_action).length,
371
+ unique_agent_node_count:nodes.filter(node=>node.type==='agent').length,
372
+ }
373
+ };
374
+ }
375
+
376
+ if(typeof execweaveDashboardGraph==='function'){
377
+ const execweaveDashboardGraphBeforeExecutionFlow=execweaveDashboardGraph;
378
+ execweaveDashboardGraph=function(data){
379
+ return execweaveExecutionFlowProjection(execweaveDashboardGraphBeforeExecutionFlow(data),data);
380
+ };
381
+ }
382
+
383
+ function execweaveApplyExecutionFlowColumns(topo){
384
+ if(!topo?.spec||typeof nodeById==='undefined')return topo;
385
+ const ranked=[];
386
+ for(const [id,spec] of topo.spec){
387
+ const node=nodeById.get(id),rank=Number(attrsOf(node).viewer_flow_rank);
388
+ if(!node||!Number.isFinite(rank))continue;
389
+ ranked.push({id,spec,rank});
390
+ }
391
+ if(ranked.length<2)return topo;
392
+ const widthByRank=new Map();
393
+ for(const item of ranked)widthByRank.set(item.rank,Math.max(widthByRank.get(item.rank)||160,typeof execweaveWidthOf==='function'?execweaveWidthOf(item.id):160));
394
+ const ranks=[...widthByRank.keys()].sort((a,b)=>a-b);
395
+ const minRank=ranks[0],rootItems=ranked.filter(item=>item.rank===minRank);
396
+ let x=Math.min(...rootItems.map(item=>item.spec.x));
397
+ if(!Number.isFinite(x))x=0;
398
+ const xByRank=new Map();
399
+ for(const rank of ranks){
400
+ xByRank.set(rank,x);
401
+ x+=(widthByRank.get(rank)||160)+72;
402
+ }
403
+ for(const item of ranked){
404
+ item.spec.x=xByRank.get(item.rank);
405
+ item.spec.flowRank=item.rank;
406
+ }
407
+ if(typeof execweaveRecomputePorts==='function')execweaveRecomputePorts(topo);
408
+ return topo;
409
+ }
410
+ window.execweaveApplyExecutionFlowColumns=execweaveApplyExecutionFlowColumns;
411
+
412
+ if(typeof execweaveBuildTopology==='function'){
413
+ const execweaveBuildTopologyBeforeExecutionFlow=execweaveBuildTopology;
414
+ execweaveBuildTopology=function(){
415
+ return execweaveApplyExecutionFlowColumns(execweaveBuildTopologyBeforeExecutionFlow());
416
+ };
417
+ }
418
+
419
+ window.__execweaveExecutionFlow={
420
+ version:1,
421
+ project:execweaveExecutionFlowProjection,
422
+ applyColumns:execweaveApplyExecutionFlowColumns,
423
+ };
424
+ })();
425
+ """
426
+
427
+
428
+ def inject_execution_flow(html: str) -> str:
429
+ """Add provider-neutral model/orchestration/agent presentation without changing evidence."""
430
+
431
+ if html.count(_STARTUP_SEAM) != 1:
432
+ raise RuntimeError("execution-flow startup seam changed")
433
+ return html.replace(_STARTUP_SEAM, EXECUTION_FLOW_SCRIPT + "\n" + _STARTUP_SEAM, 1)
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ _DIRECT_ASSIGN = " ['ASSIGNED_AGENT_TASK','assign_agent_task'],\n"
5
+ _DIRECT_SUBTASK = " ['REQUESTED_SUBTASK','assign_agent_task'],\n"
6
+ _ASSIGN_TARGET = " ['assign_agent_task','ASSIGNED_AGENT_TASK'],"
7
+ _SAFE_ASSIGN_TARGET = " ['assign_agent_task','TARGETED_AGENT'],"
8
+ _ACTION_NAME = """ const actionName=value=>{
9
+ const text=String(value||'').trim().toLowerCase().replace(/[\\s-]+/g,'_');
10
+ return ACTION_ALIASES.get(text)||null;
11
+ };"""
12
+ _SAFE_ACTION_NAME = """ const actionName=value=>{
13
+ const raw=value&&typeof value==='object'&&typeof value.type==='string'?value.type:value;
14
+ const text=String(raw||'').trim().toLowerCase().replace(/[\\s-]+/g,'_');
15
+ return ACTION_ALIASES.get(text)||null;
16
+ };"""
17
+ _TOOL_RESOLVE = " if(relation(edge)!=='USES_TOOL')continue;"
18
+ _SAFE_TOOL_RESOLVE = " if(!['USES_TOOL','RESOLVED_TOOL'].includes(relation(edge)))continue;"
19
+ _TOOL_CONSUME = " if(relation(edge)==='USES_TOOL')actionToolIds.add(edge.target);"
20
+ _SAFE_TOOL_CONSUME = " if(['USES_TOOL','RESOLVED_TOOL'].includes(relation(edge)))actionToolIds.add(edge.target);"
21
+ _MODEL_ONLY_ACTIVATION = (
22
+ " if(!occurrences.length&&![...modelEventsByAgent.values()].some(list=>list.length))return display;"
23
+ )
24
+ _ACTION_ONLY_ACTIVATION = " if(!occurrences.length)return display;"
25
+ _TARGETED_ACTIVATION = """ const resolvedOccurrences=occurrences.filter(occurrence=>occurrence.targets.size>0);
26
+ if(!resolvedOccurrences.length)return display;"""
27
+ _WAIT_AWARE_ACTIVATION = """ const providerObservedEdge=edge=>{
28
+ const evidence=attrsOf(edge);
29
+ return edge?.viewer_only===true||Boolean(
30
+ evidence.provider||evidence.evidence_source||evidence.attribution||
31
+ evidence.provider_event||evidence.provider_event_type
32
+ );
33
+ };
34
+ const spawnEdges=rawEdges.filter(edge=>
35
+ providerObservedEdge(edge)&&['SPAWNED_AGENT','SPAWNED_SUBAGENT'].includes(relation(edge))&&
36
+ agents.has(agentForAnchor(edge.source))&&agents.has(agentForAnchor(edge.target))
37
+ );
38
+ const terminalEdges=rawEdges.filter(edge=>
39
+ providerObservedEdge(edge)&&['SUBAGENT_STOPPED','CLOSED_AGENT'].includes(relation(edge))
40
+ );
41
+ for(const occurrence of occurrences){
42
+ if(occurrence.kind!=='wait_agent'||occurrence.targets.size)continue;
43
+ const at=moment(occurrence.item);
44
+ for(const spawn of spawnEdges){
45
+ const owner=agentForAnchor(spawn.source),child=agentForAnchor(spawn.target);
46
+ if(owner!==occurrence.owner||!child||child===owner||!momentLE(moment(spawn),at))continue;
47
+ const ended=terminalEdges.some(edge=>{
48
+ const r=relation(edge),a=agentForAnchor(edge.source),b=agentForAnchor(edge.target);
49
+ const touches=r==='SUBAGENT_STOPPED'?a===child&&b===owner:a===owner&&b===child;
50
+ return touches&&momentLE(moment(edge),at);
51
+ });
52
+ if(!ended)occurrence.targets.add(child);
53
+ }
54
+ }
55
+ // The branch model answers "which model does this target agent belong to?".
56
+ // Prefer a unanimous target-agent model context over the actor's current model.
57
+ // This keeps old children under gpt-5.5 even if /root later switches to Luna.
58
+ for(const occurrence of occurrences){
59
+ if(!occurrence.targets.size)continue;
60
+ const targetModels=new Set(
61
+ [...occurrence.targets].map(target=>resolveModel(target,occurrence.item)).filter(Boolean)
62
+ );
63
+ if(targetModels.size===1)occurrence.modelId=[...targetModels][0];
64
+ }
65
+ const resolvedOccurrences=occurrences.filter(occurrence=>occurrence.targets.size>0);
66
+ if(!resolvedOccurrences.length)return display;
67
+ const flowKey=occurrence=>`${occurrence.owner}\\u0000${occurrence.kind}\\u0000${occurrence.modelId||''}`;
68
+ const resolvedKeys=new Set(resolvedOccurrences.map(flowKey));
69
+ // Once a group is justified by at least one resolved target, retain target-less
70
+ // provider call occurrences from that same owner/model/action group as evidence.
71
+ // This lets the projected action consume the original tool-call/tool definition
72
+ // instead of drawing a second visual spawn/send/wait node beside it.
73
+ const groupedOccurrences=occurrences.filter(occurrence=>
74
+ occurrence.targets.size>0||resolvedKeys.has(flowKey(occurrence))
75
+ );"""
76
+ _GROUP_BLOCK_START = """ const groups=new Map();
77
+ for(const occurrence of occurrences){"""
78
+ _EVIDENCE_GROUP_BLOCK_START = """ const groups=new Map();
79
+ for(const occurrence of groupedOccurrences){"""
80
+ _EAGER_CONTEXTS = """ for(const [owner,list] of modelEventsByAgent){
81
+ for(const modelId of new Set(list.map(item=>item.modelId)))ensureContext(owner,modelId);
82
+ }
83
+
84
+ """
85
+ _DIRECT_ACTION_LOOP = """ for(const edge of rawEdges){
86
+ const kind=DIRECT_ACTION_RELATIONS.get(relation(edge));
87
+ if(!kind)continue;
88
+ const owner=agentForAnchor(edge.source),target=agentForAnchor(edge.target);
89
+ if(owner&&target&&owner!==target)pushOccurrence(kind,owner,[target],edge,[],[edge.id].filter(Boolean));
90
+ }"""
91
+ _SAFE_DIRECT_ACTION_LOOP = """ for(const edge of rawEdges){
92
+ const kind=DIRECT_ACTION_RELATIONS.get(relation(edge));
93
+ if(!kind)continue;
94
+ const evidence=attrsOf(edge);
95
+ const providerObserved=edge.viewer_only===true||Boolean(
96
+ evidence.provider||evidence.evidence_source||evidence.attribution||
97
+ evidence.provider_event||evidence.provider_event_type
98
+ );
99
+ if(!providerObserved)continue;
100
+ const owner=agentForAnchor(edge.source),target=agentForAnchor(edge.target);
101
+ if(owner&&target&&owner!==target)pushOccurrence(kind,owner,[target],edge,[],[edge.id].filter(Boolean));
102
+ }"""
103
+
104
+
105
+ def harden_execution_flow_projection(html: str) -> str:
106
+ """Fail closed on ambiguous or evidence-poor execution-flow projection.
107
+
108
+ A model context is shown only for orchestration that resolves to real target agents.
109
+ The target agents' evidenced model context is authoritative when all resolved targets
110
+ agree; the actor model is only a fallback. Explicit targets are preferred.
111
+ Target-less ``wait_agent`` calls may use the provider-evidenced active-child set:
112
+ children spawned before the wait and without stop/close evidence before that wait.
113
+ Once an action group has real targets, target-less call evidence from the same
114
+ owner/model/action is folded into that single viewer action so the raw collaboration
115
+ tool is not rendered as a duplicate. Provider tool-call vocabularies ``USES_TOOL``
116
+ and ``RESOLVED_TOOL`` are equivalent call-to-tool support for this presentation-only
117
+ consumption step. Bare tool names and ambiguous provider subtask/profile evidence
118
+ never rewrite the main hierarchy.
119
+ """
120
+
121
+ for unsafe in (_DIRECT_ASSIGN, _DIRECT_SUBTASK):
122
+ if unsafe not in html:
123
+ raise RuntimeError("execution-flow assignment safety seam changed")
124
+ html = html.replace(unsafe, "", 1)
125
+ if _ASSIGN_TARGET not in html:
126
+ raise RuntimeError("execution-flow assignment target seam changed")
127
+ html = html.replace(_ASSIGN_TARGET, _SAFE_ASSIGN_TARGET, 1)
128
+
129
+ if _ACTION_NAME not in html:
130
+ raise RuntimeError("execution-flow tagged action-name seam changed")
131
+ html = html.replace(_ACTION_NAME, _SAFE_ACTION_NAME, 1)
132
+ if _TOOL_RESOLVE not in html or _TOOL_CONSUME not in html:
133
+ raise RuntimeError("execution-flow tool-resolution seam changed")
134
+ html = html.replace(_TOOL_RESOLVE, _SAFE_TOOL_RESOLVE, 1)
135
+ html = html.replace(_TOOL_CONSUME, _SAFE_TOOL_CONSUME, 1)
136
+
137
+ if _MODEL_ONLY_ACTIVATION not in html:
138
+ raise RuntimeError("execution-flow activation seam changed")
139
+ html = html.replace(_MODEL_ONLY_ACTIVATION, _ACTION_ONLY_ACTIVATION, 1)
140
+ if _ACTION_ONLY_ACTIVATION not in html:
141
+ raise RuntimeError("execution-flow targeted activation seam changed")
142
+ html = html.replace(_ACTION_ONLY_ACTIVATION, _TARGETED_ACTIVATION, 1)
143
+ if _TARGETED_ACTIVATION not in html:
144
+ raise RuntimeError("execution-flow wait target seam changed")
145
+ html = html.replace(_TARGETED_ACTIVATION, _WAIT_AWARE_ACTIVATION, 1)
146
+
147
+ # Anchor the evidence-group replacement on ``const groups``. Matching the bare
148
+ # loop text is unsafe because the wait/model-resolution block intentionally has
149
+ # earlier ``for(const occurrence of occurrences)`` loops of its own.
150
+ if _GROUP_BLOCK_START not in html:
151
+ raise RuntimeError("execution-flow evidence grouping seam changed")
152
+ html = html.replace(_GROUP_BLOCK_START, _EVIDENCE_GROUP_BLOCK_START, 1)
153
+
154
+ if _EAGER_CONTEXTS not in html:
155
+ raise RuntimeError("execution-flow eager model-context seam changed")
156
+ html = html.replace(_EAGER_CONTEXTS, "", 1)
157
+
158
+ if _DIRECT_ACTION_LOOP not in html:
159
+ raise RuntimeError("execution-flow direct action evidence seam changed")
160
+ return html.replace(_DIRECT_ACTION_LOOP, _SAFE_DIRECT_ACTION_LOOP, 1)
File without changes
File without changes
File without changes