Resolve qq-bridge.mjs by keeping the inbound-file support (hasFiles,
files passthrough) and dropping the upstream stream session, which
this branch replaces with the discrete turn-feed notices.
Show what the agent is actually doing during a turn, the way Claude
Code does: interim explanation text, every Tool call, and the error
detail when a tool fails, each as its own pushed message before the
final markdown answer.
- HarnessReplyTracker.consume now returns every frame of a polling
batch in order (same-batch text frames collapse to the latest)
instead of dropping all but the last event, which silently ate
tool-call frames that shared a batch with their result
- tool frames carry callId; status frames carry the correlated
toolName and a flattened error text parsed from the tool/result
error payload (message, or name: code)
- the QQ bridge holds interim step text until the next tool call
(or the end of the turn) pushes it, emits 'Tool call <name>' per
call, emits 'Tool call <name>\nError: <reason>' for failures, and
skips re-sending interim text that already equals the final answer
Other channels keep their existing behaviour: the status frame still
reads '正在整理结果…' and they ignore the new optional fields.
- /watch resolves the target READ-ONLY: sessions are validated against
registered workspace listings (current or any other) without binding the
conversation or switching workspaces. /unwatch and /watchlist round out
the command set; the watchlist card supports button and number-reply
unwatching.
- Watches persist in the state store (sessionId + title + chatId + lastSeq)
and resume at runtime start: the bridge starts the global event-mux
watcher in its constructor when the harness supports it.
- Completion pushes fire on turn/end, deduped by sessionId + turn id, with
the seq watermark persisted per watch. After a mux reconnect the bridge
replays each watched session's recent history (session.history) through
the normal handler, so missed turn/end events are compensated without
duplicates.
- Bound-session push targets are persisted (chatTargets) instead of living
only in memory, and refresh on every accepted message.
- Watch failures map to safe user-facing messages.
- /m (or /menu) opens a card menu; /sessionlist and /workspacelist render
cards with bind/switch buttons; number replies stay usable as a fallback
when the app does not subscribe card.action.trigger.
- card.action.trigger callbacks validate the operator's open_id against the
allowed senders: group members outside the allowlist can never drive
binding, workspace switches or other card actions.
- Session-list pagination uses page numbers everywhere (buttons carry
sessions:<page>), fixing the previous double page-size scaling that
skipped pages past 20 sessions.
- Bind/workspace failures map to safe user-facing messages instead of raw
error details.
- Apps registered after this change subscribe card.action.trigger during
the scan flow.
Local UX enhancements on top of upstream ce8b9e8:
- harness-client.mjs: add toEpochMs/sessionTimeMs, attach time to sessions
- workspace-command.mjs: /sessionlist relative time, /session N index binding