execweave 0.8.31__tar.gz → 0.8.32__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.
- {execweave-0.8.31 → execweave-0.8.32}/PKG-INFO +1 -1
- {execweave-0.8.31 → execweave-0.8.32}/pyproject.toml +2 -2
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/__init__.py +1 -1
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_generic.py +9 -2
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/framework_adapters/camel.py +6 -0
- execweave-0.8.32/src/execweave/framework_adapters/camel_channel.py +71 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/framework_adapters/metagpt.py +15 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel.py +38 -5
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_execution_flow.py +1 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_execution_flow_provider_normalization.py +3 -1
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_execution_flow_safety.py +5 -20
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_flow_canvas.py +11 -0
- execweave-0.8.32/src/execweave/viewer_framework_flow.py +54 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_semantic_projection.py +5 -1
- execweave-0.8.32/src/execweave/viewer_session_flow.py +166 -0
- {execweave-0.8.31 → execweave-0.8.32}/.gitignore +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/LICENSE +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/README.md +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/__main__.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/_conversation_records_core.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/_dashboard_shell_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/_http_proxy_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/_http_proxy_bounded.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/_http_proxy_stage.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/agent_bootstrap.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/agent_topology.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/agent_trace.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/agy_preview_sanitize.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/analysis.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/anthropic.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/anthropic_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/anthropic_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_adapter.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_adapter_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_full_fidelity_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_hook_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_record.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_subagent_linkage.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/antigravity_trace_capability.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/auto_specialized.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/backends.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/benchmark.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_adapter.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_child_transcript.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_delegation.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_hook_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_hook_contract.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_model_observer.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/claude_record.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_adapter.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_conversation.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_hook_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_hook_entry.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_hook_lifecycle.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_message_diagnostics.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_message_transport_diagnostics.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_record.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_rollout_structures.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_rollout_trace.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/codex_rollout_trace_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/collector.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/command.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/content_evidence.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/content_store.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_archive.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_message_identity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_antigravity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_claude.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_codex.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_common.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_lines.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_preview_transcript.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_records.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_records_antigravity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_records_codex.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_records_common.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/conversation_records_ollama.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/correlation.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_adapter.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_delegation.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_delegation_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_hook_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_hook_contract.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_lifecycle.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/cursor_record.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/dashboard_shell.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/entry.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/evidence_availability.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/evidence_grade.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/filesystem.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/focus.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/framework_adapters/__init__.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/framework_adapters/autogen.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/framework_adapters/base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/framework_adapters/registry.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/graph.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/graph_ops.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/http_proxy.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/http_proxy_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/inference_gateway.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/inference_gateway_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/inference_gateway_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/inference_identity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/inference_identity_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/integrity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/integrity_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/litellm_callback.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/litellm_callback_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_core.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_extra_style.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_markup.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_process_layout.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_readability.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_script_a.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_script_b.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_script_c.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_script_d.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/live_view_style.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/model_runtime.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/model_runtime_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/model_runtime_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/openai_compatible.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/openai_compatible_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/openai_compatible_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_adapter.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_event_contract.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_full_fidelity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_hook_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_plugin_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_record.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/opencode_task_linkage.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/overhead_benchmark.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/provider_capability.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/provider_lifecycle.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/provider_record.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/rule_pack.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/rule_pack_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/runtime_endpoint_availability.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/scalability_benchmark.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/schema.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/scope.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/semantic.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/sink.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/strace_backend.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/stream_assembly.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/theme.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/top.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/top_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/validate.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/vendor/dagre.min.js +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/view_cli.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_claude.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_codex.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_cursor.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_default.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_ollama.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_opencode.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_agent_panel_registry.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_content_inspector.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_dashboard_clean.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_dashboard_focus.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_dashboard_hardening.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_dashboard_pr70.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_edge_decross.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_external_endpoints.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_flow_layout.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_gif.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_layout_geometry.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_layout_v2.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_layout_v2_detached.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_limits.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_live_layout.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_projection.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_projection_base.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_projection_bridges.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_tool_mirror_identity.py +0 -0
- {execweave-0.8.31 → execweave-0.8.32}/src/execweave/workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execweave
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.32
|
|
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.
|
|
8
|
-
version = "0.8.
|
|
7
|
+
# ExecWeave v0.8.32 release metadata
|
|
8
|
+
version = "0.8.32"
|
|
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"
|
|
@@ -74,7 +74,7 @@ def _routed_agent_message(
|
|
|
74
74
|
phase = value.get("phase")
|
|
75
75
|
task_name = value.get("task_name")
|
|
76
76
|
content_state = value.get("content_state")
|
|
77
|
-
|
|
77
|
+
message = _message(
|
|
78
78
|
timestamp=timestamp,
|
|
79
79
|
ordinal=ordinal,
|
|
80
80
|
kind=kind if isinstance(kind, str) and kind else "agent_message",
|
|
@@ -84,7 +84,10 @@ def _routed_agent_message(
|
|
|
84
84
|
phase=phase if isinstance(phase, str) and phase else None,
|
|
85
85
|
task_name=task_name if isinstance(task_name, str) and task_name else None,
|
|
86
86
|
content_state=content_state if isinstance(content_state, str) and content_state else "plaintext",
|
|
87
|
-
)
|
|
87
|
+
)
|
|
88
|
+
if isinstance(value.get("message_id"), str):
|
|
89
|
+
message["message_id"] = value["message_id"]
|
|
90
|
+
return [message]
|
|
88
91
|
|
|
89
92
|
|
|
90
93
|
def _generic_content_messages(
|
|
@@ -108,6 +111,10 @@ def _generic_content_messages(
|
|
|
108
111
|
if routed:
|
|
109
112
|
return routed
|
|
110
113
|
if "model_request" in kind or "model_response" in kind or "model_failure" in kind:
|
|
114
|
+
if "model_response" in kind:
|
|
115
|
+
response = _response_messages(value, timestamp=timestamp, ordinal=ordinal, agent_path=agent_path)
|
|
116
|
+
if response:
|
|
117
|
+
return response
|
|
111
118
|
structured = _structured_messages(value, timestamp=timestamp, ordinal=ordinal, agent_path=agent_path)
|
|
112
119
|
if structured:
|
|
113
120
|
return structured
|
|
@@ -140,6 +140,12 @@ class CAMELAdapter(FrameworkAdapter):
|
|
|
140
140
|
methods[name] = _callback_method(name, self)
|
|
141
141
|
return type("ExecWeaveCAMELWorkforceCallback", (base,), methods)()
|
|
142
142
|
|
|
143
|
+
def observe_task_channel(self, channel: Any, *, publishers: dict[str, EntityRef]) -> Any:
|
|
144
|
+
"""Record actual task dispatch/receipt and result dispatch/receipt."""
|
|
145
|
+
from .camel_channel import observe_task_channel
|
|
146
|
+
|
|
147
|
+
return observe_task_channel(self, channel, publishers)
|
|
148
|
+
|
|
143
149
|
def _callback_event(self, name: str, args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
|
|
144
150
|
payload = _payload(args, kwargs)
|
|
145
151
|
if name in {"log_message", "log_stream_chunk"}:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Observe an explicitly supplied CAMEL TaskChannel instance, not global classes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from .base import EntityRef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def observe_task_channel(adapter: Any, channel: Any, publishers: Mapping[str, EntityRef]) -> Any:
|
|
10
|
+
if getattr(channel, "_execweave_channel_adapter", None) is adapter:
|
|
11
|
+
return channel
|
|
12
|
+
names = ("post_task", "get_assigned_task_by_assignee", "return_task", "get_returned_task_by_publisher")
|
|
13
|
+
originals = {name: getattr(channel, name, None) for name in names}
|
|
14
|
+
if not all(callable(method) for method in originals.values()):
|
|
15
|
+
raise TypeError("CAMEL channel does not expose the supported task delivery boundaries")
|
|
16
|
+
routes: dict[str, dict[str, Any]] = {}
|
|
17
|
+
counter = 0
|
|
18
|
+
|
|
19
|
+
def record(route, *, reply=False, received=False):
|
|
20
|
+
source, target = route["publisher"], route["assignee"]
|
|
21
|
+
task = route["task"]
|
|
22
|
+
content = str(getattr(task, "result", "") or "") if reply else str(getattr(task, "content", "") or "")
|
|
23
|
+
if reply:
|
|
24
|
+
source, target = target, source
|
|
25
|
+
message = adapter.message(
|
|
26
|
+
route["id"] + (":reply" if reply else ":dispatch"), source, target,
|
|
27
|
+
content=content, role="agent", received=received,
|
|
28
|
+
routing_source="camel_task_channel_delivery" if received else "camel_task_channel_post",
|
|
29
|
+
native_task_id=str(task.id),
|
|
30
|
+
)
|
|
31
|
+
if source is None or target is None:
|
|
32
|
+
adapter.message_unrouted(message, callback_name="TaskChannel", missing_sender=source is None, missing_recipient=target is None)
|
|
33
|
+
|
|
34
|
+
async def post_task(task, publisher_id, assignee_id):
|
|
35
|
+
nonlocal counter
|
|
36
|
+
result = await originals["post_task"](task, publisher_id, assignee_id)
|
|
37
|
+
counter += 1
|
|
38
|
+
route = {"id": f"channel:{task.id}:{counter}", "task": task,
|
|
39
|
+
"publisher": publishers.get(str(publisher_id)) or adapter._agents.get(str(publisher_id)),
|
|
40
|
+
"assignee": adapter._agents.get(str(assignee_id))}
|
|
41
|
+
routes[str(task.id)] = route
|
|
42
|
+
record(route)
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
async def assigned(assignee_id):
|
|
46
|
+
task = await originals["get_assigned_task_by_assignee"](assignee_id)
|
|
47
|
+
route = routes.get(str(task.id))
|
|
48
|
+
if route:
|
|
49
|
+
record(route, received=True)
|
|
50
|
+
return task
|
|
51
|
+
|
|
52
|
+
async def return_task(task_id):
|
|
53
|
+
result = await originals["return_task"](task_id)
|
|
54
|
+
route = routes.get(str(task_id))
|
|
55
|
+
if route:
|
|
56
|
+
record(route, reply=True)
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
async def returned(publisher_id):
|
|
60
|
+
task = await originals["get_returned_task_by_publisher"](publisher_id)
|
|
61
|
+
route = routes.pop(str(task.id), None)
|
|
62
|
+
if route:
|
|
63
|
+
record(route, reply=True, received=True)
|
|
64
|
+
return task
|
|
65
|
+
|
|
66
|
+
channel.post_task = post_task
|
|
67
|
+
channel.get_assigned_task_by_assignee = assigned
|
|
68
|
+
channel.return_task = return_task
|
|
69
|
+
channel.get_returned_task_by_publisher = returned
|
|
70
|
+
channel._execweave_channel_adapter = adapter
|
|
71
|
+
return channel
|
|
@@ -165,6 +165,8 @@ class MetaGPTAdapter(FrameworkAdapter):
|
|
|
165
165
|
source_name = getattr(message_object, "sent_from", None)
|
|
166
166
|
target_name = getattr(message_object, "send_to", None)
|
|
167
167
|
source = self._roles.get(str(source_name)) if source_name else None
|
|
168
|
+
if source is None and source_name == "user":
|
|
169
|
+
source = self.entity("user", "user", name="user")
|
|
168
170
|
content = _text(getattr(message_object, "content", None))
|
|
169
171
|
role = _text(getattr(message_object, "role", None))
|
|
170
172
|
common_attributes = {
|
|
@@ -222,6 +224,19 @@ class MetaGPTAdapter(FrameworkAdapter):
|
|
|
222
224
|
)
|
|
223
225
|
return message
|
|
224
226
|
|
|
227
|
+
def observe_message_delivery(self, message_object: Any, *, recipient: EntityRef, task: EntityRef | None = None) -> EntityRef:
|
|
228
|
+
"""Call after the native Role.put_message accepted a message into its queue."""
|
|
229
|
+
self._message_counter += 1
|
|
230
|
+
return self.observe_message(
|
|
231
|
+
getattr(message_object, "id", None) or f"delivery-{self._message_counter}",
|
|
232
|
+
source=(self.entity("user", "user", name="user") if getattr(message_object, "sent_from", None) == "user"
|
|
233
|
+
else self._roles.get(str(getattr(message_object, "sent_from", "")))),
|
|
234
|
+
target=recipient,
|
|
235
|
+
content=_text(getattr(message_object, "content", None)),
|
|
236
|
+
role=_text(getattr(message_object, "role", None)), task=task, received=True,
|
|
237
|
+
routing_source="metagpt.Role.put_message",
|
|
238
|
+
)
|
|
239
|
+
|
|
225
240
|
def observe_action(
|
|
226
241
|
self,
|
|
227
242
|
action_id: str | int,
|
|
@@ -15,8 +15,8 @@ _AGENT_PANEL_CSS = r"""
|
|
|
15
15
|
#open-final{display:none!important}
|
|
16
16
|
.execweave-agent-view{display:grid;gap:12px}
|
|
17
17
|
.execweave-agent-card{border:1px solid var(--border);border-radius:10px;background:var(--panel2);overflow:hidden}
|
|
18
|
-
.execweave-agent-label{padding:9px 11px;border-bottom:1px solid var(--border);font-size:10px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
|
|
19
|
-
.execweave-agent-body{margin:0;padding:11px 12px;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--text)}
|
|
18
|
+
.execweave-agent-label,.execweave-communication-label{padding:9px 11px;border-bottom:1px solid var(--border);font-size:10px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
|
|
19
|
+
.execweave-agent-body,.execweave-message-body{margin:0;padding:11px 12px;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--text)}
|
|
20
20
|
.execweave-agent-empty{color:var(--muted);font-style:italic}
|
|
21
21
|
.execweave-agent-rounds{display:grid;gap:10px}
|
|
22
22
|
.execweave-agent-round{display:grid;gap:12px}
|
|
@@ -28,6 +28,8 @@ _AGENT_PANEL_CSS = r"""
|
|
|
28
28
|
.execweave-agent-older[open]>summary{border-bottom:1px solid var(--border)}
|
|
29
29
|
.execweave-agent-older>.execweave-agent-round{padding:10px 11px 11px}
|
|
30
30
|
.execweave-agent-when{font-variant-numeric:tabular-nums;color:var(--text)}
|
|
31
|
+
.execweave-message-history{border:1px solid var(--border);border-radius:10px;background:var(--panel2);margin:6px 0}
|
|
32
|
+
.execweave-message-history>summary{cursor:pointer;padding:9px 11px;font-size:11px;color:var(--muted);overflow-wrap:anywhere}
|
|
31
33
|
""".strip()
|
|
32
34
|
|
|
33
35
|
_AGENT_PANEL_JS = r"""
|
|
@@ -119,7 +121,7 @@ function foldStateFor(node){
|
|
|
119
121
|
function rememberVisibleFoldState(){
|
|
120
122
|
if(!selectedNode)return;
|
|
121
123
|
const state=foldStateFor(selectedNode);
|
|
122
|
-
for(const fold of details.querySelectorAll('.execweave-agent-older[data-fold-key]')){
|
|
124
|
+
for(const fold of details.querySelectorAll('.execweave-agent-older[data-fold-key],.execweave-message-history[data-fold-key]')){
|
|
123
125
|
const key=String(fold.dataset.foldKey||'');
|
|
124
126
|
if(key)state.set(key,fold.open);
|
|
125
127
|
}
|
|
@@ -348,6 +350,7 @@ function execweaveNodeCardsBase(node){
|
|
|
348
350
|
return rows;
|
|
349
351
|
}
|
|
350
352
|
if(kind==='process'){
|
|
353
|
+
if(Array.isArray(a.viewer_message_evidence)&&a.viewer_message_evidence.length)add('Unrouted callback messages',`${a.viewer_message_evidence.length} observations; sender/recipient not exposed. Original evidence retained in raw graph.`);
|
|
351
354
|
add('Command',a.cmdline);
|
|
352
355
|
add('Executable',a.exe);
|
|
353
356
|
add('Process',[a.pid&&`pid ${a.pid}`,a.ppid&&`parent ${a.ppid}`].filter(Boolean).join(' \u00b7 '));
|
|
@@ -438,8 +441,38 @@ function execweaveFillAssignedTask(round,node,isRoot){
|
|
|
438
441
|
const value=round||{cards:[]},cards=Array.isArray(value.cards)?value.cards.map(card=>[card[0],card[1]]):[];
|
|
439
442
|
const label=isRoot?'Prompt':'Task',index=cards.findIndex(card=>String(card[0]||'')===label);
|
|
440
443
|
if(index<0)cards.unshift([label,task]);else if(!commandText(cards[index][1]))cards[index][1]=task;
|
|
444
|
+
else if(attrs(node).conversation_scope==='framework_agent'&&commandText(cards[index][1])!==task)cards.unshift(['Assigned task',task]);
|
|
441
445
|
return{...value,cards};
|
|
442
446
|
}
|
|
447
|
+
function agentCommunicationHistory(node,messages){
|
|
448
|
+
// Read only this exact agent's conversation record. Never sweep sibling
|
|
449
|
+
// transcripts into the parent merely because they share a run or a model.
|
|
450
|
+
const candidates=messages.filter(message=>isObserved(message)&&!isInjected(message)&&(
|
|
451
|
+
String(message?.kind||'')==='agent_message'||
|
|
452
|
+
(String(message?.sender||'').startsWith('/')&&String(message?.recipient||'').startsWith('/')&&message.sender!==message.recipient)
|
|
453
|
+
));
|
|
454
|
+
const byMessage=new Map();
|
|
455
|
+
for(const message of candidates){
|
|
456
|
+
const key=message.message_id?JSON.stringify([message.message_id,message.sender,message.recipient,message.text]):messageKey(message);
|
|
457
|
+
const previous=byMessage.get(key);
|
|
458
|
+
if(!previous||message.phase==='received')byMessage.set(key,message);
|
|
459
|
+
}
|
|
460
|
+
const routed=[...byMessage.values()];
|
|
461
|
+
if(!routed.length)return null;
|
|
462
|
+
const section=document.createElement('section');section.className='execweave-agent-communication';
|
|
463
|
+
section.dataset.agentId=String(node.id);
|
|
464
|
+
const title=document.createElement('div');title.className='execweave-communication-label';title.textContent='Agent communication';section.appendChild(title);
|
|
465
|
+
const state=foldStateFor(node);
|
|
466
|
+
for(const message of [...routed].reverse()){
|
|
467
|
+
const fold=document.createElement('details');fold.className='execweave-message-history';
|
|
468
|
+
const key='message:'+messageKey(message);fold.dataset.foldKey=key;fold.open=state.get(key)===true;
|
|
469
|
+
const summary=document.createElement('summary');
|
|
470
|
+
summary.textContent=[moment(message.timestamp),`${message.sender||'Unknown sender'} → ${message.recipient||'Recipient not recorded'}`].filter(Boolean).join(' · ');
|
|
471
|
+
const body=document.createElement('pre');body.className='execweave-message-body';body.textContent=displayText(message);
|
|
472
|
+
fold.append(summary,body);fold.addEventListener('toggle',()=>state.set(key,fold.open));section.appendChild(fold);
|
|
473
|
+
}
|
|
474
|
+
return section;
|
|
475
|
+
}
|
|
443
476
|
function renderNode(node){
|
|
444
477
|
const rows=nodeCards(node);
|
|
445
478
|
if(!rows.length)return false;
|
|
@@ -460,8 +493,8 @@ function render(node){
|
|
|
460
493
|
const isRoot=nodeHasRootAuthority(node)||previewUsesRootRenderer(preview);
|
|
461
494
|
const rounds=(isRoot?rootRounds(messages,path||'/root'):childRounds(messages,path)).map(round=>execweaveFillAssignedTask(round,node,isRoot));
|
|
462
495
|
const tools=toolCallsFor(String(node.id||''));
|
|
463
|
-
const
|
|
464
|
-
const appendTools=()=>{if(
|
|
496
|
+
const communication=agentCommunicationHistory(node,messages);
|
|
497
|
+
const appendTools=()=>{if(communication)details.appendChild(communication);if(tools)details.appendChild(card('Tools',tools))};
|
|
465
498
|
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}
|
|
466
499
|
// A subagent borrows the moment and the wording of the unique canonical root round
|
|
467
500
|
// it belongs to. If root identity is ambiguous, the child keeps its own timestamp.
|
|
@@ -571,6 +571,7 @@ EXECUTION_FLOW_SCRIPT = r"""
|
|
|
571
571
|
const ranks=[...widthByRank.keys()].sort((a,b)=>a-b);
|
|
572
572
|
const minRank=ranks[0],rootItems=ranked.filter(item=>item.rank===minRank);
|
|
573
573
|
let x=Math.min(...rootItems.map(item=>item.spec.x));
|
|
574
|
+
if(rootItems.some(item=>attrsOf(nodeById.get(item.id)).viewer_session_flow))x=Math.min(...[...topo.spec.values()].map(spec=>spec.x));
|
|
574
575
|
if(!Number.isFinite(x))x=0;
|
|
575
576
|
const xByRank=new Map();
|
|
576
577
|
for(const rank of ranks){
|
{execweave-0.8.31 → execweave-0.8.32}/src/execweave/viewer_execution_flow_provider_normalization.py
RENAMED
|
@@ -102,4 +102,6 @@ def normalize_provider_execution_flow(html: str) -> str:
|
|
|
102
102
|
if html.count(needle) != 1:
|
|
103
103
|
raise RuntimeError(f"execution-flow provider normalization seam changed: {label}")
|
|
104
104
|
html = html.replace(needle, replacement, 1)
|
|
105
|
-
|
|
105
|
+
from .viewer_session_flow import inject_session_flow
|
|
106
|
+
|
|
107
|
+
return inject_session_flow(html)
|
|
@@ -123,23 +123,8 @@ _WAIT_AWARE_ACTIVATION = """ const spawnEdges=rawEdges.filter(edge=>
|
|
|
123
123
|
if(!ended)occurrence.targets.add(child);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// This keeps old children under gpt-5.5 even if /root later switches to Luna.
|
|
129
|
-
for(const occurrence of occurrences){
|
|
130
|
-
if(!occurrence.targets.size)continue;
|
|
131
|
-
// Child model lookup ignores the actor tool-call/item hint. Only the child's
|
|
132
|
-
// own model evidence may win, and every target must share one known model.
|
|
133
|
-
const targetModels=[];
|
|
134
|
-
let allKnown=true;
|
|
135
|
-
for(const target of occurrence.targets){
|
|
136
|
-
const modelId=resolveModel(target,null);
|
|
137
|
-
if(!modelId){allKnown=false;break}
|
|
138
|
-
targetModels.push(modelId);
|
|
139
|
-
}
|
|
140
|
-
const unique=new Set(targetModels);
|
|
141
|
-
if(allKnown&&unique.size===1)occurrence.modelId=[...unique][0];
|
|
142
|
-
}
|
|
126
|
+
// Actions belong to the actor's model at the time of the action. A child's
|
|
127
|
+
// model never changes the model that issued a delegation, message, or wait.
|
|
143
128
|
const resolvedOccurrences=occurrences.filter(occurrence=>occurrence.targets.size>0);
|
|
144
129
|
if(!resolvedOccurrences.length)return display;
|
|
145
130
|
const flowKey=occurrence=>`${occurrence.owner}\\u0000${occurrence.kind}\\u0000${occurrence.modelId||''}`;
|
|
@@ -284,8 +269,8 @@ def harden_execution_flow_projection(html: str) -> str:
|
|
|
284
269
|
"""Fail closed on ambiguous or evidence-poor execution-flow projection.
|
|
285
270
|
|
|
286
271
|
A model context is shown only for orchestration that resolves to real target agents.
|
|
287
|
-
The
|
|
288
|
-
|
|
272
|
+
The actor's evidenced model context owns the action, independently of the target's
|
|
273
|
+
model. Explicit targets are preferred.
|
|
289
274
|
Target-less ``wait_agent`` calls may use the provider-evidenced active-child set:
|
|
290
275
|
children spawned before the wait and without stop/close evidence before that wait.
|
|
291
276
|
Exact requester -> subtask -> assigned-child chains and exact owner-bound tool-call
|
|
@@ -361,4 +346,4 @@ def harden_execution_flow_projection(html: str) -> str:
|
|
|
361
346
|
if html.count(_FLOW_EARLY_RETURN) != 1 or html.count(_FLOW_END) != 1:
|
|
362
347
|
raise RuntimeError("execution-flow final geometry seam changed")
|
|
363
348
|
html = html.replace(_FLOW_EARLY_RETURN, _FLOW_SAFE_EARLY_RETURN, 1)
|
|
364
|
-
return html.replace(_FLOW_END, _FLOW_SAFE_END, 1)
|
|
349
|
+
return html.replace(_FLOW_END, _FLOW_SAFE_END, 1)
|
|
@@ -91,6 +91,10 @@ function execweaveFlowDisplay(data,display){
|
|
|
91
91
|
// another one inherits that node's layer and row, and reading those back places it
|
|
92
92
|
// exactly on top of the node it was copied from.
|
|
93
93
|
function execweaveFlowPreference(id){
|
|
94
|
+
const projected=nodeById.get(id)?.attributes||{};
|
|
95
|
+
// The browser's session projection runs after the Python flow projection. Its
|
|
96
|
+
// explicit ranks supersede copied resource layers, including synthetic nodes.
|
|
97
|
+
if(Number.isInteger(projected.viewer_flow_rank)&&[...nodeById.values()].some(n=>n.attributes?.viewer_session_flow))return{layer:projected.viewer_flow_rank,row:0};
|
|
94
98
|
const owned=execweaveFlowNodeIds();
|
|
95
99
|
// A folded member is still drawn when the browser's file/type budget keeps it. Its
|
|
96
100
|
// annotation carries the survivor's solved layer and row, so keep that preference;
|
|
@@ -163,6 +167,12 @@ function execweaveFlowSolve(){
|
|
|
163
167
|
// stack, which is to say only when it closes a cycle. Every other edge is kept, so a
|
|
164
168
|
// node always takes a column after the nodes that lead to it.
|
|
165
169
|
const feedback=new Set(),state=new Map(ids.map(id=>[id,0]));
|
|
170
|
+
// A recorded reply remains a return even if the dispatch was outside this
|
|
171
|
+
// recording; do not move the main agent behind its received message history.
|
|
172
|
+
for(const pair of pairs){
|
|
173
|
+
const source=nodeById.get(pair.source),target=nodeById.get(pair.target);
|
|
174
|
+
if(target?.attributes?.viewer_session_flow&&(source?.attributes?.viewer_framework_messages||source?.attributes?.viewer_return_message))feedback.add(pair.source+'|'+pair.target);
|
|
175
|
+
}
|
|
166
176
|
// With a viewer_flow payload, prefer the projection's layer order when choosing the
|
|
167
177
|
// DFS roots. Without one (the dashboard shell also accepts a raw static graph), keep
|
|
168
178
|
// the graph's insertion order: sorting IDs would visit `agent:child:*` before
|
|
@@ -180,6 +190,7 @@ function execweaveFlowSolve(){
|
|
|
180
190
|
const peers=after.get(frame.id);
|
|
181
191
|
if(frame.next>=peers.length){state.set(frame.id,2);stack.pop();continue}
|
|
182
192
|
const peer=peers[frame.next++];
|
|
193
|
+
if(feedback.has(frame.id+'|'+peer))continue;
|
|
183
194
|
const seen=state.get(peer);
|
|
184
195
|
if(seen===1)feedback.add(frame.id+'|'+peer);
|
|
185
196
|
else if(seen===0){state.set(peer,1);stack.push({id:peer,next:0})}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Framework-only communication projection. Provider graphs pass through unchanged."""
|
|
2
|
+
|
|
3
|
+
FRAMEWORK_FLOW_SCRIPT = r"""
|
|
4
|
+
function execweaveFrameworkMessages(display,raw){
|
|
5
|
+
const a=n=>n?.attributes||{},r=e=>String(e?.relation||'').toUpperCase();
|
|
6
|
+
const agents=new Map((raw?.nodes||[]).filter(n=>n.type==='agent'&&a(n).conversation_scope==='framework_agent').map(n=>[n.id,n]));
|
|
7
|
+
if(!agents.size)return display;
|
|
8
|
+
const groups=new Map();
|
|
9
|
+
for(const e of raw.edges||[]){
|
|
10
|
+
if(!['MESSAGE_SENT','MESSAGE_RECEIVED'].includes(r(e))||!agents.has(e.source)||!agents.has(e.target)||e.source===e.target||e.inferred===true)continue;
|
|
11
|
+
const key=`${e.source}\0${e.target}`;
|
|
12
|
+
if(!groups.has(key))groups.set(key,{source:e.source,target:e.target,edges:[]});
|
|
13
|
+
groups.get(key).edges.push(e);
|
|
14
|
+
}
|
|
15
|
+
if(!groups.size)return display;
|
|
16
|
+
let nodes=display.nodes.map(n=>({...n,attributes:{...a(n)}}));
|
|
17
|
+
const hidden=new Set(),hiddenNodes=new Set(),added=[];
|
|
18
|
+
// Stream-only messages and callback logs remain in their owner's inspector.
|
|
19
|
+
// They are not delivery events and must not create dangling routing branches.
|
|
20
|
+
const providers=new Set([...agents.values()].map(n=>a(n).provider));
|
|
21
|
+
for(const n of raw.nodes||[]){
|
|
22
|
+
if(n.type!=='message'||!providers.has(a(n).provider))continue;
|
|
23
|
+
const anchors=(raw.edges||[]).filter(e=>(e.target===n.id&&agents.has(e.source)&&r(e)==='MESSAGE_SENT')||(e.source===n.id&&r(e)==='OBSERVED_AT_PROCESS'));
|
|
24
|
+
if(!anchors.length)continue;
|
|
25
|
+
for(const e of anchors){
|
|
26
|
+
const owner=nodes.find(x=>x.id===(e.source===n.id?e.target:e.source));
|
|
27
|
+
if(owner)owner.attributes.viewer_message_evidence=[...(a(owner).viewer_message_evidence||[]),{message_id:n.id,routing_status:'recipient_not_recorded',evidence_edge_id:e.id}];
|
|
28
|
+
}
|
|
29
|
+
hiddenNodes.add(n.id);
|
|
30
|
+
}
|
|
31
|
+
nodes=nodes.filter(n=>!hiddenNodes.has(n.id));
|
|
32
|
+
for(const group of groups.values()){
|
|
33
|
+
const id=`viewer:messages:${encodeURIComponent(group.source)}:${encodeURIComponent(group.target)}`;
|
|
34
|
+
const receiver=agents.get(group.target);
|
|
35
|
+
const evidence=group.edges.map(e=>e.id).filter(Boolean);
|
|
36
|
+
for(const eid of evidence)hidden.add(eid);
|
|
37
|
+
nodes.push({id,type:'message',name:`Messages → ${receiver.name||group.target}`,attributes:{
|
|
38
|
+
viewer_only:true,viewer_framework_messages:true,provider:a(receiver).provider,
|
|
39
|
+
sender_agent_id:group.source,recipient_agent_id:group.target,
|
|
40
|
+
viewer_message_observations:group.edges.map(e=>({relation:e.relation,count:e.count||1,first_seen:e.first_seen,last_seen:e.last_seen,evidence_edge_id:e.id})),
|
|
41
|
+
evidence_edge_ids:evidence,
|
|
42
|
+
}});
|
|
43
|
+
const delivery=group.edges.some(e=>r(e)==='MESSAGE_RECEIVED')?'DELIVERED_AGENT_MESSAGE':'ADDRESSED_TO';
|
|
44
|
+
for(const [source,target,relation] of [[group.source,id,'MESSAGE_SENT'],[id,group.target,delivery]]){
|
|
45
|
+
added.push({id:`viewer:${source}--${relation}-->${target}`,source,target,relation,count:1,viewer_only:true,causal:false,inferred:false,
|
|
46
|
+
first_sequence:Math.min(...group.edges.map(e=>e.first_sequence).filter(Number.isInteger)),
|
|
47
|
+
first_seen:group.edges.map(e=>e.first_seen).filter(Boolean).sort()[0],
|
|
48
|
+
attributes:{evidence_edge_ids:evidence,routing_basis:'framework_explicit_participants'}});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const edges=display.edges.filter(e=>!hidden.has(e.id)&&!hiddenNodes.has(e.source)&&!hiddenNodes.has(e.target)).concat(added);
|
|
52
|
+
return {...display,nodes,edges,node_count:nodes.length,edge_count:edges.length};
|
|
53
|
+
}
|
|
54
|
+
"""
|
|
@@ -355,7 +355,11 @@ def collapse_framework_tasks(
|
|
|
355
355
|
task_id
|
|
356
356
|
for task_id, _ in sorted(
|
|
357
357
|
candidates.items(),
|
|
358
|
-
key=lambda item: (
|
|
358
|
+
key=lambda item: (
|
|
359
|
+
not bool(_attrs(node_by_id[item[0]]).get("action")),
|
|
360
|
+
bool(_attrs(node_by_id[item[0]]).get("task_prompt") or _attrs(node_by_id[item[0]]).get("prompt")),
|
|
361
|
+
item[1], item[0],
|
|
362
|
+
),
|
|
359
363
|
reverse=True,
|
|
360
364
|
)
|
|
361
365
|
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Session/actor presentation over evidence; never mutates the recorded graph."""
|
|
2
|
+
|
|
3
|
+
SESSION_FLOW_SCRIPT = r"""
|
|
4
|
+
function execweaveSessionFlow(display,raw){
|
|
5
|
+
const a=n=>n?.attributes||{},r=e=>String(e?.relation||'').toUpperCase();
|
|
6
|
+
const rawNodes=raw?.nodes||[],rawEdges=raw?.edges||[];
|
|
7
|
+
const byId=new Map(rawNodes.map(n=>[n.id,n]));
|
|
8
|
+
const sessions=rawNodes.filter(n=>n.type==='session');
|
|
9
|
+
// A recording can contain multiple roots/sessions. Never choose by array order.
|
|
10
|
+
const roots=rawNodes.filter(n=>n.type==='agent'&&(a(n).agent_role==='root'||a(n).viewer_root===true||a(n).agent_path==='/root'||n.name==='/root'));
|
|
11
|
+
if(!sessions.length||!roots.length)return display;
|
|
12
|
+
let nodes=display.nodes.map(n=>({...n,attributes:{...a(n),...(Number.isFinite(a(n).viewer_flow_rank)&&a(n).viewer_flow_rank>0?{viewer_flow_rank:a(n).viewer_flow_rank+1}:{})}}));
|
|
13
|
+
let edges=display.edges.map(e=>({...e}));
|
|
14
|
+
const observed=e=>e&&e.inferred!==true&&e.viewer_only!==true&&a(e).inferred!==true;
|
|
15
|
+
const unique=items=>{const ids=[...new Set(items.filter(Boolean))];return ids.length===1?ids[0]:null};
|
|
16
|
+
const ownerOf=(id,seen=new Set())=>{
|
|
17
|
+
const n=byId.get(id);if(!n||seen.has(id))return null;
|
|
18
|
+
if(n.type==='agent')return id;
|
|
19
|
+
if(!['provider_session','agent_turn','agent_execution','tool_call','tool_call_observation'].includes(n.type))return null;
|
|
20
|
+
seen=new Set(seen);seen.add(id);
|
|
21
|
+
return unique(rawEdges.filter(e=>e.target===id&&observed(e)).map(e=>ownerOf(e.source,seen)));
|
|
22
|
+
};
|
|
23
|
+
const rootSessions=new Map(),unresolved=[];
|
|
24
|
+
for(const root of roots){
|
|
25
|
+
const direct=rawEdges.filter(e=>e.source===root.id&&sessions.some(s=>s.id===e.target)&&observed(e));
|
|
26
|
+
let sid=unique(direct.map(e=>e.target));
|
|
27
|
+
// The run id is explicit recording membership, not a claim that the provider
|
|
28
|
+
// launched a process. Show that membership with a non-causal display edge.
|
|
29
|
+
if(!sid&&roots.length===1&&sessions.length===1&&sessions[0].id===`session:${raw.session_id}`)sid=sessions[0].id;
|
|
30
|
+
if(!sid){unresolved.push(root.id);continue}
|
|
31
|
+
rootSessions.set(root.id,sid);
|
|
32
|
+
const session=byId.get(sid);
|
|
33
|
+
if(!nodes.some(n=>n.id===sid))nodes.push({...session,attributes:{...a(session)}});
|
|
34
|
+
if(!edges.some(e=>e.source===root.id&&e.target===sid))edges.push({
|
|
35
|
+
id:`viewer:${root.id}--RECORDED_IN_SESSION-->${sid}`,source:root.id,target:sid,
|
|
36
|
+
relation:'RECORDED_IN_SESSION',count:1,viewer_only:true,causal:false,inferred:false,
|
|
37
|
+
attributes:{evidence_node_ids:[root.id,sid],recording_session_id:raw.session_id}
|
|
38
|
+
});
|
|
39
|
+
const sessionNode=nodes.find(n=>n.id===sid);sessionNode.attributes.viewer_flow_rank=1;
|
|
40
|
+
sessionNode.attributes.viewer_owner_agent_id=root.id;
|
|
41
|
+
const rootNode=nodes.find(n=>n.id===root.id);if(rootNode){rootNode.attributes.viewer_flow_rank=0;rootNode.attributes.viewer_session_flow=true}
|
|
42
|
+
}
|
|
43
|
+
if(!rootSessions.size)return display;
|
|
44
|
+
const modelRelations=new Set(['USED_MODEL','INVOKED_MODEL','INVOKES_MODEL','REQUESTED_MODEL','REQUESTS_MODEL_CALL','INFERRED','SWITCHED_MODEL','MODEL_INVOCATION_COMPLETED','MODEL_CALL_RESPONDS']);
|
|
45
|
+
const modelUses=[];
|
|
46
|
+
for(const e of rawEdges){
|
|
47
|
+
if(byId.get(e.target)?.type!=='model'||!modelRelations.has(r(e))||!observed(e))continue;
|
|
48
|
+
const owner=ownerOf(e.source);if(owner)modelUses.push({owner,model:e.target,edge:e});
|
|
49
|
+
}
|
|
50
|
+
const contextByKey=new Map();
|
|
51
|
+
for(const n of nodes)if(a(n).viewer_model_context)contextByKey.set(`${a(n).owner_agent_id}\0${a(n).model_resource_id}`,n.id);
|
|
52
|
+
const ensure=(owner,model)=>{
|
|
53
|
+
const key=`${owner}\0${model}`;let id=contextByKey.get(key);
|
|
54
|
+
const sid=rootSessions.get(owner);if(!sid)return id||null;
|
|
55
|
+
if(!id){
|
|
56
|
+
const resource=byId.get(model);if(!resource)return null;
|
|
57
|
+
id=`viewer:model-context:${encodeURIComponent(owner)}:${encodeURIComponent(model)}`;
|
|
58
|
+
nodes.push({...resource,id,attributes:{...a(resource),viewer_only:true,viewer_model_context:true,owner_agent_id:owner,model_resource_id:model,viewer_flow_rank:2}});
|
|
59
|
+
contextByKey.set(key,id);
|
|
60
|
+
}
|
|
61
|
+
const node=nodes.find(n=>n.id===id);if(node)node.attributes.viewer_flow_rank=2;
|
|
62
|
+
edges=edges.filter(e=>!(e.source===owner&&e.target===id&&r(e)==='MODEL_CONTEXT'));
|
|
63
|
+
if(!edges.some(e=>e.source===sid&&e.target===id))edges.push({
|
|
64
|
+
id:`viewer:${sid}--MODEL_CONTEXT-->${id}`,source:sid,target:id,relation:'MODEL_CONTEXT',
|
|
65
|
+
count:1,viewer_only:true,causal:false,inferred:false,
|
|
66
|
+
attributes:{owner_agent_id:owner,model_resource_id:model,evidence_edge_ids:modelUses.filter(u=>u.owner===owner&&u.model===model).map(u=>u.edge.id)}
|
|
67
|
+
});
|
|
68
|
+
return id;
|
|
69
|
+
};
|
|
70
|
+
for(const use of modelUses)if(rootSessions.has(use.owner))ensure(use.owner,use.model);
|
|
71
|
+
for(const n of [...nodes])if(a(n).viewer_model_context&&rootSessions.has(a(n).owner_agent_id))ensure(a(n).owner_agent_id,a(n).model_resource_id);
|
|
72
|
+
// Replace only the same actor/model's invocation edge. Child usage remains
|
|
73
|
+
// independent; a shared model name cannot steal its owner or session.
|
|
74
|
+
edges=edges.filter(e=>{
|
|
75
|
+
if(!modelRelations.has(r(e)))return true;
|
|
76
|
+
const owner=ownerOf(e.source);
|
|
77
|
+
return !(rootSessions.has(owner)&&contextByKey.has(`${owner}\0${e.target}`));
|
|
78
|
+
});
|
|
79
|
+
const modelAt=(owner,item)=>{
|
|
80
|
+
const hint=a(item).model||a(item).model_name||a(item).codex_model;
|
|
81
|
+
const uses=modelUses.filter(u=>u.owner===owner&&!['MODEL_INVOCATION_COMPLETED','MODEL_CALL_RESPONDS'].includes(r(u.edge)));
|
|
82
|
+
if(hint){const matched=unique(uses.filter(u=>u.model===hint||byId.get(u.model)?.name===hint||a(byId.get(u.model)).model_name===hint).map(u=>u.model));if(matched)return matched;return null}
|
|
83
|
+
const seq=item.first_sequence,stamp=item.first_seen;
|
|
84
|
+
const points=uses.flatMap(u=>[u,...(u.edge.last_sequence!==u.edge.first_sequence||u.edge.last_seen!==u.edge.first_seen?[{...u,edge:{...u.edge,first_sequence:u.edge.last_sequence,first_seen:u.edge.last_seen}}]:[])]);
|
|
85
|
+
// An aggregated model edge may have omitted intervening switches. Without
|
|
86
|
+
// an exact model hint, do not pretend its two endpoints are a complete log.
|
|
87
|
+
if(new Set(uses.map(u=>u.model)).size>1&&uses.some(u=>u.edge.count>2&&(Number.isInteger(seq)?u.edge.first_sequence<seq&&seq<u.edge.last_sequence:u.edge.first_seen<stamp&&stamp<u.edge.last_seen)))return null;
|
|
88
|
+
const timed=points.filter(u=>Number.isInteger(seq)&&Number.isInteger(u.edge.first_sequence)?u.edge.first_sequence<=seq:stamp&&u.edge.first_seen&&u.edge.first_seen<=stamp);
|
|
89
|
+
if(!timed.length)return null;
|
|
90
|
+
timed.sort((x,y)=>Number.isInteger(seq)?(y.edge.first_sequence||0)-(x.edge.first_sequence||0):String(y.edge.first_seen).localeCompare(String(x.edge.first_seen)));
|
|
91
|
+
const best=timed[0].edge;
|
|
92
|
+
return unique(timed.filter(u=>Number.isInteger(seq)?u.edge.first_sequence===best.first_sequence:u.edge.first_seen===best.first_seen).map(u=>u.model));
|
|
93
|
+
};
|
|
94
|
+
// Split call edges by exact occurrence ownership and model. A tool resource
|
|
95
|
+
// can be shared, but its calls must never inherit another actor's model.
|
|
96
|
+
const routedEdges=[];
|
|
97
|
+
for(const e of [...edges]){
|
|
98
|
+
if(!rootSessions.has(e.source)||!['CALLED_TOOL','REQUESTED_TOOL_CALL','REQUESTS_TOOL_CALL','USES_TOOL','PERFORMED_ORCHESTRATION','MESSAGE_SENT'].includes(r(e))){routedEdges.push(e);continue}
|
|
99
|
+
const target=nodes.find(n=>n.id===e.target);if(!target){routedEdges.push(e);continue}
|
|
100
|
+
const occurrences=(e.viewer_tool_call_occurrences||a(target).viewer_tool_call_occurrences||[]).filter(o=>!o.owner_id||o.owner_id===e.source);
|
|
101
|
+
const buckets=new Map();
|
|
102
|
+
for(const item of occurrences.length?occurrences:[null]){
|
|
103
|
+
const native=item&&(item.call_ids||[]).map(id=>byId.get(id)).find(Boolean);
|
|
104
|
+
const model=modelAt(e.source,item?{...item,attributes:a(native)}:{...e,attributes:a(target)});
|
|
105
|
+
if(!buckets.has(model))buckets.set(model,[]);
|
|
106
|
+
if(item)buckets.get(model).push(item);
|
|
107
|
+
}
|
|
108
|
+
for(const [model,items] of buckets){
|
|
109
|
+
const context=model&&ensure(e.source,model);
|
|
110
|
+
const source=context||e.source;
|
|
111
|
+
routedEdges.push({...e,id:context?`viewer:${context}--${r(e)}-->${e.target}:${encodeURIComponent(e.id)}`:e.id,source,
|
|
112
|
+
...(occurrences.length?{count:items.length,evidence_call_count:items.length,viewer_tool_call_occurrences:items}:{}),
|
|
113
|
+
...(context?{viewer_only:true,causal:false,viewer_original_edge_id:e.id}:{}),
|
|
114
|
+
attributes:{...a(e),...(context?{model_resource_id:model}: {model_attribution:'not_observed'})}});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
edges=routedEdges;
|
|
118
|
+
// A received message goes back to its actual recipient. The sender's model is
|
|
119
|
+
// a separate invocation, never a substitute destination for the reply.
|
|
120
|
+
for(const n of nodes){
|
|
121
|
+
if(!a(n).viewer_orchestration_action||a(n).orchestration_kind!=='send_input')continue;
|
|
122
|
+
const owner=a(n).owner_agent_id;
|
|
123
|
+
const returns=edges.filter(e=>e.source===n.id&&rootSessions.has(e.target)&&e.target!==owner);
|
|
124
|
+
if(!returns.length)continue;
|
|
125
|
+
edges=edges.map(e=>e.target===n.id&&r(e)==='PERFORMED_ORCHESTRATION'?{...e,source:owner,relation:'SENT_AGENT_MESSAGE',viewer_original_source:e.source}:e);
|
|
126
|
+
n.attributes.viewer_return_message=true;
|
|
127
|
+
delete n.attributes.viewer_flow_rank;
|
|
128
|
+
}
|
|
129
|
+
const usedIds=new Set(edges.flatMap(e=>[e.source,e.target]));
|
|
130
|
+
nodes=nodes.filter(n=>n.type!=='model'||usedIds.has(n.id));
|
|
131
|
+
const known=new Set(nodes.map(n=>n.id));
|
|
132
|
+
edges=edges.filter(e=>known.has(e.source)&&known.has(e.target));
|
|
133
|
+
// Root/session are the forward spine. Return edges remain in the graph but
|
|
134
|
+
// cannot push the root behind its children during layout.
|
|
135
|
+
const ranks=new Map([...rootSessions.keys()].map(id=>[id,0]));
|
|
136
|
+
const queue=[...ranks.keys()];
|
|
137
|
+
while(queue.length){
|
|
138
|
+
const source=queue.shift();
|
|
139
|
+
const outgoing=edges.filter(e=>e.source===source&&!rootSessions.has(e.target));
|
|
140
|
+
outgoing.sort((x,y)=>(r(x)==='MODEL_CONTEXT'?-1:0)-(r(y)==='MODEL_CONTEXT'?-1:0));
|
|
141
|
+
for(const e of outgoing){
|
|
142
|
+
if(ranks.has(e.target))continue;
|
|
143
|
+
ranks.set(e.target,ranks.get(source)+1);queue.push(e.target);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for(const n of nodes){
|
|
147
|
+
if(ranks.has(n.id))n.attributes.viewer_flow_rank=ranks.get(n.id);
|
|
148
|
+
else if(n.type==='agent')n.attributes.viewer_flow_rank=3;
|
|
149
|
+
}
|
|
150
|
+
return {...display,nodes,edges,node_count:nodes.length,edge_count:edges.length,dashboard_projection:{...display.dashboard_projection,session_flow:true,unresolved_root_sessions:unresolved}};
|
|
151
|
+
}
|
|
152
|
+
function execweaveExecutionFlowProjection(display,raw){
|
|
153
|
+
return execweaveSessionFlow(execweaveFrameworkMessages(execweaveExecutionFlowBeforeSession(display,raw),raw),raw);
|
|
154
|
+
}
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def inject_session_flow(script: str) -> str:
|
|
159
|
+
from .viewer_framework_flow import FRAMEWORK_FLOW_SCRIPT
|
|
160
|
+
|
|
161
|
+
seam = " function execweaveExecutionFlowProjection(display,raw){"
|
|
162
|
+
anchor = " if(typeof execweaveDashboardGraph==='function'){"
|
|
163
|
+
if script.count(seam) != 1 or script.count(anchor) != 1:
|
|
164
|
+
raise RuntimeError("session flow projection seam changed")
|
|
165
|
+
script = script.replace(seam, " function execweaveExecutionFlowBeforeSession(display,raw){", 1)
|
|
166
|
+
return script.replace(anchor, FRAMEWORK_FLOW_SCRIPT + SESSION_FLOW_SCRIPT + "\n" + anchor, 1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|