Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3d67eb5
feat(serve): Add cursor-paged transcript replay endpoint
doudouOUC Jul 8, 2026
5cd34a5
fix(cli): Bound transcript replay indexing
doudouOUC Jul 8, 2026
a5d5414
fix(core): Validate transcript cursors
doudouOUC Jul 8, 2026
ef05a62
fix(core): Lazy-init transcript cursor secret
doudouOUC Jul 8, 2026
d03c010
fix(serve): Address transcript replay review suggestions
doudouOUC Jul 8, 2026
c31804d
fix(serve): Stabilize transcript replay pagination
doudouOUC Jul 8, 2026
f0f77e2
fix(core): Avoid quadratic transcript line scanning
doudouOUC Jul 8, 2026
8c776a5
chore: Merge main into transcript replay branch
doudouOUC Jul 8, 2026
d39c588
fix(core): Mark transcript history gaps
doudouOUC Jul 8, 2026
72c50a1
chore: Merge main into transcript replay branch
doudouOUC Jul 8, 2026
19376bc
fix(core): Address transcript reader review comments
doudouOUC Jul 9, 2026
f0701fe
fix(serve): Address transcript replay review feedback
doudouOUC Jul 9, 2026
a1985ac
fix(serve): align transcript cursor preflight errors
doudouOUC Jul 9, 2026
5984bef
test(cli): Cover paged dangling tool call replay
doudouOUC Jul 9, 2026
6bb7780
fix(core): Bound transcript index cache bytes
doudouOUC Jul 9, 2026
02da532
fix: Address transcript replay review follow-ups
doudouOUC Jul 9, 2026
031fd19
fix(cli): Preserve pending tool calls on transcript replay errors
doudouOUC Jul 9, 2026
6354fb4
Merge remote-tracking branch 'origin/main' into agent/full-transcript…
doudouOUC Jul 9, 2026
954239d
codex: address PR review feedback (#6525)
doudouOUC Jul 9, 2026
dd09263
merge: sync main into transcript replay branch
doudouOUC Jul 9, 2026
f287b4a
codex: fix CI failure on PR #6525
doudouOUC Jul 9, 2026
1a6975d
fix(serve): warm transcript-replay tools leniently
doudouOUC Jul 9, 2026
cf8a171
merge: sync main into transcript replay branch
qwen-code-dev-bot Jul 9, 2026
8e062f8
fix(core): invalidate transcript index cache on in-place rewrites
doudouOUC Jul 9, 2026
24457b6
codex: address PR review feedback (#6525)
doudouOUC Jul 9, 2026
07a7ef7
merge: sync main into transcript replay branch
doudouOUC Jul 9, 2026
f69708c
qwen: fix CI failure on PR #6525
doudouOUC Jul 9, 2026
87b3121
qwen: address PR review feedback (#6525)
doudouOUC Jul 9, 2026
0968d5f
qwen: address PR review feedback (#6525)
doudouOUC Jul 9, 2026
61f31d8
fix(serve): address transcript review feedback
doudouOUC Jul 10, 2026
8455983
qwen: address transcript review feedback (#6525)
doudouOUC Jul 10, 2026
fc4d0a0
Merge remote-tracking branch 'origin/main' into fix/pr6525-lenient-wa…
doudouOUC Jul 10, 2026
13f8f90
qwen: adopt transcript review suggestions (#6525)
doudouOUC Jul 10, 2026
e7fbb58
chore(serve): resolve merge conflict with main in session routes
qwen-code-dev-bot Jul 10, 2026
032cce6
qwen: clean up transcript test fixtures to fix no-AK CI flake (#6525)
doudouOUC Jul 10, 2026
1ea2bcb
qwen: harden transcript reader test timestamps + assert page fields (…
doudouOUC Jul 10, 2026
7e20f1a
Merge remote-tracking branch 'origin/main' into fix/pr6525-lenient-wa…
doudouOUC Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions docs/developers/qwen-serve-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ registry. Clients **must** gate UI off `features`, not off `mode` (per design

```
['health', 'capabilities', 'session_create', 'session_scope_override',
'session_load', 'session_resume',
'session_load', 'session_resume', 'session_transcript',
'unstable_session_resume',
'session_list', 'session_prompt', 'session_cancel', 'session_events',
'slow_client_warning', 'typed_event_schema',
Expand Down Expand Up @@ -179,6 +179,8 @@ registry. Clients **must** gate UI off `features`, not off `mode` (per design

`session_load` and `session_resume` advertise the explicit-restore routes (`POST /session/:id/load` and `POST /session/:id/resume`). Older daemons return `404` for these paths, so SDK clients should pre-flight `caps.features` before calling. `unstable_session_resume` is still advertised as a deprecated alias for compatibility with SDKs that shipped while the underlying ACP method was named `connection.unstable_resumeSession`; new clients should gate on `session_resume`.

`session_transcript` advertises `GET /session/:id/transcript`, a read-only paged replay view over the persisted active-session JSONL. It is separate from `/load`: it does not attach a client, seed the live EventBus, create a live session, or change the live replay window. Clients should use it when they need the complete on-disk transcript for a long session, and continue using `/load` only for bounded live replay during cold UI restore.

`slow_client_warning` covers SSE backpressure behavior: (a) the daemon emits a `slow_client_warning` synthetic event-stream frame when a subscriber's live frame backlog or live serialized-byte backlog crosses 75% full, once per overflow episode (rearmed after both measurements drain below 37.5%); (b) `GET /session/:id/events` accepts a `?maxQueued=N` query param (range `[16, 2048]`) to pre-size the per-subscriber frame backlog for cold reconnects against a large replay ring. The serialized-byte cap is daemon-owned (default **2 MiB** per subscriber), live-only, and intentionally has no query parameter. The daemon-wide ring size is controlled by `--event-ring-size` (default **8000**, per #3803 §02). Old daemons silently lack the warning/query behavior — pre-flight this tag before opting in.

`typed_event_schema` advertises daemon event payloads that match the SDK's `KnownDaemonEvent` schema. Older daemons may still stream compatible frames, but SDK clients should pre-flight this tag before assuming typed event coverage.
Expand Down Expand Up @@ -450,6 +452,7 @@ Capability tags:
- `session_supported_commands` → `GET /session/:id/supported-commands`
- `session_tasks` → `GET /session/:id/tasks`
- `session_status` → `GET /session/:id/status`
- `session_transcript` → `GET /session/:id/transcript`

Common status cell:

Expand Down Expand Up @@ -1260,7 +1263,7 @@ Response:

`attached: true` means the session was already live (either from a prior `session/load`/`session/resume`, or because a coalesced concurrent caller raced just ahead).

**History replay over SSE.** While `loadSession` is in flight on the agent side, the agent may emit `session_update` notifications for persisted turns, or return bulk replay updates in the response metadata. The daemon seeds those events into the session's bounded replay snapshot window before the route response returns. For live sessions, `POST /session/:id/load` only promises that bounded window (`compactedReplay`, `liveJournal`, `lastEventId`), not the full transcript. The window is byte-capped by `--compacted-replay-max-bytes` (default 4 MiB, maximum 256 MiB); if older replay entries were dropped, `compactedReplay[0]` is an id-less `history_truncated` marker. Clients should render that marker as status and continue applying retained events. Full transcript access must use a future paginated or streaming endpoint rather than a single array response.
**History replay over SSE.** While `loadSession` is in flight on the agent side, the agent may emit `session_update` notifications for persisted turns, or return bulk replay updates in the response metadata. The daemon seeds those events into the session's bounded replay snapshot window before the route response returns. For live sessions, `POST /session/:id/load` only promises that bounded window (`compactedReplay`, `liveJournal`, `lastEventId`), not the full transcript. The window is byte-capped by `--compacted-replay-max-bytes` (default 4 MiB, maximum 256 MiB); if older replay entries were dropped, `compactedReplay[0]` is an id-less `history_truncated` marker. Clients should render that marker as status and continue applying retained events. Full persisted transcript access is exposed separately through `GET /session/:id/transcript`.

**Errors:**

Expand All @@ -1272,6 +1275,56 @@ Response:
- `409` — `session_archiving` when archive or unarchive is in flight for the same id. `Retry-After: 5`.
- `409` — `session_conflict` when the id exists in both `chats/` and `chats/archive/`; delete the session with `POST /sessions/delete` before loading.

### `GET /session/:id/transcript`

Return one page of id-less `session_update` replay frames reconstructed from the active persisted JSONL transcript. Pre-flight `caps.features.session_transcript` — older daemons return `404` for this route.

Query parameters:

| Field | Required | Notes |
| -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor` | no | Opaque base64url cursor returned by the previous page. Omit for the first page. The cursor is daemon-issued and tamper-checked; modifying it returns `400 invalid_transcript_cursor`. It binds to the transcript file identity and frozen first-page byte size; deleting, truncating, replacing, or archiving the file invalidates it and returns `409`. |
| `limit` | no | Number of active `ChatRecord`s to include in the page. Defaults to `100`, maximum `500`. One record can produce multiple replay frames, so `events.length` may be larger than `limit`. Invalid values return `400 invalid_transcript_limit`. |

Response:

```json
{
"v": 1,
"sessionId": "persisted-1",
"events": [
{
"v": 1,
"type": "session_update",
"data": {
"sessionUpdate": "user_message_chunk",
"content": { "type": "text", "text": "..." }
}
}
],
"nextCursor": "opaque",
"hasMore": true,
"startTime": "2026-07-08T00:00:00.000Z",
"lastUpdated": "2026-07-08T00:01:00.000Z"
}
```

`events` are replay frames only: `{ v: 1, type: "session_update", data: SessionUpdate }`. They do not carry EventBus ids, and the response never includes `lastEventId`. Calling this route does not call `/load`, attach a client, seed the live EventBus, create a live session, or change the current live replay window. Live and inactive active sessions are both reconstructed by the child-side read-only status method so replay uses the same workspace settings, runtime output directory, emitters, and `/load` history semantics without mutating daemon session state.

The first page freezes the current JSONL snapshot size. Later pages read only that byte prefix, so appends after page 1 do not change the result set. If the file disappears, is truncated below the frozen size, is replaced with a different inode, or is moved to archive, the next page returns `409` and the client should restart from page 1 or ask the user to reopen the transcript.

To protect daemon memory and latency, snapshots above the transcript indexing cap fail before the daemon scans the JSONL. Clients receive `413 transcript_too_large` and should fall back to export/offline processing or ask the user to shorten/archive older history.

`partial: true` and `replayError` may appear if replay conversion fails after producing some frames. Treat the page as incomplete and do not continue with the cursor.

**Errors:**

- `400` — invalid `limit`, `cursor`, or session id shape.
- `404` — active persisted session id does not exist.
- `409` — `session_archived`, `session_archiving`, or `session_conflict` from the same loadability checks as `/load`.
- `409` — transcript snapshot is unavailable because the file was deleted, truncated, replaced, or archived after the cursor was issued.
- `413` — `transcript_too_large` when the frozen transcript snapshot exceeds the daemon indexing cap.

### `POST /session/:id/resume`

Restore a persisted ACP session by id WITHOUT replaying history through SSE. The model context is restored internally on the agent side (via `geminiClient.initialize` reading `config.getResumedSessionData`); the SSE stream stays clean for clients that already have history rendered. Pre-flight `caps.features.session_resume`; `unstable_session_resume` remains a deprecated compatibility alias for older clients.
Expand Down
Loading
Loading