* feat(serve): batch workspace session live-state snapshots * test(serve): include batch live-state capability in integration baseline * fix(serve): close review gaps in the batch session live-state route (#12513) Index POST /sessions/live-state in the REST reference's persisted-catalog row so the docs contract sees the protocol section, give the batch route the single-workspace route's server-side records (routing-failure warn on untrusted members, daemonLog error on unexpected bridge failures), make the mid-batch disconnect guard observable with an AbortController and a macrotask yield between members, and move the duplicated member-failure, generation-currency, and bound literals into workspace-route-runtime for both plural batch routes. Six tests pin the new witnesses. Co-authored-by: Qwen-Coder <[email protected]> * fix(serve): align live-state scope docs and pin batch route guards (#12513) Co-authored-by: Qwen-Coder <[email protected]> * test(cli): pin grouped REST doc rows against bare SDK names and column shifts Co-authored-by: Qwen-Coder <[email protected]> * test(cli): fail closed on grouped REST doc rows and ambiguous SDK qualifiers Co-authored-by: Qwen-Coder <[email protected]> * test(serve): pin documented live-state batch bounds against silent drift (#12513) Co-authored-by: Qwen-Coder <[email protected]> * test(cli): isolate the suite from the operator's settings home (#12513) The autofix verification gate runs the packages/cli suite with the host's real HOME on a machine shared with other jobs. A malformed $HOME/.qwen/settings.json there (observed content: "X") makes every operator-settings reader fail closed at once — readOperatorSettingsScopes is designed to refuse a corrupted policy file — and one such file failed 408 tests across 15 files in the measured gate rejection. Pin a private empty QWEN_HOME per test-file process in the suite setup when the environment did not select one, following the file's existing doctrine for operator-owned environment. Tests that exercise QWEN_HOME set, stub, or delete it themselves. settings.test.ts keys every user-settings and .env path off a mocked os.homedir, so it now deletes an ambient QWEN_HOME explicitly; with any ambient value set, 13 of its tests fail regardless of this change. * test(cli): harden the QWEN_HOME suite pin and batch maxima test (#12513) - Pin on truthiness so an ambient empty QWEN_HOME no longer falls through to $HOME/.qwen, matching every production reader. - Sweep the pinned home from afterAll instead of an 'exit' listener that vitest workers never fire (51 leaked dirs on src/config -> 0). - Restore QWEN_DEBUG_LOG_FILE around loadCliConfig tests so the debug logger's fire-and-forget writes stop racing the sweep. - Witness that the suite never resolves settings from the ambient $HOME/.qwen, and assert member shape in the 20-selector maxima half so twenty failed members cannot read as twenty snapshots. * test(cli): keep AppContainer store work off the pinned per-file QWEN_HOME (#12513) * test(managed-agent): settle fresh group leaders before age-judged sweeps (#12513) A just-forked process can read in ps with procps's negative-elapsed wraparound (e.g. 441077234-00:18:40), which parsePsElapsed deliberately leaves undatable. An undatable leader judges every worker/group identity 'unknown', so an unwitnessed sweep conservatively holds the live process and rejects unproven — flaking the real-process sweep tests within milliseconds of the spawn. settledGroupTable now also waits for the leader's row to be datable, and the four sweep sites that judged a fresh process without the guard use it. * fix(cli): keep the QWEN_HOME test pin unconditional and alive for the process lifetime (#12513) The conditional pin honored an ambient QWEN_HOME, so agent-gate vitest runs read and wrote the operator's live settings home; the teardown sweep then deleted the live pin while un-awaited extension-store work was still reading it, turning Test (ubuntu-latest) red on ENOENT. Pin unconditionally, keep the pin for the process lifetime, reap stale pins by age at setup, await the floated extensions-list handler, and add teardown, pin, reaper, and memory-only-contract witnesses. Co-authored-by: Qwen-Coder <[email protected]> * fix(cli): keep the QWEN_HOME teardown witness inside test-setup.ts (#12513) The previous commit registered the pin teardown witness as a second setupFiles entry in packages/cli/vitest.config.ts, which deterministic verification rejects as CI/verification machinery outside the PR footprint. Revert vitest.config.ts and fold the witness into test-setup.ts itself: vitest runs root-level afterAll hooks in reverse registration order, so the hook registered immediately after the pin runs last among this file's root hooks and still turns every file red if a teardown sweep of the live pin is reintroduced below it (mutation-probed). The separate witness setup file is deleted. Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * test(integration): raise the installed distribution case ceiling The tui-parity installed-distribution darwin-x64 leg failed on the npx-lifecycle-enabled no-relaunch-fallback case with "Test timed out in 120000ms" (run 40537979533, job 122316223603). The five preceding cases averaged ~117s on that host, right at the ceiling: startup alone can spend the whole 90s ready-prompt budget there (#15677) and the /about retry window is another 90s, so 120s cannot contain a healthy case. Raise the per-case ceiling to 240s and keep the budget helper's note in sync. --------- Co-authored-by: 西岐 <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-ci-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-autofix <[email protected]> Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost> Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com> Co-authored-by: Qwen Autofix <[email protected]>
35 KiB
Daemon REST API Reference
This is the public REST/SSE interface for integrations that run
qwen serve --no-web and provide their own UI. Start with the
integration guide, then use this page for endpoint
discovery and the HTTP protocol reference for
detailed lifecycle semantics.
OpenAPI
The curated 25-operation contract is available as OpenAPI 3.1 JSON. Import that URL into an OpenAPI-compatible renderer, client generator, or validation tool. The checked-in JSON is the portable interface contract for the operations indexed below and is validated against the guide, protocol headings, and registered routes in CI.
This index covers a curated core subset of the daemon's REST surface, not all of it. Outside it are the first-party Web Shell routes, conditional internal surfaces, and other public but non-core routes: file mutation, workspace registration, session organization and generation, and workspace MCP, skills, and providers among them. Those surfaces are advertised by their own capability tags; the HTTP protocol reference documents the session, workspace-status, and file surfaces, and MCP server management, auth providers, and device-flow sign-in are covered by the daemon auth and security notes. They are outside this contract, not deprecated.
Reading the index
- Capability is the feature tag to check in
GET /capabilities. An em dash means the operation has no dedicated feature tag; clients that need to support older daemon builds should handle404. - Scope says which runtime owns the operation.
process-globalreads daemon-wide state,selected-runtimeuses the request's workspace selection,persisted-workspaceresolves persisted session storage,live-session-ownerroutes by the live session, andlegacy-primaryalways targets the daemon's primary workspace.GET /session/:id/exportis primary-pinned: it resolves only managed internal runtimes before falling back to the primary workspace. - All operations in this index are stable in the v1 REST contract. The
deprecated
unstable_session_resumecapability name is only an alias; usesession_resumefor the stable resume route.
Creation with startupConfig: { modelServiceId, reasoningEffort? } additionally requires session_startup_config. Only the model is required; omission of reasoning does not set a default. Successful preparation returns modelApplied: true and startupConfigApplied; malformed input returns 400 invalid_startup_config, while rejected selection returns 422 startup_config_rejected. See the startup negotiation contract for ordinary and standalone behavior.
Discovery
| Operation | Capability | Scope | TypeScript SDK |
|---|---|---|---|
GET /health |
health |
process-global |
DaemonClient.health |
GET /capabilities |
capabilities |
process-global |
DaemonClient.capabilities |
Session lifecycle
| Operation | Capability | Scope | TypeScript SDK |
|---|---|---|---|
POST /session |
session_create |
selected-runtime |
DaemonClient.createOrAttachSession |
POST /session/:id/load |
session_load |
selected-runtime |
DaemonClient.loadSession |
POST /session/:id/resume |
session_resume |
selected-runtime |
DaemonClient.resumeSession |
POST /session/:id/heartbeat |
client_heartbeat |
live-session-owner |
DaemonClient.heartbeat |
PATCH /session/:id/metadata |
session_metadata |
live-session-owner |
DaemonClient.updateSessionMetadata |
POST /session/:id/model |
session_set_model |
live-session-owner |
DaemonClient.setSessionModel |
DELETE /session/:id |
session_close |
live-session-owner |
DaemonClient.closeSession |
Prompts and events
| Operation | Capability | Scope | TypeScript SDK |
|---|---|---|---|
GET /session/:id/status |
session_status |
live-session-owner |
DaemonClient.sessionStatus |
POST /session/:id/prompt |
session_prompt |
live-session-owner |
DaemonClient.promptNonBlocking |
POST /session/:id/cancel |
session_cancel |
live-session-owner |
DaemonClient.cancel |
GET /session/:id/events |
session_events |
live-session-owner |
DaemonClient.subscribeEvents |
GET /session/:id/transcript |
session_transcript |
persisted-workspace |
DaemonClient.getSessionTranscriptPage |
GET /session/:id/context |
session_context |
live-session-owner |
DaemonClient.sessionContext |
GET /session/:id/export |
session_export |
legacy-primary |
DaemonClient.exportSession |
GET /session/:id/pending-prompts |
— | live-session-owner |
DaemonClient.getPendingPrompts |
POST /session/:id/prompt returns 202 when the prompt enters the queue, not
when the Agent finishes. Subscribe first, then correlate turn_complete or
turn_error by promptId.
Permissions
| Operation | Capability | Scope | TypeScript SDK |
|---|---|---|---|
POST /session/:id/permission/:requestId |
session_permission_vote |
live-session-owner |
DaemonClient.respondToSessionPermission |
POST /permission/:requestId |
permission_vote |
legacy-primary |
DaemonClient.respondToPermission |
New multi-workspace integrations should always use the session-scoped route.
The legacy route can return the same 404 for a request owned by another
runtime as it does for an already-resolved vote.
Read-only workspace context
| Operation | Capability | Scope | TypeScript SDK |
|---|---|---|---|
GET /workspace/tools |
— | legacy-primary |
DaemonClient.workspaceTools |
GET /file |
workspace_file_read |
legacy-primary |
DaemonClient.readWorkspaceFile |
GET /file/bytes |
workspace_file_bytes |
legacy-primary |
DaemonClient.readWorkspaceFileBytes |
GET /stat |
workspace_file_read |
legacy-primary |
DaemonClient.fileStat |
GET /list |
workspace_file_read |
legacy-primary |
DaemonClient.dirList |
GET /glob |
workspace_file_read |
legacy-primary |
DaemonClient.glob |
These singular routes target the primary workspace. Integrations that expose
multiple registered workspaces should use the workspace-qualified counterparts
documented in the full protocol and preflight workspace_qualified_rest_core.
Additional documented APIs
The 25 operations above are the stable OpenAPI integration contract. The following operations complete the index of HTTP routes with dedicated protocol sections. They are documented v1 surfaces, but they are outside that compact OpenAPI contract because they are conditional, administrative, or primarily support first-party clients. Preflight every listed capability and treat a missing capability as an unavailable route. A grouped row can contain several operations when they share ownership and an SDK family.
| Area | Operations | Capability and scope | TypeScript SDK |
|---|---|---|---|
| Operator state | GET /daemon/status · GET /brand · GET /daemon/update · POST /daemon/update/prepare · POST /daemon/update/restart |
daemon_status, web_shell_brand, daemon_update; process-global |
DaemonClient.daemonStatus, DaemonClient.brand, DaemonClient.daemonUpdateStatus, DaemonClient.prepareDaemonUpdate, DaemonClient.restartDaemonForUpdate |
| Workspace registration | POST /workspaces · PATCH /workspaces/:workspace · DELETE /workspaces/:workspace · GET /workspace-registrations · DELETE /workspace-registrations/:id |
dynamic_workspace_registration, persistent_workspace_registration, workspace_display_name, workspace_runtime_removal; process-global or selected-runtime |
DaemonClient.addWorkspace, DaemonClient.updateWorkspace, WorkspaceDaemonClient.remove; registration-store routes use raw REST |
| Workspace runtime status | GET /workspace/mcp · GET /workspace/skills · GET /workspace/providers · GET /workspace/env · GET /workspace/preflight |
workspace_mcp, workspace_skills, workspace_providers, workspace_env, workspace_preflight; legacy-primary |
DaemonClient.workspaceMcp, DaemonClient.workspaceSkills, DaemonClient.workspaceProviders, DaemonClient.workspaceEnv, DaemonClient.workspacePreflight |
| File mutation | POST /file/write · POST /file/edit |
workspace_file_write; legacy-primary |
DaemonClient.writeWorkspaceFile, DaemonClient.editWorkspaceFile |
| Directory mutation | POST /workspaces/:workspace/directory/create · POST /workspaces/:workspace/directory/delete |
workspace_qualified_rest_core plus workspace_directory_create or workspace_directory_delete; selected-runtime, trusted local only |
WorkspaceDaemonClient.createWorkspaceDirectory, WorkspaceDaemonClient.deleteWorkspaceDirectory |
| Session inspection and tasks | GET /session/:id/supported-commands · GET /session/:id/tasks · GET /session/:id/tasks/:taskId/runtime · POST /session/:id/tasks/:taskId/workflow-action · GET /session/:id/lsp · GET /session/:id/resources |
session_supported_commands, session_tasks, session_lsp, session_resources; live-session-owner |
DaemonClient.sessionSupportedCommands, sessionTasks, sessionTaskRuntime, sessionWorkflowTaskAction, sessionLspStatus, sessionResources |
| Workspace-qualified history | GET /workspaces/:workspace/session/:id/transcript · GET /workspaces/:workspace/session/:id/export · GET /workspaces/:workspace/session/:id/archive/export |
workspace_persisted_transcript, workspace_session_export, workspace_archived_session_export; persisted-workspace |
WorkspaceDaemonClient.getSessionTranscriptPage, WorkspaceDaemonClient.exportSession, exportArchivedSession |
| Worktree recovery | POST /session/:id/worktree-reset |
session_worktree_reset_v1; live-session-owner |
DaemonClient.resetWorktreeSession |
| Persisted session catalog | POST /sessions/catalog · GET /workspace/:id/session-info · GET /workspaces/:workspace/session-info · GET /workspace/:id/sessions · GET /workspaces/:workspace/sessions · GET /workspaces/:workspace/sessions/live-state · POST /sessions/live-state |
session_info, session_list; persisted-workspace; session_catalog_batch; persisted-workspace per member; workspace_session_live_state, workspace_session_live_state_batch; selected-runtime (batch: per member), trusted-only, memory-only |
DaemonClient.getStandaloneSession, DaemonClient.listWorkspaceSessions, listSessionsCatalog, getWorkspaceSessionLiveState, getSessionsLiveState |
| Session organization | GET /workspace/:id/session-groups · POST /workspace/:id/session-groups · PATCH /workspace/:id/session-groups/:groupId · DELETE /workspace/:id/session-groups/:groupId · PATCH /session/:id/organization · PATCH /workspaces/:workspace/session/:id/organization |
session_organization; legacy-primary or persisted-workspace |
DaemonClient.listSessionGroups, DaemonClient.createSessionGroup, DaemonClient.updateSessionGroup, DaemonClient.deleteSessionGroup, DaemonClient.updateSessionOrganization; WorkspaceDaemonClient.updateSessionOrganization |
| Bulk persisted-session changes | POST /sessions/delete · POST /sessions/archive · POST /sessions/unarchive |
session_archive; legacy-primary |
DaemonClient.deleteSessionsData, DaemonClient.archiveSessionsData, DaemonClient.unarchiveSessionsData |
| Message deletion | GET /session/:id/message-groups · POST /session/:id/messages/delete |
session_message_deletion; live-session-owner |
DaemonClient.getMessageGroups, DaemonClient.deleteMessageGroups |
| Streaming side questions | POST /session/:id/btw/stream · DELETE /session/:id/btw/stream/:requestId |
session_btw_stream; live-session-owner |
DaemonClient.streamSessionBtw, raw REST for cancellation |
| Optional session controls | POST /session/:id/recap · POST /session/:id/generate · POST /session/:id/approval-mode |
session_recap, session_generation, session_approval_mode_control; live-session-owner |
DaemonClient.recapSession, raw REST for generation, DaemonClient.setSessionApprovalMode |
| Workspace configuration | POST /workspace/tools/:name/enable · POST /workspace/skills/:name/enable · POST /workspace/skills/enable · POST /workspace/init · POST /workspace/mcp/reload · POST /workspace/mcp/:server/restart · POST /language |
workspace_tool_toggle, workspace_skill_settings_toggle, workspace_skill_settings_batch_toggle, workspace_init, workspace_mcp_manage, workspace_mcp_restart, user_language_sync; legacy-primary or process-global |
DaemonClient.setWorkspaceToolEnabled, DaemonClient.setWorkspaceSkillEnabled, DaemonClient.setWorkspaceSkillsEnabled, DaemonClient.initWorkspace, DaemonClient.reloadWorkspaceMcp, DaemonClient.restartMcpServer, setUserLanguage |
| Skill runtime refresh | POST /workspace/skills/refresh |
workspace_session_refresh; legacy-primary |
DaemonClient.refreshWorkspaceSkills |
| Device-flow authentication | POST /workspace/auth/device-flow · GET /workspace/auth/device-flow/:id · DELETE /workspace/auth/device-flow/:id · GET /workspace/auth/status |
auth_device_flow; legacy-primary |
DaemonClient.startDeviceFlow, getDeviceFlow, cancelDeviceFlow, getAuthStatus |
Routes without a dedicated protocol section are intentionally absent from this index. They may be first-party Web Shell plumbing or conditional implementation surfaces and are not promoted to an integration contract by omission.
Common protocol rules
- Authenticate normal routes with
Authorization: Bearer <token>. A default loopback/healthprobe may be exempt; non-loopback binds are not. - Send
X-Qwen-Client-Idwhen a create/load response supplied one. It is an attachment and attribution identifier, not an end-user security principal. - Treat error bodies as additive. Branch primarily on HTTP status and the
stable
codeorerrorKindwhen present. - Preserve SSE response headers and disable proxy buffering. Resume with both
Last-Event-IDandX-Qwen-Event-Epochwhen the daemon supplied an epoch. - A workspace trust boundary is not tenant isolation. Run separate daemons when security principals or process-level failure boundaries must be independent.