execweave 0.8.29__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.29 → execweave-0.8.32}/PKG-INFO +1 -1
- {execweave-0.8.29 → execweave-0.8.32}/pyproject.toml +2 -2
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/__init__.py +1 -1
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_generic.py +9 -2
- {execweave-0.8.29 → 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.29 → execweave-0.8.32}/src/execweave/framework_adapters/metagpt.py +15 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel.py +40 -5
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_execution_flow.py +1 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_execution_flow_provider_normalization.py +3 -1
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_execution_flow_safety.py +5 -20
- {execweave-0.8.29 → 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.29 → execweave-0.8.32}/src/execweave/viewer_semantic_projection.py +154 -0
- execweave-0.8.32/src/execweave/viewer_session_flow.py +166 -0
- {execweave-0.8.29 → execweave-0.8.32}/.gitignore +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/LICENSE +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/README.md +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/__main__.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/_conversation_records_core.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/_dashboard_shell_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/_http_proxy_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/_http_proxy_bounded.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/_http_proxy_stage.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/agent_bootstrap.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/agent_topology.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/agent_trace.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/agy_preview_sanitize.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/analysis.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/anthropic.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/anthropic_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/anthropic_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_adapter.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_adapter_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_full_fidelity_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_full_fidelity_collaboration_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_hook_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_record.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_subagent_linkage.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/antigravity_trace_capability.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/auto_specialized.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/backends.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/benchmark.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_adapter.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_child_transcript.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_delegation.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_hook_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_hook_contract.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_model_observer.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/claude_record.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_adapter.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_conversation.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_hook_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_hook_entry.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_hook_lifecycle.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_message_diagnostics.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_message_transport_diagnostics.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_record.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_rollout_structures.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_rollout_trace.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/codex_rollout_trace_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/collector.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/command.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/content_evidence.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/content_store.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_archive.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_message_identity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_antigravity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_claude.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_codex.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_common.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_lines.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_preview_transcript.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_records.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_records_antigravity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_records_codex.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_records_common.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/conversation_records_ollama.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/correlation.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_adapter.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_delegation.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_delegation_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_hook_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_hook_contract.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_lifecycle.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/cursor_record.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/dashboard_shell.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/entry.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/evidence_availability.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/evidence_grade.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/filesystem.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/focus.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/framework_adapters/__init__.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/framework_adapters/autogen.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/framework_adapters/base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/framework_adapters/registry.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/graph.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/graph_ops.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/http_proxy.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/http_proxy_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/inference_gateway.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/inference_gateway_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/inference_gateway_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/inference_identity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/inference_identity_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/integrity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/integrity_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/litellm_callback.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/litellm_callback_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_core.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_extra_style.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_markup.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_process_layout.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_readability.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_script_a.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_script_b.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_script_c.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_script_d.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/live_view_style.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/model_runtime.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/model_runtime_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/model_runtime_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/openai_compatible.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/openai_compatible_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/openai_compatible_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_adapter.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_event_contract.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_full_fidelity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_hook_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_plugin_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_record.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/opencode_task_linkage.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/overhead_benchmark.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/provider_capability.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/provider_lifecycle.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/provider_record.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/rule_pack.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/rule_pack_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/runtime_endpoint_availability.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/scalability_benchmark.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/schema.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/scope.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/semantic.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/sink.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/strace_backend.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/stream_assembly.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/theme.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/top.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/top_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/validate.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/vendor/dagre.min.js +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/view_cli.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_antigravity.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_claude.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_codex.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_cursor.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_default.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_ollama.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_opencode.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_agent_panel_registry.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_content_inspector.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_dashboard_clean.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_dashboard_focus.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_dashboard_hardening.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_dashboard_hardening_v2.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_dashboard_pr70.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_edge_decross.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_external_endpoints.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_flow_layout.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_gif.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_layout_geometry.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_layout_v2.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_layout_v2_detached.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_limits.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_live_layout.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_projection.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_projection_base.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_projection_bridges.py +0 -0
- {execweave-0.8.29 → execweave-0.8.32}/src/execweave/viewer_tool_mirror_identity.py +0 -0
- {execweave-0.8.29 → 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 '));
|
|
@@ -420,6 +423,8 @@ function toolOccurrenceSection(node){
|
|
|
420
423
|
}
|
|
421
424
|
function execweaveAssignedTaskText(agent){
|
|
422
425
|
if(!agent||String(agent.type||'')!=='agent')return '';
|
|
426
|
+
const projected=attrs(agent),attached=projected.viewer_assigned_task_prompt||projected.viewer_assigned_task_name;
|
|
427
|
+
if(attached)return commandText(attached);
|
|
423
428
|
const graph=rawGraph(),nodes=Array.isArray(graph.nodes)?graph.nodes:[],edges=Array.isArray(graph.edges)?graph.edges:[];
|
|
424
429
|
const nodeMap=new Map(nodes.filter(node=>node&&node.id).map(node=>[String(node.id),node]));
|
|
425
430
|
const exactAssignment=['ASSIGNED','AGENT','TASK'].join('_');
|
|
@@ -436,8 +441,38 @@ function execweaveFillAssignedTask(round,node,isRoot){
|
|
|
436
441
|
const value=round||{cards:[]},cards=Array.isArray(value.cards)?value.cards.map(card=>[card[0],card[1]]):[];
|
|
437
442
|
const label=isRoot?'Prompt':'Task',index=cards.findIndex(card=>String(card[0]||'')===label);
|
|
438
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]);
|
|
439
445
|
return{...value,cards};
|
|
440
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
|
+
}
|
|
441
476
|
function renderNode(node){
|
|
442
477
|
const rows=nodeCards(node);
|
|
443
478
|
if(!rows.length)return false;
|
|
@@ -458,8 +493,8 @@ function render(node){
|
|
|
458
493
|
const isRoot=nodeHasRootAuthority(node)||previewUsesRootRenderer(preview);
|
|
459
494
|
const rounds=(isRoot?rootRounds(messages,path||'/root'):childRounds(messages,path)).map(round=>execweaveFillAssignedTask(round,node,isRoot));
|
|
460
495
|
const tools=toolCallsFor(String(node.id||''));
|
|
461
|
-
const
|
|
462
|
-
const appendTools=()=>{if(
|
|
496
|
+
const communication=agentCommunicationHistory(node,messages);
|
|
497
|
+
const appendTools=()=>{if(communication)details.appendChild(communication);if(tools)details.appendChild(card('Tools',tools))};
|
|
463
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}
|
|
464
499
|
// A subagent borrows the moment and the wording of the unique canonical root round
|
|
465
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.29 → 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
|
+
"""
|
|
@@ -258,6 +258,155 @@ def orphan_audit(nodes: list[dict[str, Any]], edges: list[dict[str, Any]]) -> di
|
|
|
258
258
|
return {"count": sum(item["count"] for item in by_type), "by_type": by_type}
|
|
259
259
|
|
|
260
260
|
|
|
261
|
+
_FRAMEWORK_TASK_RELATIONS = frozenset(
|
|
262
|
+
{"ASSIGNED_TO", "TASK_CREATED", "TASK_STARTED", "TASK_UPDATED", "TASK_COMPLETED", "TASK_FAILED"}
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _framework_task_edge_score(edge: dict[str, Any]) -> tuple[int, str, str]:
|
|
267
|
+
sequence = edge.get("last_sequence")
|
|
268
|
+
if not isinstance(sequence, int) or isinstance(sequence, bool):
|
|
269
|
+
sequence = edge.get("first_sequence")
|
|
270
|
+
if not isinstance(sequence, int) or isinstance(sequence, bool):
|
|
271
|
+
sequence = -1
|
|
272
|
+
return (sequence, str(edge.get("last_seen") or edge.get("first_seen") or ""), str(edge.get("id") or ""))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _framework_task_text(task: dict[str, Any]) -> str:
|
|
276
|
+
attributes = _attrs(task)
|
|
277
|
+
for value in (
|
|
278
|
+
attributes.get("task_prompt"),
|
|
279
|
+
attributes.get("prompt"),
|
|
280
|
+
attributes.get("description"),
|
|
281
|
+
task.get("name"),
|
|
282
|
+
):
|
|
283
|
+
if isinstance(value, str) and value.strip():
|
|
284
|
+
return value.strip()
|
|
285
|
+
return ""
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def collapse_framework_tasks(
|
|
289
|
+
nodes: list[dict[str, Any]],
|
|
290
|
+
edges: list[dict[str, Any]],
|
|
291
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, int]]:
|
|
292
|
+
"""Fold framework task presentation into the owning agent, like provider views.
|
|
293
|
+
|
|
294
|
+
Framework adapters retain first-class task entities in the raw graph because task
|
|
295
|
+
lifecycle and prompt evidence must remain auditable. Provider dashboards expose
|
|
296
|
+
that same information on the agent card, however, so the viewer projection hides
|
|
297
|
+
only framework task nodes and copies the latest authoritative task text onto each
|
|
298
|
+
assigned framework agent. Provider graphs are untouched because they do not use
|
|
299
|
+
the framework-agent conversation scope.
|
|
300
|
+
"""
|
|
301
|
+
node_by_id = {
|
|
302
|
+
str(node["id"]): node
|
|
303
|
+
for node in nodes
|
|
304
|
+
if isinstance(node, dict) and isinstance(node.get("id"), str)
|
|
305
|
+
}
|
|
306
|
+
framework_agents = {
|
|
307
|
+
node_id
|
|
308
|
+
for node_id, node in node_by_id.items()
|
|
309
|
+
if node.get("type") == "agent" and _attrs(node).get("conversation_scope") == "framework_agent"
|
|
310
|
+
}
|
|
311
|
+
if not framework_agents:
|
|
312
|
+
return nodes, edges, {
|
|
313
|
+
"framework_task_node_count": 0,
|
|
314
|
+
"framework_task_content_node_count": 0,
|
|
315
|
+
"framework_task_agent_count": 0,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
task_ids: set[str] = set()
|
|
319
|
+
tasks_by_agent: dict[str, list[tuple[tuple[int, str, str], str]]] = defaultdict(list)
|
|
320
|
+
for edge in edges:
|
|
321
|
+
if not isinstance(edge, dict) or edge.get("relation") not in _FRAMEWORK_TASK_RELATIONS:
|
|
322
|
+
continue
|
|
323
|
+
source = str(edge.get("source") or "")
|
|
324
|
+
target = str(edge.get("target") or "")
|
|
325
|
+
source_node = node_by_id.get(source)
|
|
326
|
+
target_node = node_by_id.get(target)
|
|
327
|
+
task_id = source if source_node and source_node.get("type") == "task" else target if target_node and target_node.get("type") == "task" else None
|
|
328
|
+
if task_id is None:
|
|
329
|
+
continue
|
|
330
|
+
agent_id = source if source in framework_agents else target if target in framework_agents else None
|
|
331
|
+
if agent_id is None:
|
|
332
|
+
continue
|
|
333
|
+
task_ids.add(task_id)
|
|
334
|
+
tasks_by_agent[agent_id].append((_framework_task_edge_score(edge), task_id))
|
|
335
|
+
|
|
336
|
+
if not task_ids:
|
|
337
|
+
return nodes, edges, {
|
|
338
|
+
"framework_task_node_count": 0,
|
|
339
|
+
"framework_task_content_node_count": 0,
|
|
340
|
+
"framework_task_agent_count": 0,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
projected_nodes: list[dict[str, Any]] = []
|
|
344
|
+
for node in nodes:
|
|
345
|
+
node_id = str(node.get("id") or "")
|
|
346
|
+
if node_id in task_ids:
|
|
347
|
+
continue
|
|
348
|
+
if node.get("type") == "agent" and node_id in framework_agents:
|
|
349
|
+
candidates: dict[str, tuple[int, str, str]] = {}
|
|
350
|
+
for score, task_id in tasks_by_agent.get(node_id, []):
|
|
351
|
+
previous = candidates.get(task_id)
|
|
352
|
+
if previous is None or score > previous:
|
|
353
|
+
candidates[task_id] = score
|
|
354
|
+
ordered_ids = [
|
|
355
|
+
task_id
|
|
356
|
+
for task_id, _ in sorted(
|
|
357
|
+
candidates.items(),
|
|
358
|
+
key=lambda item: (
|
|
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
|
+
),
|
|
363
|
+
reverse=True,
|
|
364
|
+
)
|
|
365
|
+
]
|
|
366
|
+
latest_task = node_by_id.get(ordered_ids[0]) if ordered_ids else None
|
|
367
|
+
attributes = dict(_attrs(node))
|
|
368
|
+
attributes["viewer_assigned_task_ids"] = ordered_ids
|
|
369
|
+
attributes["viewer_assigned_task_count"] = len(ordered_ids)
|
|
370
|
+
if latest_task is not None:
|
|
371
|
+
attributes["viewer_assigned_task_id"] = str(latest_task.get("id") or "")
|
|
372
|
+
attributes["viewer_assigned_task_name"] = str(latest_task.get("name") or "")
|
|
373
|
+
task_text = _framework_task_text(latest_task)
|
|
374
|
+
if task_text:
|
|
375
|
+
attributes["viewer_assigned_task_prompt"] = task_text
|
|
376
|
+
projected_nodes.append({**node, "attributes": attributes})
|
|
377
|
+
continue
|
|
378
|
+
projected_nodes.append(node)
|
|
379
|
+
|
|
380
|
+
hidden_content_ids: set[str] = set()
|
|
381
|
+
for node in nodes:
|
|
382
|
+
node_id = str(node.get("id") or "")
|
|
383
|
+
if node.get("type") != "observed_content" or not node_id:
|
|
384
|
+
continue
|
|
385
|
+
incident = [
|
|
386
|
+
edge
|
|
387
|
+
for edge in edges
|
|
388
|
+
if edge.get("source") == node_id or edge.get("target") == node_id
|
|
389
|
+
]
|
|
390
|
+
if incident and all(
|
|
391
|
+
(edge.get("source") in task_ids or edge.get("target") in task_ids)
|
|
392
|
+
for edge in incident
|
|
393
|
+
):
|
|
394
|
+
hidden_content_ids.add(node_id)
|
|
395
|
+
|
|
396
|
+
hidden_ids = task_ids | hidden_content_ids
|
|
397
|
+
projected_nodes = [node for node in projected_nodes if str(node.get("id") or "") not in hidden_content_ids]
|
|
398
|
+
projected_edges = [
|
|
399
|
+
edge
|
|
400
|
+
for edge in edges
|
|
401
|
+
if edge.get("source") not in hidden_ids and edge.get("target") not in hidden_ids
|
|
402
|
+
]
|
|
403
|
+
return projected_nodes, projected_edges, {
|
|
404
|
+
"framework_task_node_count": len(task_ids),
|
|
405
|
+
"framework_task_content_node_count": len(hidden_content_ids),
|
|
406
|
+
"framework_task_agent_count": len(tasks_by_agent),
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
|
|
261
410
|
def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, Any]:
|
|
262
411
|
"""Provider-neutral display projection; raw graph/evidence remains unchanged."""
|
|
263
412
|
from . import viewer_projection_base as base
|
|
@@ -268,6 +417,7 @@ def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, An
|
|
|
268
417
|
nodes = [deepcopy(n) for n in filtered.get("nodes", []) if isinstance(n, dict)]
|
|
269
418
|
edges = [deepcopy(e) for e in filtered.get("edges", []) if isinstance(e, dict)]
|
|
270
419
|
entries = _entries(graph)
|
|
420
|
+
nodes, edges, framework_tasks = collapse_framework_tasks(nodes, edges)
|
|
271
421
|
nodes, edges, inference = collapse_inference_requests(nodes, edges, entries)
|
|
272
422
|
roots, _ = _roots(nodes, entries)
|
|
273
423
|
nodes, edges, files = collapse_orphan_files(nodes, edges, roots[0] if len(roots) == 1 else None)
|
|
@@ -297,6 +447,7 @@ def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, An
|
|
|
297
447
|
or inference["collapsed_request_count"]
|
|
298
448
|
or local is not None
|
|
299
449
|
or files is not None
|
|
450
|
+
or framework_tasks["framework_task_node_count"]
|
|
300
451
|
)
|
|
301
452
|
if not topology_changed:
|
|
302
453
|
if audit["count"]:
|
|
@@ -313,6 +464,9 @@ def project_provider_neutral_viewer_graph(graph: dict[str, Any]) -> dict[str, An
|
|
|
313
464
|
"orphan_file_node_count": len(files.get("nodes") or []) if files else 0,
|
|
314
465
|
"inference_request_count": inference["collapsed_request_count"], "logical_inference_count": inference["logical_inference_count"],
|
|
315
466
|
"direct_inference_edge_count": inference["direct_inference_edge_count"], "unresolved_inference_requests": inference["unresolved"],
|
|
467
|
+
"framework_task_node_count": framework_tasks["framework_task_node_count"],
|
|
468
|
+
"framework_task_content_node_count": framework_tasks["framework_task_content_node_count"],
|
|
469
|
+
"framework_task_agent_count": framework_tasks["framework_task_agent_count"],
|
|
316
470
|
"internal_hook_node_count": len(hook_nodes), "internal_hook_edge_count": len(hook_edges),
|
|
317
471
|
"projection_topology_changed": True,
|
|
318
472
|
})
|