forked from molecule-ai/molecule-core
Root-cause fix for #118 (chat attachments rendering as plain text links instead of download chips). User flagged with screenshot 2026-04-26 showing the Design Director agent pasting https://files.catbox.moe/… in the message body — chat rendered the URL as plain markdown text, unclickable in the canvas's bubble layout, and unreachable in any SaaS deployment where the user's browser can't egress to catbox. The structured `attachments` field already exists, the canvas's AttachmentChip already renders well, the WebSocket broadcast already carries attachments verbatim — the missing piece was the LLM choosing the body over the structured field. Tighten the tool description so it trains the right behavior. Three targeted strengthenings: 1. Top-level tool description: enumerated use case (4) now reads "via the `attachments` field (NEVER paste file URLs in `message`)". The all-caps NEVER + the explicit field name move the LLM toward the structured path on first read. 2. `message` param: adds an explicit DO NOT rule with rationale. Includes the SaaS-reachability reason so operators can grep for "SaaS" and find this design constraint instead of re-discovering it after a tenant complaint. Calls out catbox.moe + file:// by name as concrete examples of forbidden hosts (those are the two we've seen in production). 3. `attachments` param: leads with REQUIRED, lists the bad alternatives explicitly (pasting URLs, base64-encoding, telling user to look at a path). LLMs handle "use X, NOT Y" framings better than "use X" alone — observed during prompt-engineering iteration on hermes' tool descriptions. Tests pin all three load-bearing phrases (4 new in test_a2a_mcp_server.py) so a future doc edit that softens or drops them fails CI. Brittle by design — these are prompt-engineering invariants, not implementation details. This is the root-cause fix. A defensive canvas-side backstop (auto- detect download-shaped URLs in body and convert to chips) is a follow-up that could land separately if the steering proves insufficient in practice. Verification: - 1190/1190 workspace pytest pass - 4 new test_a2a_mcp_server.py cases all green Closes the steering half of #118. The structured-attachments-only contract was already enforced server-side (PR #2130 added per-attachment validation); this PR closes the prompt-side gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| adapters | ||
| __init__.py | ||
| conftest.py | ||
| test_a2a_cli.py | ||
| test_a2a_client.py | ||
| test_a2a_executor.py | ||
| test_a2a_mcp_server.py | ||
| test_a2a_tools_impl.py | ||
| test_a2a_tools_module.py | ||
| test_agent_base_urls.py | ||
| test_agent.py | ||
| test_agents_md.py | ||
| test_approval.py | ||
| test_audit_ledger.py | ||
| test_audit.py | ||
| test_awareness_client_full.py | ||
| test_compliance.py | ||
| test_config.py | ||
| test_consolidation.py | ||
| test_coordinator_parent.py | ||
| test_coordinator_routing.py | ||
| test_delegation.py | ||
| test_events.py | ||
| test_executor_helpers.py | ||
| test_gh_wrapper.sh | ||
| test_governance.py | ||
| test_heartbeat_runtime_metadata.py | ||
| test_heartbeat.py | ||
| test_hitl.py | ||
| test_main_initial_prompt.py | ||
| test_mcp_memory.py | ||
| test_memory.py | ||
| test_molecule_ai_status.py | ||
| test_namespaces.py | ||
| test_openclaw_adapter.py | ||
| test_platform_auth.py | ||
| test_plugins_builtins.py | ||
| test_plugins_registry.py | ||
| test_plugins.py | ||
| test_pre_stop.py | ||
| test_preflight.py | ||
| test_prompt.py | ||
| test_routing_policy.py | ||
| test_runtime_capabilities.py | ||
| test_runtime_wedge.py | ||
| test_safe_env.py | ||
| test_sandbox.py | ||
| test_secret_redact.py | ||
| test_security_scan.py | ||
| test_skills_loader.py | ||
| test_skills_watcher.py | ||
| test_snapshot_scrub.py | ||
| test_telemetry.py | ||
| test_temporal_workflow.py | ||
| test_transcript_auth.py | ||
| test_watcher.py | ||