docs(serve): fix stale permission-route note and document pending-prompt events (#13281)

docs/users/qwen-serve.md still described session-scoped permission routing
as Stage 1.5 future work and pointed integrators at the legacy process-global
vote route. Rewrite the note: the session-scoped route ships (Wave 4 / F3) and
is the recommended form for new multi-workspace integrations; the legacy route
remains for pre-F3 single-workspace clients with its 404 semantics spelled out
(the same body as a lost vote for a session owned by another runtime).

Add event-reference entries for pending_prompt_added / pending_prompt_started /
pending_prompt_completed to docs/developers/daemon/09-event-schema.md: payload
fields, the state union, the idle-session no-event caveat, and the "queue-view
bookkeeping, not a turn terminal" guidance (correlate turn_complete /
turn_error by promptId for completion).

Derived from the field notes and triage in #13279.
This commit is contained in:
LUOSENGWA 2026-10-07 04:45:41 +00:00 • committed by GitHub
parent 9d4e1e1c5d
commit 8505479d87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 10 additions and 2 deletions

View file

@ -162,6 +162,14 @@ These events are workspace-keyed, not session-keyed. The session reducer treats
| --------------------------- | --------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `mid_turn_message_injected` | S->C | Web-shell or remote client injected messages into a running turn via `POST /session/:id/inject` | `sessionId, messages: string[], originatorClientId?`; consumers MUST compare `originatorClientId` to their own id before deduping. |
### Pending prompt queue
| Type | Direction | Trigger | Key payload fields |
| -------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `pending_prompt_added` | S->C | A prompt was appended to the session's prompt FIFO and is waiting behind the active turn. Published only when the prompt is *genuinely queued* — the first prompt on an idle session starts immediately and emits no queue events. | `sessionId, promptId, text, queuedAt` |
| `pending_prompt_started` | S->C | The queued prompt reached the head of the FIFO and was promoted to running (also emitted for a promoted mid-turn message that starts immediately). Skipped when the prompt is aborted before promotion. | `sessionId, promptId, text` |
| `pending_prompt_completed` | S->C | A queued prompt settled — `state: 'completed'` (it ran; execution errors still report `completed` and ride the terminal `turn_error` frame) or `state: 'removed'` (removed from the queue and never ran). Only emitted for prompts that were genuinely queued (an `added` was published). This is queue-view bookkeeping, **not a turn terminal**; correlate `turn_complete` / `turn_error` by `promptId` for completion. | `sessionId, promptId, state: 'completed' \| 'removed'` |
### Turn lifecycle / assistant pushes
| Type | Direction | Trigger | Key payload fields |

View file

@ -771,7 +771,7 @@ provider decision with their normal tool policy and isolation boundary.
- **Per-session prompt admission cap** — defaults to 5 accepted-but-unsettled prompts per session. A buggy client cannot enqueue unbounded prompt promises or temporary SSE waits for one session.
- **Graceful shutdown** — SIGINT/SIGTERM drain the agent children before closing the listener (10s deadline per child).
> ⚠️ **Stage 1 known gap — permissions are daemon-global, not per-session (BUy4H).** `pendingPermissions` lives at daemon scope; any client holding the bearer token can vote on any `requestId` for any session it can see (and SSE `permission_request` events carry the requestId in their payload). This is acceptable under the single-user / small-team trust model where every authenticated client is the same human or collaborators they trust. Stage 1.5 will move to `POST /session/:id/permission/:requestId` + session-scoped pending map + per-client identity (must-have #3 from the downstream review); until then, don't run `qwen serve` behind a bearer shared with untrusted parties.
> ⚠️ **Permission votes are session-scoped now — plus a standing shared-bearer caveat.** Prefer `POST /session/:id/permission/:requestId` (Wave 4 / F3): it routes to the runtime that owns the named live session and never falls back to the primary bridge — new multi-workspace integrations should always use this route (pre-flight `caps.features.session_permission_vote`). The legacy process-global `POST /permission/:requestId` remains for pre-F3 single-workspace clients: it is primary-bound and `404`s for a session owned by another registered runtime — the same body as a lost vote under the default `first-responder` policy — so a `404` there does not by itself mean the request was already answered. Route table: [daemon REST API reference](../developers/daemon-rest-api-reference.md#permissions). Standing caveat: every client holding the bearer token is the same security principal — don't run `qwen serve` behind a bearer shared with untrusted parties.
>
> ⚠️ **Stage 1 known gap — `POST /session/:id/prompt` body capped at 10 MB (BUy4L).** Multimodal prompts containing images / PDFs / audio that exceed 10 MB will fail at body-parse time before route logic runs (no streaming, no mid-upload abort). Workaround: shrink the content client-side, or pass a path reference and let the agent read the file via `readTextFile`. Stage 1.5 will accept `multipart/form-data` or chunked encoding on `/prompt` so large prompts don't hit a cliff.
>
@ -958,7 +958,7 @@ The Stage 1.5 plan describes TUI as an in-process EventBus subscriber. In practi
No TUI shell runs inside the daemon. The slash commands listed above **don't exist** in this mode — there's no terminal UI to issue them from. Session state is therefore:
- **Boot-time-frozen** for `approval-mode` / `memory` / `agents` / `tools` allowlist / `auth` — all loaded from settings + disk when the daemon's `qwen --acp` child starts; immutable for the session's lifetime. Settings-defined MCP servers are likewise frozen at boot, but **runtime-added servers** (via `POST /workspace/mcp/servers`) can be added or removed without restart.
- **Mutable over HTTP** via `POST /session/:id/model` (publishes `model_switched`), `POST /workspace/mcp/servers` / `DELETE /workspace/mcp/servers/:name` (publishes `mcp_server_added` / `mcp_server_removed`), and permission votes (`POST /permission/:requestId`).
- **Mutable over HTTP** via `POST /session/:id/model` (publishes `model_switched`), `POST /workspace/mcp/servers` / `DELETE /workspace/mcp/servers/:name` (publishes `mcp_server_added` / `mcp_server_removed`), and permission votes (`POST /session/:id/permission/:requestId`; the legacy process-global `POST /permission/:requestId` remains for pre-F3 single-workspace clients).
**Consequence:** remote clients in headless mode see the **full session state**. No TUI hides additional state; no drift is possible. If you want to change `approval-mode`, restart the daemon with new settings. MCP servers can now be added/removed at runtime via the mutation routes (`POST /workspace/mcp/servers`, `DELETE /workspace/mcp/servers/:name`) — see [Runtime MCP server management](#runtime-mcp-server-management-issue-4514).