docs: clarify webchat transcript persistence

This commit is contained in:
Peter Steinberger
2026-05-04 01:40:42 +01:00
parent 2493ab1978
commit 419bcd26f0
3 changed files with 18 additions and 0 deletions

View File

@@ -155,6 +155,7 @@ Imported themes are stored only in the current browser profile. They are not wri
- Assistant/generated images are persisted as managed media references and served back through authenticated Gateway media URLs, so reloads do not depend on raw base64 image payloads staying in the chat history response.
- `chat.history` also strips display-only inline directive tags from visible assistant text (for example `[[reply_to_*]]` and `[[audio_as_voice]]`), plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks), and leaked ASCII/full-width model control tokens, and omits assistant entries whose whole visible text is only the exact silent token `NO_REPLY` / `no_reply`.
- During an active send and the final history refresh, the chat view keeps local optimistic user/assistant messages visible if `chat.history` briefly returns an older snapshot; the canonical transcript replaces those local messages once the Gateway history catches up.
- Live `chat` events are delivery state, while `chat.history` is rebuilt from the durable session transcript. After tool-final events the Control UI reloads history and merges only a small optimistic tail; the transcript boundary is documented in [WebChat](/web/webchat).
- `chat.inject` appends an assistant note to the session transcript and broadcasts a `chat` event for UI-only updates (no agent run, no channel delivery).
- The chat header model and thinking pickers patch the active session immediately through `sessions.patch`; they are persistent session overrides, not one-turn-only send options.
- Typing `/new` in the Control UI creates and switches to the same fresh dashboard session as New Chat. Typing `/reset` keeps the Gateway's explicit in-place reset for the current session.

View File

@@ -45,6 +45,17 @@ Status: the macOS/iOS SwiftUI chat UI talks directly to the Gateway WebSocket.
- History is always fetched from the gateway (no local file watching).
- If the gateway is unreachable, WebChat is read-only.
### Transcript and delivery model
WebChat has two separate data paths:
- The session JSONL file is the durable model/runtime transcript. For normal agent runs, Pi persists model-visible `user`, `assistant`, and `toolResult` messages through its session manager. WebChat does not write arbitrary delivery, status, or helper text into that transcript.
- Gateway `ReplyPayload` events are the live delivery projection. They can be normalized for WebChat/channel display, block streaming, directive tags, media embedding, TTS/audio flags, and UI fallback behavior. They are not themselves the canonical session log.
- WebChat injects assistant transcript entries only when the Gateway owns a displayed message outside a normal Pi assistant turn: `chat.inject`, non-agent command replies, aborted partial output, and WebChat-managed media transcript supplements.
- `chat.history` reads the stored session transcript and applies WebChat display projection. If live assistant text appears during a run but disappears after history reload, first check whether the raw JSONL contains the assistant text, then whether `chat.history` projection stripped it, then whether the Control UI optimistic-tail merge replaced local delivery state with the persisted snapshot.
Normal agent-run final answers should be durable because Pi writes the assistant `message_end`. Any fallback that mirrors a delivered final payload into the transcript must first avoid duplicating an assistant turn that Pi already wrote.
## Control UI agents tools panel
- The Control UI `/agents` Tools panel has two separate views:

View File

@@ -2464,6 +2464,12 @@ export const chatHandlers: GatewayRequestHandlers = {
})
.then(async () => {
await rewriteUserTranscriptMedia();
// WebChat persistence has two owners. Agent runs persist model-visible turns
// through Pi's SessionManager; this dispatcher only owns live delivery payloads.
// Do not blindly mirror agent-run final payloads into JSONL or chat.history can
// duplicate normal Pi assistant turns. The non-agent branch below has no Pi
// assistant turn, so it appends a gateway-injected assistant entry before
// broadcasting the final UI event.
if (!agentRunStarted) {
await emitUserTranscriptUpdate();
const btwReplies = deliveredReplies