The message-list shortcuts projected raw message content, so several message
kinds came out unreadable:
- speaker showed as null (real key is the bare "sender"; forwardMessages carry
the literal string "null")
- out-of-office auto-replies / cards dumped raw rich-content JSON
- forwarded chat records ("聊天记录") collapsed to a lossy "[卡片]" summary,
dropping their nested forwardMessages
- encrypted card/robot messages dumped raw base64 ciphertext (dws holds no key)
Extract the projection logic into a shared internal/shortcut/chatmsg package and
fix all five projecting read commands to reuse it (+chat-messages,
+messages-list, +messages-list-direct, +at-me, +search-msg, +thread-replies),
each keeping its own output fields:
- Sender: probe the bare "sender" key first, treat "null"/"" as absent
- CleanText: extract readable text from rich-content JSON, mark ciphertext as
[加密消息,无法解码] instead of leaking base64
- Forwarded: recursively expand forwardMessages so chat records are readable
- RecoverEncryptedContents: best-effort re-query of search_messages_by_sender
(which returns server-rendered plaintext) matched back by openMessageId
Also add --msg-id / --open-message-id aliases to `chat message download-media`
so agents that copy the openMessageId/msgId field don't hit "unknown flag".
Verified live: +messages-list-direct expands a 44-message forwarded record;
+messages-list / +at-me render clean text; download-media accepts --msg-id.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the unreachable defensive tag-skip branch in transportEnvelopeSchema
(every transport.Event field carries a non-empty JSON tag) and add a unit
test for the validatePersonalEventOutputMode success path so the changed
code coverage gate reaches 100%.
pluginDescriptorConflictsWithDistribution and the identity-owner seeding
both treated conference as distribution-owned, so the whole plugin server
was skipped before the replaceable-fallback merge in addPluginCommandsSafe
could run. Skip replaceablePluginFallbacks names in both early gates while
keeping reserved-command protection and plugin-vs-plugin ownership intact.