Files
multica/server/internal/daemon/execenv
8d292ef7ce MUL-5963: feat(server): let web chat read back history via multica chat history (#6680)
* feat(server): let web chat read back history via `multica chat history`

When an agent's provider session resume fails on a web chat / Feishu task, the
daemon injected SessionContinuityNoticeUnrecoverable, making the agent reply
"this is a new session, no memory". But the conversation was never lost — it is
persisted in chat_message, and the agent just had no command to read it back
(only Slack had a history reader).

Make `multica chat history` serve the stored chat_message transcript for any
non-Slack session (web chat, Feishu), reusing the existing task-token
authorization and the shared response shape. Route web chat / Feishu continuity
notices to a new variant that tells the agent to read the transcript back
instead of scripting a "new session" disclosure, and teach the Feishu channel
prompt the read-back command.

- handler/chat_history.go: GetChatChannelHistory falls back to chatMessageHistory
  (ListChatMessages, oldest-first, default recent 30) on ErrNoSlackSession.
- execenv: add SessionContinuityNoticeChatTranscript; keep Unrecoverable for
  channels Multica stores no transcript for (e.g. WeCom).
- prompt.go: route web chat / Feishu to the transcript notice; Feishu channel
  awareness teaches `multica chat history`.
- tests: update surface/notice matrix, Feishu prompt policy, add handler
  transcript fallback tests.

Co-Authored-By: Claude Fable 5 <[email protected]>

* test(server): fix transcript read-back test to insert a real assistant row

The insertChatVisibilityMessage helper hardcodes role='user', so the
"assistant reply" row in TestGetChatHistory_NoSlackBindingReadsStoredTranscript
was actually stored as a user message and the handler (correctly) returned
role "user". Add an insertChatMessageRole helper and use it for the assistant
row so the role-mapping assertion is meaningful.

Co-Authored-By: Claude Fable 5 <[email protected]>

* refactor(server): treat WeCom as a readable transcript surface for session continuity

WeCom is fully wired on main: it persists the conversation to chat_message and
stamps ChatChannelType="wecom", so `multica chat history` reads it back just
like Feishu's. Stop routing it to the Unrecoverable fallback; give it the
transcript notice and the `multica chat history` history command, symmetric
with Feishu (no thread reader, no upload).

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix(server): make transcript read-back reachable and honor the paging contract

Addresses review on the web-chat history read-back feature.

- GetChatChannelHistory no longer dead-ends on "no channel integration" when
  SlackHistory is nil: a no-Slack deployment (the exact target of this feature)
  now serves the stored chat_message transcript directly, and Slack-backed
  sessions still go through the Slack reader.
- chatMessageHistory swaps ListChatMessages for ListChatMessagesPage, honoring
  the ?before cursor (encoded as created_at|id) and clamping ?limit (default 30,
  max 50), and advertises NextCursor on a full page so an agent can page back
  without re-reading the recent window. Author now uses the channel vocabulary
  (Bot / User) instead of the raw role string.
- Adds a single SurfacePersistsTranscript predicate (web chat, Feishu, WeCom,
  DingTalk) as the one source of truth for "which surfaces are readable", shared
  by the continuity-notice router, the chat-prompt history copy, and the surface
  list — closing the DingTalk gap where the notice said "read it back" but the
  prompt said "no history reader for dingtalk".

Tests: nil-reader-with-rows regression, transcript paging without duplicates or
gaps, Author/TS pins, and DingTalk cases in the surface/notice and prompt tables.

Co-Authored-By: Claude Fable 5 <[email protected]>

* test(server): fix transcript paging test assertion

The paging test wrongly asserted messages are strictly oldest-first ACROSS
pages, but paging to older messages means each later page is older than the
previous one — the sequence is not monotonic. The handler was correct; the
assertion was wrong. Now it checks the real invariants: each page is
oldest-first, and the union of all pages is exactly the transcript with no
duplicates or gaps.

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix(server): stop telling transcript surfaces their history is not in Multica

The channel-awareness block opened with "This conversation and its history
live in <platform>, NOT in Multica" for every channel type, and the very
next line told a Feishu/WeCom/DingTalk agent that Multica stores the
conversation and hands it `multica chat history` to read it back. An agent
that believes the first sentence has no reason to run the command in the
second.

Only the useful half — do not go hunting through issues and comments — is
unconditional now. Where the conversation actually lives moved into each
branch: Slack keeps the original wording (true there), transcript surfaces
say the conversation happens on the platform and Multica stores a
transcript of it, and the defensive fallback keeps both claims for a
surface that really has no reader.

Co-authored-by: multica-agent <[email protected]>

* fix(server): name the channel a served transcript came from

HistoryPage.ChannelType is documented as empty ONLY for a session bound to
no channel, but the transcript branch never set it. A WeCom/Feishu/DingTalk
session that HAD messages therefore reported channel_type "" — telling the
agent it was in a web-only chat — while the same session's empty read
already named the platform. One endpoint, two answers, decided by whether
the session happened to have messages.

Reuses the existing sessionChannelType helper, so the transcript path
inherits its refusal to guess: a binding read that fails for any reason
other than "no such row" is reported rather than silently answered as
web-only. That failure now returns the same status as the empty-read path
does for the same cause, instead of the 502 reserved for a failed channel
read.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: qiushihao279-cloud <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-12 15:36:39 +08:00
..