Skip to content

feat(platform): reconstruct session-bridge forwards as Nodes - #201

Merged
BegoniaHe merged 7 commits into
masterfrom
feat/session-bridge-forward-200
Sep 15, 2026
Merged

BegoniaHe merged 7 commits into
masterfrom
feat/session-bridge-forward-200

Conversation

@BegoniaHe

Copy link
Copy Markdown
Collaborator

Summary

When /session watch, connect, or pair forwards an expanded merged forward to a target that declares forward=True, the far side now receives a reconstructed Nodes card instead of a labeled linear transcript.

Related issue

Fixes #200

Behavior

  • plan_message_delivery splits envelope content into no-sender prefixes/suffixes and sender islands. Sender islands become one-level Nodes; each synthetic author separator opens a new Node, including adjacent same-author nodes.
  • Source headers stay in a chain before the card. Ordinary messages with no sender are not wrapped.
  • Node-internal Face, MFace, Json, Poke, Markdown, MiniApp, Xml, Dice, RPS, Shake, Share, Music, Location, and Contact rehydrate without requiring matching native_namespaces. Cross-session mentions become @name / @all text.
  • forward=False targets, including WebChat, keep the labeled transcript with Face/Json/MFace summaries.
  • Media-materialize failures keep the original sender so the unavailable placeholder stays inside the card.
  • Node.to_dict encodes Video and File as base64://. aiocqhttp inbound mface becomes MFace.

Non-goals

  • Wrapping ordinary watch/connect/pair bodies as merged forwards
  • Fetching unexpanded Forward.id via get_forward_msg
  • Preserving nested Forward trees
  • Adding a Nodes slot to DeliveryBatch
  • Root-level Location/Contact delivery
  • WebChat storage of Nodes
  • Dashboard / OpenAPI / /session command syntax

Implementation notes

Reconstruction lives in plan_message_delivery, not plan_delivery. DeliveryBatch is unchanged. Projection still flattens inbound forwards and now stamps sender on NativeContent as well as PortablePart.

Bilingual adapter and command docs are updated. No OpenAPI or Dashboard change.

Validation

uv run ruff check astrbot/core/platform/message_delivery.py astrbot/core/platform/message_protocol.py astrbot/core/platform/message_projection.py astrbot/core/platform/message_media.py astrbot/core/platform/message_capabilities.py astrbot/core/message/components.py astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py tests/unit/test_message_protocol.py tests/unit/test_session_bridge.py tests/unit/test_message_component_serialization.py tests/unit/test_aiocqhttp_adapter.py
uv run pytest tests/unit/test_message_protocol.py tests/unit/test_session_bridge.py tests/unit/test_message_component_serialization.py tests/unit/test_aiocqhttp_adapter.py --test-profile blocking
make check-md

131 passed on the focused pytest invocation. make check and full blocking pytest were not run.

Compatibility and risk

Node uin/name come from the original SenderSnapshot; the speaking account remains the destination Bot. Cross-instance native replay is limited to the frozen node-internal kinds. WebChat forward is now False.

Checklist

  • A Feature request Issue exists for large work, or this is a small, obvious addition.
  • The change is focused and does not include unrelated refactoring.
  • I added or updated tests, or explained why tests are not practical.
  • User-visible behavior updates both docs/zh/ and docs/en/.
  • OpenAPI, generated client, docs/public/openapi.json, and tests change together when routes or schemas change.
  • No secrets committed. Runtime Python deps update pyproject.toml, requirements.txt, and uv.lock together.
  • I did not restore legacy shims, Python <3.14 fallbacks, or upstream publish/docs URLs as fork artifacts.
  • Breaking API or behavior changes use ! and a BREAKING CHANGE: footer.
  • I will not merge this PR myself. Merge needs a human maintainer review plus a separate AI-assisted review (AI_POLICY.md).
  • AI use follows AI_POLICY.md. Keep exactly one author note below. Do not fabricate the other.

Agent note

Goal: close #200 by reconstructing flattened session-bridge merged forwards as Nodes when the target declares forward.

Paths: astrbot/core/platform/message_{protocol,projection,media,delivery,capabilities}.py, astrbot/core/message/components.py, astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py, focused unit tests, and bilingual adapter/command docs.

Checks run: ruff on touched Python files; focused pytest 131 passed; make check-md / Prettier on the four doc pages. Residual risk: live NapCat/aiocqhttp/Satori card encoding still depends on adapter send_forward; _publish_media does not copy sender (out of scope). Tools: OpenCode / grok-4.6.

Copy SenderSnapshot onto NativeContent and keep it on media-failure
placeholders so reconstructed nodes can keep Face, Json, and unavailable
media inside the card.

AI-Generated: true
Generated-At: 2026-09-15T12:01:51Z
Rebuild sender-tagged envelope islands into one-level Nodes when the
target declares forward, and keep WebChat on the labeled transcript.

Fixes #200
AI-Generated: true
Generated-At: 2026-09-15T12:02:05Z
Add convert_to_base64 for Video and File and use the same base64://
branch in Node.to_dict as Image and Record.

AI-Generated: true
Generated-At: 2026-09-15T12:02:29Z
Map mface segments to MFace using NapCat-aligned package, id, key, and
summary fields instead of dropping them.

AI-Generated: true
Generated-At: 2026-09-15T12:02:29Z
AI-Generated: true
Generated-At: 2026-09-15T12:02:35Z
AI-Generated: true
Generated-At: 2026-09-15T12:02:35Z
Type-check Location lat/lon before float conversion so quality pyright
passes. Coerce Contact ids and omit malformed node segments instead of
raising. Guard aiocqhttp mface payloads that are not dicts.

AI-Generated: true
Generated-At: 2026-09-15T12:21:55Z
@BegoniaHe
BegoniaHe merged commit 35be83b into master Sep 15, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] reconstruct session-bridge merged forwards as Nodes

1 participant