qwen serve HTTP-Protokollreferenz
Stufe 1 des qwen-code Daemon-Designs . Alle Routen befinden sich unter der Basis-URL des Daemons (Standard http://127.0.0.1:4170).
Authentifizierung
Wenn der Daemon mit --token oder QWEN_SERVER_TOKEN gestartet wurde – oder non-loopback gebunden ist, ohne dass eines von beiden angegeben ist, wodurch ein ephemerales Bearer-Token generiert und einmalig beim Start ausgegeben wird – muss jede normale API-Route außer /health bei gewöhnlichen Loopback-Binds Folgendes enthalten:
Authorization: Bearer <token>Ohne konfigurierten Token beim Loopback-Standard ist der Header optional, und Anfragen, die über den primären Listener eintreffen, haben volle Operator-API-Autorität. Workspace-Trust, Session-Eigentümerschaft, X-Qwen-Client-Id, Berechtigungs-, Feature-, Validierungs- und Ressourcenprüfungen gelten weiterhin. Der Token-Vergleich erfolgt in konstanter Zeit. 401-Antworten sind einheitlich für missing header / wrong scheme / wrong token.
--open-with-auth. Dieser standardmäßig ausgeschaltete CLI-Modus erfordert einen Loopback-Bind und eine verfügbare Web Shell. Er verwendet die normale --token-über-QWEN_SERVER_TOKEN-Auswahl oder generiert 32 zufällige Bytes, kodiert als base64url, vor dem Daemon-Start, wenn diese Auswahl leer ist. Der Browser erhält das ausgewählte Bearer-Token über #token= und speichert es pro Tab; das Protokoll und die Middleware sehen ein gewöhnliches konfiguriertes Token. Die Fragment-Zustellung keyt auf dem aufgelösten Token, nicht auf diesem Flag: Jeder --open-Start hängt das aufgelöste Bearer-Token – konfiguriert oder generiert – an das #token=-Fragment der gestarteten URL an (für lokale Benutzer über ps / /proc sichtbar, wie der Launcher warnt), sodass ein Non-Loopback-Bind mit barem --open sein generiertes Bearer-Token auf dieselbe Weise an den Browser übergibt; die unterschiedlichen Beiträge dieses Flags sind die Token-Generierung auf Loopback und das für Browser ungeeignete manuelle-URL-Fallback. Nur direkte eingebettete Caller, die RunHandle.resolvedToken ignorieren, und Clients, die niemals einen Browser starten, erhalten keine automatische Credential. Browser-ungeeignete Umgebungen geben die secret-haltige Fragment-URL zum manuellen Öffnen aus. Loopback-/health und statische Web-Shell-Assets behalten die unten beschriebenen Ausnahmen; --require-auth blockiert weiterhin /health.
Channel-Webhook-Ingress (POST /channels/:channelName/webhooks/:source) ist in jedem Modus von diesem Bearer-Contract getrennt. Wenn er gemountet wird, wird er vor bearerAuth registriert und authentifiziert sich mit seinem konfigurierten x-qwen-webhook-secret; das Rotieren des Daemon-Bearers rotiert nicht die Webhook-Quell-Secrets.
/health-Ausnahme (Bctum): Bei Loopback-Binds (127.0.0.0/8 / localhost / ::1 / [::1]) wird /health VOR der Bearer-Middleware registriert, sodass Liveness-Probes innerhalb des Pods den Token nicht mitsenden müssen, auch wenn der Daemon mit --token gestartet wurde. Non-Loopback-Binds (--hostname 0.0.0.0 usw.) schalten /health wie jede andere Route hinter den Bearer – siehe den Abschnitt GET /health für die Begründung.
--require-auth (#4175 PR 15). Übergib diesen Flag beim Start, um die Regel “Token ist erforderlich” auch auf Loopback auszudehnen. Der Start schlägt fehl, wenn keine Token-Quelle aufgelöst werden kann – auf Loopback bedeutet das --token, QWEN_SERVER_TOKEN oder --open-with-auth (das vor dem Start sein eigenes generiertes Token installiert, sodass --require-auth --open-with-auth startet). Der Fail-Fast ist nur auf Loopback: Ein Non-Loopback-Bind löst das ephemerales Bearer-Token auf, das er generiert, wenn keine konfigurierte Quelle vorhanden ist, und das erfüllt den Flag. Die /health-Ausnahme wird in beiden Fällen aufgehoben, sodass /health ebenfalls Authorization: Bearer … erfordert.
Wenn der Flag aktiviert ist, blockiert die globale bearerAuth-Middleware jede normale API-Route – einschließlich /health und /capabilities. Channel-Webhook-Ingress bleibt unabhängig über Shared-Secret authentifiziert, und Web-Shell-Dokument- und Asset-Routen bleiben pre-auth. Ein nicht authentifizierter Client kann daher nicht caps.features vorab prüfen (pre-flight), um herauszufinden, dass Auth erforderlich ist: Die Discover-Oberfläche für diesen Fall ist der 401-Antwort-Body selbst (einheitlich für alle Bearer-geblockten Routen gemäß dem Abschnitt Authentifizierung). Der require_auth Capability-Tag ist eine Bestätigung nach der Authentifizierung – sobald sich ein Client erfolgreich authentifiziert und /capabilities liest, bestätigt das Vorhandensein des Tags, dass der Daemon mit --require-auth gestartet wurde (nützlich für Audit-/Compliance-UIs und für SDK-Clients, um “diese Bereitstellung ist gehärtet” in einem Einstellungsbereich anzuzeigen). Strikte Mutationsrouten akzeptieren vertrauenswürdige Loopback-Primary-Listener-Anfragen, Bearer-authentifizierte Anfragen oder gepaarte Local-Control-Anfragen. Nicht vertrauenswürdige Token-lose Embeds erhalten weiterhin 401 { code: "token_required", error: "…" }; mit --require-auth weist die globale Bearer-Middleware zuerst mit dem Legacy-Unauthorized-Body ab.
--allow-origin <pattern> (T2.4 #4514 ). Browser-Clients, die den Daemon cross-origin aufrufen, werden standardmäßig blockiert – jede Anfrage, die einen Origin-Header enthält, gibt 403 {"error":"Request denied by CORS policy"} zurück, da CLI/SDK-Clients niemals Origin senden und der Daemon dessen Vorhandensein als Zeichen wertet, dass die Anfrage aus einem Browser-Kontext stammt, in den der Operator nicht eingewilligt hat. Eine Ausnahme geht der Blockade bei einem Non-Loopback-Bind mit Token voraus: Eine Same-Origin-Anfrage (Origin gleich dem direkten Socket-Schema plus der normalisierten Host-Authority) wird Bearer-authentifiziert und ihre Origin auf dem primären Listener entfernt – bei gültigem Bearer folgt der eigene Status der Route, bei fehlendem oder ungültigem Bearer ein 401, ausgenommen die Pre-Auth-Web-Shell-Dokument-, /assets/*- und /mcp-app-sandbox-Routen, die ohne Credential bedient werden wie in jedem anderen Modus – und nur Cross-Origin- oder nicht übereinstimmende Origin-Werte behalten die 403-Hülle. Übergib --allow-origin <pattern> (wiederholbar) beim Start, um eine Allowlist anstelle der Blockade zu installieren. Jedes Muster ist entweder:
- Das Literal
*– lässt jede Origin zu. Riskant: Der Start wird abgelehnt, wenn*konfiguriert ist, aber kein Bearer-Token aufgelöst wird. Der Guard liest das aufgelöste Token –--token,QWEN_SERVER_TOKEN, das generierte Loopback-Token von--open-with-authoder das ephemerales Bearer-Token, das ein Non-Loopback-Bind generiert, wenn keine konfigurierte Quelle vorhanden ist – sodass diese Ablehnung nur auf Loopback erfolgt. Der Start-Breadcrumb gibt eine Stderr-Warnung aus, wenn*in der Liste ist. Empfehlung: Kombiniere dies mit--require-authbei Loopback-Binds, sodass/healthebenfalls durch den Bearer geschützt ist – es wird bei Loopback standardmäßig vor der Bearer-Middleware registriert (sodass k8s/Compose-Probes/healthohne Token erreichen können), und eine*-Allowlist macht es von jedem Cross-Origin-Browser aus erreichbar.--require-authlässt dennoch die Web-Shell-Static-Assets (/,/assets/*und/session/:idDokument-Navigationen) pre-auth auf Loopback – sie sind absichtlich vor der Bearer-Middleware gemountet – unter einer*-Allowlist bleiben sie von jedem Cross-Origin-Browser lesbar;--no-webentfernt diese Oberfläche. Bei Non-Loopback-Binds ist der Bearer beim Start bereits obligatorisch und/healthist dahinter registriert. Normale API-Routen sind Bearer-geblockt, Channel-Webhook-Ingress behält sein eigenes Shared-Secret-Gate, und Web-Shell-Static-Assets (/,/assets/*und/session/:idDokument-Navigationen) bleiben pre-auth, außer--no-webentfernt sie. - Eine kanonische URL-Origin —
<scheme>://<host>[:<port>]. Kein abschließender Schrägstrich, kein Pfad, keine Userinfo, kein Query. Der Start wird mitInvalidAllowOriginPatternErrorabgelehnt, wenn der Eintrag den Roundtripnew URL(pattern).origin === patternnicht besteht; die Fehlermeldung nennt das fehlerhafte Muster und die kanonische Form. Absichtlich strikt: Eine stille Normalisierung (z. B. das Entfernen eines abschließenden/) würde Tippfehler durchrutschen lassen und mehrdeutige Eingaben akzeptieren. Ohne ein aufgelöstes Token – was nach der Generierung einen Loopback-Bind bedeutet – sind HTTP(S)-Einträge auf Loopback-Hosts beschränkt; eine Non-Loopback-Browser-Origin erfordert ein Token, da sie sonst die volle Operator-API ausführen kann, einschließlich Code-Ausführung als Daemon-Benutzer. Explizite Browser-Extension-Originen behalten ihren bestehenden Token-losen lokalen Automatisierungspfad. Der Start protokolliert die Autorität, die jeder Token-losen erlaubten Browser-Origin gewährt wird.
Übereinstimmende Origins erhalten bei jeder Anfrage die Standard-CORS-Antwortheader:
Access-Control-Allow-Origin: <echoed origin>
Vary: Origin
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Qwen-Client-Id, Last-Event-ID, X-Qwen-Event-Epoch
Access-Control-Max-Age: 86400
Access-Control-Expose-Headers: Retry-After, X-Qwen-Event-Epoch, X-Qwen-SSE-Stream-IdAccess-Control-Allow-Origin gibt die Origin der Anfrage wortwörtlich wieder (Klein-/Großschreibung wie vom Browser gesendet) und nicht das Literal *, selbst unter dem *-Muster – Browser-Caches keyern Antworten darauf in Kombination mit Vary: Origin, und das Echo lässt Raum, um in einer späteren Version Access-Control-Allow-Credentials ohne Schemaänderung hinzuzufügen. Die exponierten Header ermöglichen es Browser-Clients, Retry-Hinweise zu beachten, die SSE-Epoche beizubehalten und akzeptierte physische Streams zu korrelieren. Die exponierten Header ermöglichen es Browser-Clients, Retry-Hinweise zu beachten, die SSE-Epoche beizubehalten und akzeptierte physische Streams zu korrelieren. Access-Control-Allow-Credentials wird heute NICHT gesendet: Konfigurierte Daemon-Credentials verwenden Bearer-in-Authorization, was cross-origin ohne credentials: 'include' funktioniert; vertrauenswürdige Loopback-Autorität benötigt keine Browser-Credential.
OPTIONS-Preflight-Anfragen (OPTIONS mit Access-Control-Request-Method oder Access-Control-Request-Headers) werden mit 204 No Content plus den obigen Headern kurzgeschlossen (short-circuit). Dies ist das konventionelle CORS-Muster und sicher – der Preflight bestätigt nur, welche Methoden/Header der Daemon akzeptiert; die eigentliche nachfolgende Anfrage durchläuft weiterhin das Host-Gate und dann entweder die Bearer/Listener-Autorität oder das Channel-Webhook-Shared-Secret-Gate, bevor ein Zustand gelesen oder verändert wird. Normale OPTIONS-Anfragen von übereinstimmenden Origins fließen weiterhin nach unten (downstream), wobei die CORS-Header angehängt bleiben.
Origins, die nicht mit der Allowlist übereinstimmen, erhalten weiterhin 403 {"error":"Request denied by CORS policy"} – dieselbe Hülle (Envelope) wie die Standard-Blockade, sodass Clients, die die Antwort der Blockade bereits geparst haben, keine Sonderbehandlung für Allowlist-bereitgestellte Daemons vornehmen müssen. Der Ablehnungspfad gibt keine Access-Control-*-Header aus (der Browser würde sie ignorieren, und die Ausgabe würde indirekt die Größe der Allowlist durch das Vorhandensein der Header bekannt geben).
Die konfigurierte Musterliste wird absichtlich NICHT in /capabilities widergespiegelt – das Browser-WebUI kennt seine eigene Origin bereits (es hat den Daemon schließlich aufgerufen), und das Offenlegen der Liste würde einem nicht authentifizierten Leser von /capabilities ermöglichen, jede vertrauenswürdige Origin aufzuzählen (nützliche Aufklärung für eine falsch konfigurierte Bereitstellung). SDK-Clients prüfen anhand des Tags caps.features.allow_origin, ob “dieser Daemon Cross-Origin-Browser-Treffer beachtet”, ohne die spezifischen Origins kennen zu müssen.
Loopback-Self-Origin-Anfragen (z. B. die Web Shell, die den Daemon auf demselben 127.0.0.1:port aufruft) werden von einem separaten Origin-Strip-Shim verarbeitet, der VOR der CORS-Middleware läuft und den Origin-Header für 127.0.0.1:port / localhost:port / [::1]:port / host.docker.internal:port oder die exakte gebundene Loopback-Adresse und -Port entfernt. Er akzeptiert auch die schema-übereinstimmenden Port-losen Formen, die Browser für Standard-Ports senden: http://host auf Port 80 und https://host auf Port 443. Diese Anfragen werden unabhängig von der --allow-origin-Konfiguration durchgelassen – Operatoren müssen den eigenen Port des Daemons nicht auflisten, damit die Web Shell funktioniert.
Allgemeines Fehlerformat
5xx-Antworten tragen den code und data des ursprünglichen Fehlers, falls vorhanden (JSON-RPC-Stil – das ACP SDK leitet {code, message, data} vom Agenten weiter):
{
"error": "Internal error",
"code": -32000,
"data": { "reason": "model quota exceeded" }
}Fehlerhaftes JSON in einem Request-Body gibt Folgendes zurück:
{ "error": "Invalid JSON in request body" }mit Status 400.
SessionNotFoundError für eine unbekannte Session-ID gibt Folgendes zurück:
{
"error": "No session with id \"<sid>\"",
"sessionId": "<sid>",
"code": "session_not_found"
}mit Status 404. Ein gleichzeitiges Schließen verwendet code: "session_closing".
WorkspaceMismatchError für ein POST /session, dessen cwd nicht zu einem registrierten Workspace kanonisiert wird, gibt 400 zurück mit:
{
"error": "Workspace mismatch: daemon is bound to \"…\"",
"code": "workspace_mismatch",
"boundWorkspace": "/path/the/daemon/uses/as-primary",
"requestedWorkspace": "/path/in/the/request"
}Verwende dies, um Fehlanpassungen vorab (pre-flight) zu erkennen: Lies workspaceCwd aus /capabilities und lass cwd bei POST /session weg (es fällt auf den primären Workspace zurück), oder wähle bei beworbenem multi_workspace_sessions eines von workspaces[].cwd.
POST /session jenseits der --max-sessions-Obergrenze des Daemons gibt 503 mit einem Retry-After: 5-Header zurück und:
{
"error": "Session limit reached (20)",
"code": "session_limit_exceeded",
"limit": 20,
"scope": "workspace"
}Wenn --max-total-sessions eine neue Session ablehnt, wird dieselbe Antwortform mit "scope": "total" zurückgegeben.
Anhängungen (Attaches) an bestehende Sessions werden NICHT auf die Obergrenze angerechnet, sodass Reconnects eines inaktiven Daemons auch bei voller Kapazität weiterhin funktionieren.
RestoreInProgressError – wird von POST /session/:id/load, POST /session/:id/resume oder einer POST /session mit vom Aufrufer bereitgestellter ID ausgegeben, wenn eine andere Registrierung diese ID bereits besitzt – gibt 409 zurück und:
{
"error": "Session \"<sid>\" is already being restored via session/<resume|load>; retry session/<load|resume> after it completes",
"code": "restore_in_progress",
"reason": "restore_in_progress",
"retryable": true,
"sessionId": "<sid>",
"activeAction": "load",
"requestedAction": "resume"
}Wird ausgelöst, wenn ein session/load für eine ID ausgegeben wird, bei der bereits ein session/resume läuft (oder umgekehrt), oder wenn ein Spawn mit vom Aufrufer bereitgestellter ID gegen eine der beiden Restore-Richtungen racet. Warte mindestens Retry-After Sekunden und versuche es erneut. Races mit derselben Aktion (load vs. load, resume vs. resume) werden zusammengeführt (coalesce), anstatt einen Fehler zu verursachen, solange der Restore aktiv ist.
reason unterscheidet zwei Zäune, die diesen Code teilen, und der Retry-After-Header verfolgt ihn:
reason | Bedeutung | Retry-After |
|---|---|---|
restore_in_progress | Ein gewöhnlicher Restore läuft. | 5 (passend zu session_limit_exceeded) |
awaiting_abandoned_cleanup | Der öffentliche Caller hat bereits einen 504 erhalten und die nicht abbrechbare ACP-Anfrage samt Cleanup ist noch nicht abgeschlossen. | das effektive Restore-Budget in Sekunden, begrenzt auf 5–120 |
Die öffentliche Restore-Anfrage unterliegt limits.sessionRestoreTimeoutMs (Standard 60 s). Nach einem 504 bleibt die ID eingezäunt, bis die späte ACP-Anfrage und das Cleanup abgeschlossen sind – ein Client, der im gewöhnlichen 5-Sekunden-Takt weiter retryt, würde sich gegen einen 409 drehen, den er nicht auflösen kann. Beachte den Budget-abgeleiteten Hinweis, der mit awaiting_abandoned_cleanup kommt.
SessionWorkspaceConflictError – wird von POST /session/:id/load und POST /session/:id/resume ausgegeben, wenn das angeforderte cwd auf einen registrierten Workspace zielt, aber dieselbe Session-ID bereits in einer anderen Runtime live ist oder wiederhergestellt wird – gibt 409 zurück mit:
{
"error": "Session \"<sid>\" is already live or restoring in another workspace runtime.",
"code": "session_workspace_conflict",
"sessionId": "<sid>",
"workspaceCwd": "/requested/workspace",
"workspaceId": "requested-workspace-id",
"liveWorkspaceCwd": "/live/owner/workspace",
"liveWorkspaceId": "live-owner-workspace-id"
}Clients sollten mit dem besitzenden Workspace erneut versuchen oder warten, bis der laufende Restore abgeschlossen ist, bevor sie die ID in einem anderen Workspace wiederherstellen. Same-Workspace-Restore-Races verwenden weiterhin das restore_in_progress-/Coalescing-Verhalten der Bridge.
SessionArchivedError wird ausgegeben, wenn ein Aufrufer versucht, eine Session zu laden oder fortzusetzen, deren JSONL sich unter chats/archive/ befindet:
{
"error": "Session \"<sid>\" is archived. Unarchive it before loading.",
"code": "session_archived",
"sessionId": "<sid>"
}mit Status 409.
SessionArchivingError wird ausgegeben, wenn für dieselbe ID bereits ein Archivierungs- oder Dearchivierungsübergang läuft:
{
"error": "Session \"<sid>\" is being archived or unarchived; retry later.",
"code": "session_archiving",
"sessionId": "<sid>"
}mit Status 409 und Retry-After: 5.
Capabilities
Der Daemon bewirbt seine unterstützten Feature-Tags aus der Serve-Capability-Registry. Clients müssen die UI anhand von features steuern, nicht anhand von mode (gemäß Design §10).
['health', 'capabilities', 'session_create', 'session_id_override', 'session_scope_override',
'session_load', 'session_resume', 'session_transcript',
'unstable_session_resume',
'session_list', 'session_info', 'session_prompt', 'session_mid_turn_message_mutation',
'session_cancel', 'session_events',
'slow_client_warning', 'typed_event_schema',
'session_set_model', 'client_identity', 'client_heartbeat',
'session_permission_vote', 'permission_vote', 'workspace_mcp', 'workspace_skills',
'workspace_skills_config_runtime',
'workspace_providers', 'workspace_acp_preheat', 'workspace_acp_status',
'auth_provider_install', 'workspace_memory',
'workspace_agents', 'workspace_agent_generate', 'workspace_env',
'workspace_preflight', 'session_context', 'session_context_usage',
'session_supported_commands', 'session_tasks', 'session_monitor_tool_correlation', 'session_stats',
'session_lsp', 'session_resources', 'session_status',
'session_close', 'session_metadata', 'session_organization',
'session_archive', 'mcp_guardrails',
'workspace_mcp_manage', 'mcp_guardrail_events',
'mcp_server_runtime_mutation',
'workspace_file_read', 'workspace_file_bytes', 'workspace_file_write',
'workspace_file_upload',
'session_approval_mode_control', 'workspace_tool_toggle',
'workspace_skill_settings_toggle', 'workspace_skill_settings_batch_toggle',
'extension_batch_activation_v2', 'extension_activation_explicit_refresh',
'extension_state',
'workspace_settings', 'workspace_init', 'workspace_mcp_restart',
'session_recap', 'session_generation', 'session_btw', 'session_shell_command',
'standalone_sessions_v1', 'standalone_session_options_v1',
'mcp_workspace_pool', 'mcp_pool_restart',
'require_auth', 'allow_origin', 'auth_device_flow',
'permission_mediation', 'prompt_absolute_deadline', 'writer_idle_timeout',
'non_blocking_prompt', 'session_language', 'user_language_sync', 'session_rewind',
'workspace_hooks', 'session_hooks', 'workspace_extensions',
'session_branch', 'rate_limit', 'workspace_reload', 'channel_delivery',
'multi_workspace_sessions', 'multi_workspace_session_rewind',
'multi_workspace_session_shell', 'persistent_workspace_registration',
'workspace_display_name', 'workspace_runtime_removal', 'workspace_runtime',
'workspace_qualified_rest_core', 'workspace_qualified_voice',
'workspace_qualified_memory', 'extension_management_v2', 'extension_git_credentials',
'extension_local_path_install',
'workspace_persisted_transcript',
'workspace_session_export', 'workspace_archived_session_export',
'workspace_session_live_state',
'client_mcp_over_ws', 'cdp_tunnel_over_ws', 'browser_automation_mcp']Bedingte Tags erscheinen nur, wenn der entsprechende Deployment-Toggle aktiviert ist (siehe Tabelle unten). Das
permission_mediation-Tag von F3 ist immer aktiv und enthältmodes: ['first-responder', 'designated', 'consensus', 'local-only'], sodass SDK-Clients die vom Build unterstützte Menge introspektieren können; die zur Laufzeit aktive Strategie befindet sich unterbody.policy.permission.
session_scope_override ist der Negotiation-Handle für das anfragebezogene sessionScope-Feld bei POST /session (siehe unten). Ältere Daemons ignorieren dieses Feld stillschweigend, daher sollten SDK-Clients caps.features vor dem Senden auf dieses Tag prüfen.
session_id_override ist der Negotiation-Handle für die optionale vom Aufrufer bereitgestellte sessionId bei POST /session und ACP session/new-Metadaten. Clients müssen bestätigen, dass caps.features dieses Tag enthält, bevor sie das Feld senden, da ältere Daemons es stillschweigend ignorieren können.
persistent_workspace_registration bewirbt dauerhafte Registrierung für zur Laufzeit hinzugefügte Workspaces. POST /workspaces akzeptiert { "cwd": "/absolute/path", "persist": true }; der Erfolg enthält persisted: true. Registrierungen sind auf den kanonischen primären Workspace des Daemons unter dem Qwen-Home-Verzeichnis des Benutzers beschränkt und werden beim nächsten Daemon-Start wiederhergestellt. Das Weglassen von persist bewahrt die prozesslokale Registrierung. GET /workspace-registrations listet die gespeicherte gewünschte Menge auf, und DELETE /workspace-registrations/:id vergisst einen Eintrag für den nächsten Neustart, ohne eine aktive Runtime hot zu entfernen.
workspace_display_name bewirbt die optionale displayName-Eingabe bei POST /workspaces, Workspace-Metadatenupdates über PATCH /workspaces/:workspace und optionale Display-Name-Felder in Workspace-Projektionen. Namen nehmen nicht an Lookup oder Routing teil: id und kanonisches cwd bleiben die einzigen Selektoren, und doppelte Namen sind erlaubt.
workspace_runtime_removal bewirbt das synchrone Hot Removal über DELETE /workspaces/:workspace. Capability-Workspace-Einträge fügen ein optionales removable hinzu; nur Zeilen mit removable: true dürfen entfernt werden. Das Entfernen vergisst auch alle persistenten Registrierungsaliase für die Runtime, löscht aber niemals Dateien, Einstellungen, Transkripte oder Archive.
workspace_runtime bewirbt GET /workspace/runtime/status, POST /workspace/runtime/ensure und ihre /workspaces/:workspace/runtime/...-Äquivalente. ensure akzeptiert keine Capability-Selektion: es startet oder wiederverwendet die ACP-Runtime des ausgewählten vertrauenswürdigen Workspaces und gibt seinen Lifecycle-Zustand und die monotone Runtime-Epoche zurück. Die Primary-Routen gehören nur der Primary-Runtime; qualifizierte Routen lösen nur die ausgewählte registrierte Runtime auf und fallen niemals zurück. Ein erfolgreiches Ensure erneuert das Zehn-Minuten-Keepalive-Fenster der Runtime. Status ist schreibgeschützt und startet niemals das Kind. Gleichzeitige Ensures teilen sich einen physischen Startup. Ein laufender Startup oder Fehler gibt das retrybare 503 runtime_still_starting oder 503 runtime_initialization_failed zurück. Die Capability-Vorbereitung kann nach Ablauf des Ensure-Beobachtungsbudgets fortsetzen; in diesem Fall gibt Ensure weiterhin die Live-Runtime mit einem nicht-bereiten Capability-Zustand für Status-Polling zurück. Die Capability wird nur beworben, wenn alle aktiven Runtime-Bridges den autoritativen Lifecycle-Snapshot bereitstellen; eine ausgewählte Legacy-injizierte Bridge gibt 501 workspace_runtime_not_supported zurück, anstatt einen geratenen Zustand oder eine Epoche auszugeben.
Runtime-Status- und Ensure-Antworten verwenden folgende Form:
{
"v": 1,
"workspaceCwd": "/work/project",
"state": "active",
"runtimeLive": true,
"runtimeEpoch": 4,
"capabilities": {
"skills": {
"state": "ready",
"revision": 2,
"runtimeEpoch": 4
}
}
}capabilities.skills.state ist not_started, starting, ready, stale oder error; Fehler enthalten {code, message} in capabilities.skills.error. Ein Skills-Katalog ist nur aktuell, wenn die Top-Level- und Capability-runtimeEpoch-Werte übereinstimmen. revision ordnet die Skills-Vorbereitung innerhalb einer Runtime-Epoche und darf nicht über Epochen hinweg verglichen werden.
workspace_skills_config_runtime bewirbt die aufgeteilten Skills-Lesevorgänge und Konfigurationsmutationen unter /workspace/{config,runtime}/skills und /workspaces/:workspace/{config,runtime}/skills. Die WebShell verwendet diese Routen für Skills-Management und, beim Zusammenstellen einer neuen Session, um Slash-Befehle sofort aus der Konfiguration zu befüllen, bevor sie durch einen Runtime-Katalog mit passender Epoche ersetzt werden. Wenn das Feature fehlt, müssen Clients die Legacy-Skills-Routen weiterhin verwenden und dürfen nicht allein für Skills ein Runtime-Ensure aufrufen.
session_load und session_resume bewerben die Explicit-Restore-Routen (POST /session/:id/load und POST /session/:id/resume). Ältere Daemons geben für diese Pfade 404 zurück, daher sollten SDK-Clients caps.features vor dem Aufruf prüfen. unstable_session_resume wird weiterhin als veraltetes Alias für die Kompatibilität mit SDKs beworben, die ausgeliefert wurden, als die zugrunde liegende ACP-Methode noch connection.unstable_resumeSession hieß; neue Clients sollten auf session_resume prüfen.
limits.sessionRestoreTimeoutMs ist, wenn vorhanden, das Wall-Clock-Budget des Daemons für die zugrunde liegende ACP-loadSession- / unstable_resumeSession-Anfrage. Es ist ein additives v1-Feld. Das TypeScript-SDK gibt dem Daemon 10 Sekunden Client-Headroom, und der WebUI-Watchdog gibt ihm 15 Sekunden; Clients, die mit einem älteren Daemon sprechen, sollten jeweils 70 Sekunden und 75 Sekunden verwenden.
session_transcript bewirbt GET /session/:id/transcript, eine schreibgeschützte seitenweise Replay-Ansicht über das persistierte aktive Session-JSONL. Sie ist getrennt von /load: Sie hängt keinen Client an, seedet nicht den live EventBus, erstellt keine Live-Session und ändert nicht das Live-Replay-Fenster. Clients sollten sie verwenden, wenn sie das vollständige Transkript auf der Festplatte für eine lange Session benötigen, und weiterhin /load nur für begrenztes Live-Replay beim Cold-UI-Restore verwenden.
workspace_persisted_transcript bewirbt GET /workspaces/:workspace/session/:id/transcript, einen daemon-lokalen rein persistierten Pager, der keinen ACP startet, den Live-Bridge-Zustand abfragt, Einstellungen lädt, Projekt-Capabilities entdeckt oder den Legacy-Persisted-Cursor-Key erstellt. Das Tag ist bedingungslos, da vertrauenswürdige Single-Workspace-Primaries die Plural-Route verwenden können; die Workspace-Trust-Autorisierung wird weiterhin bei jeder Anfrage ausgewertet. Registrierte nicht vertrauenswürdige sekundäre Workspaces dürfen lesen, während ein nicht vertrauenswürdiger Primary weiterhin abgelehnt wird.
workspace_session_export bewirbt GET /workspaces/:workspace/session/:id/export, einen rein vertrauenswürdigen vollständigen Export der aktiven persistierten Session des ausgewählten Workspaces. Er ist unabhängig von session_export und workspace_qualified_rest_core: Veröffentlichte Daemons können beide älteren Tags bewerben, ohne die Plural-Route zu implementieren, daher müssen Clients dieses Tag direkt vorab prüfen. Das Tag ist bedingungslos, da ein vertrauenswürdiger Single-Workspace-Primary die Route nach ID oder CWD verwenden kann. Der Export löst keinen Live-Owner auf, startet keinen ACP, hängt keinen Client an oder fällt auf einen anderen Workspace zurück.
workspace_archived_session_export bewirbt GET /workspaces/:workspace/session/:id/archive/export, einen rein vertrauenswürdigen vollständigen Export aus dem archivierten persistierten Speicher des ausgewählten Workspaces. Er ist unabhängig von workspace_session_export und workspace_qualified_rest_core; Clients müssen dieses Tag direkt vorab prüfen. Eine eigene Route verhindert, dass ein älterer Daemon die Archiv-Absicht ignoriert und ein aktives Transkript mit derselben ID zurückgibt.
workspace_session_live_state bewirbt GET /workspaces/:workspace/sessions/live-state, einen rein vertrauenswürdigen, speicherbasierten Snapshot der Live-Sessions der ausgewählten Workspace-Runtime plus eine speicherinterne Katalogversion, die Clients mitteilt, wann ein vollständiges persistiertes Katalog-Reload erforderlich ist. Er ist unabhängig von workspace_qualified_rest_core: Veröffentliche Daemons können die breitere Workspace-REST-Capability bewerben, ohne diese Route zu implementieren, daher müssen Clients dieses Tag direkt vorab prüfen. Das Tag ist bedingungslos, weil ein vertrauenswürdiger Single-Workspace-Primary die Route nach ID oder CWD verwenden kann; Pro-Workspace-Trust-Prüfungen gelten weiterhin bei jeder Anfrage, und die Route erweitert nicht die permissive Lese-Richtlinie für nicht vertrauenswürdige Secondaries auf den Live-Bridge-State. Das Tag bedeutet, dass der Endpunkt existiert; es verspricht nicht, dass jedes Live-Element den optionalen updatedAt-Aktivitäts-Watermark trägt, der lebenszyklusabhängig ist.
Das optionale Top-Level-/capabilities-Feld sessionLiveStatePollIntervalMs bewirbt das Daemon-weite Live-State-Polling-Intervall in Millisekunden. Es wird einmal aus der Startup-Umgebungsvariable QWEN_SESSION_LIVE_STATE_POLL_INTERVAL_MS aufgelöst, unabhängig von Workspace-Umgebungs-Overlays. Ganzzahlige Werte von 1000 bis 2147483647 werden akzeptiert; fehlende oder ungültige Werte verwenden 5000. Die WebShell verbraucht diesen Hinweis für das Live-State-Polling aller Workspaces und fällt auf 5000 zurück für ein fehlendes oder ungültiges Feld von einem älteren oder inkompatiblen Daemon. Dieses Feld ändert nicht die Snapshot-Semantik der Route, sofortige lokale/Sichtbarkeits-Aktualisierungen oder das Vollkatalog-Polling. SDK-Clients bleiben für ihre eigenen Timer verantwortlich.
slow_client_warning deckt das SSE-Backpressure-Verhalten ab: (a) Der Daemon emittiert einen synthetischen slow_client_warning-Event-Stream-Frame, wenn der Live-Frame-Backlog oder der Live-Serialized-Byte-Backlog eines Subscribers 75 % Kapazität überschreitet, einmal pro Überlauf-Episode (wird wieder aktiviert, nachdem beide Messwerte unter 37,5 % abgefallen sind); (b) GET /session/:id/events akzeptiert einen ?maxQueued=N-Query-Parameter (Bereich [16, 2048]), um den subscriberbezogenen Frame-Backlog für Cold-Reconnects gegen einen großen Replay-Ring vorzudimensionieren. Das Serialized-Byte-Limit liegt in der Verantwortung des Daemons (Standard 2 MiB pro Subscriber), ist nur für Live-Daten gedacht und hat absichtlich keinen Query-Parameter. Die Daemon-weite Ringgröße wird durch --event-ring-size gesteuert (Standard 8000, gemäß #3803 §02). Ältere Daemons unterstützen das Warnungs-/Query-Verhalten nicht und ignorieren es stillschweigend – prüfe dieses Tag vor der Aktivierung.
typed_event_schema bewirbt Daemon-Event-Payloads, die dem KnownDaemonEvent-Schema des SDK entsprechen. Ältere Daemons streamen möglicherweise weiterhin kompatible Frames, aber SDK-Clients sollten dieses Tag prüfen, bevor sie von einer Typed-Event-Abdeckung ausgehen.
client_heartbeat bewirbt POST /session/:id/heartbeat. Ältere Daemons geben 404 zurück; prüfe dieses Tag, bevor du periodische Heartbeats sendest.
session_close und session_metadata bewerben DELETE /session/:id und PATCH /session/:id/metadata. Ältere Daemons geben 404 zurück; prüfe diese Tags, bevor du Close- oder Rename-Funktionen bereitstellst.
session_organization bewirbt benutzerdefinierte Session-Gruppen und Pinning. Es fügt GET/POST/PATCH/DELETE /workspace/:id/session-groups, PATCH /session/:id/organization und die optionale organisierte Listenansicht GET /workspace/:id/sessions?view=organized hinzu. Wenn sowohl session_organization als auch workspace_qualified_rest_core beworben werden, ist auch die Workspace-qualifizierte Organisationsmutation PATCH /workspaces/:workspace/session/:id/organization verfügbar. Die Legacy-Mutation bleibt nur für den primären Workspace. Ältere Daemons geben für die Mutations-/Gruppenrouten 404 zurück und ignorieren den Contract der organisierten Ansicht. WebShell-/SDK-Clients müssen diese Tags daher prüfen, bevor sie die entsprechende Gruppierungs- oder Pinning-UI anzeigen.
session_archive bewirbt die v1 Directory-State-Archive-API: POST /sessions/archive, POST /sessions/unarchive und GET /workspace/:id/sessions?archiveState=active|archived. Archivierte Sessions können erst wieder geladen oder fortgesetzt werden, wenn sie dearchiviert (unarchived) sind. session_storage_conflict_repair bewirbt die additive resolveConflicts-Request-Option und den resolvedConflicts-Response-Block, die unten beschrieben werden.
workspace_qualified_rest_core bewirbt die pluralen Core-REST-Routen unter /workspaces/:workspace/.... Der Selektor löst sich zuerst als exakte Workspace-ID auf, dann als URL-kodiertes absolutes CWD nach Kanonisierung. Neuere Single-Workspace-Daemons schließen die primäre Runtime in workspaces[] ein, auch wenn multi_workspace_sessions fehlt, sodass Clients die für Workspace-qualifizierte Routen erforderliche ID entdecken können; Clients sollten für ältere Daemons, die das Array weglassen, auf capabilities.workspaceCwd zurückfallen. Trust-Status und Trust-Request-Routen sind für registrierte nicht vertrauenswürdige Workspaces verfügbar; Datei-Lese-Routen folgen der bestehenden Dateisystem-Lese-Policy. Registrierte nicht vertrauenswürdige sekundäre Workspaces legen auch rein persistierte Session- und Session-Group-Kataloge offen: Diese Lesevorgänge hängen sich nicht an eine Session an, starten keinen ACP oder mergen Live-Bridge-Zustand. Dateischreibvorgänge, Katalogmutationen und andere Plural-Core-Routen erfordern einen vertrauenswürdigen Workspace, es sei denn, eine separate Capability definiert explizit eine engere schreibgeschützte Policy, wie workspace_persisted_transcript. Ein nicht vertrauenswürdiger Primary erhält weiterhin 403 { code: "untrusted_workspace" } von den Plural-Katalog- und Transkript-Routen; Legacy-Singular-Primary-Routen behalten ihr bestehendes Kompatibilitätsverhalten. Dieses Tag deckt die Core-Datei-, Status-, Einstellungs-, Berechtigungs-, Trust-, Lifecycle-, MCP-Control-, Tool- und Skill-Toggle-, Memory-, Workspace-Agent-CRUD- und Session-Speicher-Oberflächen ab. Es deckt nicht Auth, Voice, Extensions, ACP/WebSocket-Transport, Channel-Worker-Routing oder Workspace-qualifizierten Session-Export ab; prüfe workspace_session_export oder workspace_archived_session_export separat. Workspace-Trust ist keine ACL: Ein Client, der das Daemon-Token besitzt, kann jede registrierte Workspace-Oberfläche lesen, die von dieser Policy erlaubt ist.
workspace_qualified_voice bewirbt Voice-Routen, die nach einer vertrauenswürdigen Workspace-Runtime selektiert werden: GET und POST /workspaces/:workspace/voice, POST /workspaces/:workspace/voice/transcribe und WS /workspaces/:workspace/voice/stream. Es wird nur beworben, wenn Multi-Workspace-Runtimes und der gemeinsame ACP/Voice-WebSocket-Listener beide aktiviert sind. Der Selektor folgt denselben ID-oder-kodiertes-absolutes-CWD-Regeln wie andere Plural-Routen. Bei REST gibt ein unbekannter Selektor 400 { code: "workspace_mismatch" } zurück und ein nicht vertrauenswürdiger Selektor 403 { code: "untrusted_workspace" }; WebSocket-Upgrade-Ablehnung legt den entsprechenden HTTP 400/403-Status ohne strukturiertes JSON-Envelope offen. Keiner der Transport fällt auf Primary zurück. Legacy /workspace/voice, /workspace/voice/transcribe und /voice/stream bleiben nur für Primary. Clients verwenden workspace_qualified_voice für alle qualifizierten Voice-Modalitäten und lassen die ausgewählte Runtime konfigurationsspezifische Fehler melden. Die Legacy-Tags workspace_voice, workspace_voice_transcription und voice_transcribe beschreiben nur die Primary-gebundenen Routen und dürfen keine qualifizierte sekundäre Konfiguration verbergen.
workspace_qualified_memory bewirbt die Workspace-qualifizierten Managed-Memory-Routen: POST /workspaces/:workspace/memory/{remember,forget,dream} stellen Tasks in die Warteschlange und GET /workspaces/:workspace/memory/{remember,forget,dream}/:taskId liest sie zurück. Es wird nur beworben, wenn ACP HTTP und Multi-Workspace-Runtimes beide aktiviert sind. Der Selektor folgt denselben ID-oder-kodiertes-absolutes-CWD-Regeln wie andere Plural-Routen. Jeder registrierte Workspace erhält eine eigene Task-Lane; die qualifizierte Lane des Primaries ist dieselbe Instanz wie die Singular-/workspace/memory-Oberfläche, sodass ein auf einer in die Warteschlange gestellter Task auf der anderen lesbar ist. Die Auflösung erfolgt strikt nach ausgewählter Runtime ohne Primary-Fallback: Ein unbekannter Selektor gibt 400 { code: "workspace_mismatch" } zurück, ein nicht vertrauenswürdiger Selektor 403 { code: "untrusted_workspace" }, und eine inaktive oder drainende Runtime 503 { code: "workspace_runtime_unavailable" }. Lesevorgänge weisen niemals eine Lane zu, daher gibt das Pollen eines Workspaces ohne Tasks 404 { code: "<kind>_task_not_found" } zurück. Task-IDs sind auf ihre Lane beschränkt und überleben keine Workspace-Rekonfiguration oder Runtime-Ersetzung; eine veraltete ID gibt 404 zurück, keinen Datenverlust. Wenn ACP HTTP deaktiviert ist, wird das Tag nicht beworben und eine nicht primäre qualifizierte Anfrage gibt ein nicht wiederholbares 501 { code: "workspace_memory_unavailable" } zurück, während die primäre qualifizierte Route über die lokal besessene Lane weiterhin funktioniert.
session_lsp bewirbt GET /session/:id/lsp, den schreibgeschützten strukturierten LSP-Status-Snapshot für Daemon-Clients. Ältere Daemons geben 404 zurück; prüfe dieses Tag, bevor du den Remote-LSP-Status bereitstellst.
session_resources bewirbt GET /session/:id/resources, ein schreibgeschütztes Paar bereinigter Skill- und MCP-Snapshots, das aus der Config der ausgewählten Live-Session erstellt wurde. Die Route ist auf den Live-Session-Owner beschränkt: sie fällt niemals auf die Primary-Runtime zurück und leitet die Ressourcen der Session nicht aus dem Workspace-Status ab. Die verschachtelten skills- und mcp-Objekte verwenden die entsprechenden Workspace-Status-Payloads wieder, lassen aber MCP-Authentifizierung, Pool, Workspace-Budget und Workspace-Discovery-Error-Anreicherungen weg. Status, Discovery und Accounting, die vom MCP-Manager der ausgewählten Session gemeldet werden, bleiben vorhanden. Ältere Daemons geben 404 zurück; prüfe dieses Tag, bevor du einen Session-Ressourcenkatalog anzeigst.
session_status bewirbt GET /session/:id/status, die Live-Bridge-Zusammenfassung für eine einzelne Session anhand der ID. Neben clientCount und hasActivePrompt legen Live-Sessions isWaitingForPermission, isWaitingForUserQuestion, pendingInteractionCount und einen beibehaltenen turnError nach einem fehlgeschlagenen Turn offen. Der Fehler wird gelöscht, wenn der nächste Prompt tatsächlich startet. Eine Live-Session, die einen laufenden Turn in der aktuellen Bridge abgeschlossen hat, trägt außerdem updatedAt, denselben Aktivitäts-Watermark, der unter der Live-State-Route dokumentiert ist; da diese Route die Bridge-Zusammenfassung direkt zurückgibt, wird der Wert nicht mit der persistierten Transkript-Mtime zusammengeführt und kann früher sein als der, den eine Session-Liste meldet. Sowohl die Single-Session-Status-Antwort als auch Workspace-Session-Listen enthalten turnError und pendingInteractions: renderbereite Berechtigungsaktionen oder ask_user_question-Fragen plus die requestId und auswählbaren Optionen, die von den bestehenden Permission-Vote-Routen erforderlich sind. Jede Benutzerfrage hat einen answerKey; stimme mit answers ab, z. B. { "0": "Polling" }, nach diesem Wert keyiert. Rein persistierte Sessions lassen Runtime-Zustand weg, da keine Runtime existiert. Ältere Daemons geben 404 zurück; prüfe dieses Tag, bevor du den Status einer einzelnen Session abfragst, anstatt die gesamte Session-Liste zu scannen.
session_info bewirbt GET /workspace/:id/session-info und seinen /workspaces/:workspace/session-info-Zwilling. Die Antwort aggregiert persistierte aktive und archivierte Session-Zahlen ohne Hydratierung von Listen-Metadaten. Sie ist ein expliziter O(n)-Disk-Scan und darf nicht gepollt werden; Clients sollten truncated: true als Untergrenze behandeln.
session_approval_mode_control, workspace_tool_toggle, workspace_skill_settings_toggle, workspace_skill_settings_batch_toggle, extension_batch_activation_v2, workspace_init und workspace_mcp_restart bewerben die unten dokumentierten Mutations-Control-Routen. Die Approval-Mode-Steuerung behält ihr nicht-striktes Kompatibilitäts-Gate. Die anderen Controls sind strikt nach Operator-Autorität gegated: Trusted-Loopback-Primary-, Bearer-authentifizierte oder gepaarte Local-Control-Anfragen passieren. Eine Token-lose Primary-Anfrage, die das strikte Gate ohne Trusted-Loopback-Autorität erreicht, gibt 401 token_required zurück; fehlende oder ungültige konfigurierte Credentials und nicht gepaarte Local-Control-Credentials werden früher von der Bearer-Middleware mit einfachem 401 Unauthorized abgelehnt. Daemons, denen eine dieser Routen fehlt, geben 404 zurück. Die Settings-spezifischen Skill-Tags sind anders: Daemons aus der Generation mit zurückgezogenen Tags bewerben workspace_skill_toggle und workspace_skill_batch_toggle und bedienen ihren katalogvalidierten Contract unter denselben Pfaden. Die zurückgezogene Single-Target-Route kann HTTP 404 skill_not_found oder 409 skill_not_toggleable zurückgeben; die zurückgezogene Batch-Route gibt HTTP 200 zurück und platziert katalogabgeleitete Fehler in errors[]. Prüfe jedes Tag im Pre-Flight, bevor du seine Affordance bereitstellst, und leite den Settings-spezifischen Skill-Contract nicht durch Abtasten der Route-Erreichbarkeit ab. Die Routenpfade und Request-Bodies haben sich nicht geändert.
mcp_guardrails (Issue #4175 PR 14) deckt die MCP-Budget-Oberfläche ab: die Felder clientCount / clientBudget / budgetMode / budgets[] bei GET /workspace/mcp, das Feld disabledReason in den Server-Zellen und die CLI-Flags --mcp-client-budget / --mcp-budget-mode. Ältere Daemons lassen die neuen Felder vollständig weg; SDK-Clients sollten dieses Tag prüfen, bevor sie sich auf die budgets[]-Semantik verlassen. Der Registry-Descriptor enthält außerdem modes: ['warn', 'enforce'] für die zukünftige Bereitstellung von Feature-Modi – vorerst leiten Clients den Modus aus dem Feld budgetMode des Snapshots ab. Server-Ablehnungen im enforce-Modus sind deterministisch nach der Deklarationsreihenfolge von Object.entries(mcpServers); eine zukünftige Scope-Precedence-Schicht (falls Qwen Code eine einführt) würde dies auf “niedrigste Priorität zuerst” umstellen, um die Konvention plugin < user < project < local von claude-code zu spiegeln.
Scope ist Capability-getrieben. Mit
mcp_workspace_poolteilen sich Sessions innerhalb einer Workspace-Runtime einen Transport-Pool undWorkspaceMcpBudget, und der Snapshot emittiertbudgets[0].scope: 'workspace'. Verschiedene Workspace-Runtimes besitzen unabhängige Pools. Ohne das Tag verwendet jede ACP-Session ihren Legacy-McpClientManager, der Snapshot emittiertscope: 'session', und N Sessions können jeweils die konfigurierte Obergrenze verbrauchen.
workspace_file_read deckt die Text/List/Stat/Glob-Workspace-File-Routen ab
(GET /file, GET /list, GET /glob, GET /stat). workspace_file_bytes
deckt GET /file/bytes ab, das später hinzugefügt wurde, damit Clients die Unterstützung
für rohe Byte-Fenster gegen Daemons aus der PR19-Ära prüfen können. workspace_file_write deckt
die Hash-bewussten Textmutationsrouten ab (POST /file/write, POST /file/edit).
Das Write-Tag bedeutet, dass der Routen-Contract existiert; es bedeutet nicht, dass die aktuelle
Bereitstellung für anonyme Mutationen offen ist. Write/Edit sind strikte Mutationsrouten
und erfordern Operator-Autorität; Token-loser Trusted-Loopback qualifiziert sich.
workspace_file_upload deckt POST /file/upload ab, die binäre Ingress-Route:
ein application/octet-stream-Body, gedeckelt bei MAX_UPLOAD_BYTES (50 MiB),
wird in den Workspace geschrieben, ohne jemals zu überschreiben – ein belegter Name
wird automatisch nummeriert (name (1).ext, name (2).ext, …). Es ist ebenfalls
eine strikte Mutationsroute.
Wenn workspace_qualified_rest_core beworben wird, ist dieselbe Datei-Oberfläche auch unter /workspaces/:workspace/file, /workspaces/:workspace/file/bytes, /workspaces/:workspace/stat, /workspaces/:workspace/list, /workspaces/:workspace/glob, /workspaces/:workspace/file/write, /workspaces/:workspace/file/edit und /workspaces/:workspace/file/upload verfügbar.
Dasselbe Tag legt auch Workspace-qualifizierten Projekt-Agent-CRUD unter /workspaces/:workspace/agents und /workspaces/:workspace/agents/:agentType offen. Diese Plural-Routen lesen oder mutieren nur Projekt-Level-Agenten für den ausgewählten Workspace; global- und user-Scope-Anfragen geben 400 { code: "global_scope_not_supported_for_workspace_route" } zurück. Workspace-lose /workspace/agents-Routen behalten ihr bestehendes Primary-Workspace-Verhalten und bleiben die einzige REST-Oberfläche für User-Level-Agent-Scope.
extension_management_v2 bewirbt einen User-Level-Extension-Katalog und eine Mutationsoberfläche unter /extensions/* sowie Workspace-Aktivierungs-Projektionen unter /workspaces/:workspace/extensions/*. Artefakte sind global; Workspace-Routen legen nur Projektions-Lesevorgänge, exakte Aktivierungs-Overrides und Runtime-Refresh offen. Lesevorgänge dürfen auf einen nicht vertrauenswürdigen registrierten Workspace zielen, während Aktivierung, Refresh und Workspace-scopige Installation ein vertrauenswürdiges Ziel erfordern. Langsame Mutationen verwenden daemon-lokale Vorgänge unter /extensions/operations/:operationId; Store-Generation, nicht Vorgangshistorie, ist maßgeblich über Neustarts und Daemons hinweg. Die veröffentlichte workspace_extensions-Capability und /workspace/extensions/*-Routen bleiben ein Primary-Workspace-Kompatibilitätsadapter. Clients müssen extension_management_v2 vorab prüfen und dürfen es nicht vom Daemon-Modus oder workspace_qualified_rest_core ableiten.
extension_git_credentials bewirbt authentifizierte HTTPS-Git-Installationen auf sowohl POST /workspace/extensions/install als auch POST /extensions/install. Clients müssen dieses Tag vorab prüfen, bevor sie URL-Userinfo oder credentialPersistence senden; ältere Daemons weisen URL-Anmeldedaten zurück. Das Tag beschreibt die Backend-Protokollunterstützung, nicht die Verfügbarkeit eines Keychains: Der gespeicherte Modus meldet das ausgewählte Backend im terminalen Operationsergebnis.
extension_local_path_install bewirbt Daemon-lokale Extension-Quellen auf sowohl POST /workspace/extensions/install als auch POST /extensions/install. Die source muss ein absoluter Pfad sein, der auf dem Daemon-Host existiert. Relative Pfade bleiben nicht unterstützt, sodass das Process-CWD des Daemons die Quellenidentität nicht ändern oder einen GitHub-owner/repo-Shorthand verdecken kann. Der bestehende Installationsvorgang kopiert die Extension in den verwalteten Speicher; er verlinkt die Quelle nicht. Clients müssen dieses Tag vorab prüfen, da ältere Daemons lokale Quellen ablehnen.
extension_batch_activation_v2 fügt PUT /extensions/activation und PUT /workspaces/:workspace/extensions/activation hinzu. Beide akzeptieren 1–100 Namen in extensionNames, deduplizieren sie case-insensitive unter Beibehaltung der Erstsehens-Reihenfolge, persistieren geänderte Ziele in einer Generation und geben einen einzigen 202-Operations-Handle zurück. Ein Ziel muss nicht installiert sein, wenn enabled oder disabled gesetzt wird: Sein Name erzeugt eine Desired-State-Deklaration, die erhalten bleibt, wenn eine Extension mit diesem Namen installiert wird. Die globale Route akzeptiert state: "enabled" | "disabled" und schreibt V2-defaultActivation; die Workspace-Route akzeptiert außerdem "inherit" und wendet exakte Overrides für die ausgewählte vertrauenswürdige Runtime an oder löscht sie. inherit deklariert keinen unbekannten Namen, und ein Clear mit nur unbekannten Namen meldet updated: false.
extension_activation_explicit_refresh bedeutet, dass singuläre und Batch-Aktivierungsoperationen nach dem dauerhaften Policy-Commit abschließen, ohne aktive Sessions direkt zu aktualisieren. Caller, die eine sofortige Anwendung benötigen, sollten auf den Aktivierungserfolg warten und dann entweder das synchrone Primary-Workspace-POST /workspace/extensions/refresh, das Refresh-Zählungen direkt zurückgibt, oder das asynchrone POST /workspaces/:workspace/extensions/refresh des ausgewählten Workspaces einreichen, das einen separaten Operations-Handle zurückgibt. Die beiden Formen sind nicht austauschbar: Die asynchrone Operation zeichnet die angewendete Generation auf, wenn ihre Runtime-Reconciliation abgeschlossen ist, während die synchrone Route nichts aufzeichnet, sodass der Generation-Reconciler diesen Workspace weiterhin als ausstehend behandelt und seine Sessions bei seinem nächsten Durchlauf erneut aktualisiert. Caller, die einen bestimmten Workspace adressieren können, sollten die asynchrone Form bevorzugen. Ein Refresh-Fehler rollt das Aktivierungsergebnis nicht zurück oder stuft es nicht herab. Daemons ohne diese Capability beziehen bereits Runtime-Refresh in die Aktivierung ein, daher dürfen Kompatibilitäts-Clients keinen zweiten Refresh einreichen. Der unabhängige 30-Sekunden-Generation-Reconciler bleibt aktiviert und wendet die committete Policy normalerweise bei seinem nächsten Durchlauf an; fehlgeschlagene Reconciliation wird von späteren Durchläufen erneut versucht.
Extension Management V2 Wire-Contract
Alle Routen verwenden die oben beschriebenen Bearer-Authentifizierungsregeln des Daemons. X-Qwen-Client-Id ist für die V2-Mutationsrouten optional; wenn angegeben, muss es einen Client identifizieren, der bei einer der Workspace-Runtimes des Mutationsziels registriert ist. :extensionId ist die 64-Hex-Extension-Identität in Kleinbuchstaben. :workspace wird zuerst als exakte Workspace-ID aufgelöst und andernfalls als URL-kodiertes absolutes CWD nach Kanonisierung.
| Methode und Pfad | Erfolg |
|---|---|
GET /extensions | 200 globaler Artefaktkatalog |
PUT /extensions/activation | 202 globale Standard-Aktivierungs-Batch-Operation |
PUT /extensions/:extensionId/activation | 202 globale Standard-Aktivierungs-Operation |
POST /extensions/install | 202 Installations-Operation |
POST /extensions/check-updates | 202 Update-Check-Operation |
POST /extensions/:extensionId/update | 202 Update-Operation |
DELETE /extensions/:extensionId | 202 Deinstallations-Operation oder idempotentes 204 wenn die Extension fehlt |
GET /extensions/operations/:operationId | 200 Operations-Snapshot |
GET /workspaces/:workspace/extensions | 200 Workspace-Aktivierungs-Projektion |
GET /workspaces/:workspace/extensions/:extensionId/state | 200 Workspace-Ressourcen-Zustand (extension_state) |
PUT /workspaces/:workspace/extensions/:extensionId/state | 202 Workspace-Ressourcen-Zustands-Operation (extension_state) |
PUT /workspaces/:workspace/extensions/activation | 202 exakte Workspace-Aktivierungs-Batch-Operation |
PUT /workspaces/:workspace/extensions/:extensionId/activation | 202 exakte Workspace-Aktivierungs-Operation |
DELETE /workspaces/:workspace/extensions/:extensionId/activation | 202 Clear-Override-Operation |
POST /workspaces/:workspace/extensions/refresh | 202 Runtime-Refresh-Operation |
Workspace-Ressourcen-Zustand
Preflight extension_state unabhängig. Es unterstützt nur Skills; es impliziert kein MCP-Ressourcen-Management. Beide Routen wählen die registrierte Workspace-Runtime aus und fallen niemals auf Primary zurück. GET folgt den bestehenden schreibgeschützten Trust-Regeln; PUT erfordert einen vertrauenswürdigen Workspace.
{
"skills": [
{ "name": "review", "state": "enabled" },
{ "name": "deploy", "state": "disabled" }
]
}PUT akzeptiert 1–100 Einträge, einschließlich eines Einzelelement-Batches. Es weist fehlerhafte Einträge, Case-insensitive Duplikate und nicht unterstützte Gruppen vor dem Queuing zurück. Alle Namen müssen zur installierten Ziel-Extension gehören, einschließlich derzeit deaktivierter Skills; ein ungültiges Ziel schlägt die Operation ohne partielle Writes fehl. Ein einzelner gesperrter Store-Commit mergt die aufgelisteten Overrides für den exakten kanonischen Workspace. Nicht aufgelistete Skills und andere Workspaces bleiben erhalten. Es erfolgt kein Settings-Write, keine Future-Name-Deklaration und keine implizite Aktivierung der Parent-Extension.
GET gibt v: 1, workspaceId, workspaceCwd, extensionId, name und skills zurück. Jeder Skill hat name, defaultEnabled, nullable workspaceEnabled, effectiveEnabled und optionale disabledReason/lockedScope. Das optionale skillStates des Manifests liefert Defaults; fehlende Werte sind standardmäßig enabled. Für eine aktive Parent gilt die Precedence: Settings-Hard-Disable, Settings-Explicit-Enable, Settings-Default-Disable, Workspace-Internal-Override, Manifest-Default. Interne Deaktivierung verwendet disabledReason: "default" und sperrt niemals Settings. Eine deaktivierte oder entfernte Parent kann durch ein Skill-Override nicht wiederbelebt werden.
Die set_extension_state-Operation gibt status: "updated" mit geordneten result.resourceStates.skills zurück; result.states behält seine Update-Check-Bedeutung. Persistierter Zustand ist kein Beweis, dass jede Session aktualisiert wurde. Der Daemon aktualisiert nur Skills und ihre Commands/Modell-Kontexte für die Ziel-Runtime, einschließlich Bootstrap- und Live-Sessions, ohne nicht verwandte MCP/LSP/Hooks neu zu starten. Post-Commit-Refresh-Fehler erzeugen Warnings ohne State-Rollback. Overrides überleben Neustart und Extension-Updates und werden bei Deinstallation entfernt. Clients dürfen nicht auf die Skill-Settings-API zurückfallen, die eine höherpriore Setting schreibt.
Globaler Katalog
Die globale Katalogantwort ist:
{
"v": 1,
"generation": 12,
"extensions": [
{
"id": "<64 lowercase hex characters>",
"name": "demo",
"version": "1.2.3",
"installType": "npm",
"defaultActivation": "enabled",
"workspaceOverrideCount": 1
}
]
}installType wird weggelassen, wenn keine Installationsmetadaten verfügbar sind. defaultActivation ist enabled oder disabled. workspaceOverrideCount schließt gespeicherte inherit-Einträge aus.
Die Workspace-Projektionsantwort ist:
{
"v": 1,
"workspaceId": "workspace-id",
"workspaceCwd": "/absolute/workspace",
"trusted": true,
"desiredGeneration": 12,
"appliedGeneration": 11,
"extensions": [
{
"extensionId": "<64 lowercase hex characters>",
"name": "demo",
"version": "1.2.3",
"defaultActivation": "enabled",
"workspaceActivation": "disabled",
"effectiveActivation": "disabled",
"activationSource": "workspace_override"
}
]
}workspaceActivation ist enabled, disabled oder null für Vererbung. activationSource ist default, workspace_override, legacy_path_rule oder cli_override. desiredGeneration ist die dauerhafte Store-Generation; appliedGeneration ist die neueste Generation, die der Controller als auf diese Workspace-Runtime angewendet aufgezeichnet hat, und kann vorübergehend hinterherhinken.
Die Installation erfordert explizite Zustimmung und eine initiale Aktivierung:
{
"source": "@scope/demo",
"consent": true,
"activation": { "scope": "user" },
"ref": "optional-git-ref",
"autoUpdate": true,
"allowPreRelease": false,
"registry": "https://registry.npmjs.org"
}Für eine rein Workspace-initiale Aktivierung verwende { "scope": "workspace", "workspaceId": "target-workspace-id" }; das Ziel muss existieren und vertrauenswürdig sein. Daemon-Installationen akzeptieren GitHub-, Git- und npm-Quellen. Wenn extension_local_path_install beworben wird, akzeptieren sie auch einen absoluten Pfad, der auf dem Daemon-Host existiert. ref gilt nicht für npm, ref und autoUpdate gelten nicht für lokale Quellen, und registry gilt nur für npm. ref, autoUpdate, allowPreRelease und registry sind optional.
Wenn extension_git_credentials beworben wird, darf eine HTTPS-Git-Quelle Userinfo enthalten, zum Beispiel https://username:token@git.example.com/org/repository.git. credentialPersistence ist nur mit einer solchen Quelle gültig. Es ist stored oder one_time und standardmäßig one_time, wenn weggelassen. Der gespeicherte Modus speichert die Anmeldedaten über den hybriden Secret-Speicher des Daemons und behält nur die saubere Repository-URL in den Installationsmetadaten, sodass die Extension aktualisierbar bleibt. Der Einmal-Modus speichert weder die Repository-URL noch die Anmeldedaten und erstellt einen nicht aktualisierbaren snapshot; autoUpdate: true wird für diesen Modus abgelehnt. Die Angabe des Felds ohne URL-Anmeldedaten, die Angabe ungültiger Anmeldedaten oder die Verwendung von Anmeldedaten mit npm-, Archiv-, lokalen-, SSH- oder Nicht-Git-Quellen gibt 400 zurück.
Installationsantworten und -operationen mit Anmeldedaten legen credentialPersistence offen und können credentialStorage als keychain oder encrypted_file offenlegen. Einmal-Operationen lassen source weg; gespeicherte Operationen können die saubere Quelle zurückgeben. Snapshot-Katalog-/Statuseinträge lassen die Quelle weg, setzen credentialPersistence auf one_time und melden not updatable. Update schlägt mit extension_not_updatable fehl; ein nicht verfügbarer gespeicherter Secret schlägt vor dem Netzwerkzugriff mit extension_credential_unavailable fehl.
Globale und Workspace-Aktivierungs-PUT-Anfragen verwenden denselben Body:
{ "state": "enabled" }state ist enabled oder disabled. Update-, Deinstallations-, Check-Updates-, Clear-Activation- und Refresh-Anfragen haben keinen erforderlichen Body.
Batch-Aktivierungsanfragen verwenden Extension-Namen:
{
"extensionNames": ["formatter", "review-tools"],
"state": "disabled"
}Der Workspace-Batch akzeptiert außerdem "state": "inherit". Terminale globale Ergebnisse enthalten name und defaultActivation; Workspace-Ergebnisse enthalten name, workspaceActivation (null für inherit) und effectiveActivation. Fehlerhafte Namen weisen die Anfrage ab; Konflikte mit bestehenden Store-Identitäten schlagen atomar ohne partiellen Commit fehl. Ein unbekanntes inherit-Ziel wird nicht persistiert, da das Löschen eines Overrides keine Default-Activation-Deklaration erzeugen oder eine spätere Installations-Zustimmung ersetzen darf.
Jede akzeptierte asynchrone Mutation gibt Folgendes zurück:
HTTP/1.1 202 Accepted
Location: /extensions/operations/<operation-id>
Retry-After: 1
Content-Type: application/json
{"accepted":true,"operationId":"<operation-id>"}Workspace-qualifizierte Mutationen verwenden denselben globalen /extensions/operations/:operationId-Polling-Pfad. Die Operationshistorie ist prozesslokal, behält nur eine begrenzte Anzahl terminaler Einträge und geht beim Daemon-Neustart verloren; Clients müssen den Katalog oder die Workspace-Projektion erneut lesen und Generationen vergleichen, wenn eine Operations-ID verschwindet.
Ein Operations-Snapshot hat diese Form:
{
"v": 1,
"operationId": "<operation-id>",
"operation": "install",
"status": "running",
"phase": "preparing",
"createdAt": 1750000000000,
"updatedAt": 1750000000100,
"source": "owner/repository",
"name": "demo"
}status wechselt von queued zu running, dann zu succeeded, succeeded_with_warnings oder failed. Während der Ausführung ist phase entweder preparing, committing oder reconciling. Terminaler Erfolg kann result mit status gleich installed, enabled, disabled, updated, uninstalled, checked oder refreshed enthalten; Reconciliation-Ergebnisse können zusätzlich refreshed, failed und error enthalten, während Batch-Aktivierungsergebnisse geordnete results enthalten. Update-Checks geben result.states zurück, keyiert nach Extension-Name, mit Werten wie checking for updates, update available, up to date, not updatable oder error. Anmeldedaten und Autorisierungsheader sind niemals Operationsfelder.
Ein dauerhafter Commit gefolgt von unvollständiger Bereinigung oder Runtime-Reconciliation wird nicht als fehlgeschlagene Mutation gemeldet. Er gibt succeeded_with_warnings zurück und bewahrt das committete Ergebnis:
{
"v": 1,
"operationId": "<operation-id>",
"operation": "activation",
"status": "succeeded_with_warnings",
"createdAt": 1750000000000,
"updatedAt": 1750000000200,
"result": {
"status": "disabled",
"name": "demo",
"refreshed": 1,
"failed": 1
},
"warnings": [
{
"workspaceId": "workspace-id",
"workspaceCwd": "/absolute/workspace",
"code": "reconcile_slow",
"error": "Runtime reconciliation took 31000ms."
}
]
}Warning-workspaceId und code sind optional; workspaceCwd und error sind immer vorhanden. Clients sollten Warnings anzeigen, ihren Katalog/ihre Projektion aktualisieren und dürfen die dauerhafte Mutation nicht blind wiederholen.
Validierungs- und Autorisierungsfehler sind synchrone HTTP-Fehler mit { "error": "...", "code": "..." }, wenn ein stabiler Code existiert. Wichtige Fälle sind 400 invalid_extension_id, 400 invalid_extension_names, 400 invalid_extension_name, 400 invalid_extension_activation, 400 workspace_mismatch, 403 untrusted_workspace, 404 extension_operation_not_found und 429 extension_queue_full. Installationsvalidierung gibt auch 400 für ungültige Source/Ref/Registry-Optionen, fehlende Zustimmung oder fehlende/ungültige initiale Aktivierung zurück. Eine nach 202 fehlgeschlagene Mutation wird, solange sie in der Operationshistorie behalten wird, mit status: "failed", error und einem optionalen stabilen code dargestellt; häufige Codes umfassen extension_prepare_timeout und extension_conflict. HTTP 404 für eine Operation impliziert kein Rollback, da die Operationshistorie nicht dauerhaft ist.
daemon_status bewirbt GET /daemon/status, den konsolidierten schreibgeschützten
Operator-Diagnose-Snapshot, der unten dokumentiert ist.
Bedingte Tags. Eine kleine Anzahl von Feature-Tags wird nur beworben, wenn der entsprechende Bereitstellungs-Toggle, Runtime-Wiring oder die Verfügbarkeitsbedingung aktiv ist. Vorhandensein des Tags = Verhalten ist aktiviert; Fehlen = entweder ein älterer Daemon, der älter als das Tag ist, ODER ein aktueller Daemon, bei dem diese Bedingung nicht zutrifft. Aktuell:
| Tag | Beworben, wenn … |
|---|---|
require_auth | der Daemon mit --require-auth (oder requireAuth: true über die eingebettete API) gestartet wurde. Der Bearer-Token ist auf jeder Route zwingend erforderlich, einschließlich /health bei Loopback-Binds. |
mcp_workspace_pool | der gemeinsame MCP-Transport-Pool aktiv ist. Wird weggelassen, wenn QWEN_SERVE_NO_MCP_POOL=1 den Pool deaktiviert. |
mcp_pool_restart | der gemeinsame MCP-Transport-Pool aktiv ist; Restart-Antworten können Pool-bewusste Multi-Entry-Formen enthalten. |
external_tool_guard | qwen serve den Startup-Handshake für --external-tool-guard-mode=required abgeschlossen hat; jeder erzeugte ACP-Channel muss den installierten Callback vor der Session-Erstellung bestätigen, und jede unterstützte Top-Level-Managed-ACP-Tool-Invocation, die die endgültige Ausführungsgrenze erreicht, muss eine externe Pre-Execution-Erlaubnis erhalten. Frühere Permission-/Hook-Ablehnungen stellen keine Provider-Anfrage. Verschachtelte AgentCore-Ausführung liegt außerhalb von v1 und wird abgelehnt, während dieser externe Provider-Modus aktiv ist. Der Tag spiegelt nur den externen Provider wider: unabhängig davon wendet jeder Daemon die eingebaute Git-Relocation-Guard auf die verwalteten Tools an, die eine Shell-Befehlszeile tragen (run_shell_command und monitor), sodass das Fehlen dieses Tags nicht bedeutet, dass keine Pre-Execution-Ablehnungen erfolgen. |
allow_origin | T2.4 (#4514 ). Der Daemon wurde mit mindestens einem --allow-origin <pattern> (oder allowOrigins: [...] über die eingebettete API) gestartet. Cross-Origin-Anfragen von übereinstimmenden Origins erhalten die entsprechenden CORS-Antwortheader; nicht übereinstimmende Origins erhalten weiterhin den Standard-403-Fehler. Die konfigurierte Pattern-Liste wird absichtlich NICHT in /capabilities ausgegeben, um zu verhindern, dass das Trusted-Origin-Set an unauthentifizierte Leser weitergegeben wird – die Browser-WebUI kennt ihre eigene Origin bereits. |
prompt_absolute_deadline | --prompt-deadline-ms / QWEN_SERVE_PROMPT_DEADLINE_MS / ServeOptions.promptDeadlineMs auf eine positive Ganzzahl gesetzt ist. |
writer_idle_timeout | --writer-idle-timeout-ms / QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS / ServeOptions.writerIdleTimeoutMs auf eine positive Ganzzahl gesetzt ist. |
workspace_settings | der Daemon mit verfügbarer Settings-Persistenz erstellt wurde. |
workspace_voice | Settings-Persistenz verfügbar ist, sodass die Legacy-Primary-Workspace-Voice-Einstellungsrouten aktiv sind. |
workspace_voice_transcription | der primäre Workspace ein konfiguriertes Voice-Transkriptionsmodell hat. |
session_shell_command | die Session-Shell-Ausführung explizit aktiviert ist. |
standalone_sessions_v1 | der Daemon die vollständige Standalone-Session-Runtime, den Lifecycle-Koordinator, das dauerhafte Lösch-Journal, die Managed-Directory-Implementierung und die /standalone/sessions-Routenfamilie installiert hat. Direkte Embeds ohne den vollständigen Dependency-Graph lassen sowohl die Routen als auch dieses Tag weg. |
session_artifacts_persistence | die Session-Artefakt-Persistenz für die Runtime verdrahtet ist. |
session_generation | Session-Generation-Helper verfügbar sind. |
scheduled_task_session_reuse | das dauerhafte Scheduled-Task-Session-Management aktiv ist und jede verwaltete Daemon-Runtime den Callback installiert hat, der es einem Task ermöglicht, sich explizit an seine aktuelle bestehende Session zu binden. |
workspace_generation | Workspace-scopige Generation-Helper verfügbar sind. |
rate_limit | --rate-limit / QWEN_SERVE_RATE_LIMIT=1 / ServeOptions.rateLimit aktiviert ist. |
workspace_reload | Workspace-Reload-Unterstützung in der eingebetteten Routenkonfiguration verfügbar ist. |
workspace_trust_hot_reload | Workspace-Trust-Policy-Überwachung und Runtime-Generation-Reconciliation verdrahtet sind, sodass Trust-Änderungen ohne Daemon-Neustart wirksam werden und v2-Trust-Status-Berichte Konvergenz melden. |
channel_reload | ein Daemon-verwalteter Channel-Worker-Manager aktiviert ist und seine aktuelle Auswahl neu laden kann. |
channel_control | die Runtime-Steuerung des Daemon-verwalteten Channel-Workers verdrahtet ist. |
channel_management | Workspace-scopige Channel-Einstellungen, Lifecycle und Pairing-Management verdrahtet sind. |
multi_workspace_sessions | mehr als eine Workspace-Runtime registriert ist, sodass die Session-Erstellung eine vertrauenswürdige Runtime nach CWD auswählen kann. |
multi_workspace_session_rewind | mehr als eine Workspace-Runtime registriert ist; Singular-Live-Session-Rewind-Routen lösen die besitzende Runtime auf. |
multi_workspace_session_shell | mehr als eine Workspace-Runtime registriert ist und die Session-Shell-Ausführung explizit aktiviert ist; Singular-REST-Shell löst die besitzende Runtime auf. |
dynamic_workspace_registration | eine Workspace-Runtime-Factory in den Daemon verdrahtet ist, sodass ein bestehendes vertrauenswürdiges Verzeichnis zur Laufzeit als sekundäre Runtime registriert werden kann. |
persistent_workspace_registration | ein Workspace-Registrierungs-Store in den Daemon verdrahtet ist. Das Produktions-runQwenServe liefert den benutzerbezogenen Store automatisch; direkte createServeApp-Embeds müssen einen explizit injizieren und die Start-Wiederherstellung ihrer Workspace-Registry selbst verwalten. |
scratch_workspace_registration | die verwaltete Scratch-Workspace-Erstellung verfügbar ist – eine Runtime-Factory, ein validiertes verwaltetes Scratch-Root und Runtime-Entsorgung sind verdrahtet, und jede verwaltete Runtime respektiert die Scratch-Root-Grenze. |
workspace_runtime_removal | entfernbare dynamische oder persistent wiederhergestellte sekundäre Runtimes gedrainet und über die Management-Route entfernt werden können. |
workspace_local_open | der Daemon-Host ein Workspace-Verzeichnis im OS-Dateimanager des Hosts öffnen kann (open auf macOS, explorer.exe auf Windows, xdg-open auf einem Linux-Host mit Display). Headless-Hosts lassen den Tag weg, sodass Clients die „Lokal öffnen“-Affordance verbergen, anstatt einen garantierten Startfehler zu zeigen. Der geöffnete Pfad ist immer das aufgelöste registrierte Workspace-CWD, über POST /workspaces/:workspace/open; die Route akzeptiert einen optionalen JSON-Body { "target": "terminal" } (fehlend/anders = Ordner) und antwortet { kind: 'workspace-local-open', opened: true, target } wobei target auf folder oder terminal gesetzt ist. |
workspace_local_terminal | der Daemon-Host ein Terminalfenster in einem Workspace-Verzeichnis öffnen kann (open -a Terminal auf macOS, wt.exe mit cmd.exe-Fallback auf Windows, gnome-terminal/konsole/xterm auf einem Linux-Host mit Display). Headless-Hosts lassen den Tag weg, sodass Clients die „Im Terminal öffnen“-Affordance verbergen, anstatt einen garantierten Startfehler zu zeigen. Bereitgestellt über POST /workspaces/:workspace/open mit Body { "target": "terminal" }. |
workspace_qualified_acp | ACP HTTP und Multi-Workspace-Runtimes aktiv sind, sodass der Plural-ACP-Endpunkt eine sekundäre Runtime auswählen kann. |
workspace_qualified_voice | Multi-Workspace-Runtimes und der gemeinsame ACP/Voice-WebSocket-Listener aktiv sind, sodass jede Workspace-qualifizierte Voice-Modalität für eine sekundäre Runtime erreichbar ist. |
workspace_qualified_memory | ACP HTTP und Multi-Workspace-Runtimes aktiv sind, sodass Workspace-qualifizierte Managed-Memory-Routen eine pro-Workspace-Task-Lane für Remember-, Forget- und Dream-Operationen auswählen können. |
client_mcp_over_ws | der Daemon Client-gehostete MCP-Server über den ACP-WebSocket akzeptiert. Dies ist ein explizites Opt-in, nicht für den CDP-Tunnel-Pfad erforderlich. |
cdp_tunnel_over_ws | der Daemon den umgekehrten /cdp-WebSocket-Tunnel bereitstellt, entweder durch explizites Opt-in oder weil eine Chrome-Extension-Origin erlaubt ist. Dies bedeutet nur, dass der Tunnel existiert; nicht, dass Chrome-DevTools-MCP-Tools registriert sind. |
browser_automation_mcp | ACP HTTP aktiviert ist, cdp_tunnel_over_ws aktiv ist, kein Bearer-Token /cdp blockiert und QWEN_CDP_MCP_COMMAND einen externen Stdio-MCP-Adapter benennt. Das Haupt-CLI-Paket bündelt keinen Browser-Automatisierungs-Adapter; ohne dieses Tag kann die Chrome-Extension-Side-Panel-Chat weiterhin funktionieren, aber Console/Network/Screenshot/Click-Tools werden standardmäßig nicht registriert. |
voice_transcribe | der Voice-WebSocket-Endpunkt gemountet ist; ein konfiguriertes Voice-Modell wird dennoch für eine erfolgreiche Transkription benötigt. |
realtime_voice | der macOS-WebShell-Daemon Live Voice aktiviert und native Host-Integration aktiv hat. /live/status meldet Bereitschaft, aber die Capability wird zurückgezogen, bis das Feature aktiviert ist. |
web_terminal | ACP HTTP aktiviert ist, sodass der authentifizierte Web-Terminal-Endpunkt verfügbar ist. |
mcp_guardrails steht nicht in dieser bedingten Tabelle – es ist ein immer aktives Tag, das immer dann beworben wird, wenn die Binärdatei die neuen /workspace/mcp Budget-Felder unterstützt, unabhängig davon, ob der Operator ein Budget konfiguriert hat. Operatoren, die --mcp-client-budget nicht gesetzt haben, erhalten trotzdem die neuen Felder (mit budgetMode: 'off', budgets: []).
mcp_guardrail_events (Issue #4175 PR 14b) bewirbt die typisierten SSE-Push-Events, die Überschreitungen des MCP-Budgetstatus ohne Polling-Loop sichtbar machen. Zwei Frame-Typen werden auf GET /session/:id/events empfangen:
mcp_budget_warning— wird einmalig beim Überschreiten der 75%-Marke vonreservedSlots.size / clientBudgetnach oben ausgelöst. Wird erst wieder scharfgeschaltet, wenn das Verhältnis unter 37,5 % fällt (MCP_BUDGET_REARM_FRACTION). Spiegelt die Hysterese vonslow_client_warningaus PR 10 wider, jedoch auf Manager-Ebene und nicht auf der Backlog-Ebene pro Subscriber. Payload:{ liveCount, reservedCount, budget, thresholdRatio: 0.75, mode: 'warn' | 'enforce' }. Wird in den Modiwarnundenforceausgelöst; niemals inoff.mcp_child_refused_batch— wird am Ende jedesdiscoverAllMcpTools*-Durchlaufs ausgelöst, wenn einer oder mehrere Server abgelehnt wurden, UND als Batch der Länge 1 auf demreadResource-Pfad für Lazy-Spawn-Ablehnungen. Payload:{ refusedServers: [{ name, transport, reason: 'budget_exhausted' }, ...], budget, liveCount, reservedCount, mode: 'enforce' }.modeist wörtlich'enforce', da derwarn-Modus niemals ablehnt.
Beide Events leben im SSE-Replay-Ring pro Session (sie tragen eine id), sodass ein Client, der sich mit Last-Event-ID reconnectet, durch sie hindurch fortsetzt; der Snapshot unter GET /workspace/mcp bleibt die Single Source of Truth für den Zustand nach einer längeren Unterbrechung. Einmal beworben, immer aktiv – es gibt keinen bedingten Toggle. Der SDK-Reducer-State (DaemonSessionViewState) stellt mcpBudgetWarningCount, lastMcpBudgetWarning, mcpChildRefusedBatchCount und lastMcpChildRefusedBatch für Adapter bereit, die eine einfache Lag-ähnliche UI wünschen.
Routen
Clients können session_turn_status per Feature-Detection erkennen und GET /session/:id/turns/current oder GET /session/:id/turns/:promptId pollen. Diese Routen erfordern die Live-Owner-Session und laden oder scannen niemals einen anderen Workspace. Abgeschlossene Ergebnisse sind Best-Effort-Transkript-Datensätze, die aus dem aktiven Branch mit einem begrenzten Scan gelesen werden; prompt_not_found bedeutet, dass kein Ergebnis in der Live-Queue, dem 64-Einträge-Terminal-Overlay oder dem begrenzten aktiven Fenster gefunden wurde. resultText ist die rohe finale Parent-Modell-Antwort nach der letzten Tool-Grenze, vor optionalem Message-Rewriting, und kann fehlen. Ergebnisse über 32.768 UTF-16 Code Units enthalten resultTruncated: true und resultCode: "RESULT_TEXT_TRUNCATED".
GET /health
Liveness Probe. Die Standardform gibt 200 {"status":"ok"} zurück, wenn der Listener aktiv ist – ressourcenschonend, kein Bridge-Zugriff, geeignet für hochfrequente Liveness Probes in k8s/Compose.
Übergib ?deep=1 (akzeptiert auch ?deep=true oder einfach ?deep) für eine Daemon-weite Probe, die Bridge-Zähler über jede verwaltete Workspace-Runtime hinweg aggregiert, einschließlich eines Workspaces, der noch drainend ist (nur informativ, keine echte Liveness-Prüfung):
{
"status": "ok",
"workspaceCount": 2,
"sessions": 3,
"pendingPermissions": 1,
"activePrompts": 1,
"activeWork": true,
"activeWorkReporting": "full",
"activeWorkStaleMs": 4200,
"connectedClients": 2,
"channelAlive": true,
"lastActivityAt": "2026-07-15T08:30:00.000Z",
"idleSinceMs": 120000
}sessions, pendingPermissions und activePrompts sind Summen. activeWork ist true, wenn eine Runtime einen akzeptierten aber nicht abgeschlossenen Prompt hat (einschließlich eines FIFO-waitenden Prompts), einen laufenden Hintergrund-Agenten, eine queued/in-progress Agent-Terminal-Benachrichtigung oder Session-verwaltete Hintergrund-Shell-Arbeit. Shell-Arbeit bleibt aktiv, während die Shell-Registry einen laufenden Eintrag meldet und während ihre Terminal-Benachrichtigung gequeuet ist oder die Parent-Fortsetzung antreibt; eine beliebige Anzahl von Shells trägt eine begrenzte aggregierte Haltefrist bei. Monitore, Workflows, Cron-Jobs, Follow-up-Vorschläge und externe Prozesse, die die Shell-Registry nicht mehr verfolgen kann, bleiben außerhalb des Feldes. Es ist Session-scopig: Channel-Level-Arbeit ohne angehängte Session – ein Spawn im Flug, ein ausstehender Restore, MCP-Discovery oder Authentifizierung – wird nicht gezählt, daher kann activeWork false lesen, während der Daemon weiterhin ablehnt, den Channel zurückzufordern. Lies dieses Feld nicht als “der Daemon kann zurückgefordert werden”; es beschreibt nur Session-eigene Arbeit. activeWorkReporting gibt an, wie viel dieses Booleans tatsächlich bestätigt wird: full, wenn jede Live-Session durch einen frischen Report eines Childs abgedeckt ist, der alle erforderlichen Kategorien meldet, none, wenn keine Session Reporting ausgehandelt hat, und partial für alles dazwischen – einschließlich eines veralteten Snapshots oder eines ausgehandelten Childs, das eine erforderliche Kategorie weglässt. Ein Snapshot, der älter als drei Report-Intervalle ist, zählt nicht mehr als Abdeckung: Er ist kein Report, dass die Session idle ist, daher geht die Session zurück auf “beibehalten”, genau als hätte das Child nie berichtet. Die gewöhnliche automatische Bereinigung ist auch für ein ausgehandeltes, aber unvollständiges Child deaktiviert; ein Child, das shell nicht versteht, kann kein bedingtes Schließen gemäß dem vollständigen aktuellen Prädikat sicher autorisieren. Vollständig nicht unterstützte historische Children behalten das Legacy-Bereinigungsverhalten, und explizites Schließen, Killen, Herunterfahren und Channel-Exit bleiben Force-Operationen. activeWorkStaleMs ist das Alter des ältesten Snapshots, auf dem der Boolean basiert unter den abgedeckten Sessions, und 0, wenn keine Session abgedeckt ist; er ist diagnostisch, weil die Frische bereits in activeWorkReporting durch den Daemon eingestuft wird (nur der Daemon kennt das ausgehandelte Tempo jedes Channels). Die Einstufung wird einmal über jede verwaltete Runtime berechnet, nicht pro Runtime, und dann kombiniert – eine Runtime ohne Sessions ist trivialerweise vollständig, und das als Beweis zu behandeln würde einem leeren Workspace erlauben, für nicht gemeldete Sessions eines anderen Workspaces zu bürgen. lastActivityAt ist der späteste nicht-null Workspace-Aktivitätszeitpunkt und idleSinceMs wird aus demselben Snapshot abgeleitet. channelAlive bedeutet, dass mindestens ein verwalteter Workspace-Channel live ist; es bedeutet nicht, dass jeder Workspace gesund ist. connectedClients und das optionale rateLimitHits bleiben Daemon-weite Zähler und keine pro-Workspace-Summen.
Restart-Controller sollten den Daemon als beschäftigt behandeln, wenn:
const busy =
health.activePrompts > 0 ||
health.activeWork ||
health.activeWorkReporting !== 'full';Das Weglassen des dritten Terms macht activeWork === false nicht unterscheidbar von “kein Child hat mir etwas erzählt”, was der einzige Fall ist, in dem das Handeln unsicher ist. Unbekannte Antworten und fehlgeschlagene Probes müssen ebenfalls einen Restart verhindern. activePrompts bleibt ein unabhängiges Kompatibilitätssignal.
Diese Felder sind ein Beobachtungs-Cache, keine Restart-Lease: Auch eine frische, vollständig eingestufte, leere Antwort beschreibt den Moment, in dem sie abgetastet wurde, und Arbeit kann sofort danach beginnen. Die obige Regel senkt das Risiko eines falschen Restarts erheblich, beseitigt es aber nicht – strikte Sicherheit benötigt einen Prepare-Restart-Zaun, der neue Work-Zulassung stoppt, den Drain bestätigt und erst dann herunterfährt.
⚠️ Die Deep Probe ist informativ, keine echte Liveness-Verifizierung oder ein atomarer Reclaim-Lease. Ausgehandelte ACP-Children veröffentlichen Channel-weite Active-Work-Snapshots im ausgehandelten Takt, und der Daemon stuft ihre Frische in
activeWorkReportingein – aber er tötet niemals einen Channel wegen eines fehlenden Reports, weil das Schweigen einer Session kein Beweis ist, dass der Prozess gestorben ist. Transport-Liveness und Stalled-Agent-Erkennung sind separate Mechanismen.connectedClientszählt REST-SSE-Connections, nicht jeden ACP-Transport. Verwende wiederholte Samples und Graceful Shutdown für Idle-Reclaiming; verwende authentifiziertes/daemon/statusfür Transport- und pro-Workspace-Diagnostik. Wenn ein Getter einer verwalteten Runtime einen Fehler wirft, failt die Deep Probe fail-closed mit503 {"status":"degraded","reason":"aggregation_failed"}, anstatt Teilsummen zurückzugeben, und das Daemon-Log identifiziert die fehlschlagende Workspace-Runtime. Während des Bootstraps, bevor die Runtime-Registry bereit ist, gibt sie503 {"status":"degraded","reason":"bootstrap"}mitRetry-After: 1zurück. Verwende für Listener-Liveness das Standard-/healthohne?deep.
Auth: erforderlich bei Non-Loopback-Binds und wenn Loopback mit --require-auth gehärtet ist. Bei einem gewöhnlichen Loopback-Bind (127.0.0.0/8, localhost, ::1, [::1]) wird /health vor der Bearer-Middleware registriert, sodass k8s/Compose-Probes innerhalb des Pods kein Token mitführen müssen. Bei Non-Loopback (--hostname 0.0.0.0 usw.) oder gehärtetem Loopback wird die Route nach der Bearer-Middleware registriert und gibt ohne gültiges Token 401 zurück – andernfalls könnte ein unauthentifizierter Caller beliebige Adressen abfragen, um zu bestätigen, dass ein qwen serve existiert, was ein Info-Leak mit geringer Schwere ist, das sich schlecht mit Port-Scanning verträgt. CORS-Deny + Host-Allowlist gelten weiterhin für die gewöhnliche-Loopback-Ausnahme.
GET /daemon/status
Read-only Operator-Diagnostik. Im Gegensatz zu /health ist dies eine normale Daemon-API:
sie wird nach der Bearer-Auth und dem Rate-Limiting registriert, auch bei Loopback-
Binds. Query-Parameter:
detail=summary(Standard) liest nur den In-Memory-Daemon-State.detail=fullbeinhaltet zusätzlich Live-Session-Diagnostik, ACP-Connection- Diagnostik, Auth-Device-Flow-Zählungen und Workspace-Status-Sektionen.- Jeder andere
detail-Wert gibt400 { "code": "invalid_detail" }zurück.
summary fragt absichtlich keine Workspace-Status-Methoden ab, startet keinen ACP-
Child und erzeugt keine Session. full fragt jede Workspace-Sektion unabhängig ab;
ein Timeout oder eine Exception markiert nur diese Sektion als unavailable und fügt ein
workspace_status_unavailable Issue hinzu.
Response-Shape:
{
"v": 1,
"detail": "summary",
"generatedAt": "2026-06-16T00:00:00.000Z",
"status": "ok",
"issues": [],
"daemon": {
"pid": 12345,
"uptimeMs": 3600000,
"mode": "http-bridge",
"workspaceCwd": "/repo",
"qwenCodeVersion": "0.18.1",
"daemonId": "serve-..."
},
"security": {
"tokenConfigured": true,
"requireAuth": false,
"loopbackBind": true,
"allowOriginConfigured": false,
"allowOriginMode": "none",
"sessionShellCommandEnabled": false
},
"limits": {
"maxRegisteredWorkspaces": 256,
"maxChannelControlWorkspaces": 25,
"maxSessions": 32,
"maxTotalSessions": 800,
"maxPendingPromptsPerSession": 5,
"listenerMaxConnections": 256,
"eventRingSize": 8000,
"compactedReplayMaxBytes": 4194304,
"promptDeadlineMs": null,
"writerIdleTimeoutMs": null,
"channelIdleTimeoutMs": 0,
"sessionIdleTimeoutMs": 1800000,
"acpConnectionCap": 64
},
"runtime": {
"sessions": { "active": 0 },
"permissions": { "pending": 0, "policy": "first-responder" },
"channel": { "live": false },
"channelWorker": {
"enabled": false,
"state": "disabled",
"channels": []
},
"transport": {
"restSseActive": 0,
"acp": {
"enabled": true,
"connections": 0,
"connectionStreams": 0,
"sessionStreams": 0,
"sseStreams": 0,
"wsStreams": 0,
"pendingClientRequests": 0
}
},
"perf": {
"eventLoop": { "meanMs": 0, "p50Ms": 0, "p99Ms": 0, "maxMs": 0 },
"promptQueueWait": {
"count": 0,
"meanMs": 0,
"maxMs": 0,
"lastMs": null
},
"pipe": {
"inbound": { "count": 0, "totalBytes": 0, "maxBytes": 0 },
"outbound": { "count": 0, "totalBytes": 0, "maxBytes": 0 }
}
},
"activity": {
"activePrompts": 0,
"pendingPrompts": 0,
"queuedPrompts": 0,
"lastActivityAt": null,
"idleSinceMs": null
}
}
}Multi-Workspace-Antworten enthalten auch Top-Level-workspaces[]-Zeilen mit
{ id, cwd, displayName?, primary, trusted }. Der optionale Anzeigename wird
weggelassen, wenn nicht gesetzt, und bleibt rein präsentativ; Status-Consumer müssen
weiterhin id oder cwd verwenden, um Runtimes zu korrelieren.
runtime.perf ist optional. Wenn vorhanden, meldet es ausschließlich den Event-Loop-
Lag des Daemon-Prozesses, Prompt-FIFO-Queue-Wartezeit-Samples und Daemon-Child-Pipe-Byte-Zähler;
der Event-Loop-Lag des ACP-Childs ist in /daemon/status nicht enthalten.
status ist error, wenn ein Issue den Schweregrad Error hat, warning, wenn ein Issue den
Schweregrad Warning hat, andernfalls ok. Issue-Codes sind stabil und umfassen
session_capacity_high, connection_capacity_high, pending_permissions,
acp_channel_down, preflight_error, mcp_budget_warning,
mcp_budget_exhausted, rate_limit_hits, channel_worker_exited und
channel_worker_partial_connect sowie workspace_status_unavailable. Während
des kurzen Fensters, nachdem der Listener bereit ist, aber bevor die vollständige Runtime
gemountet ist, kann /daemon/status daemon_runtime_starting melden; wenn das asynchrone
Runtime-Mount fehlschlägt, meldet es daemon_runtime_failed, während Non-Status-
Runtime-Routen 503 zurückgeben.
runtime.activity meldet die Daemon-weite Prompt-Aktivität. activePrompts zählt Sessions mit einem laufenden Prompt. pendingPrompts zählt alle akzeptierten Prompts, die noch nicht abgeschlossen sind, einschließlich des laufenden Prompts und der in der FIFO-Warteschlange wartenden Prompts. queuedPrompts zählt die in der FIFO-Warteschlange wartenden Prompts, die akzeptiert, aber noch nicht dispatched wurden. lastActivityAt ist der ISO-8601-Timestamp des letzten Prompt-Starts/-Endes oder Session-Spawns; null, wenn der Daemon seit dem Booten noch keine Aktivität verarbeitet hat. idleSinceMs wird zum Zeitpunkt der Response-Generierung aus lastActivityAt berechnet.
limits.memory ist additiv und meldet die aufgelösten Speicherzahlen des Daemons: ein erforderliches enforced: false, ein childHeap-Objekt (mode; maxConcurrentChildren und perChildCeilingMb, beide null unter mode: 'off', was nichts modelliert – und perChildCeilingMb zusätzlich null, wo immer keine Partition innerhalb von modeled.minChildHeapMb modelliert werden kann – entweder deckt der Pool nicht ein Child auf diesem Floor, oder die Obergrenze würde darunter liegen, sobald sie auf modeled.legacyChildCeilingMb begrenzt wird, was floor(available / 2) ist und somit auf einem Host unter 1024 MB unter den Floor fällt. Es ist niemals 0, und maxConcurrentChildren ist 0 in diesen Fällen, da ein Host, der keine Partition modelliert, eine berechnete Antwort ist und kein fehlendes Modell; sowie refusals, die Spawns, die das modellierte Limit überschritten hätten), configuredBudgetMb, effectiveBudgetMb (der konfigurierte Wert, begrenzt auf aufgelösten Cgroup/Host-Speicher), budgetSource (flag / derived), availableMemoryMb, availableMemorySource (constrained / host), insufficientMemory und ein modeled-Objekt mit rootReserveMb, childPoolMb, minChildHeapMb, maxChildHeapMb und legacyChildCeilingMb (ein konservatives Modell der Obergrenze, die ein ACP-Child heute erhält, das unter dem tatsächlichen Wert liegen kann). runtime.memory meldet zusätzlich registeredWorkspaces (die Registrierungszahl – nicht entfernte Workspace-Einträge, einschließlich drainender, wechselnder oder blockierter; keine Live-Child-Zahl), activeAcpChildren (Daemon-verwaltete ACP-Children mit einem live, nicht sterbenden Channel – umfasst wechselnde oder blockierte Einträge, schließt aber einen Workspace aus, dessen Kill begonnen hat, auch wenn das Child nicht beendet ist; keine Channel-Worker, MCP-Descendants oder nicht angehängte Spawn-Reservierungen), childRssCoverage (active_children – jedes ACP-Child mit einem live Channel, also die Menge, die activeAcpChildren zählt; ältere Daemons senden primary_only), ein unten beschriebenes children-Objekt und ein modeled-Objekt mit recommendedShareAtRegisteredMb (null, wenn kein Workspace registriert ist) und recommendedShareAtActiveMb (null, wenn kein Child aktiv ist). Jede Share wird auf die Legacy-Child-Obergrenze begrenzt und nur auf den minimalen Child-Heap gesetzt, wenn die Obergrenze dies zulässt – auf einem kleinen Host liegt die Obergrenze unter dem Minimum, sodass Share × Anzahl den Child-Pool überschreiten kann. Lies eine Share als beratend, nicht als Partition des Pools. All dies ist Beobachtung: Kein Child-Spawn-Argument leitet sich aus diesen Werten ab, und keine Anfrage wird auf ihrer Grundlage abgelehnt. childHeap modelliert eine feste Partition von modeled.childPoolMb – jedes Child würde dieselbe perChildCeilingMb erhalten, sodass das modellierte Total innerhalb des Pools bleibt, anstatt sich als pro-Spawn-Share zu akkumulieren. Lies refusals nur als Admission-Druck: Ein Count von 0 bedeutet nicht, dass die Partition sicher anwendbar ist, da Children auf der viel größeren Host-abgeleiteten Obergrenze laufen, sodass ein Workload, der mehr Old Space als perChildCeilingMb benötigt, hier gesund ist und erst nach Anwendung der Partition fehlschlagen würde. Zwei weitere Gründe, warum ein Count ungleich null keinen Capacity-Druck bedeuten muss: die Admission-Entscheidung zählt ein terminierendes Child bis es beendet ist, sodass auf einem Daemon bereits bei maxConcurrentChildren jeder Channel-Ersatz eine Refusal während des Überlappungsfensters bucht; und auf einem Host, der zu klein ist, um eine Partition zu modellieren, ist maxConcurrentChildren 0, sodass refusals dem gesamten ACP-Spawn-Count entspricht, wobei insufficientMemory das erklärende Feld ist. Auf dem normalen runQwenServe-Pfad wird das Budget aufgelöst, bevor die Bootstrap-App erstellt wird, sodass limits.memory bereits während des Bootstrap-Fensters belegt ist. Es ist nur null auf Pfaden, die kein Budget auflösen (wie Direct-Embed, das runQwenServeImpl umgeht). Der SDK-Typ erlaubt null, daher kommen korrekte Clients damit zurecht.
runtime.memory.children ist additiv innerhalb dieses Blocks und meldet aggregiertes RSS über die Children, die childRssCoverage benennt: rssBytes (ihre summierten selbstberichteten RSS), sampled (wie viele einen Messwert geliefert haben) und oldestReadingAgeMs (das Alter des ältesten Messwerts in der Summe, sodass ein Aufrufer erkennen kann, wie weit auseinander die Teile aufgenommen wurden). Der Nenner für sampled ist das Geschwisterfeld activeAcpChildren, das nicht innerhalb des Blocks wiederholt wird; wenn sampled niedriger ist, ist rssBytes ein Floor statt ein Total. Sampling erfordert einen aktiven SSE/WS-Watcher, sodass eine Status-Anfrage an einen Daemon, von dem niemand streamt, sampled: 0 meldet, selbst mit live Children – activeAcpChildren daneben macht diese Lücke sichtbar, und rssBytes: 0 mit sampled: 0 bedeutet niemals eine gemessene Null. oldestReadingAgeMs ist null, wenn nichts gesampled wurde, und auch wenn jeder Beiträger eine Bridge ist, die das Feld noch nicht kennt, es bedeutet also niemals “frisch”. Lies die Summe gleichzeitig als Über- und Unterzählung: Das Summieren von Pro-Zessess-RSS zählt Seiten doppelt, die die Children teilen, während jedes Child nur seinen eigenen Prozess meldet, sodass seine MCP-Descendants und alle Channel-Worker fehlen. Es ist nicht der Speicher des Daemon-Trees. Das Feld ist im SDK-Mirror optional, da Daemons, die primary_only melden, es niemals senden.
runtime.memory.children.heap ist additiv innerhalb dieses Blocks und meldet die lebenslangen V8-Old-Generation-High-Water-Marks jedes ACP-Childs, aggregiert als Maximum, nicht als Summe: peakOldGenerationBytes, peakLiveSetBytes, peakTotalHeapBytes, majorGcCount, majorGcMs, unclassifiedSpaceNames und reported. Eine Heap-Obergrenze gilt pro Child und die Peaks wurden zu unterschiedlichen Zeiten erreicht, sodass eine Summe keine Frage beantworten würde; jedes Feld ist ein unabhängiges Maximum über die berichtenden Children, kein Porträt eines einzelnen Childs, und eine pro-Child-Obergrenze wird gegen jede Achse einzeln beurteilt. reported zählt, wie viele von sampled beigetragen haben, und ist niedriger, wenn einige Children die Felder noch nicht kennen. Jeder Byte-Wert betrifft die Old-Generation – was --max-old-space-size tatsächlich begrenzt – und nicht nur old_space, weil ein Child seine Obergrenze erschöpfen kann, während old_space nur wenige Megabyte belegt und large_object_space alles enthält. peakOldGenerationBytes sind Commit-Bytes und steigen mit der Obergrenze, die dem Child zugewiesen wurde; lies dies als obere Grenze dessen, was der Workload benötigt, statt als seinen Bedarf; peakLiveSetBytes ist, was eine Major-GC überlebt und sich nicht mit der Obergrenze bewegt, weshalb es die Zahl ist, die aussagen kann, dass ein Child nicht passt; lies es als obere Grenze statt als exakten Live-Set, weil GC-Einträge asynchron ankommen und alles, was zwischen der Collection und dem Lesen alloziert wurde, mitgezählt wird. peakLiveSetBytes ist 0, bis eine Major-GC beobachtet wird, was eine Abwesenheit statt eine Messung ist. unclassifiedSpaceNames ist die Vereinigung der Heap-Spaces, die kein berichtendes Child klassifizieren konnte; V8 benennt und fügt Spaces zwischen Versionen hinzu, ein unbekannter Space wird aus den Summen gestrichen, und Streichen führt zu Unterzählung – ein nicht leeres Array bedeutet also, dass die Byte-Werte unvollständig sind und nicht als vollständige Messung gelesen werden dürfen. Das gesamte Objekt ist null, niemals ein genulltes Objekt, wenn kein gesampeltes Child eines gemeldet hat; ohne angehängten SSE/WS-Watcher wird gar nichts gesampelt, das ist also ein Routinezustand statt ein Sonderfall. All dies ist beobachtend: nichts hiervon bestimmt eine Child-Größe, lehnt einen Spawn ab oder setzt limits.memory.enforced von false weg.
runtime.memory.pressure ist additiv innerhalb dieses Blocks und meldet den eigenen Speicherdruck des Daemon-Root: mode (off / observe), level (normal / soft / hard / critical), source (rss / heap / unknown), ratio und die sechs Rohwerte, aus denen die Verhältnisse berechnet werden – rssBytes, rssRatio, availableBytes, heapUsedBytes, heapRatio, heapLimitBytes. ratio ist das größere von rssRatio und heapRatio, und source benennt, welches es war; Gleichstände werden als rss gemeldet. availableBytes ist limits.memory.availableMemoryMb in Bytes – absichtlich der erkannte Cgroup/Host-Wert statt effectiveBudgetMb, weil den Prozess die reale Grenze beendet, nicht die Policy-Zahl eines Operators. source: "unknown" bedeutet, dass keiner der Nenner messbar war, und darf nicht als gesund gelesen werden; level ist nur in diesem Fall normal, weil es nichts zu klassifizieren gibt. Die Werte betreffen ausschließlich den Daemon-Root-Prozess: es ist das eigene memoryUsage() dieses Prozesses, sodass wachsende Children sie nicht bewegen. runtime.memory.children meldet diese separat, und keiner der Werte ist der Prozess-Tree-Speicher. Beide Modi melden den gesamten Block; nur observe löst zusätzlich die pfadlose daemon_memory_pressure-Warnung in das Status-Rollup aus, sodass off den Top-Level-status unverändert lässt. In keinem Modus wird etwas remediiert. Das Feld ist im SDK-Mirror optional, da Daemons, die runtime.memory vor dessen Existenz ausgeliefert haben, den Block ohne es senden.
limits.maxRegisteredWorkspaces ist additiv und meldet das aufgelöste Benutzer-Registrierungslimit (Standard 256, konfigurierbar von 1 bis 256). limits.maxChannelControlWorkspaces meldet das unabhängige Control/Recovery-Owner-Limit von 25 auf dem Standard-Daemon, auch während Channels deaktiviert sind. Custom Controller bewerben dieses Feld nur, wenn sie es durchsetzen. Diese Felder sind auf Bootstrap- und Ready-Antworten vorhanden; ältere Daemons können sie weglassen. Der Channel-Controller weist transitionierende Owner-Unionen über seinem Limit mit 409 channel_control_workspace_limit_reached zurück, bevor er Candidate-Worker konstruiert; eine anfängliche Boot-Zeit-Union über dem Limit schlägt den Startup vor dem Veröffentlichen des Listeners fehl, hat also keine HTTP-Oberfläche. Die Registrierungskapazität bestimmt nicht den SDK-Channel-Timeout.
limits.maxTotalSessions ist additiv. null bedeutet, dass das effektive Daemon-weite Frisch-Session-Limit deaktiviert ist. Wenn die aufgelöste Registrierungskapazität (Standard 256, konfigurierbar über QWEN_SERVE_MAX_WORKSPACES oder eingebettetes maxRegisteredWorkspaces) 25 übersteigt und --max-total-sessions weggelassen wird, verwendet der Standard-Daemon ein festes Total von 800, selbst mit einem einzigen Startup-Workspace. Bei Registrierungskapazitäten von 25 oder weniger leiten mehrere Startup/wiederhergestellte Workspaces mit einem endlichen maxSessionsPerWorkspace das effektive Total einmal als maxSessionsPerWorkspace * workspaceCount über dieselbe Startup-plus-wiederhergestellte Anzahl ab; ein einzelner Startup- oder wiederhergestellter Workspace behält ein unbegrenztes Default. Explizite Total-Limits, einschließlich deaktivierter Werte, haben Vorrang. Spätere dynamische Registrierung berechnet das Total nicht neu. Direkte createServeApp-Embeds müssen ihre eigene gemeinsame Admission-Policy bereitstellen. Wenn gesetzt, begrenzt es die Frisch-Session-Erstellung Daemon-weit und meldet Gesamtlimit-Fehler mit der bestehenden session_limit_exceeded-Fehlerform plus scope: "total".
runtime.channel.live meldet den ACP-Bridge-Channel innerhalb des Daemons. Es ist
nicht der Channel-Adapter-Worker. Daemon-verwaltete Channels nutzen
runtime.channelWorker, dessen state einer von disabled, starting,
running, exited, failed oder stopped ist. Wenn ein Worker running erreicht
und dann beendet wird, hält /daemon/status den Daemon online und meldet den Warning-
Issue-Code channel_worker_exited.
Der Start von Daemon-verwalteten Channel-Workern über ein explizites qwen serve --channel ... bleibt fail-fast und hat Vorrang vor persistierten Startup-Einstellungen.
Ein Boot ohne Flags stellt serve.channels aus dem vertrauenswürdigen primären Workspace wieder her.
Sekundäre Workspaces stellen ihre eigenen serve.channels nicht unabhängig wieder her.
Ohne explizite oder Primary-Workspace-Auswahl bleibt das Laden der Channel-Runtime lazy.
Gespeicherte Startup-Namen müssen nicht leer sein, dürfen keinen führenden oder abschließenden Whitespace haben
und keine unsicheren Steuer- oder unsichtbaren Zeichen enthalten. Ungültige Einträge werden
individuell übersprungen und nach Array-Index protokolliert; der Startup trimmt sie nicht in
andere Instanznamen oder schreibt Einstellungen um. Worker-Argumente verwenden
--channel=<value>, wobei ein führender Bindestrich als Teil des Namens erhalten bleibt.
Ein ungültiges Startup-Feld oder ein Validierungs- oder Lease-Fehler vor dem Start der Worker
überspringt die automatische Wiederherstellung, mit einem Log, das serve.channels identifiziert, während
unabhängige Einstellungen wirksam bleiben. Ein fehlgeschlagener Worker-Startup erlaubt dem Daemon,
nach erfolgreicher Bereinigung fortzufahren. Globale Runtime-Startup-Timeouts und
unbestätigte Worker-Stopps folgen dem bestehenden Startup-Fehlerpfad; die Lease
bleibt gehalten, während die Worker-Terminierung unbestätigt ist. Inspiziere Daemon-Logs für
übersprungene oder fehlgeschlagene Wiederherstellungen. Das Channel-Management meldet persistierte Startup-
Einstellungen und den tatsächlichen Runtime-Zustand.
Nachdem ein Worker Ready erreicht hat, werden unerwartete Beendigungen vom Serve-
Supervisor innerhalb einer begrenzten Policy neu gestartet: bis zu 3 Neustartversuche in einem 5-Minuten-
Fenster, mit 1s, 5s und dann 15s Backoff. Der Worker sendet alle
15s IPC-Heartbeats; wenn 45s lang keine Heartbeat beobachtet wird, behandelt der Supervisor den Worker als
veraltet, killt ihn, protokolliert staleHeartbeatAt und nutzt denselben Neustart-Pfad.
runtime.channelWorker kann additive operative Felder enthalten:
requestedChannels, pid, startedAt, exitCode, signal, error,
restartCount, lastExitAt, lastRestartAt, nextRestartAt,
lastHeartbeatAt, staleHeartbeatAt, startupFailures und
startupFailuresTruncated. Jeder Startup-Fehler hat channel, phase
(derzeit connect), optionales vom Adapter bereitgestelltes code und eine Credential-
bereinigtes message. Maximal 64 Fehler werden für die aktuelle Worker-Generation
behalten; das Truncation-Flag bedeutet, dass mehr Fehler beobachtet wurden. code ist
diagnostisch und keine stabile Cross-Adapter-Klassifikation. restartCount ist die über die Lebensdauer
Gesamtzahl der Neustartversuche, die von diesem Serve-Prozess unternommen wurden; ein laufender Worker mit
restartCount > 0 ist gesund, sofern kein anderes Issue vorliegt. Ein laufender Worker,
dessen requestedChannels Namen enthalten, die in channels fehlen, meldet
channel_worker_partial_connect.
Bei einem Multi-Workspace-Daemon (--workspace wiederholt) enthält runtime zusätzlich
channelWorkers[] – ein Eintrag pro besitzendem Workspace, jeweils ein
channelWorker-Snapshot, annotiert mit workspaceId, workspaceCwd und
primary. channelWorker bleibt als Snapshot des primären Workspaces für
Kompatibilität belegt. Single-Workspace-Daemons lassen channelWorkers[] weg.
Daemon-verwaltete Channel-Steuerung
Die channel_control-Capability bewirbt die Runtime-Selection-Ressource.
Die Ressource ist Daemon-weit, auch wenn ihr Kompatibilitätspfad den
Singular-/workspace-Prefix verwendet. Runtime-Selections werden nicht persistiert und
modifizieren nicht die Boot-Zeit---channel-Option des Daemons.
GET /workspace/channel gibt einen unveränderlichen Manager-Snapshot zurück:
{
"enabled": true,
"selection": { "mode": "names", "names": ["telegram", "feishu"] },
"pendingSelection": { "mode": "names", "names": ["telegram"] },
"transition": "reconciling",
"workers": [
{
"workspaceId": "primary-id",
"workspaceCwd": "/work/primary",
"primary": true,
"enabled": true,
"state": "running",
"channels": ["telegram"],
"pid": 1234
}
]
}selection ist null, wenn deaktiviert. pendingSelection ist nur während
einer Mutation vorhanden. transition ist einer von idle, starting, reconciling,
stopping oder rolling_back.
PUT /workspace/channel ist strikt-gated und akzeptiert genau eine Selection:
{ "selection": { "mode": "all" } }{ "selection": { "mode": "names", "names": ["telegram", "feishu"] } }Namen werden getrimmt und dedupliziert ohne Sortierung; ein leeres Namens-Array ist
ungültig. all bleibt nur für den primären Workspace. Eine Disabled-to-Enabled-Änderung
gibt 201 zurück; ein idempotentes PUT oder Ersatz gibt 200 zurück. Die Antwort ist
{ changed, replaced, partial, state }. Eine gleiche Selection behält gesunde
Worker bei, stellt aber eine gleiche Selection wieder her, deren Worker gestoppt oder
fehlgeschlagen ist.
DELETE /workspace/channel ist strikt-gated und idempotent. Es gibt
{ changed, state } zurück; ein erfolgreicher Zustand ist disabled. POST /workspace/channel/reload ist ebenfalls strikt-gated und liest Einstellungen neu,
löst Workspace-Gruppen neu auf und erzwingt die Reconciliation der committeten Selection.
Es gibt 409 channel_worker_not_enabled zurück, wenn deaktiviert. Die
channel_reload-Capability wird dynamisch nur beworben, während der Manager
eine committete, reloadbare Selection hat.
Jedes Enable, Replace, Reload, Stop und Daemon-Shutdown betritt eine FIFO-
Lifecycle-Lane. GET wartet nicht auf diese Lane. Workspace-Gruppen, deren geordnete
Selection sich nicht geändert hat, bleiben online. Ersatzfehler versuchen, neu gestartete
Worker zu stoppen und die vorherige committete Selection wiederherzustellen. Clients
müssen rolledBack, rollbackError und state inspizieren, da Bereinigung oder
Wiederherstellung ebenfalls fehlschlagen können. Der Daemon behält die Channel-Service-PID-Lease
während einer Transaktion und gibt sie nicht frei, bis jeder relevante Child-
Exit bestätigt ist.
Stabile Control-Fehler sind:
400 invalid_channel_selection,channel_workspace_mismatchoderambiguous_channel_workspace403 untrusted_workspace409 channel_service_conflict,channel_worker_not_enabledoderchannel_control_workspace_limit_reached500 channel_worker_stop_failed502 channel_worker_start_failed, mitrolledBackund einem optionalen Credential-bereinigtenrollbackError503 daemon_draining
Strikte Schreibvorgänge von einer Token-losen Primary-Anfrage, die das Gate ohne
Trusted-Loopback-Autorität erreicht, geben 401 token_required zurück, bevor der Control-Code
läuft. Fehlende oder ungültige konfigurierte Credentials und nicht gepaarte Local-Control-
Credentials werden früher mit einfachem 401 Unauthorized abgelehnt.
Trusted-Loopback-Primary-Anfragen werden normal ausgeführt.
Sobald eine Anfrage beginnt, annulliert das Trennen des HTTP-Clients die Lifecycle-Transaktion nicht; Clients können dasselbe PUT
sicher wiederholen.
Für 502 channel_worker_start_failed kann die Antwort auch
startupFailures[] und startupFailuresTruncated enthalten. Jeder Fehler fügt das
vertrauenswürdige workspaceCwd des versuchten Workers hinzu. Diese Felder beschreiben die
fehlgeschlagene Transaktion, während state den aktuellen Zustand nach dem Rollback beschreibt;
ein späteres GET behält den fehlgeschlagenen Versuch nicht. Ein teilweise verbundener Worker
gibt stattdessen Erfolg zurück und legt seine Fehler im Worker-Snapshot offen. Ein expliziter
--channel-Boot ohne verbundenen Adapter schlägt den Startup fehl. Ein aus Einstellungen
abgeleiteter Startup-Fehler wird protokolliert und erlaubt dem Daemon, nach erfolgreicher
Bereinigung fortzufahren, unterliegt dem globalen Runtime-Startup-Timeout. Das Nichtbestätigen
der Bereinigung behält das normale Startup-Fehlerverhalten und die Service-Lease.
qwen channel status ohne --daemon-url liest weiterhin Pidfile-Metadaten;
mit --daemon-url liest es GET /workspace/channel. Während eines Neustart-
Fensters bleibt das Serve-eigene Pidfile reserviert, aber workerPid wird weggelassen, damit
Clients keinen veralteten Worker-Prozess anzeigen. Bei einem Multi-Workspace-Daemon trägt das
Pidfile auch ein additives workers[]-Array (pro-Workspace
workspaceId / workspaceCwd / channels / live workerPid), während die
Top-Level-channels (Union) und workerPid (Primary) für ältere
Leser belegt bleiben; Single-Workspace-Daemons behalten die ursprüngliche Single-Worker-Form. Worker-
stdout/stderr werden in das Daemon-Log weitergeleitet, wobei Bearer-Tokens, sensible Worker-Umgebungs-
Werte und Proxy-URL-Credentials geschwärzt werden.
Workspace-Channel-Management
Die channel_management-Capability bewirbt Workspace-scopige Channel-
Konfiguration und Runtime-Management. Die Singular-/workspace-Routen zielen auf
die primäre Runtime. /workspaces/:workspace löst die exakte registrierte,
vertrauenswürdige Runtime auf und fällt niemals auf die primäre Runtime zurück.
Read-only Discovery verwendet:
GET /workspace/channel-typesGET /workspace/channelsGET /workspaces/:workspace/channel-typesGET /workspaces/:workspace/channels
Der Katalog markiert die von dieser Management-API unterstützten Typen mit
manageable: true. Instanz-Snapshots enthalten eine Revision, geschwärzte Secret-
Präsenzmetadaten, Startup-Zustand und Runtime-Zustand; literale Secrets werden niemals
zurückgegeben. Channel-Snapshots verwenden Cache-Control: no-store.
Field-Deskriptoren können über properties verschachtelte Objekt-Metadaten bereitstellen.
Numerische Deskriptoren können exclusiveMinimum für offene Untergrenzen verwenden. String-
und Secret-Deskriptoren können multiline verwenden, um Clients nach einem mehrzeiligen Text-
Bereich zu fragen; die Deskriptor-Typen erlauben dies nur auf Top-Level-Feldern. Clients, die
einen beworbenen Field-Kind nicht rendern, müssen seinen bestehenden Konfigurationswert
beibehalten, anstatt ihn zu erzwingen oder zu löschen, und ein Client, der ein multiline-
Feld in einem einzeiligen Control rendert, muss den gespeicherten Wert wortwörtlich beibehalten,
anstatt seinen um Newlines bereinigten Eingabewert zurückzuschreiben. Objekt-Felder können nicht erforderlich sein,
und verschachtelte Properties können keine Secrets oder Environment-auflösbaren Felder sein;
diese Management-Protokolle bleiben nur auf Top-Level-Ebene. Eine verschachtelte required-Property
wird nur durchgesetzt, während ihr Elternobjekt im Write vorhanden ist; das Weglassen des
Elternobjekts lässt seine verschachtelten Anforderungen ungeprüft. Writes ersetzen den
gespeicherten Wert jedes Feldes vollständig, sodass das Beibehalten eines Objekts das erneute Senden des
gespeicherten Objekts bedeutet; der Daemon führt keine partiellen Objekte zusammen.
Konfigurationsschreibvorgänge verwenden optimistische Concurrency und das strikte Operator-Authority- Gate:
PUT /workspace/channels/:nameDELETE /workspace/channels/:namePUT /workspace/channels/:name/startup- die äquivalenten
/workspaces/:workspace/...-Routen
Jede Einstellungsmutation enthält expectedRevision. Upsert-Anfragen enthalten ein
config-Objekt und können explizite Secret-Operationen enthalten: preserve,
replace oder clear. Eine Channel-Konfiguration kann kein Arbeitsverzeichnis außerhalb des aufgelösten Workspaces auswählen.
Runtime-Aktionen sind strikt-gated POST-Anfragen an
.../channels/:name/start, stop oder restart. Sie wirken nur auf den
Worker, der dem aufgelösten Workspace gehört.
Pairing-Management ist nur für Instanzen verfügbar, die mit der
pairing-Sender-Policy oder Group-Policy konfiguriert sind:
GET .../channels/:name/pairing-requestsPOST .../channels/:name/pairing-requests/approvemit{ "code": "..." }GET .../channels/:name/pairing-approvalsDELETE .../channels/:name/pairing-approvalsmit entweder{ "senderId": "..." }oder{ "groupId": "..." }
Alle Pairing-Routen erfordern strikte Operator-Autorität und verwenden Cache-Control: no-store.
Anfragen, Genehmigungen und Widerrufe sind auf die ausgewählte Channel-
Instanz und den Workspace beschränkt. Ausstehende Anfragen enthalten ein typisiertes User- oder Group-Subject;
Group-Anfragen behalten zusätzlich den Sender, der die Anfrage initiiert hat. Genehmigungs-Snapshots enthalten
senderIds und groupIds, da Allowlists keine Anzeigenamen persistieren. Der Widerruf eines unbekannten Users
oder einer unbekannten Group gibt 404 channel_pairing_approval_not_found zurück.
Channel-Delivery und Notify
channel_delivery bewirbt sofortige, best-effort Delivery-Unterstützung. Es ist eine
Protokoll-Capability, kein Worker-Gesundheitssignal. Delivery startet niemals einen
fehlenden Worker, fällt auf einen anderen Workspace zurück, wiederholt, persistiert eine Outbox
oder spielt historische Benachrichtigungen ab.
Direct Notify umgeht Agent und Session und wartet auf einen Sendeversuch:
POST /workspace/notify
POST /workspaces/:workspace/notify
Authorization: Bearer <token>
Content-Type: application/json
{
"text": "service unavailable",
"delivery": {
"kind": "channel",
"target": {
"channelName": "dingtalk",
"type": "user",
"id": "platform-user-id"
}
}
}Beide Routen verwenden das strikte Mutations-Gate. Die qualifizierte Route löst nur einen
registrierten, vertrauenswürdigen Workspace auf. Erfolg ist 200 {delivered:true,deliveryId}.
delivered:true bedeutet, dass das Channel-Send-Promise aufgelöst wurde; es beweist nicht
Provider-Akzeptanz, Benutzer-Empfang oder eine Lesebestätigung. Providerspezifische
Antwortvalidierung und konsistente Error-Reason-Semantik über IM-Adapter hinweg
liegen außerhalb dieses V1-Contracts.
Fehler sind 400 channel_delivery_invalid, 503 channel_worker_unavailable oder
channel_delivery_queue_full, 504 channel_delivery_timeout und 502 channel_delivery_rejected oder channel_delivery_failed. Ein Timeout hat ein
unbekanntes Ergebnis und wird nicht wiederholt.
Es gibt absichtlich keinen separaten Konnektivitätstest-Endpunkt: ein normaler
Notify-Aufruf ist der End-to-End-Test.
Das replaybare Ergebnis-Event enthält nur Korrelation und bereinigten Status:
{
"type": "channel_delivery_result",
"promptId": "prompt-1",
"data": {
"sessionId": "session-1",
"deliveryId": "prompt-1",
"source": "prompt",
"status": "failed",
"promptId": "prompt-1",
"code": "channel_worker_unavailable",
"error": "Channel worker is not running."
}
}Ein leerer erfolgreicher Prompt-Abschluss lässt Fehlerfelder weg:
{
"type": "channel_delivery_result",
"promptId": "prompt-1",
"data": {
"sessionId": "session-1",
"deliveryId": "prompt-1",
"source": "prompt",
"status": "skipped",
"promptId": "prompt-1"
}
}source ist prompt oder scheduled; status ist delivered, failed oder
skipped. skipped bedeutet, dass der berechtigte Turn erfolgreich abgeschlossen wurde, aber sein
letzter toolfreier Assistant-Antwortblock leer oder nur Whitespace war. Der
Daemon verbraucht die Delivery-Autorisierung und veröffentlicht das Event, ohne
einen Channel-Worker aufzulösen. Die Scheduled-Korrelation verwendet taskId und firedAt.
Das Event enthält niemals Ziel-IDs, Nachrichtentext, Credentials oder Webhook-
Secrets.
Sicherheit: Die Response enthält niemals Bearer-Tokens, Client-IDs, vollständige ACP-
Connection-IDs, Device-Flow-User-Codes oder Verifizierungs-URLs. Beide Detail-
Ebenen können additive daemon.runId, daemon.logMode und
daemon.logHealth enthalten. summary lässt den Daemon-Log-Pfad und Verlustdetails
weg; full kann logPath, logIssues, logDroppedRecords und
logDroppedBytes für authentifizierte Operatoren enthalten. Degraded File Logging fügt den
pfadfreien daemon_log_degraded-Warning zur normalen Status-Zusammenfassung hinzu.
GET /capabilities
{
"v": 1,
"protocolVersions": {
"current": "v1",
"supported": ["v1"]
},
"mode": "http-bridge",
"features": [
"health",
"daemon_status",
"capabilities",
"multi_workspace_sessions",
"..."
],
"limits": {
"maxRegisteredWorkspaces": 256,
"maxChannelControlWorkspaces": 25,
"maxPendingPromptsPerSession": 5,
"maxSessionsPerWorkspace": 32,
"maxTotalSessions": 800,
"sessionRestoreTimeoutMs": 60000
},
"modelServices": [],
"workspaceCwd": "/canonical/path/to/primary-workspace",
"workspaces": [
{
"id": "stable-workspace-id",
"cwd": "/canonical/path/to/primary-workspace",
"primary": true,
"trusted": true
},
{
"id": "stable-secondary-workspace-id",
"cwd": "/canonical/path/to/secondary-workspace",
"displayName": "Payments Production",
"primary": false,
"trusted": true
}
]
}Stabiler Contract: Wenn v erhöht wird, hat sich das Frame-Layout auf eine abwärtsinkompatible Weise geändert.
protocolVersionsbeschreibt die Serve-Protokollversionen, die der Daemon sprechen kann.currentist die bevorzugte Protokollversion des Daemons undsupportedist die kompatible Menge. Clients, die ein bestimmtes Protokoll benötigen, solltensupportedprüfen; feature-spezifische UIs sollten weiterhin auffeaturesprüfen. Additiv zu v=1: Ältere v=1-Daemons lassen dieses Feld weg, daher sollten SDK-Clients, die auf ältere Builds abzielen, es als optional behandeln.
modelServicesist in Stage 1 immer[]. Der Agent nutzt seinen einzelnen Standard-Model-Service und zählt ihn nicht über das Wire auf. Stage 2 wird dies aus registrierten Model-Adaptern befüllen, damit SDK-Clients Service-Picker bauen können; verlasse dich bis dahin NICHT darauf, dass dieses Feld nicht leer ist.
workspaceCwdist der kanonische absolute Pfad für den primären Workspace des Daemons. Verwende ihn, umcwdbeiPOST /sessionwegzulassen (die Route fällt auf diesen primären Pfad zurück) und um alte Single-Workspace-Clients kompatibel zu halten. Additiv zu v=1: Pre-§02 v=1-Daemons lassen das Feld weg – Clients, die auf ältere Builds abzielen, sollten einen Null-Check durchführen, bevor sie es konsumieren.
workspaces[]listet jede registrierte Runtime auf. Neuere Single-Workspace-Daemons schließen die primäre Runtime ein, auch wennmulti_workspace_sessionsfehlt, sodass Clients die stabile ID entdecken können, die für Workspace-qualifizierte Routen erforderlich ist; ältere Daemons können das Array weglassen. Jeder Eintrag ist{ id, cwd, displayName?, primary, trusted, removable? }.displayNameist rein präsentativ und wird weggelassen, wenn nicht gesetzt. Der erste/primäre Workspace wird weiterhin vonworkspaceCwdgespiegelt; neue Clients wählen eine nicht primäre Runtime, indem sie dascwddieses Eintrags anPOST /sessionübergeben. Nicht vertrauenswürdige Workspaces werden für die Diagnostik beworben, lehnen aber die Frisch-Session-Erstellung mit403 untrusted_workspaceab, bis sich der Trust ändert.removableist auf Daemons vorhanden, die Runtime-Entfernung unterstützen, und ist nur für prozessdynamische oder persistent wiederhergestellte sekundäre Runtimes true.
session_worktree_persistence_v1bedeutet, dass der Daemon Part-4A-Worktree-Eigentümerschaft persistieren und verifizieren kann. Erfolgreiche Worktree-Erstellungsantworten und Restore-Antworten, deren Child entweder im Idle-Zustand relociert wurde oder bereits das verifizierte Worktree-CWD meldet, tragenworktree-Metadaten plusworktreeState: "persisted-v1". Ein Legacy-Best-Effort-Restore oder ein Cold-Restore, dessen Restore-Prompt gefeuert statt geparkt wurde (suppressWorktreeContextRestoreaus, sodass die Route bei der Bridge keine Verzögerung angefragt hat) und der daher einen aktiven Prompt ohne aktuelles CWD meldet, kann weiterhinworktreeohne diese Bestätigung zurückgeben. Clients, die Isolation anfordern, müssen dieses Tag vorab prüfen und jede Antwort verifizieren; dasworktree-Objekt allein ist kein Beweis für dauerhafte Eigentümerschaft.
session_worktree_reset_v1bedeutet, dass der Daemon Worktree-Eigentümerschafts-Übertragung unterstützt:POST /session/:id/worktree-resetverschiebt die Checkout-Eigentümerschaft einer persistierten Worktree-Session auf eine frische Ersatz-Session. Restore-Antworten erhalten drei typisierte 409-Klassifizierungen daneben:worktree_session_superseded(der Sidecar der Session trägt einensupersededBy-Link – die Klassifizierung wird allein aus diesem Link entschieden, vor jedem Marker-Read, und der Link wird geschrieben, bevor der Marker umschaltet, sodass diereplacementSessionIdim Body eine zu verifizierende Umleitung statt ein Eigentümerschaftsbeweis ist: in einem Pre-Commit-unterbrochenen Zustand benennt sie eine Session, die nicht der Marker-Owner ist, nicht selbst wiederhergestellt werden kann und vom erneut versuchten Reset geerntet wird),worktree_marker_missing(der Checkout-Marker fehlt; setze den Task zurück, um ihn neu zu erstellen – ein Restore-Retry kann das nicht, weil kein Restore-Pfad einen Marker schreibt) undworktree_reset_interrupted(eine vorherige Übertragung ist während des Flugs mit übereinstimmenden Sidecar-Links abgestürzt; wiederhole den Reset). Die unterbrochene Klassifizierung wird zuerst geprüft: ein fehlender Marker, dessensupersedes/supersededBy-Links übereinstimmen, erscheint alsworktree_reset_interrupted, niemals alsworktree_marker_missing. Siehe den Routen-Abschnitt unten für das Übertragungsprotokoll und die Fehler-Taxonomie.
Die Workspace-Feature-Tags und workspaces[] sind dynamisch. Clients, die einen Workspace hinzufügen, müssen /capabilities erneut abrufen, nachdem die Mutation abgeschlossen ist; der Daemon broadcastet keine Capability-Änderungen an Clients, die eine frühere Antwort zwischengespeichert haben. Das Vergessen der Persistenz entlädt keine aktive Runtime, sodass diese Runtime bis zum Neustart beworben bleibt.
POST /workspaces
Registriere eine zusätzliche Workspace-Runtime. Der Pfad muss ein bestehendes, zugängliches, absolutes Verzeichnis sein, das nicht mit einem anderen registrierten Workspace dupliziert oder verschachtelt ist. Die Registrierung ist prozesslokal, es sei denn, der Client sendet persist: true; Clients müssen persistent_workspace_registration vorab prüfen, bevor sie Persistenz anfordern. Wenn workspace_display_name beworben wird, kann die Anfrage auch einen optionalen displayName enthalten.
{
"cwd": "/canonical/path/to/secondary-workspace",
"persist": true,
"displayName": "Payments Production"
}Eine neu erstellte Runtime gibt 201 zurück; das Befördern eines bereits aktiven sekundären Workspaces auf persistent gibt 200 zurück. Persistenter Erfolg enthält persisted: true:
{
"id": "stable-workspace-id",
"cwd": "/canonical/path/to/secondary-workspace",
"displayName": "Payments Production",
"primary": false,
"trusted": true,
"persisted": true
}displayName muss ein String mit maximal 256 Zeichen sein, nachdem umgebender Whitespace getrimmt wurde. Ein leeres Ergebnis wird als kein Name behandelt, und interne C0- (U+0000–U+001F) oder DEL-(U+007F) Steuerzeichen werden abgelehnt. JSON null ist kein Erstellungswert und gibt 400 invalid_display_name zurück; lass das Feld weg, um keinen initialen Namen anzugeben. Doppelte Anzeigenamen sind erlaubt. Ein Name, der mit einer prozesslokalen Registrierung angegeben wird, gilt nur für diesen Daemon-Prozess; persist: true speichert ihn mit der persistenten Registrierung, sodass er nach dem Neustart wiederhergestellt werden kann. Das Wiederholen der Anfrage für einen bereits persistenten Workspace ist idempotent und benennt ihn nicht um.
Fehler umfassen 400 invalid_path / invalid_persist_flag / invalid_persist_target / invalid_display_name, 409 workspace_exists / workspace_nested / workspace_limit_reached / workspace_registration_store_too_large, 500 workspace_registration_store_error / runtime_creation_failed und 501 persistence_not_available / not_implemented.
PATCH /workspaces/:workspace
Aktualisiere eine aktive Workspace-Ressource, ausgewählt nach Workspace-ID oder URL-kodiertem absoluten CWD. Der Endpunkt unterstützt derzeit nur Display-Name-Metadaten:
{ "displayName": "Payments Production" }Sende { "displayName": null }, um den Namen zu löschen. Hier ist null ein nur-updatefähiges Lösch-Sentinel; Nicht-Null-Werte folgen denselben String-Normalisierungsregeln wie POST /workspaces. Die Antwort ist die aktualisierte { id, cwd, displayName?, primary, trusted, removable? } Workspace-Projektion. Runtime-Metadaten werden immer aktualisiert. Wenn die Runtime passende persistente Registrierungsidentitäten hat, wird jeder Alias atomar über den bestehenden Schema-v1-Registrierungs-Store aktualisiert; der Endpunkt erstellt oder befördert niemals eine persistente Registrierung.
Nicht unterstützte Felder schlagen fail-closed fehl, anstatt stillschweigend ignoriert zu werden. Fehler umfassen 400 empty_patch / invalid_display_name / unsupported_field / workspace_mismatch, 409 workspace_registration_in_progress, 500 workspace_registration_store_error und 503 daemon_shutting_down.
DELETE /workspaces/:workspace
Entferne eine entfernbare sekundäre Runtime. Der Selektor folgt den Plural-Workspace-Routing-Regeln und akzeptiert entweder eine Workspace-ID oder ein URL-kodiertes absolutes CWD. Der optionale JSON-Body ist { "force": boolean }; das Weglassen fordert nicht-erzwungene Entfernung an.
Nicht-erzwungene Entfernung gibt 409 workspace_busy mit einem activity-Snapshot zurück, wenn die eingefrorene Runtime Sessions, Prompts, ausstehende Starts, ACP-Connections, Memory-Tasks oder Workspace-Channel-Worker hat. Das Senden von { "force": true } fordert die Beendigung dieser Ressourcen an. Die Persistenzentfernung ist der Commit-Punkt: nachfolgende Bereinigung ist begrenzt und best-effort, Bereinigungsfehler werden protokolliert, und die logische Entfernung konvergiert weiterhin, anstatt die Runtime wiederherzustellen. Eine erfolgreiche Antwort ist:
{
"removed": true,
"workspaceId": "stable-workspace-id",
"workspaceCwd": "/canonical/path/to/secondary-workspace",
"forced": true,
"persistedRegistrationRemoved": true,
"activity": {
"sessions": 2,
"activePrompts": 1,
"pendingSessionStarts": 0,
"acpConnections": 1,
"memoryTasks": 0,
"channelWorkers": 0,
"voiceSessions": 0
}
}Eine sofort beschäftigte nicht-erzwungene Anfrage gibt einen schnellen Pre-Drain-Aktivitäts-Snapshot zurück. Sobald der Drain beginnt, enthält die Busy- oder Erfolgsantwort den endgültigen Snapshot, der nach dem Schließen der Admission- und ACP-Drain-Gates und vor dem Beginn der Bereinigung aufgenommen wurde. Fehler umfassen 400 invalid_force_flag / workspace_mismatch, 409 workspace_busy / primary_workspace_removal_forbidden / static_workspace_removal_forbidden / workspace_removal_in_progress / workspace_registration_in_progress, 500 workspace_persist_failed / workspace_runtime_removal_failed, 501 workspace_runtime_removal_unsupported und 503 daemon_shutting_down.
GET /workspace-registrations
Liste die persistierte gewünschte Workspace-Menge für diesen primären Workspace auf. Einträge bleiben mit active: false sichtbar, wenn ein gespeichertes Verzeichnis während des aktuellen Starts nicht wiederhergestellt werden konnte.
Ein Eintrag bleibt active: true, während seine Runtime drainend ist, da die Runtime weiterhin Live-Ressourcen besitzt, bis die Entfernung abgeschlossen ist.
Einträge enthalten optional displayName, wenn die persistente Registrierung einen hat.
{
"schemaVersion": 1,
"primaryWorkspace": "/canonical/path/to/primary-workspace",
"entries": [
{
"id": "stable-registration-id",
"cwd": "/canonical/path/to/secondary-workspace",
"displayName": "Payments Production",
"active": true,
"persisted": true
}
]
}Gibt 501 persistence_not_available zurück, wenn kein Registrierungs-Store konfiguriert ist, und 500 workspace_registration_store_error, wenn der Store nicht gelesen werden kann.
DELETE /workspace-registrations/:id
Vergiss eine persistierte Registrierung. Dies entlädt keine aktive Runtime oder beendet ihre Sessions; restartRequired: true bedeutet, dass die aktive Runtime beim nächsten Daemon-Neustart verschwindet.
{ "removed": true, "active": true, "restartRequired": true }Gibt 404 workspace_registration_not_found, 500 workspace_registration_store_error oder 501 persistence_not_available zurück. Wie andere Mutationsrouten erfordert dieser Endpunkt Mutationsauthentifizierung, wenn die Daemon-Authentifizierung aktiviert ist.
Read-only Runtime-Status-Routen
Diese Routen melden Daemon-seitige Runtime-Snapshots. Es sind additive v1-Routen,
die den Zustand nicht verändern und die Serve-Protokollversion nicht ändern. Workspace-
Status-Routen starten absichtlich nicht den ACP-Child-Prozess, nur weil
ein Client eine GET-Route pollt: Wenn der Daemon im Leerlauf ist, geben sie
initialized: false mit einem leeren Snapshot zurück. Session-Status-Routen erfordern eine
Live-Session und geben 404 { code: "session_not_found", ... } für unbekannte
IDs zurück.
Capability-Tags:
workspace_mcp→GET /workspace/mcpworkspace_skills→GET /workspace/skillsworkspace_providers→GET /workspace/providersworkspace_acp_status→GET /workspace/acp/statusworkspace_env→GET /workspace/envworkspace_preflight→GET /workspace/preflightsession_context→GET /session/:id/contextsession_supported_commands→GET /session/:id/supported-commandssession_tasks→GET /session/:id/taskssession_monitor_tool_correlation→ Monitor-Einträge ausGET /session/:id/tasksenthaltentoolUseIdfür Transkript-zu-Task-Korrelationsession_status→GET /session/:id/statussession_info→GET /workspace/:id/session-infoundGET /workspaces/:workspace/session-infosession_transcript→GET /session/:id/transcriptworkspace_persisted_transcript→GET /workspaces/:workspace/session/:id/transcriptworkspace_session_export→GET /workspaces/:workspace/session/:id/exportworkspace_archived_session_export→GET /workspaces/:workspace/session/:id/archive/exportworkspace_session_live_state→GET /workspaces/:workspace/sessions/live-stateworkspace_qualified_memory→POST /workspaces/:workspace/memory/{remember,forget,dream}undGET /workspaces/:workspace/memory/{remember,forget,dream}/:taskId
workspace_acp_status meldet die punktuelle Liveness des primären Workspace-ACP-Channels
als { channelLive: boolean }. Der Handler erstellt keinen Channel, aber das Erreichen einer
Runtime-Route kann zuerst eine verzögerte Daemon-Runtime starten, deren konfigurierte
Startup-Policy unabhängig ACP vorheizen kann. Der Snapshot ist kein Lease: Clients müssen
die Session-Erstellung den Channel neu validieren oder starten lassen.
ACP-Preheat
Capability-Tag: workspace_acp_preheat.
POST /workspace/acp/preheat?timeoutMs=N initialisiert best-effort den primären
Workspace-ACP-Channel. timeoutMs ist standardmäßig 5000 und muss eine positive
Ganzzahl sein, die nicht größer als 60000 ist. Gleichzeitige Aufrufer und Session-Erstellung teilen
sich dieselbe Bridge-Initialisierung. Ein Anfrage-Timeout beendet nur dieses HTTP-Warten; er
annulliert nicht die gemeinsame Initialisierung.
interface WorkspaceAcpPreheatResult {
ready: boolean;
channelLive: boolean;
durationMs: number;
reason?: 'timeout' | 'error';
error?: string;
}ready entspricht immer channelLive. Eine Live-Antwort lässt reason und
error weg; andernfalls ist reason timeout oder error. durationMs misst den
aktuellen HTTP-Aufruf, nicht die volle Lebensdauer einer Initialisierung, der der Aufruf beigetreten ist.
Operativer Timeout oder Fehler gibt HTTP 200 zurück. Ungültiges timeoutMs gibt
400 zurück, während Authentifizierungs-, Rate-Limiting- und Deferred-Runtime-Fehler ihre
normalen Antworten behalten.
Beide ACP-Workspace-Routen sind Singular und nur für den primären Workspace. Clients dürfen sie nicht für einen sekundären Workspace verwenden oder eine der beiden Antworten als dauerhafte Bereitschaftszusage interpretieren.
Allgemeine Status-Zelle:
type DaemonStatus =
| 'ok'
| 'warning'
| 'error'
| 'disabled'
| 'not_started'
| 'unknown';
type DaemonErrorKind =
| 'missing_binary'
| 'blocked_egress'
| 'auth_env_error'
| 'init_timeout'
| 'restore_timeout'
| 'protocol_error'
| 'missing_file'
| 'parse_error';
interface DaemonStatusCell {
kind: string;
status: DaemonStatus;
error?: string;
errorKind?: DaemonErrorKind;
hint?: string;
}errorKind ist eine geschlossene Enum, die von /workspace/preflight,
/workspace/env und (schließlich) MCP-Guardrails gemeinsam genutzt wird, damit SDK-Clients Remediation pro Kategorie rendern können, anstatt Freiform-Nachrichten zu parsen. Die ursprünglichen sieben Status-Literale stammen aus #4175; restore_timeout wurde separat für Session-Restore-Anfragen hinzugefügt. blocked_egress bleibt reserviert, bis die Egress-Probe implementiert ist.
Status-Payloads legen niemals MCP-Umgebungsvariablen-Werte, Header, OAuth/Service-Account-Details, Provider-API-Keys, Provider-baseUrl / envKey, Skill-Body, Skill-Dateisystempfade, Hook-Definitionen oder Werte geheimer Umgebungsvariablen offen. /workspace/env meldet nur das Vorhandensein von Whitelist-Umgebungsvariablen; Proxy-URLs werden vor der Übertragung von Zugangsdaten bereinigt und auf host:port reduziert.
GET /workspace/mcp
{
"v": 1,
"workspaceCwd": "/canonical/path",
"initialized": true,
"discoveryState": "completed",
"servers": [
{
"kind": "mcp_server",
"status": "ok",
"name": "docs",
"mcpStatus": "connected",
"transport": "stdio",
"disabled": false,
"description": "Documentation server",
"extensionName": "docs-ext"
}
]
}discoveryState ist entweder not_started, in_progress oder completed.
transport ist entweder stdio, sse, http, websocket, sdk oder
unknown. errors wird weggelassen, wenn die Discovery erfolgreich ist.
MCP-Client-Guardrails (Issue #4175 ). Aktuelle Daemons erweitern die Payload um vier additive Felder und eine Capability-scopige Budget-Zelle:
{
"v": 1,
"workspaceCwd": "/canonical/path",
"initialized": true,
"discoveryState": "completed",
"clientCount": 3,
"clientBudget": 2,
"budgetMode": "enforce",
"budgets": [
{
"kind": "mcp_budget",
"scope": "workspace",
"status": "error",
"errorKind": "budget_exhausted",
"hint": "Raise --mcp-client-budget or remove servers from mcpServers config.",
"liveCount": 2,
"budget": 2,
"mode": "enforce",
"refusedCount": 1,
},
],
"servers": [
{
"kind": "mcp_server",
"status": "ok",
"name": "a",
"mcpStatus": "connected",
"transport": "stdio",
"disabled": false,
},
{
"kind": "mcp_server",
"status": "ok",
"name": "b",
"mcpStatus": "connected",
"transport": "stdio",
"disabled": false,
},
{
"kind": "mcp_server",
"status": "error",
"name": "c",
"mcpStatus": "disconnected",
"transport": "stdio",
"disabled": false,
"disabledReason": "budget",
"errorKind": "budget_exhausted",
"hint": "...",
},
],
}budgetMode ist entweder enforce, warn oder off. clientBudget fehlt, wenn kein Budget festgelegt wurde. budgets[] ist bei Daemons, die mcp_guardrails bewerben, immer ein Array (möglicherweise leer, wenn budgetMode === 'off'); ältere Daemons lassen das Feld komplett weg. Wenn mcp_workspace_pool beworben wird, hat die Zelle scope: 'workspace' und deckt den gemeinsamen Pool der ausgewählten Workspace-Runtime ab. Wenn dieses Tag fehlt, einschließlich unter QWEN_SERVE_NO_MCP_POOL=1, emittiert der Legacy-Manager scope: 'session'. Consumer MÜSSEN zusätzliche nicht erkannte Scope-Werte tolerieren.
disabledReason in den Server-Zellen unterscheidet zwischen vom Operator deaktiviert ('config' – disabledMcpServers-Konfigurationsliste) und wegen Budget abgelehnt ('budget' – entdeckt, aber aufgrund des enforce-Modus nie verbunden). Ablehnungen sind deterministisch nach der Deklarationsreihenfolge von Object.entries(mcpServers). Der serverbezogene status: 'error', errorKind: 'budget_exhausted' überlagert den rohen mcpStatus: 'disconnected' (was zwar zutrifft, aber nicht der für den Operator relevante Schweregrad ist).
Die Budget-Durchsetzung ist Capability-getrieben. Mit mcp_workspace_pool teilen sich Sessions innerhalb einer Workspace-Runtime Transports und ein WorkspaceMcpBudget; verschiedene Workspace-Runtimes teilen sich niemals einen Pool oder ein Budget. Ohne das Tag erzwingt der McpClientManager jeder ACP-Session seine eigene Kopie der Obergrenze, und der Snapshot repräsentiert diese Legacy-Session-Ansicht.
Erkennen von Budget-Druck. Zwei Oberflächen, beide nach PR-14b befüllt:
-
Push-Events (beworben über
mcp_guardrail_events): abonniereGET /session/:id/eventsund filteremcp_budget_warning/mcp_child_refused_batch-Frames überKnownDaemonEvent. Die State Machine feuert einmal pro Überschreitung der 75%-Marke nach oben (wird unter 37,5 % erneut scharf geschaltet); Ablehnungen werden imenforce-Modus einmal pro Discovery-Durchlauf zusammengefasst. -
Snapshot-Poll (beworben über
mcp_guardrails):GET /workspace/mcpaufrufen und die Budget-Zelle (budgets[0]) zusammen mitmcp_workspace_poolinspizieren, um ihren Scope zu bestimmen: -
budgets[0].status === 'warning'⇔liveCount >= 0.75 * clientBudget(entspricht dem Hysterese-Schwellenwert, den das Push-Event in PR 14b verwenden wird). -
budgets[0].status === 'error'⇔refusedCount > 0(ein oder mehrere Server wurden in diesem Discovery-Durchlauf abgelehnt). -
budgets[0].status === 'ok'⇔ unter dem 75%-Schwellenwert UND keine Ablehnungen.
Empfohlenes Poll-Intervall: abgestimmt auf das, was ohnehin bereits /workspace/mcp pollt; der Snapshot ist ressourcenschonend und die Budget-Zelle verursacht keine zusätzlichen Discovery-Kosten. SDK-Clients, die Push-Events abonnieren, profitieren dennoch vom Snapshot für den Zustand nach einer längeren Trennung (die SSE-Replay-Ring-Tiefe ist begrenzt – --event-ring-size, Standard 8000 – sodass ein Client, der länger offline ist als die Ring-Abdeckung, auf einen Snapshot-Resync zurückfällt).
GET /workspace/skills
{
"v": 1,
"workspaceCwd": "/canonical/path",
"initialized": true,
"skills": [
{
"kind": "skill",
"status": "ok",
"name": "review",
"description": "Review code",
"level": "project",
"modelInvocable": true,
"userInvocable": false,
"installedPath": "/home/alice/project/.qwen/skills/review/SKILL.md",
"argumentHint": "[path]"
},
{
"kind": "skill",
"status": "ok",
"name": "database-review",
"description": "Review database changes",
"level": "extension",
"modelInvocable": true,
"installedPath": "/home/alice/.qwen/extensions/alibabacloud-database-suite/skills/database-review/SKILL.md",
"extensionName": "alibabacloud-database-suite",
"extensionDisplayName": "Alibaba Cloud Database Suite"
}
]
}level ist entweder project, user, extension oder bundled.
userInvocable (Boolean, optional) wird für normale Skills weggelassen
(bedeutet true) und ist nur als false vorhanden, wenn der Skill nicht manuell
aufgerufen werden kann. Er gateet nicht die unten beschriebenen Settings-only-Skill-Toggle-Routen. modelInvocable ist unabhängig: false
bedeutet, dass der Skill weiterhin manuell verfügbar bleibt, aber vor der Modellaufrufung
verborgen ist. installedPath ist der bestehende absolute Pfad zur SKILL.md des Skills; der
Daemon gibt ihn wie gespeichert zurück, ohne Symlinks separat aufzulösen
oder ihn zu kanonisieren. Aktuelle Daemons emittieren ihn für jeden Skill, während Clients
sein Fehlen von älteren v1-Daemons tolerieren müssen. Skill-Bodies, Hooks, skillRoot
und andere Skill-Konfiguration bleiben ausgeschlossen. errors wird weggelassen, wenn
die Discovery erfolgreich ist.
Für Extension-eigene Skills ist extensionName der kanonische Manifestname und kann sicher als Owner-Identität verwendet werden. extensionDisplayName ist ein optionaler, lokalisierter Präsentationswert und ist möglicherweise nicht eindeutig. Neue Clients sollten extensionDisplayName ?? extensionName anzeigen; ältere Daemons lassen das Anzeigefeld weg.
Wiederholte Lesevorgänge werden aus dem letzten committeten Workspace-Snapshot bedient,
periodisch gegen den In-Memory-Cache des Childs revalidiert. Ein Lesevorgang scannt niemals
Skill-Verzeichnisse oder parst SKILL.md-Dateien neu. Das Child überprüft, dass seine
Extension-Quellen unverändert sind – ein readdir des Extensions-
Verzeichnisses plus ein stat pro Eintrag, die Enablement-Datei und der Aktivierungs-
zustand des Stores – und aktualisiert nur, wenn sie sich verschoben haben, sodass eine Extension,
die außerhalb des Daemons installiert oder umgeschaltet wurde, beim nächsten Lesevorgang
noch erkannt wird. Safe- und Bare-Mode überspringen die Überprüfung, passend zu ihrem
Ausschluss von Extensions.
GET /workspace/providers
{
"v": 1,
"workspaceCwd": "/canonical/path",
"initialized": true,
"current": { "authType": "qwen", "modelId": "qwen3(qwen)" },
"providers": [
{
"kind": "model_provider",
"status": "ok",
"authType": "qwen",
"current": true,
"models": [
{
"modelId": "qwen3(qwen)",
"baseModelId": "qwen3",
"name": "Qwen 3",
"description": null,
"contextLimit": 4096,
"isCurrent": true,
"isRuntime": false
}
]
}
]
}Modelle werden nach Auth-Typ gruppiert. Die Verbindungsdiagnostik für Provider befindet sich in der providers-Zelle von /workspace/preflight; der Environment-Preflight befindet sich in /workspace/preflight und /workspace/env (unten). errors wird weggelassen, wenn die Snapshot-Erstellung erfolgreich ist.
GET /workspace/env
Meldet die Runtime, Plattform, Sandbox, den Proxy und das Vorhandensein von Whitelist-Geheimnissen (secret environment variables) des Daemon-Prozesses. Antwortet immer aus dem process.*-Zustand – der Daemon startet niemals ein ACP-Kind, um diese Route zu bedienen, und die Antwort ist identisch, egal ob ACP läuft oder im Leerlauf ist. Das Feld acpChannelLive ist nur informativ.
{
"v": 1,
"workspaceCwd": "/canonical/path",
"initialized": true,
"acpChannelLive": false,
"cells": [
{ "kind": "runtime", "name": "node", "status": "ok", "value": "22.4.0" },
{ "kind": "platform", "name": "darwin", "status": "ok", "value": "arm64" },
{
"kind": "sandbox",
"name": "SANDBOX",
"status": "disabled",
"present": false
},
{
"kind": "proxy",
"name": "HTTPS_PROXY",
"status": "ok",
"present": true,
"value": "proxy.internal:1080"
},
{
"kind": "proxy",
"name": "NO_PROXY",
"status": "disabled",
"present": false
},
{
"kind": "env_var",
"name": "OPENAI_API_KEY",
"status": "ok",
"present": true
},
{
"kind": "env_var",
"name": "ANTHROPIC_BASE_URL",
"status": "disabled",
"present": false
}
]
}Zellstruktur:
type DaemonEnvKind =
| 'runtime' // name: 'node' | 'bun' | 'unknown'; value: process.versions.node
| 'platform' // name: process.platform; value: process.arch
| 'sandbox' // name: 'SANDBOX' | 'SEATBELT_PROFILE'; value optional
| 'proxy' // name: HTTP_PROXY | HTTPS_PROXY | NO_PROXY | ALL_PROXY; value: redacted host
| 'env_var'; // presence-only; value field is ALWAYS omitted
interface DaemonEnvCell extends DaemonStatusCell {
kind: DaemonEnvKind;
name: string;
present?: boolean;
value?: string;
}Maskierungsrichtlinie. Zellen mit kind: 'env_var' enthalten niemals ein value-Feld; Clients sehen nur present: boolean. Zellen mit kind: 'proxy' leiten den rohen Umgebungsvariablen-Wert durch die Zugangsdaten-Maskierung (redactProxyCredentials) und dann durch URL-Parsing, sodass die Übertragung nur host:port enthält. NO_PROXY wird wortwörtlich durch die Maskierung gereicht, da es sich um eine Host-Liste und nicht um eine URL handelt. Die Whitelist der aufgeführten geheimen Umgebungsvariablen umfasst derzeit OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, DASHSCOPE_API_KEY, OPENROUTER_API_KEY und QWEN_SERVER_TOKEN. Andere Umgebungsvariablen werden nicht aufgeführt, sodass versehentlich gesetzte Geheimnisse unsichtbar bleiben.
GET /workspace/preflight
Meldet Daemon-Bereitschaftsprüfungen. Zellen auf Daemon-Ebene (node_version,
cli_entry, workspace_dir, ripgrep, git, npm) werden immer aus process.* und node:fs befüllt. Zellen auf ACP-Ebene (auth,
mcp_discovery, skills, providers, tool_registry, egress)
erfordern ein aktives ACP-Kind – wenn der Daemon im Leerlauf ist, geben sie status: 'not_started'-Platzhalter aus. Die Route startet ACP niemals ausschließlich zum Befüllen von Zellen; die entsprechenden Zellen fallen auf not_started zurück.
Leerlauf-Antwort (kein ACP-Kind):
{
"v": 1,
"workspaceCwd": "/canonical/path",
"initialized": true,
"acpChannelLive": false,
"cells": [
{
"kind": "node_version",
"status": "ok",
"locality": "daemon",
"detail": { "version": "22.4.0", "required": ">=22" }
},
{
"kind": "cli_entry",
"status": "ok",
"locality": "daemon",
"detail": { "path": "/usr/local/bin/qwen", "source": "process.argv[1]" }
},
{
"kind": "workspace_dir",
"status": "ok",
"locality": "daemon",
"detail": { "path": "/canonical/path" }
},
{ "kind": "ripgrep", "status": "ok", "locality": "daemon" },
{
"kind": "git",
"status": "ok",
"locality": "daemon",
"detail": { "version": "2.45.0" }
},
{
"kind": "npm",
"status": "ok",
"locality": "daemon",
"detail": { "version": "10.7.0" }
},
{
"kind": "auth",
"status": "not_started",
"locality": "acp",
"hint": "spawn a session to populate"
},
{
"kind": "mcp_discovery",
"status": "not_started",
"locality": "acp",
"hint": "spawn a session to populate"
},
{
"kind": "skills",
"status": "not_started",
"locality": "acp",
"hint": "spawn a session to populate"
},
{
"kind": "providers",
"status": "not_started",
"locality": "acp",
"hint": "spawn a session to populate"
},
{
"kind": "tool_registry",
"status": "not_started",
"locality": "acp",
"hint": "spawn a session to populate"
},
{
"kind": "egress",
"status": "not_started",
"locality": "acp",
"hint": "egress probing lands in PR 14 (#4175)"
}
]
}Zellstruktur:
type DaemonPreflightKind =
| 'node_version'
| 'cli_entry'
| 'workspace_dir'
| 'ripgrep'
| 'git'
| 'npm'
| 'auth'
| 'mcp_discovery'
| 'skills'
| 'providers'
| 'tool_registry'
| 'egress';
interface DaemonPreflightCell extends DaemonStatusCell {
kind: DaemonPreflightKind;
locality: 'daemon' | 'acp';
detail?: Record<string, unknown>;
}errorKind-Semantik:
missing_binary— Node-Version unterhalb des erforderlichen Minimums, fehlenderQWEN_CLI_ENTRY, ripgrep / git / npm nicht im PATH (bei optionalen Binärdateien eher Warnungen als Fehler).missing_file—boundWorkspaceexistiert nicht oder ist kein Verzeichnis; Skill-Parse-Fehler, der auf eine fehlende oder unlesbare Datei verweist.parse_error—SKILL.md-Parse-Fehler, fehlerhaftes Konfigurations-JSON.auth_env_error—validateAuthMethodhat eine Fehlermeldung ungleich null zurückgegeben oder eineModelConfigError-Unterklasse wurde von der Provider-Auflösung weitergereicht.init_timeout—withTimeout-Reject in der Bridge (ein tatsächlicher Timeout beim Warten auf einen ACP-Roundtrip). Wird über die typisierte KlasseBridgeTimeoutErrorerkannt. Hinweis: Eine vorübergehendemcp_discovery-warning-Zelle mitconnecting > 0trägt NICHT diesen Kind – das ist ein normaler Handshake-in-Progress-Zustand, der sich von einem echten Timeout unterscheidet.restore_timeout— ein Session-Load oder -Resume hat das dedizierte Restore-Budget überschritten. Die REST-Antwort ist504und retrybar; er unterscheidet sich von der Child-Initialisierung und den begrenzten Replay-Fenster-Limits.protocol_error— ACP-extMethodwurde abgelehnt, weil der Kanal mitten in der Anfrage geschlossen wurde oder weil die Tool-Registry unerwartet fehlte.blocked_egress— reserviert für PR 14 (#4175). PR 13 belässt dieegress-Zelle aufstatus: 'not_started'.
Wenn die Bridge beim Bearbeiten einer Preflight-Anfrage das ACP-Child nicht erreichen kann
(z. B. durch ein Schließen des Kanals mitten in der Anfrage), enthält das errors-Array
des Envelopes eine einzelne ServeStatusCell, die den Fehler beschreibt, und die Zellen
fallen auf not_started-ACP-Platzhalter zurück. Daemon-level Zellen werden weiterhin zurückgegeben.
Workspace-Dateirouten
Alle Dateipfade werden über den primären Workspace des Daemons aufgelöst. Antworten verwenden Workspace-relative Pfade und geben im normalen Erfolgsfall niemals absolute Dateisystempfade zurück. Erfolgreiche Datei-Antworten enthalten:
Cache-Control: no-store
X-Content-Type-Options: nosniffDateisystemfehler verwenden diese JSON-Struktur:
{
"errorKind": "hash_mismatch",
"error": "expected sha256:..., found sha256:...",
"hint": "re-read the file and retry with the latest hash",
"status": 409
}errorKind-Werte umfassen path_outside_workspace, symlink_escape,
path_not_found, binary_file, file_too_large, untrusted_workspace,
permission_denied, parse_error, hash_mismatch,
file_already_exists, text_not_found und ambiguous_text_match.
GET /file
Liest eine Textdatei. Query-Parameter: path (erforderlich), maxBytes, line, limit
und cursor. Der Daemon lehnt Binärdateien ab. Dateien über der 256 KiB
Full-Snapshot-Obergrenze erfordern mindestens ein explizites Fensterargument (line, limit oder
maxBytes); eine Anfrage ohne eines davon bleibt file_too_large. Solch ein
Fenster wird gestreamt, und sein zurückgegebener UTF-8-Inhalt bleibt auf 256 KiB begrenzt.
maxBytes gilt immer für die UTF-8-Antwort-Bytes nach der Dekodierung, einschließlich
wenn die Quelle eine andere unterstützte Kodierung innerhalb der Full-Snapshot-Obergrenze verwendet.
Line-Offsets werden durch Scannen vom Anfang der Datei aufgelöst, daher wird ein Fenster
auch mit file_too_large abgelehnt, wenn das Erreichen mehr als
8 MiB (MAX_TEXT_SCAN_BYTES) lesen würde. Verwende GET /file/bytes, um ein tieferes Offset
direkt zu erreichen. Großer Text in einer Kodierung, die die Route nicht dekodieren kann, gibt
binary_file zurück, nicht file_too_large – das Wiederholen mit einem kleineren Fenster kann
nicht helfen, und readBytes ist dasselbe Mittel, das bereits für Binärdateien gilt.
Für Dateien innerhalb der Full-Snapshot-Obergrenze enthält die Antwort hash, einen SHA-256-
Digest über die rohen On-Disk-Bytes der gesamten Datei, auch wenn line, limit
oder maxBytes nur einen Ausschnitt zurückgegeben haben. Große Teilfenster lassen hash weg, behalten das
vollständige sizeBytes, setzen truncated: true und geben
originalLineCount: null zurück, wenn der Stream vor EOF stoppt.
Paging mit cursor
Erfordert die workspace_file_read_cursor-Capability. Eine Antwort, die noch mehr hat,
gibt hasMore: true und, wenn ein Datei-Byte-Offset ableitbar ist, ein
nextCursor-Token zurück. Das Zurückgeben als cursor setzt in O(1) fort, während ein tiefer
line-Offset einen Scan ab Byte 0 kostet und jenseits von 8 MiB abgelehnt wird.
GET /file?path=big.log&limit=500 → { content, nextCursor, hasMore: true }
GET /file?path=big.log&limit=500&cursor=… → nächste Seitecursor und line schließen sich gegenseitig aus (parse_error) – beide benennen einen
Startpunkt. Ein fehlerhafter oder überlanger Cursor ist parse_error; ein Cursor,
dessen Datei ersetzt oder gekürzt wurde, ist hash_mismatch (409). Anhängen
invalidiert einen ausstehenden Cursor nicht, was der Anwendungsfall ist, für den das Feature
existiert.
content lässt das abschließende Newline seiner letzten Zeile weg, wie jeder andere Lesevorgang,
sodass ein Client, der Seiten zusammensetzt, sie mit \n verbindet. hasMore ist keine
Wiederholung von nextCursor: eine kleine Nicht-UTF-8-Datei, die mit einem limit gelesen wird, hat
mehr Inhalt, aber kein ableitbares Byte-Offset, daher meldet sie hasMore: true mit
nextCursor: null. Der Cursor ist auch null, wenn die Byte-Obergrenze die aktuelle Zeile
abschneidet, da das Fortsetzen ab diesem Offset eine Teilzeile zurückgeben würde. Für viele
kurze Zeilen senke limit, bis die Seite vor der Byte-Obergrenze endet und einen Cursor
zurückgibt. Für eine einzelne übergroße Zeile fordere die folgende Zeile explizit an
(z. B. line=2 beim Start bei Zeile 1), dann fahre mit Cursorn fort;
verwende GET /file/bytes, wenn die vollständige übergroße Zeile erforderlich ist.
{
"kind": "file",
"path": "src/index.ts",
"content": "export {};\n",
"encoding": "utf-8",
"bom": false,
"lineEnding": "lf",
"sizeBytes": 11,
"returnedBytes": 11,
"truncated": false,
"hash": "sha256:...",
"matchedIgnore": null,
"originalLineCount": null
}GET /file/bytes
Liest rohe Bytes aus einer Datei ohne Dekodierung. Query-Parameter: path (erforderlich),
offset (Standard 0) und maxBytes (Standard 65536, Maximum 262144). Diese
Route unterstützt begrenzte Fenster bei großen Binärdateien, ohne die gesamte Datei einzulesen. Die Antwort enthält hash nur, wenn das zurückgegebene Fenster die gesamte Datei abdeckt.
{
"kind": "file_bytes",
"path": "assets/logo.png",
"offset": 0,
"sizeBytes": 3912,
"returnedBytes": 3912,
"truncated": false,
"contentBase64": "...",
"hash": "sha256:..."
}GET /stat
Gibt Metadaten für einen Primary-Workspace-Pfad zurück. Query-Parameter path ist erforderlich.
{
"kind": "stat",
"path": "src/index.ts",
"type": "file",
"sizeBytes": 128,
"modifiedMs": 1700000000123
}type ist file, directory, symlink oder other. Die Route verwendet die Workspace-Dateigrenze und das oben beschriebene allgemeine Dateisystem-Fehler-Envelope. Die TypeScript-SDK-Methode ist fileStat().
GET /list
Listet ein Primary-Workspace-Verzeichnis auf. Query-Parameter path ist erforderlich; includeIgnored=1 (oder true) schließt Einträge ein, die von Ignore-Regeln erfasst werden. Die Antwort ist auf 2.000 Einträge begrenzt und setzt truncated: true, wenn mehr vorhanden sind.
{
"kind": "list",
"path": ".",
"entries": [{ "name": "src", "kind": "directory", "ignored": false }],
"truncated": false,
"matchedIgnore": null
}Der kind jedes Eintrags ist file, directory, symlink oder other. Die TypeScript-SDK-Methode ist dirList().
GET /glob
Findet Pfade innerhalb des Primary-Workspace. Query-Parameter pattern ist erforderlich. Optionales cwd grenzt die Suche ein, includeIgnored=1 (oder true) schließt ignorierte Pfade ein, und maxResults ist eine Ganzzahl von 1 bis 50.000 (Standard 5.000).
{
"kind": "glob",
"pattern": "**/*.ts",
"cwd": "",
"matches": ["src/index.ts"],
"count": 1,
"truncated": false,
"durationMs": 4
}Treffer sind Workspace-relativ. Ungültige Query-Werte geben 400 zurück; Workspace-Trust, Containment, fehlende Pfade und unerwartete Fehler verwenden das allgemeine Dateisystem-Fehler-Envelope. Die TypeScript-SDK-Methode ist glob().
POST /file/write
Erstellt oder ersetzt eine Textdatei. Dies ist eine strikte Mutations-Route: Eine Token-lose Trusted-Loopback-Primary-Anfrage wird autorisiert. Eine Token-lose Primary-Anfrage, die das Gate ohne Trusted-Loopback-Autorität erreicht, gibt 401 { "code": "token_required" } zurück. Fehler bei konfigurierten Token- und Local-Control-Credentials werden früher mit einfachem 401 Unauthorized abgelehnt; mit --require-auth weist die globale Bearer-Middleware nicht authentifizierte Anfragen ab, bevor die Route ausgeführt wird.
Body:
{
"path": "src/new.ts",
"content": "export const value = 1;\n",
"mode": "create"
}{
"path": "src/existing.ts",
"content": "export const value = 2;\n",
"mode": "replace",
"expectedHash": "sha256:..."
}mode muss create oder replace sein. create überschreibt niemals eine bestehende
Datei (409 file_already_exists). replace erfordert expectedHash; fehlende oder
fehlerhafte Hashes resultieren in 400 parse_error, und veraltete Hashes in
409 hash_mismatch. expectedHash ist sha256: gefolgt von 64 hexadezimalen Kleinbuchstaben,
berechnet über die rohen On-Disk-Bytes.
bom, encoding und lineEnding können angegeben werden. Beim Ersetzen wird standardmäßig das
Encoding-Profil der bestehenden Datei beibehalten; explizite Felder überschreiben dies.
Binäre Schreibvorgänge sind nicht im Funktionsumfang enthalten.
Der Daemon schreibt in eine zufällige Temp-Datei im Zielverzeichnis, führt wo unterstützt ein fsync durch,
prüft den aktuellen Hash unmittelbar vor rename() erneut und benennt die Datei dann an den Zielort um.
Dies verhindert die Beobachtung unvollständiger Dateien und serialisiert Daemon-initiierte Schreibvorgänge
auf dieselbe Datei, ist jedoch kein Cross-Process-Kernel-Compare-and-Swap: Ein externer Editor kann
immer noch in dem winzigen Zeitfenster zwischen der finalen Hash-Prüfung und dem Rename konkurrieren.
{
"kind": "file_write",
"path": "src/existing.ts",
"mode": "replace",
"created": false,
"sizeBytes": 24,
"hash": "sha256:...",
"encoding": "utf-8",
"bom": false,
"lineEnding": "lf",
"matchedIgnore": null
}POST /file/edit
Wendet genau eine exakte Textersetzung auf eine bestehende Textdatei an. Dies ist ebenfalls eine
strikte Mutations-Route und erfordert expectedHash.
{
"path": "src/config.ts",
"oldText": "timeout: 30000",
"newText": "timeout: 60000",
"expectedHash": "sha256:..."
}oldText darf nicht leer sein und muss genau einmal vorkommen. Keine Übereinstimmung gibt
422 text_not_found zurück; mehrere Übereinstimmungen geben 422 ambiguous_text_match zurück.
Die Route bewahrt Encoding, BOM und Zeilenenden und prüft expectedHash unmittelbar vor dem atomaren Rename.
Explizite Schreib-/Bearbeitungsvorgänge auf ignorierte Pfade sind erlaubt, da der authentifizierte
Aufrufer den Pfad explizit angegeben hat. Erfolgs-Antworten und Audit-Events enthalten
matchedIgnore: "file" | "directory" | null.
{
"kind": "file_edit",
"path": "src/config.ts",
"replacements": 1,
"sizeBytes": 128,
"hash": "sha256:...",
"encoding": "utf-8",
"bom": false,
"lineEnding": "lf",
"matchedIgnore": null
}GET /session/:id/context
{
"v": 1,
"sessionId": "<sid>",
"workspaceCwd": "/canonical/path",
"state": {
"models": {},
"modes": {},
"configOptions": []
}
}Für eine Top-Level-Session spiegelt state dieselben ACP-Model/Mode/Config-Option-Strukturen wider, die von POST /session, POST /session/:id/load und POST /session/:id/resume verwendet werden. Eine mit subagent. präfixierte virtuelle Session-ID wird gegen ihre Parent-Runtime aufgelöst und gibt ein leeres state-Objekt zurück.
GET /session/:id/supported-commands
{
"v": 1,
"sessionId": "<sid>",
"availableCommands": [
{
"name": "init",
"description": "Initialize the project",
"input": null,
"_meta": { "source": "builtin" }
}
],
"availableSkills": ["review"]
}availableCommands ist derselbe Befehls-Snapshot, der von der
available_commands_update-SSE-Benachrichtigung verwendet wird. availableSkills listet nur Skill-Namen auf; Clients dürfen über diese Route keine Skill-Bodies oder Pfade erwarten.
GET /session/:id/tasks
{
"v": 1,
"sessionId": "<sid>",
"now": 1700000000000,
"tasks": [
{
"kind": "agent",
"id": "agent-1",
"label": "reviewer: check failure",
"description": "check failure",
"status": "running",
"startTime": 1699999999000,
"runtimeMs": 1000,
"outputFile": "/tmp/agent-1.jsonl",
"isBackgrounded": true,
"subagentType": "reviewer"
},
{
"kind": "agent",
"id": "agent-2",
"label": "general-purpose: run the failing test",
"description": "run the failing test",
"status": "running",
"startTime": 1699999999500,
"runtimeMs": 500,
"outputFile": "/tmp/agent-2.jsonl",
"isBackgrounded": false,
"subagentType": "general-purpose",
"parentAgentId": "agent-1",
"parentName": "reviewer",
"depth": 1
}
]
}Diese Route ist ein schreibgeschütztes Out-of-Band-Snapshot. Sie ist absichtlich kein Prompt und kann abgefragt werden, während die Session streamt. Die Antwort enthält nur Whitelisted-Metadaten aus den Agent-, Shell- und Monitor-Task-Registries; Controller, Timer, Offsets, ausstehende Nachrichten und rohe Registry-Objekte werden niemals offengelegt.
Agent-Tasks, die von einem anderen Sub-Agenten erzeugt wurden (verschachtelte Sub-Agenten, begrenzt durch
maxSubagentDepth), enthalten drei optionale Lineage-Felder: parentAgentId (die
id des erzeugenden Agent-Tasks), parentName (der subagentType des erzeugenden Agents,
bei der Registrierung erfasst, damit er die Entfernung des Parents aus der Registry überlebt) und depth (0-basierte Starttiefe; 0 = erzeugt von der
Top-Level-Session). Agents, die von der Top-Level-Session gestartet werden, lassen
parentAgentId und parentName weg; Clients sollten alle drei Felder als optional behandeln und auf eine flache Liste zurückfallen, wenn sie fehlen.
GET /session/:id/lsp
{
"v": 1,
"sessionId": "<sid>",
"workspaceCwd": "/canonical/path",
"enabled": true,
"configuredServers": 1,
"readyServers": 1,
"failedServers": 0,
"inProgressServers": 0,
"notStartedServers": 0,
"servers": [
{
"name": "typescript",
"status": "READY",
"languages": ["typescript", "javascript"],
"transport": "stdio",
"command": "typescript-language-server"
}
]
}status ist einer der Werte NOT_STARTED, IN_PROGRESS, READY oder FAILED.
Das optionale error ist bei fehlgeschlagenen Servern vorhanden, wenn verfügbar. Deaktiviertes LSP
(einschließlich Bare-Mode) gibt HTTP 200 mit enabled: false, Null-Zählungen und
servers: [] zurück. Aktiviertes LSP ohne konfigurierte Server gibt enabled: true,
configuredServers: 0 und servers: [] zurück. Wenn die Initialisierung fehlschlägt, bevor der
Client existiert, kann die Antwort initializationError enthalten; wenn ein aktiver Client
kein Snapshot bereitstellen kann, enthält die Antwort statusUnavailable: true.
Diese Route legt nur stabile, clientseitige Felder offen. Sie lässt absichtlich Debug-Interna wie Prozess-IDs, Spawn-Args, Stderr-Tails, Root-URIs und Workspace-Ordnerpfade weg.
GET /session/:id/resources
{
"v": 1,
"sessionId": "<sid>",
"workspaceCwd": "/canonical/session/path",
"skills": {
"v": 1,
"workspaceCwd": "/canonical/session/path",
"initialized": true,
"skills": []
},
"mcp": {
"v": 1,
"workspaceCwd": "/canonical/session/path",
"initialized": true,
"discoveryState": "completed",
"servers": []
}
}Diese Live-Session-Owner-Route liest die Config der ausgewählten Session über ihre eigene ACP-Verbindung. Sie kombiniert nicht die prozessglobalen oder Workspace-Level-Statusrouten, initialisiert keine kalte Runtime, hängt keinen Client an und fällt nicht auf den Primary-Workspace zurück. Unbekannte und rein persistierte Sessions geben die bestehende session_not_found-Antwort zurück.
Die verschachtelten Objekte verwenden exakt die GET /workspace/skills- und GET /workspace/mcp-Status-Contracts. Ihre workspaceCwd-Felder identifizieren die Config, die den Snapshot erzeugt hat, und stimmen mit dem Top-Level-Wert überein. Bestehende Maskierungsregeln gelten weiterhin: MCP-Credentials und -Header, Umgebungswerte, Skill-Bodies und rohe Einstellungen erscheinen niemals in der Antwort. MCP-Authentifizierung, Pool, Workspace-Budget- und Workspace-Discovery-Error-Anreicherungen fehlen, weil ihr zugrunde liegender Zustand Workspace-eigen ist oder nur nach Servername statt nach Session keyiert wird. Status, Discovery und Accounting aus dem eigenen MCP-Manager der ausgewählten Session bleiben verfügbar.
Standalone-Session-Lifecycle (standalone_sessions_v1)
Wenn /capabilities.features standalone_sessions_v1 enthält, legt der Daemon eine prozessglobale Routenfamilie für Top-Level-Standalone-Sessions offen, die seiner dedizierten Conversations-Runtime gehören. Diese Routen akzeptieren niemals einen Workspace-Selektor und fallen niemals auf den primären Workspace zurück. Direkte Embeds, die den vollständigen Conversations-Ownership-, Runtime-, Directory-, Lifecycle- und Deletion-Journal-Dependency-Graph nicht konstruieren können, lassen sowohl das Feature als auch alle folgenden Routen weg.
| Route | Request | Erfolg |
|---|---|---|
POST /standalone/sessions | { "sessionId": "<UUID>", "modelServiceId"?: string, "approvalMode"?: ApprovalMode } | 200 mit der Standalone-Session, context: { "kind": "standalone" } und ihrem verwalteten projektlosen Ausgabeverzeichnis. Erstellung ist prompt-frei. |
GET /standalone/session-options | keine; jedes Query-Feld wird mit 400 abgelehnt | 200 mit { v, initialized, current?, approvalMode?, providers, errors? }; der interne Workspace-Pfad und der ACP-Channel-Zustand werden weggelassen |
GET /standalone/sessions | Query: cursor?, size? (1-100), archiveState? (active oder archived) | 200 { sessions, nextCursor?, liveMergeFailed?, truncated? } |
GET /standalone/sessions/:id | keine | 202 { sessionId, state: "creating" } während die lokale Erstellung läuft, andernfalls 200 mit der exakten Zusammenfassung. |
POST /standalone/sessions/:id/load | Nur bestehende Restore-Optionen: historyPageSize?, liveReplayMode?, hideInheritedHistory?, approvalMode?; Client-Identität bleibt in X-Qwen-Client-Id. | 200 wiederhergestellte Standalone-Session. |
POST /standalone/sessions/:id/resume | Dieselben Restore-Optionen wie load. | 200 wiederhergestellte Standalone-Session ohne Load-Historien-Replay. |
POST /standalone/sessions/:id/repair-directory | Leerer Body oder {} | 200 mit dem verifizierten oder neu erstellten verwalteten Verzeichnis. |
PATCH /standalone/sessions/:id/metadata | { "displayName": string } | 200 { sessionId, displayName } |
GET /standalone/sessions/:id/export | Query: format=html, format=md, format=json oder format=jsonl (Standard html). | Bestehender Export-Content-Type, Dateiname und Body. |
POST /standalone/sessions/archive | { "sessionIds": ["<UUID>", ...] } | 200 { archived, alreadyArchived, notFound, errors } |
POST /standalone/sessions/unarchive | { "sessionIds": ["<UUID>", ...] } | 200 { unarchived, alreadyActive, notFound, errors } |
POST /standalone/sessions/delete | { "sessionIds": ["<UUID>", ...] } | 200 { removed, notFound, errors, fileCleanupPending } |
Wenn POST /standalone/sessions modelServiceId enthält, beinhaltet die Response modelApplied: false bedeutet, dass der Modellwechsel zur Spawn-Zeit fehlgeschlagen ist (auch über das model_switch_failed-Session-Event sichtbar) und die Session auf dem Standardmodell des Agenten läuft – die Erstellung selbst gelingt trotzdem, sodass der Caller warnen, freigeben oder explizit erneut versuchen kann.
Bodies müssen JSON-Objekte ohne unbekannte Felder sein. IDs sind RFC-UUID-v1–v5-Werte; der Daemon kanonisiert sie auf Kleinbuchstaben. Batch-Anfragen enthalten 1–100 Strings und werden vor der Mutation validiert und dedupliziert. Ein Batch-Fehler wird als { sessionId, code, message } gemeldet und setzt erfolgreiche Operationen auf anderen IDs nicht zurück. fileCleanupPending bedeutet, dass die Transkript-Löschung committet wurde, aber journal-autorisierte Sidecar- oder Managed-Directory-Bereinigung durch Reconciliation wiederholt werden muss; die Session ist bereits logisch entfernt.
Nur explizite Standalone-Transkripte und die dokumentierte Top-Level-Legacy-Kompatibilitätsform sind sichtbar. Child-, Live-, Projekt-, Worktree-, mehrdeutige, unlesbare oder Deletion-journalisierte Records schlagen fail-closed fehl. Die Erstellung läuft weiter, wenn ihre HTTP-Antwort getrennt wird; eine committete Session wird nicht gelöscht, und der Response-Client wird abgekoppelt. Stelle durch exaktes GET gefolgt von Load/Resume wieder her, anstatt Create als Attach erneut zu versuchen.
Archive, Unarchive, Repair, Rename und Delete teilen sich dieselbe Pro-Session-Lifecycle-Admission wie Load/Resume und Prompts. Delete verwendet Transkript-Unlink als dauerhaften Commit-Punkt und ein privates Journal plus atomares Managed-Directory-Staging für die Crash-Wiederherstellung. Die Wiederherstellung stellt das Verzeichnis wieder her, wenn das Transkript intakt bleibt, und schließt die Bereinigung ab, wenn das Transkript verschwunden ist; jede nicht übereinstimmende Identität, jeder konfliktäre Pfad, jeder fremde Owner oder jedes mehrdeutige Transkript gibt einen strukturierten Fail-Closed-Fehler zurück.
POST /session
Erzeugt einen neuen Agent oder hängt sich an einen bestehenden an (unter sessionScope: 'single', dem Standard).
Request:
{
"cwd": "/absolute/path/to/workspace",
"modelServiceId": "qwen-prod",
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"sessionScope": "thread",
"worktree": { "slug": "feature-a" }
}| Field | Required | Notes |
|---|---|---|
cwd | nein | Absoluter Pfad, der einem registrierten Workspace entspricht. Wenn weggelassen, fällt die Route auf den primären Workspace zurück (über /capabilities.workspaceCwd auslesen). Wenn features multi_workspace_sessions enthält, können Clients jedes vertrauenswürdige workspaces[].cwd übergeben; andernfalls wird nur der primäre Workspace akzeptiert. Ein nicht übereinstimmendes, nicht leeres cwd gibt 400 workspace_mismatch zurück. Workspace-Pfade werden über realpathSync.native kanonisiert (mit einem Resolve-only-Fallback für nicht existierende Pfade), damit Case-insensitive Dateisysteme Sessions nicht aufgrund der Schreibweise ablehnen. |
modelServiceId | nein | Wählt aus, durch welchen konfigurierten Model Service der Agent routet (der Backend-Provider – Alibaba ModelStudio, OpenRouter, usw.). Wenn weggelassen, verwendet der Agent seinen Standard. Wenn der Workspace bereits eine Session hat, ruft dies setSessionModel auf der bestehenden Session auf und sendet model_switched. Unterscheidet sich von modelId bei POST /session/:id/model, welches das Modell innerhalb eines bereits gebundenen Service auswählt. Das modelServices-Array bei /capabilities ist für die Bekanntgabe konfigurierter Services reserviert; in Stage 1 ist es immer [] (der Standard-Service des Agents wird verwendet und nicht über HTTP aufgezählt). |
sessionId | nein | RFC-Variante UUID v1–v5, vom Aufrufer gewählt. Der Daemon normalisiert sie auf Kleinbuchstaben und erstellt immer eine frische Thread-Session; er behandelt dieses Feld niemals als idempotentes Attach. Bestätige, dass caps.features session_id_override enthält, bevor du es sendest, da ältere Daemons unbekannte Felder ignorieren können. null ist äquivalent zum Weglassen. |
sessionScope | nein | Pro-Request-Override für das Session-Sharing. 'single' (der Daemon-weite Standard) bewirkt, dass ein zweiter POST /session für denselben Workspace die bestehende Session wiederverwendet (attached: true); 'thread' erzwingt bei jedem Aufruf eine neue, eigenständige Session. Weglassen, um den Daemon-weiten Standard zu erben. Werte außerhalb der Enum geben 400 { code: 'invalid_session_scope' } zurück. Ältere Daemons (vor #4175 PR 5) ignorieren das Feld stillschweigend – vor dem Senden caps.features.session_scope_override im Pre-flight prüfen. Der Daemon-weite Standard ist in der Produktion derzeit hart auf 'single' codiert; #4175 könnte in einem Follow-up ein --sessionScope-CLI-Flag hinzufügen. |
worktree | nein | Erstelle eine frische Thread-Session in einem vom Benutzer benannten Git-Worktree. Der optionale slug verwendet die Worktree-Namensvalidierung des Daemons. Pre-flight session_worktree_persistence_v1; Clients dürfen keine dauerhafte Isolation von älteren Worktree-förmigen Antworten ableiten. Die Worktree-Erstellung ist keine Attach-Operation und kann nicht mit der Branch-Erstellung kombiniert werden. Die Route gibt nur nach Relocation, Erstellung eines exklusiven Ownership-Markers, Sidecar-Persistenz und einer abschließenden Runtime-Generation-Prüfung Erfolg zurück. |
Antwort:
{
"sessionId": "<uuid>",
"workspaceCwd": "/canonical/path",
"attached": false,
"worktree": {
"slug": "feature-a",
"path": "/canonical/path/.qwen/worktrees/feature-a",
"branch": "worktree-feature-a"
},
"worktreeState": "persisted-v1"
}attached: true bedeutet, dass für diesen Workspace bereits eine Session existierte und du sie nun teilst.
worktreeState ist nur für eine attestierte Worktree-Session vorhanden. Legacy-Best-Effort-Restore kann worktree ohne worktreeState zurückgeben. Ein Cold-Part-4A-Restore, dessen Restore-Prompt gefeuert statt geparkt wurde (suppressWorktreeContextRestore aus, sodass die Route bei der Bridge keine Verzögerung angefragt hat), gibt ebenfalls unverifizierte worktree-Metadaten zurück, bis der Prompt sich beruhigt: das Child liest sich als aktiv ohne gemeldetes CWD, Relocation ist unter einem laufenden Prompt unmöglich, und die Route behält die Session, anstatt die gerade vom Caller wiederhergestellte zu töten. Keine der beiden Antworten ist ein dauerhafter Execution-Root-Beweis. Ein Client, der Worktree-Isolation angefordert hat, muss worktreeState: "persisted-v1" und den erwarteten kanonischen Pfad verlangen, bevor er einen Prompt routet; ein initiales Load kann nach dem Beruhigen des aktiven Prompts wiederholt werden. Beim SDK-Reattach teilt sich das Ergebnis danach, ob die Response noch worktree-Metadaten trägt: eine Response, die sie mit fehlender Attestierung oder geändertem Pfad trägt, ist terminal für diesen Client – das neue Attach wird abgekoppelt und der Prompt nicht wiederholt – während eine Response ohne worktree-Objekt heilt: der Client verwirft seinen gecachten Worktree-Claim, behält das neue Attach und wiederholt den Prompt im selben Aufruf. Die Heilung blockiert diesen Retry nicht, sodass ein Turn noch in einem Verzeichnis ausgeführt werden kann, das der Client nicht mehr attestieren kann (ein exit_worktree, das der Client nie gesehen hat, ist die gewöhnliche Ursache, und der Client kann es nicht von einer gelöschten In-Memory-Assoziation unterscheiden). Ein Isolation-bewusster Caller muss daher die Worktree-Attestierung selbst vor dem Dispatchen eines Prompts erneut verifizieren; der verworfene Claim lässt diesen Caller nur an seinem eigenen Identity-Gate beim nächsten Load oder der nächsten Auswahl fail-closed scheitern.
Vom Aufrufer bereitgestellte IDs sind über alle derzeit registrierten Workspace-Runtimes und jede noch lebende Bridge-Generation eindeutig, einschließlich drainender Ersetzungen. Ein lebendes, ausstehendes, aktives, archiviertes oder Worktree-gestütztes Duplikat gibt 409 session_id_conflict zurück. Ungültige Werte geben 400 invalid_session_id zurück; eine nicht verfügbare Live-Owner- oder Persisted-State-Prüfung gibt das retrybare 503 session_id_admission_unavailable zurück. Retry mit begrenztem Backoff nach Bridge- oder Storage-Gesundheitsänderungen; retryable bedeutet, dass ein weiterer Versuch sicher ist, nicht dass ein sofortiger Retry erfolgreich sein wird. Wenn der nachgelagerte Agent eine andere ID zurückgibt, entfernt der Daemon diesen Waisen und gibt 500 session_id_not_honored zurück. Nach einer mehrdeutigen Antwort lade oder setze die bekannte ID fort, anstatt Create als Attach erneut zu versuchen.
Multi-Client-Integrationen, die unabhängige Konversationen wünschen, sollten bei jedem POST /session sessionScope: "thread" senden. Verwende den Standard-Scope single nur, wenn Clients absichtlich eine gemeinsame kollaborative Session teilen; gemeinsame Sessions serialisieren Prompts über eine FIFO, sichtbar über /daemon/status als runtime.activity.pendingPrompts und runtime.activity.queuedPrompts.
Gleichzeitige POST /session-Aufrufe für denselben Workspace werden zu einem einzigen Spawn zusammengeführt — beide Aufrufer erhalten dieselbe sessionId, genau einer meldet attached: false. Wenn der zugrunde liegende Spawn fehlschlägt (Init-Timeout, fehlerhafte Agent-Ausgabe, OOM), erhalten alle zusammengeführten Aufrufer denselben Fehler — der In-Flight-Slot wird freigegeben, sodass ein Folgeaufruf den Vorgang von Grund auf neu starten kann.
⚠️ Die Ablehnung einer
modelServiceIdbei einer neuen Session bleibt in der HTTP-Antwort stumm. Eine fehlerhaftemodelServiceId(Tippfehler, nicht konfigurierter Service) löst beim Erstellen KEINEN 500-Fehler aus — die Session bleibt auf dem Standardmodell des Agenten betriebsbereit, sodass der Aufrufer dennoch einesessionIderhält, mit der er den Modellwechsel erneut versuchen kann (viaPOST /session/:id/model). Das sichtbare Fehlersignal ist einmodel_switch_failed-Event auf dem SSE-Stream der Session, das zwischen dem Spawn-Handshake und deinem ersten Subscribe ausgelöst wird. Subscriber, die dieses Event beobachten müssen, sollten bei ihrem erstenGET /session/:id/eventsLast-Event-ID: 0übergeben, um vom ältesten verfügbaren Event des Rings zu replayen (deckt dasmodel_switch_failedzur Spawn-Zeit ab, selbst wenn das Subscribe erst ein paar ms nach der Create-Antwort eintrifft).
GET /session/:id/status
Gibt die Live-Zusammenfassung von der Runtime zurück, die die Session besitzt. Diese Route lädt
keine rein persistierte Session und fällt niemals auf die primäre Runtime zurück.
Pre-flight caps.features.session_status.
{
"sessionId": "<sid>",
"workspaceCwd": "/canonical/path",
"createdAt": "2026-09-10T08:00:00.000Z",
"clientCount": 1,
"hasActivePrompt": true,
"isWaitingForPermission": false,
"isWaitingForUserQuestion": false,
"pendingInteractionCount": 0,
"pendingInteractions": []
}Die Response ist die DaemonSessionSummary-Wire-Form. Optionale Felder umfassen
Display- und Quellmetadaten, activeWorkState, updatedAt, turnError,
Worktree- oder Branch-Metadaten und PR-Bindungen. 404 bedeutet,
dass kein Live-Owner existiert; ein bootstrapping, drainender oder nicht verfügbarer Owner gibt
stattdessen 503 zurück, anstatt zurückzufallen. Ein nicht vertrauenswürdiger nicht primärer Owner gibt
403 untrusted_workspace zurück, und eine in mehr als einem Workspace lebende ID gibt
500 ambiguous_session_owner zurück. Die TypeScript-SDK-Methode ist sessionStatus().
ACP session/new vom Aufrufer bereitgestellte ID
ACP-Clients fordern dasselbe Verhalten über das Extension-Metadatenfeld an:
{
"jsonrpc": "2.0",
"id": 1,
"method": "session/new",
"params": {
"cwd": "/absolute/path/to/workspace",
"_meta": {
"qwen-code/sessionId": "550E8400-E29B-41D4-A716-446655440000"
}
}
}Die Antwort enthält die normalisierte Kleinbuchstaben-ID. Primary- und Workspace-qualifizierte ACP-Mounts teilen sich die Admission mit REST, einschließlich session/load und session/resume. Ungültige IDs verwenden ACP INVALID_PARAMS mit data.httpStatus=400 und data.errorKind="invalid_session_id"; Konflikte verwenden data.httpStatus=409; nicht verfügbare Live-Owner- oder Persisted-State-Prüfungen verwenden data.httpStatus=503 und data.retryable=true.
Eine über ACP erstellte Session, die niemals einen Prompt erhält, hinterlässt keine persistierte Spur, und der Daemon entfernt sie (reap), wenn ihre besitzende Connection mit null angehängten Sessions geschlossen wird. Nach diesem Reap kann dieselbe ID erneut erstellt werden – das ist Connection-Lifecycle, nicht ID-Reuse: Während die Connection (oder irgendein Attach) lebt, lehnt die Admission das Duplikat ab.
POST /session/:id/load
Stellt eine persistierte ACP-Session anhand der ID wieder her und spielt deren Historie über SSE ab. Die Pfad-ID ist maßgeblich; jedes sessionId-Feld im Body wird ignoriert. Pre-flight caps.features.session_load — ältere Daemons geben für diese Route 404 zurück.
Request:
{
"cwd": "/absolute/path/to/workspace"
}| Feld | Erforderlich | Hinweise |
|---|---|---|
cwd | nein | Dieselben Kanonisierungs- und workspace_mismatch-Regeln wie bei POST /session. Weglassen, um /capabilities.workspaceCwd zu erben. Wenn features multi_workspace_sessions enthält, können Aufrufer jedes vertrauenswürdige registrierte workspaces[].cwd übergeben; nicht vertrauenswürdige nicht primäre Workspaces geben 403 untrusted_workspace zurück. mcpServers wird hier absichtlich NICHT akzeptiert — daemon-weites MCP wird über Einstellungen gesteuert (entspricht POST /session). |
Antwort:
{
"sessionId": "persisted-1",
"workspaceCwd": "/canonical/path",
"attached": false,
"state": {
"models": { ... },
"modes": { ... },
"configOptions": [ ... ]
}
}state spiegelt ACPs LoadSessionResponse wider — models ist ein SessionModelState, modes ein SessionModeState, configOptions ein Array von SessionConfigOption. Fehlende Felder werden vom Agenten bestimmt. Nachträgliche Attacher (die attached: true-Pfade unten) erhalten denselben state-Snapshot, den der ursprüngliche Load-Aufrufer gesehen hat — der Daemon cacht ihn beim Entry; Runtime-Mutationen (z. B. model_switched) werden über den SSE-Stream ausgeliefert, nicht über nachfolgende Attach-Antworten.
attached: true bedeutet, dass die Session bereits live war (entweder durch ein vorheriges session/load/session/resume oder weil ein zusammengeführter, gleichzeitiger Aufrufer knapp voraus war).
Historien-Replay über SSE. Während loadSession auf der Agentenseite in-flight ist, kann der Agent session_update-Benachrichtigungen für persistierte Turns emittieren oder Bulk-Replay-Updates in den Response-Metadaten zurückgeben. Der Daemon seedet diese Events in das begrenzte Replay-Snapshot-Fenster der Session, bevor die Route-Antwort zurückkehrt. Für Live-Sessions verspricht POST /session/:id/load nur dieses begrenzte Fenster (compactedReplay, liveJournal, lastEventId), nicht das vollständige Transkript. Das Fenster ist byte-begrenzt durch --compacted-replay-max-bytes (Standard 4 MiB, Maximum 256 MiB); wenn ältere Replay-Einträge verworfen wurden, ist compactedReplay[0] ein ID-loser history_truncated-Marker. Das in-flight liveJournal ist separat durch --max-journal-events (Standard 10 000 Replay-Einträge) und --max-journal-bytes (Standard 8 MiB serialisierter Source-Events) begrenzt. Dies sind pro-Session-Baseline-Caps. Wenn ein In-Flight-Turn sie überschreitet, versucht der Daemon zuerst adaptives Wachstum: Er erhöht die Caps dieser Session auf das Doppelte (bis zu einem harten pro-Session-Limit von 256 MiB, Einträge proportional skaliert, begrenzt durch den verbleibenden Pool-Spielraum), solange das über alle Live-Sessions gewährte Wachstum in einen daemon-weiten Wachstumspool passt, der 5 % des effektiven Speicherbudgets des Daemons entspricht – der --memory-budget-mb-Wert, wenn angegeben, begrenzt auf den aufgelösten verfügbaren Speicher, andernfalls 50 % des automatisch erkannten Speichers – begrenzt auf 1024 MB. Die Abrechnung erfolgt daemon-weit – ein Multi-Workspace-Daemon betreibt eine Bridge pro Workspace und alle teilen sich den einzigen Pool. Wachstum erfolgt on-demand und nur soweit der Pool es erlaubt; ein operator-seitig festgelegtes --max-journal-events oder --max-journal-bytes deaktiviert es, ebenso wie ein Host, dessen effektives Budget unter das Minimum von 1024 MB fällt (insufficientMemory): Der Pool ist 0 und adaptives Wachstum ist vollständig deaktiviert. Aufeinanderfolgende kompatible agent_message_chunk- oder agent_thought_chunk-Source-Events teilen sich einen Replay-Eintrag, bis zu 256 Source-Events pro Eintrag, während Tool-, Attributions-, Provenance- und diskrete Nachrichtengrenzen intakt bleiben. Wenn ein In-Flight-Turn seine (möglicherweise gewachsenen) Caps überschreitet, nachdem der Pool das Wachstum erlaubt hat – einschließlich wenn kein Spielraum gewährt wird oder eine Gewährung nur einen Teil des Überschusses abdeckt – werden die ältesten Einträge komplett verworfen (sodass der behaltene Tail viel kleiner als das Byte-Limit sein kann) und ein history_truncated-Marker mit scope: 'live_journal' vorangestellt; seine truncatedEvents- und retainedEvents-Felder zählen Source-Events, nicht Replay-Einträge. Clients sollten diesen Marker als Status rendern und angewandte beibehaltene Events weiter verarbeiten. Der vollständige persistierte Transkript-Zugriff wird separat über GET /session/:id/transcript bereitgestellt.
Die Replay-Fenster-Byte-Caps gelten, nachdem das Child das persistierte Transkript rekonstruiert hat; sie begrenzen nicht das On-Disk-JSONL-Lesen. Ein Restore, der das Daemon-Budget überschreitet, gibt 504 zurück mit einem Retry-After, der vom Restore-Budget abgeleitet ist (begrenzt auf 5–120 s), und {code: "session_restore_timeout", errorKind: "restore_timeout", retryable: true, sessionId, action, timeoutMs}. Der Daemon zäunt die noch laufende ACP-Anfrage ein und räumt jede späte Session auf, anstatt sie zu registrieren. Ein Retry für dieselbe ID gibt 409 restore_in_progress mit reason: "awaiting_abandoned_cleanup" und einem Retry-After des Restore-Budgets (begrenzt auf 5–120 s) zurück, bis das Cleanup abgeschlossen ist. Wenn das späte Cleanup ungewiss ist oder der aufgegebene Restore ein volles Restore-Budget nach seiner Frist noch nicht abgeschlossen hat, geben neue Sessions auf diesem Workspace 503 acp_channel_unavailable zurück mit reason: "restore_cleanup_failed" oder "restore_settlement_overdue"; bereits lebende Sessions bleiben verwendbar, während der Channel drainend ist.
Fehler:
404— Persistierte Session-ID existiert nicht (SessionNotFoundError).400—workspace_mismatch(gleiche Form wie beiPOST /session).403—untrusted_workspace, wenncwdauf einen nicht vertrauenswürdigen nicht primären Workspace zielt.503—session_limit_exceeded(zählt auf--max-sessionsan; In-Flight-Restores werden ebenfalls berücksichtigt).504—session_restore_timeout; retrybar, mit einemRetry-After, der vom Restore-Budget abgeleitet ist (begrenzt auf 5–120 s), da dieselbe Session-ID eingezäunt bleibt, bis das späte Cleanup abgeschlossen ist.503—acp_channel_unavailable, wenn der Workspace-Channel für neue Session-Arbeit geschlossen ist.reasongibt an, warum:restore_cleanup_failed, wenn ein aufgegebener Restore nicht endgültig aufgeräumt werden konnte, oderrestore_settlement_overdue, wenn ein aufgegebener Restore ein volles Restore-Budget nach seiner Frist noch nicht abgeschlossen hat. In beiden Fällen bleiben vorhandene Sessions verfügbar, und neue Session-Arbeit kann retryt werden, nachdem der Workspace-Channel gedrainet ist – der Body trägtretryAfterSecondsund der Header ein passendes Budget-abgeleitetesRetry-After, weil die Quarantäne den Zaun überdauert und eine frische ID niemals den 409 sieht, der den Hinweis tragen würde.409—restore_in_progress(einsession/resumefür dieselbe ID ist bereits in-flight, oder ein frischer Spawn hat eine ID bereitgestellt, die ein Restore besitzt).Retry-After: 5, solange der Restore aktiv ist; ein Budget-abgeleiteter Hinweis, sobald er alsawaiting_abandoned_cleanupeingezäunt ist. Gleichartige Race-Conditions (zwei gleichzeitigesession/loadfür dieselbe ID) werden zusammengeführt — genau eines gibtattached: falsezurück, die anderen gebenattached: truemit demselbenstatezurück.409—session_workspace_conflict, wenn dieselbe Session-ID bereits in einer anderen Workspace-Runtime live ist oder wiederhergestellt wird.409—session_archived, wenn die ID nur unterchats/archive/existiert; rufePOST /sessions/unarchivevorloadoderresumeauf.409—session_archiving, wenn Archive oder Unarchive für dieselbe ID in-flight ist.Retry-After: 5.409—session_conflict, wenn die ID sowohl inchats/als auch inchats/archive/existiert; lösche die Session mitPOST /sessions/deletevor dem Laden.
GET /session/:id/transcript
Gibt eine Seite ID-loser session_update-Replay-Frames zurück, die aus dem aktiven persistierten JSONL-Transkript rekonstruiert wurden. Pre-flight caps.features.session_transcript — ältere Daemons geben für diese Route 404 zurück.
Query-Parameter:
| Feld | Erforderlich | Hinweise |
|---|---|---|
cursor | nein | Opaker Base64url-Cursor, der von der vorherigen Seite zurückgegeben wurde. Für die erste Seite weglassen. Der Cursor wird vom Daemon ausgestellt und tamper-geprüft; das Modifizieren gibt 400 invalid_transcript_cursor zurück. Er bindet an die Transkript-Datei-Identität und die eingefrorene First-Page-Byte-Größe; das Löschen, Kürzen, Ersetzen oder Archivieren der Datei invalidiert ihn und gibt 409 zurück. |
limit | nein | Anzahl der aktiven ChatRecords, die in der Seite enthalten sind. Standardmäßig 100, Maximum 500. Ein Record kann mehrere Replay-Frames erzeugen, daher kann events.length größer als limit sein. Ungültige Werte geben 400 invalid_transcript_limit zurück. |
Antwort:
{
"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 sind nur Replay-Frames: { v: 1, type: "session_update", data: SessionUpdate }. Sie tragen keine EventBus-IDs, und die Antwort enthält niemals lastEventId. Das Aufrufen dieser Route ruft nicht /load auf, hängt keinen Client an, seedet nicht den live EventBus, erstellt keine Live-Session oder ändert das aktuelle Live-Replay-Fenster. Live und inaktive aktive Sessions werden beide von der Child-seitigen schreibgeschützten Statusmethode rekonstruiert, sodass das Replay dieselben Workspace-Einstellungen, das Runtime-Ausgabeverzeichnis, die Emitter und die /load-Historiensemantik verwendet, ohne den Daemon-Session-Zustand zu mutieren.
Die erste Seite friert die aktuelle JSONL-Snapshot-Größe ein. Spätere Seiten lesen nur dieses Byte-Präfix, sodass Anhänge nach Seite 1 die Ergebnismenge nicht ändern. Wenn die Datei verschwindet, unter die eingefrorene Größe gekürzt wird, durch eine andere Inode ersetzt oder ins Archiv verschoben wird, gibt die nächste Seite 409 zurück, und der Client sollte ab Seite 1 neu starten oder den Benutzer bitten, das Transkript erneut zu öffnen.
Zum Schutz von Daemon-Speicher und Latenz schlagen Snapshots über dem Transkript-Indexierungs-Limit fehl, bevor der Daemon das JSONL scannt. Clients erhalten 413 transcript_too_large und sollten auf Export/Offline-Verarbeitung zurückfallen oder den Benutzer bitten, ältere Historie zu kürzen/archivieren.
partial: true und replayError können erscheinen, wenn die Replay-Konvertierung nach dem Erzeugen einiger Frames fehlschlägt. Teilantworten enthalten niemals nextCursor, sodass Clients nicht stillschweigend über nicht konvertierte Records hinweg paginieren können.
Fehler:
400— ungültigelimit-,cursor- oder Session-ID-Form.404— Aktive persistierte Session-ID existiert bei der First-Page-Anfrage nicht.409—session_archived,session_archivingodersession_conflictaus denselben Loadability-Prüfungen wie/load.409— Transkript-Snapshot ist nicht verfügbar, da die Datei nach der Cursor-Ausstellung gelöscht, gekürzt, ersetzt oder archiviert wurde; dies gilt auch, wenn der Preflight die aktive Datei für eine Cursor-Anfrage nicht mehr finden kann.413—transcript_too_large, wenn der eingefrorene Transkript-Snapshot das Daemon-Indexierungslimit überschreitet.413—transcript_page_too_large, wenn ein aggregierter Record das Workspace-qualifizierte Seitenbudget überschreitet oder die serialisierte Seite ihr Antwortbudget überschreitet.
GET /workspaces/:workspace/session/:id/transcript
Gibt dieselbe DaemonSessionTranscriptPage-Projektion wie die Singular-Route aus dem aktiven persistierten JSONL des ausgewählten registrierten Workspaces zurück. Pre-flight workspace_persisted_transcript; diese Capability ist unabhängig von multi_workspace_sessions und funktioniert für einen vertrauenswürdigen Single-Workspace-Primary, der nach ID oder CWD ausgewählt wird.
Der Selektor und die Query-Parameter folgen den bestehenden Plural-Workspace- und Transkript-Regeln. Vertrauenswürdige primäre und sekundäre Runtimes und nicht vertrauenswürdige sekundäre Runtimes dürfen lesen. Ein nicht vertrauenswürdiger Primary gibt 403 untrusted_workspace zurück. Archivierte Inhalte werden nicht zurückgegeben.
Für diese Workspace-qualifizierte Route ist limit die maximale Record-Anzahl. Eine Seite kann früher beim 4-MiB-persistierten-Quellen-Budget stoppen und einen Fortsetzungs-Cursor zurückgeben. Serialisierte Antworten sind auf 32 MiB begrenzt und Cursor auf 64 KiB. Wenn der Replay-Zustand die Cursor-Obergrenze überschreiten würde, gibt die Seite ihre erfolgreich konvertierten Events mit partial: true, hasMore: false und keinem nextCursor zurück.
Im Gegensatz zur Legacy-Singular-Route wird dieser Pfad vollständig innerhalb des Daemon-Prozesses implementiert. Er ruft nicht die Workspace-Bridge auf, startet keinen ACP, lädt keine Einstellungen, parst keine Projekt-definierten Agenten oder Skills oder erstellt/repariert session-transcript-cursor-key. Tool-Frames verwenden persistierte Tool-Namen und -Beschreibungen, ohne die Runtime-Tool-Registry zu konsultieren. Sein HMAC-Cursor-Key existiert nur im Daemon-Speicher, ist pro Workspace isoliert und rotiert beim Neustart; ein Cursor aus einem vorherigen Daemon-Prozess gibt 400 invalid_transcript_cursor zurück.
GET /workspaces/:workspace/session/:id/export
Exportiere die aktive persistierte Session des ausgewählten registrierten Workspaces als Anhang. Pre-flight workspace_session_export; leite Unterstützung nicht von session_export oder workspace_qualified_rest_core ab. Der Selektor wird zuerst als exakte Workspace-ID aufgelöst und andernfalls als URL-kodiertes absolutes CWD nach Kanonisierung. Sowohl primäre als auch sekundäre Runtimes müssen vertrauenswürdig sein. Eine nicht vertrauenswürdige Runtime gibt 403 untrusted_workspace vor der Session- oder Formatvalidierung zurück.
Der optionale format-Query ist html (Standard), md, json oder jsonl. Body, MIME-Typ, Dateinamenbereinigung, Cache-Control: no-store, X-Content-Type-Options: nosniff und Attachment-Disposition entsprechen GET /session/:id/export. Die Legacy-Route bleibt an den primären Speicher gebunden.
Die Plural-Route liest nur das aktive persistierte JSONL des ausgewählten Workspaces unter dem bestehenden gemeinsamen Archive-Koordinator. Sie scannt keine anderen Workspace-Speicher, fällt nicht auf Primary zurück, löst keinen Live-Owner auf, ruft nicht die Workspace-Bridge auf, startet keinen ACP, hängt keinen Client an oder lädt keine Einstellungen. Eine Session-ID, die nur in einem anderen Workspace existiert, gibt 404 { code: "session_not_found" } zurück; archivierte Sessions geben 409 session_archived zurück. Ungültige Formate geben 400 invalid_export_format zurück, und Storage-Races behalten die bestehenden session_archiving- und session_conflict-Fehler.
GET /workspaces/:workspace/session/:id/archive/export
Exportiere die archivierte persistierte Session des ausgewählten registrierten Workspaces als Anhang. Pre-flight workspace_archived_session_export; Unterstützung kann nicht aus dem aktiven Export oder den Plural-Core-Capabilities abgeleitet werden. Workspace-Selektor-Auflösung und Trust-Prüfungen laufen vor der Session-ID- und Formatvalidierung.
TypeScript-SDK-Aufrufer verwenden WorkspaceDaemonClient.exportArchivedSession(sessionId, options). Die Methode verwendet immer natives REST und gibt die bestehende DaemonSessionExportResult-Anhang-Projektion zurück.
Der optionale format-Query, Antwort-Body, MIME-Typ, bereinigter Dateiname, Cache-Policy, Sicherheits-Header und Attachment-Disposition sind identisch mit dem aktiven Workspace-Export. Das archivierte Quell-JSONL ist vor der Rekonstruktion auf 256 MiB begrenzt; eine größere Datei gibt 413 transcript_too_large mit sessionId, snapshotSize und maxBytes zurück. Der aktive Export behält sein bestehendes Größenverhalten.
Die Route liest nur chats/archive/<id>.jsonl im ausgewählten vertrauenswürdigen Workspace unter einer gemeinsamen Archive-Koordinator-Lease. Sie inspiziert keinen aktiven Inhalt für Fallback, scannt keinen anderen Workspace, löst keinen Live-Owner auf, ruft keine Bridge auf, startet keinen ACP, hängt keinen Client an oder lädt keine Einstellungen. Eine nur-aktive ID gibt 409 { code: "session_not_archived" } zurück; eine fehlende ID gibt 404 { code: "session_not_found" } zurück; gleichzeitige aktive und archivierte Dateien geben 409 session_conflict zurück; und ein Archiv-Übergang gibt 409 session_archiving mit Retry-After: 5 zurück.
GET /workspaces/:workspace/sessions/live-state
Gibt den speicherbasierten Live-Session-Snapshot der ausgewählten Workspace-Runtime plus eine speicherinterne Katalogversion zurück, sodass Clients das persistierte Katalog-Polling unter GET /workspaces/:workspace/sessions für flüchtigen State wie hasActivePrompt, Waiting-Flags und clientCount einstellen können. Pre-flight workspace_session_live_state; das Tag ist unabhängig von workspace_qualified_rest_core, daher implementieren ältere Daemons, die die breitere Workspace-REST-Capability bewerben, diese Route nicht. Der Selektor löst sich zuerst als exakte Workspace-ID auf, dann als URL-kodiertes absolutes CWD nach Kanonisierung, passend zu den anderen pluralen Session-Routen. Die Route ist nur für vertrauenswürdige Primary- und Secondary-Runtimes: Sie fällt niemals auf die Primary-Runtime zurück und verwendet nicht die permissive persistierte-Katalog-Policy, die einem nicht vertrauenswürdigen Secondary begrenzte Katalog-Lesevorgänge gewährt. Der Endpunkt hat keine Query-Parameter und führt keine Session-Speicher-, Einstellungs-, externen Befehls- oder ACP-Roundtrips durch, sodass seine Kosten unabhängig von der Anzahl persistierter Sessions und der JSONL-Größe sind; das Standard-Live-Session-Limit hält die Antwort begrenzt, und bei deaktiviertem Limit bleiben die Kosten proportional nur zur Anzahl der Live-Sessions.
Response:
{
"v": 1,
"catalogVersion": {
"generation": "7eca3164-bce1-4f50-94d8-c842c480f213",
"revision": 17
},
"sessions": [
{
"sessionId": "session-123",
"clientCount": 1,
"hasActivePrompt": true,
"isWaitingForPermission": false,
"isWaitingForUserQuestion": false,
"updatedAt": "2026-08-18T08:12:30.123Z"
}
]
}v ist die Response-Schema-Version. Jede erfolgreiche Response enthält Cache-Control: no-store. sessions ist die vollständige, nicht-paginierte, unsortierte Menge der derzeit in der ausgewählten Runtime live vorhandenen Sessions; eine leere Live-Runtime gibt 200 mit sessions: [] zurück. clientCount, hasActivePrompt, isWaitingForPermission und isWaitingForUserQuestion sind erforderliche Wire-Felder, und fehlende optionale Bridge-Werte projizieren auf 0 oder false. Statische Katalogfelder wie Anzeigename, Zeitstempel, Organisation und Quellmetadaten sind absichtlich ausgeschlossen und bleiben im Besitz des vollständigen Katalogs. Eine fehlende Live-State-Zeile löscht nur die flüchtigen Felder einer bekannten Katalogzeile; sie löscht niemals eine persistierte Katalogzeile.
updatedAt ist ein optionaler, vom Daemon beobachteter Aktivitäts-Watermark, vorhanden, wenn ein Prompt, der den Running-Zustand erreicht hat, ein formales Terminal in der aktuellen Bridge veröffentlicht hat. Er rückt genau einmal pro solchem Terminal vor – Erfolg, Fehler, Abbruch und Deadline gleichermaßen – wird vor der Veröffentlichung des Terminal-Events geschrieben und ist streng zunehmend pro Live-Session, selbst wenn zwei Terminals in einer Wall-Clock-Millisekunde landen oder die Wall-Clock rückwärts läuft; ein Vorwärtssprung der Uhr bleibt daher bestehen, bis die Wall-Time aufholt. Er ist niemals früher als das createdAt der Session: Der erste Vorschub ist auf die Erstellungszeit geerdet, sodass ein Wall-Clock-Rollback zwischen Erstellung und erstem Terminal eine Zeile nicht hinter dem createdAt zurücklassen kann, bei dem sie bereits aufgelistet war. Prompt-Aufnahme, Queue-Wartezeiten, gestreamte Updates, Queue-only-Abbruch und Interaktionswarten rücken ihn niemals vor. Clients verwenden ihn, um die Aktualität einer Katalogzeile, die sie bereits halten, aufzufrischen, anstatt den vollständigen Katalog nach einem abgeschlossenen Turn neu zu laden. Er ist keine Persistierungs-Bestätigung: Der Recorder schreibt Turn-Ergebnisse asynchron, sodass der Wert nur beweist, dass der Daemon einen laufenden Versuch abschließen sah. Er fehlt vor dem ersten laufenden Terminal in einer Bridge-Generation – einschließlich für eine von der Festplatte wiederhergestellte Session – sodass die Abwesenheit kein Support-Probe ist, und er verschwindet, wenn ein Daemon-Neustart oder Workspace-Runtime-Ersatz eine neue Bridge installiert. Wenn sowohl eine Live- als auch eine persistierte Zusammenfassung für eine Session existiert, melden Vollkatalog-Antworten den späteren gültigen Zeitstempel, sodass GET /session/:id/status, das die Bridge-Zusammenfassung direkt ohne diese Zusammenführung zurückgibt, einen früheren Wert als eine Listenantwort melden kann.
catalogVersion ist ein Gleichheitstoken für vom Daemon beobachtete Katalogänderungen. generation ist eine zufällige UUID, die mit jeder Bridge-Instanz erstellt wird und sich bei Daemon-Neustart oder Workspace-Runtime-Ersetzung ändert; revision beginnt bei null und steigt monoton innerhalb einer Generation. Die einzige unterstützte Operation ist Gleichheit über das gesamte Paar: Gleiche Generation und Revision bedeutet keine vom Daemon beobachtete Katalogänderung, und jeder Unterschied bedeutet, den vollständigen Katalog neu zu laden. Clients dürfen keine Revisionsarithmetik durchführen oder Revisionen über Generationen hinweg vergleichen, und konservative zusätzliche Inkremente sind erlaubt. Die Version deckt vom Daemon beobachtete Katalogmitgliedschaft und statische Metadatenänderungen ab; gewöhnliche Turn-Aktivität, Prompt-Lifecycle, Attach/Detach und Waiting-State-Übergänge rücken sie nicht vor, da der Live-Snapshot bereits die entsprechenden flüchtigen Felder trägt. Zwei flüchtige Overlay-Werte liegen absichtlich außerhalb beider Signale: Turn-Error-State (hasTurnError/turnError) und der Pending-Interaction-Count/Content (pendingInteractionCount/pendingInteractions) rücken die Version weder vor noch erscheinen sie im Snapshot, sodass ein Client, der sie benötigt, weiterhin den pro-Session-Event-Stream oder den vollständigen Katalog lesen muss, anstatt sich auf diese Route zu verlassen; jedes Feld kann wire-additiv hinzugefügt werden, wenn ein konkreter Consumer es benötigt. Mutationen, die direkt von einem anderen Daemon, einer TUI oder einem externen Prozess geschrieben werden, werden nicht beobachtet, sodass ein Client, der das periodische Vollkatalog-Polling einstellt, diese Schreibvorgänge ohne begrenzte Entdeckungszeit hat und sie nur nach einem expliziten vollständigen Reload, einer anderen beobachteten Katalogmutation, einem Reconnect oder einem Daemon-/Runtime-Ersatz erscheinen.
Clients gleichen ein Katalog-Bündel mit einem Two-Read-Handshake ab: Lese Live-State A, lade die vollständige Session-Liste (plus GET /workspaces/:workspace/session-groups, wenn der Client session_organization konsumiert), dann lese Live-State B. Gleiche A- und B-Versionen akzeptieren das Bündel; unterschiedliche Versionen markieren den Katalog als veraltet und kooaleszieren höchstens einen nachlaufenden Reload, anstatt in eine enge Retry-Schleife zu geraten. Jede akzeptierte Kataloganfrage muss nach A initiiert werden – eine Anfrage oder deduplizierte Promise, die vor A begann, kann die Versöhnung nicht erfüllen. Versionsgesteuerte Reloads sind Single-Flight pro Workspace und gehorchen einem nicht-null Hintergrund-Mindestintervall, sodass anhaltende Katalog-Änderungen nicht einen vollständigen Katalog-Scan pro Live-State-Poll auslösen können; explizite lokale Mutationen dürfen weiterhin eine sofortige Aktualisierung über dieselbe Single-Flight-Operation anfordern.
Fehler:
400— bestehendes Selektor-Validierungs- oderworkspace_mismatch-Verhalten für einen unbekannten, fehlerhaften, verschachtelten oder nicht registrierten Selektor; die Route löst einen unbekannten Selektor niemals auf die Primary-Runtime auf.403—untrusted_workspacefür jede nicht vertrauenswürdige Runtime, einschließlich eines nicht vertrauenswürdigen Primary.503—workspace_runtime_unavailablemitRetry-Afterfür eine bootstrapende, transitionierende, drainierende, blockierte oder entfernte Runtime oder eine Runtime-Generation, die während der Anfrage schließt.500— unerwartete lokale Fehler verwenden das bestehende Bridge-Fehler-Mapping.
POST /session/:id/resume
Stellt eine persistierte ACP-Session anhand der ID wieder her, OHNE die Historie über SSE abzuspielen. Der Modellkontext wird intern auf der Agentenseite wiederhergestellt (über geminiClient.initialize, das config.getResumedSessionData liest); der SSE-Stream bleibt sauber für Clients, die die Historie bereits gerendert haben. Pre-flight caps.features.session_resume; unstable_session_resume bleibt ein deprecated Kompatibilitäts-Alias für ältere Clients.
Gleiche Request-Form wie bei /load. Gleiche Response-Form — state spiegelt ACPs ResumeSessionResponse wider. Gleiches Error-Envelope, einschließlich 409 restore_in_progress (wird ausgelöst, wenn ein session/load in-flight ist; session/resume, das einem anderen session/resume hinterherrennt, wird zusammengeführt).
Verwende /load, wenn der Client keine Historie gerendert hat (Cold Reconnect, Picker → Open). Verwende /resume, wenn der Client die Turns bereits auf dem Bildschirm hat und nur das daemon-seitige Handle zurückbenötigt.
⚠️ Warum wird
unstable_session_resumenoch immer advertised? Die HTTP-Route des Daemons und diesession_resume-Capability sind stabil für v1, aber die Bridge ruft weiterhin ACPsconnection.unstable_resumeSessionauf. Der alte Tag bleibt nur erhalten, damit SDKs, die vorsession_resumeausgeliefert wurden, weiterhin funktionieren.
GET /workspace/:id/session-info und GET /workspaces/:workspace/session-info
Gibt aggregierte persistierte Session-Zahlen für den ausgewählten Workspace zurück, ohne den paginierten Session-Listen-Pfad zu ändern:
{
"active": 450,
"archived": 30,
"total": 480,
"live": 2,
"expensive": true,
"cost": "disk_scan"
}active, archived und total zählen lokale JSONL-Sessions. live ist die passende In-Memory-Bridge-Zahl und wird für einen registrierten nicht vertrauenswürdigen sekundären Workspace weggelassen, da dieser rein persistierte Lesevorgang keinen Live-Zustand abfragen darf. expensive ist immer true und cost ist immer "disk_scan"; Clients müssen diesen Endpunkt selten aufrufen, anstatt ihn zu pollen. Wenn der Scan sein Sicherheitslimit erreicht oder nicht jede Kandidatendatei klassifizieren kann, fügt die Antwort "truncated": true hinzu, und die persistierten Zahlen sind Untergrenzen. Fehlender Speicher gibt Null persistierte Zahlen zurück. Die Plural-Route verwendet denselben Workspace-Selektor und dieselbe Trust-Policy wie der Plural-Session-Katalog; ein nicht vertrauenswürdiger Primary gibt weiterhin 403 untrusted_workspace zurück.
Das TypeScript-Daemon-SDK legt die Plural-Route über workspaceById(...) oder workspaceByCwd(...) offen, gefolgt von getWorkspaceSessionInfo().
GET /workspace/:id/sessions und GET /workspaces/:workspace/sessions
Listet Sessions auf, deren kanonischer Workspace mit :id oder :workspace übereinstimmt. Der Pfadparameter wird zuerst als exakte Workspace-ID aufgelöst und dann als URL-kodiertes absolutes CWD. Primäre Workspaces enthalten das bestehende persistierte/live-Merge: Die Standardliste enthält aktive Sessions aus chats/; übergib archiveState=archived, um archivierte Sessions aus chats/archive/ aufzulisten. Vertrauenswürdige nicht primäre Workspaces enthalten aktive persistierte Sessions aus ihrem eigenen chats/-Store und mergen passende Live-Zusammenfassungen ohne Duplikate; wenn keine aktiven persistierten Sessions existieren, bewahrt die Route das bisherige Live-only-Cursor-Verhalten. Vertrauenswürdige nicht primäre Workspaces unterstützen auch archiveState=archived, die organisierte view=organized-Liste und group-Filter, die aus ihren eigenen chats/, chats/archive/ und Session-Organisations-Speichern lesen; eine kombinierte view=organized&archiveState=archived-Anfrage gibt nur archivierte Sessions ohne Live-Merge zurück. Registrierte nicht vertrauenswürdige nicht primäre Workspaces unterstützen dieselben Listen-, Filter- und Paginierungsformen, geben aber nur persistierte Einträge zurück: Der Daemon fragt nicht die Live-Bridge ab oder füllt Pending Interactions, Turn Errors oder Client-Zustand aus der Runtime. Persistierte Standardwerte wie clientCount: 0 und hasActivePrompt: false bleiben für Wire-Kompatibilität vorhanden. Fehlender Speicher gibt eine leere Liste zurück. Die Plural-Route gibt weiterhin 403 { code: "untrusted_workspace" } für einen nicht vertrauenswürdigen Primary zurück; Legacy-Primary-Routen behalten ihr bestehendes Kompatibilitätsverhalten. archiveState=all wird in v1 nicht unterstützt. Primäre und persistiert-gestützte Listen behalten die bestehende numerische cursor-Semantik; der nicht-persistierte vertrauenswürdige nicht primäre Live-Fallback behält seinen bestehenden opaken Live-Cursor.
curl http://127.0.0.1:4170/workspace/$(jq -rn --arg c "$PWD" '$c|@uri')/sessions
curl http://127.0.0.1:4170/workspace/$(jq -rn --arg c "$PWD" '$c|@uri')/sessions?archiveState=archived
curl http://127.0.0.1:4170/workspaces/<workspace-id>/sessionsWenn workspace_qualified_rest_core beworben wird, sind Workspace-scopige Session-Batch-Operationen, Gruppen-CRUD und Session-Organisations-Mutation unter /workspaces/:workspace/sessions/{delete,archive,unarchive}, /workspaces/:workspace/session-groups und /workspaces/:workspace/session/:id/organization verfügbar. Für einen nicht vertrauenswürdigen sekundären Workspace bleibt Gruppen-GET verfügbar; jede Gruppen-, Session- und Organisations-Mutation bleibt Trust-gated. Workspace-lose Batch- und Organisations-Mutationsrouten bleiben aus Kompatibilitätsgründen nur für den primären Workspace.
Query-Parameter:
| Feld | Erforderlich | Hinweise |
|---|---|---|
archiveState | nein | active (Standard) oder archived. Jeder andere Wert gibt 400 { code: "invalid_archive_state" } zurück. |
cursor | nein | Paginierungs-Cursor aus der vorherigen Response. |
size | nein | Seitengröße. Ungültige Werte geben 400 { code: "invalid_cursor" } oder die bestehende Seitengrößen-Validierung zurück. |
view | nein | Weglassen für die Legacy-Recent-Liste. organized aktiviert die serverseitige Pinned/Group-Sortierung und fügt optionale Organisationsfelder hinzu. Jeder andere Wert gibt 400 { code: "invalid_session_view" } zurück. |
group | nein | Nur sinnvoll mit view=organized. all (Standard), pinned, ungrouped oder eine benutzerdefinierte Gruppen-ID. Unbekannte Gruppen-IDs geben 404 { code: "group_not_found" } zurück. |
Antwort:
{
"sessions": [
{
"sessionId": "<uuid>",
"workspaceCwd": "/canonical/path",
"createdAt": "2026-05-17T08:30:00.000Z",
"displayName": "My Session",
"clientCount": 2,
"hasActivePrompt": false,
"isArchived": false
}
],
"nextCursor": 1772251200000
}Mit view=organized liest der Daemon <Storage.getProjectDir(cwd)>/session-organization.v1.json, gibt gepinnte Sessions zuerst zurück, dann absteigend nach Aktivitätszeit und schließlich nach sessionId für stabile Gleichstände. Der organisierte Cursor ist ein opaque base64url-JSON und darf nicht mit der Legacy-Recent-Liste wiederverwendet werden. pinned ist ein virtueller Filter, keine Gruppe. groupId: null bedeutet nicht gruppiert. Archivierte Sessions behalten ihre Organisationsmetadaten, aber archiveState=archived&view=organized gibt dennoch nur archivierte Sessions zurück.
Zusätzliche Felder können bei jeder Session erscheinen, wenn view=organized:
{
"isPinned": true,
"pinnedAt": "2026-07-04T12:00:00.000Z",
"groupId": "018f..."
}Vertrauenswürdige aktive Listen enthalten Live-Daemon-Overlay-Felder wie clientCount und hasActivePrompt. Nicht vertrauenswürdige sekundäre und archivierte Listen sind rein speicherbasiert: Live-Overlay-Felder bleiben absent oder false, und archivierte Einträge setzen isArchived auf true. Leeres Array (nicht 404), wenn keine Sessions existieren — eine Session-Picker-UI sollte keinen Fehler werfen, nur weil der Workspace inaktiv ist.
GET /workspace/:id/session-groups
Listet benutzerdefinierte Session-Gruppen für einen Workspace auf. Der Singular-GET-Selektor akzeptiert jede registrierte Workspace-ID oder URL-kodiertes kanonisches CWD. Das Plural-GET-Alias ist auch für einen nicht vertrauenswürdigen sekundären Workspace verfügbar und liest nur den Organisations-Sidecar. Plural-Gruppenmutationen bleiben Trust-gated, während Singular-Gruppenmutationen ihr bisheriges Primary-only-Kompatibilitätsverhalten beibehalten. Pre-flight caps.features.includes('session_organization').
Antwort:
{
"groups": [
{
"id": "018f...",
"name": "Frontend",
"color": "blue",
"order": 0,
"createdAt": "2026-07-04T12:00:00.000Z",
"updatedAt": "2026-07-04T12:00:00.000Z"
}
],
"colorOptions": ["red", "orange", "yellow", "green", "blue", "purple"]
}Farben sind reine Protokoll-Tokens; Clients lokalisieren die Anzeigenamen. Es werden keine Standardgruppen mit Farbnamen erstellt.
POST /workspace/:id/session-groups
Erstellt eine benutzerdefinierte Session-Gruppe. Striktes Mutations-Gate. Pre-flight caps.features.includes('session_organization').
Request:
{ "name": "Frontend", "color": "blue" }name wird getrimmt, muss 1-64 Zeichen lang sein, darf keine Steuerzeichen enthalten und ist innerhalb des Workspace eindeutig durch einen Case-insensitive getrimmten Vergleich. Doppelte Namen geben 409 { code: "group_name_conflict" } zurück. color muss eine der zurückgegebenen colorOptions sein.
Antwort:
{
"group": {
"id": "018f...",
"name": "Frontend",
"color": "blue",
"order": 0,
"createdAt": "...",
"updatedAt": "..."
}
}PATCH /workspace/:id/session-groups/:groupId
Aktualisiert eine benutzerdefinierte Session-Gruppe. Striktes Mutations-Gate. Pre-flight caps.features.includes('session_organization'). Body-Felder sind optional: { "name"?: string, "color"?: string, "order"?: number }. Unbekannte Gruppen-IDs geben 404 { code: "group_not_found" } zurück; doppelte/ungültige Namen und Farben verwenden dieselben Fehler wie beim Erstellen.
DELETE /workspace/:id/session-groups/:groupId
Löscht eine benutzerdefinierte Session-Gruppe. Striktes Mutations-Gate. Pre-flight caps.features.includes('session_organization'). Sessions, die auf die Gruppe verweisen, werden auf groupId: null gesetzt; der Pinned-Status bleibt erhalten. Die Response ist { "deleted": true }, wenn eine Gruppe entfernt wurde, und { "deleted": false }, wenn die ID nicht existierte.
POST /sessions/delete
Hard-Delete einer oder mehrerer persistierter Session-JSONL-Dateien. Der Daemon schließt zunächst best-effort aktive Sessions und entfernt dann die aktive oder archivierte JSONL. Wenn für dieselbe ID sowohl eine aktive als auch eine archivierte Kopie existiert, werden beide entfernt. Worktree-Sidecars auf beiden Seiten werden bereinigt; Dateihistorie, Subagent-Transkripte und Runtime-Sidecars bleiben absichtlich erhalten.
Request:
{ "sessionIds": ["<uuid>"] }Response:
{
"removed": ["<uuid>"],
"notFound": [],
"errors": []
}POST /sessions/archive
Archiviert eine oder mehrere Sessions. Das Archivieren ist ein Zustandsübergang, kein Löschen: Die JSONL wird von chats/<id>.jsonl nach chats/archive/<id>.jsonl verschoben. Dateihistorie, Subagent-Transkripte und Runtime-Sidecars bleiben unverändert. Wenn eine Session aktiv (live) ist, führt der Daemon zunächst ein striktes Schließen (strict close) durch und verlangt, dass der Close-Handler des ACP-Agents die Chat-Aufzeichnung flushen muss; wenn das Schließen oder Flushen fehlschlägt, wird die JSONL nicht verschoben. Pre-flight caps.features.session_archive.
Request:
{ "sessionIds": ["<uuid>"], "resolveConflicts": true }sessionIds muss ein nicht leeres String-Array mit höchstens 100 IDs sein. Duplikate werden zusammengeführt.
Response:
{
"archived": ["<uuid>"],
"alreadyArchived": [],
"resolvedConflicts": ["<uuid>"],
"notFound": [],
"errors": []
}resolveConflicts ist optional und standardmäßig false. Standardmäßig werden aktive und archivierte Dateien mit derselben ID in errors gemeldet, und keine Kopie wird verschoben, entfernt oder überschrieben. Das Archivieren einer Live-Session führt weiterhin das oben beschriebene strikte Schließen durch, bevor der Konflikt klassifiziert wird, sodass dieses Schließen Warteschlangen-Datensätze in das aktive Transkript flushen kann. Mit resolveConflicts: true behält Archive die archivierte Kopie, entfernt die aktive Kopie und meldet die ID sowohl in archived als auch in resolvedConflicts. errors-Einträge haben das Format { "sessionId": "<uuid>", "error": "message" }.
Lifecycle-Konflikte sind Batch-Item-Ergebnisse: die Workspace-losen und Workspace-qualifizierten Routen geben HTTP 200 mit dem Konflikt in errors zurück. Dies ersetzt das frühere Workspace-qualifizierte HTTP-409 session_conflict-Envelope; Clients, die diese Route aufgerufen haben, müssen die Batch-Antwort prüfen. Internal-Runtime-REST-Batches behalten die sichere Konfliktmeldung bei und redaktieren weiterhin andere details zu fehlgeschlagenen Sessions.
POST /sessions/unarchive
Stellt archivierte Sessions im aktiven Verzeichnis wieder her. Dies setzt die Session nicht automatisch fort; es verschiebt lediglich chats/archive/<id>.jsonl zurück nach chats/<id>.jsonl. Nach erfolgreichem Unarchivieren können Clients POST /session/:id/load oder POST /session/:id/resume aufrufen.
Request:
{ "sessionIds": ["<uuid>"], "resolveConflicts": true }Response:
{
"unarchived": ["<uuid>"],
"alreadyActive": [],
"resolvedConflicts": ["<uuid>"],
"notFound": [],
"errors": []
}resolveConflicts ist optional und standardmäßig false. Standardmäßig erzeugen gleichzeitige aktive und archivierte JSONL-Dateien einen Konflikt in errors, und keine Kopie wird verschoben, entfernt oder überschrieben; eine nur aktive Session wird in alreadyActive zurückgegeben, nachdem der Daemon eine Writer- oder Maintenance-Lease erworben hat, um ausstehende Sidecar-Bereinigung abzugleichen. Wenn eine Live-Session noch die Writer-Lease hält, wird die nur aktive ID in errors gemeldet, bis diese Session geschlossen wird. Mit resolveConflicts: true repariert Unarchive den Konflikt nur, wenn beide Kopien reguläre Transkriptdateien sind, die der ausgewählte Workspace verwalten darf, einschließlich eigener leerer oder beschädigter Transkripte. Es behält die aktive Kopie, entfernt die archivierte Kopie und meldet die ID sowohl in unarchived als auch in resolvedConflicts. Die Option umgeht keine Ownership-Prüfungen; gemischte lokale/fremde oder anderweitig mehrdeutige Eigentümerschaft wird in errors gemeldet, und keine Kopie wird verschoben. Archive oder Unarchive, die für dieselbe ID bereits laufen, geben 409 session_archiving zurück, bevor der Batch gestartet wird.
Die Transkript-Verschiebung oder Konfliktreparatur wird nicht zurückgesetzt, wenn eine spätere Cleanup-Ownership-Prüfung fehlschlägt. In diesem Fall erscheint die ID möglicherweise nur in errors, obwohl sich der Archivzustand bereits geändert hat, und kann in unarchived und resolvedConflicts fehlen. Wiederhole dieselbe Lifecycle-Anfrage für denselben Workspace, bevor du den Fehler als Beweis dafür behandelst, dass die archivierte Kopie oder der Konflikt weiterhin besteht. Wenn der Service die gespeicherte Transkript-Identität verifizieren und die erforderliche Writer- oder Maintenance-Lease erwerben kann, meldet der Retry den autoritativen alreadyActive-Zustand, selbst für leere oder beschädigte Transkripte, die die Session-Auflistung weglässt, und setzt die ausstehende Sidecar-Bereinigung fort. Transkripte, deren gespeicherte Identität nicht verifiziert werden kann, melden weiterhin errors beim Retry und erfordern manuelle Inspektion; Retries, die die erforderliche Lease nicht erwerben können, einschließlich weil eine aktive Session sie noch hält, verbleiben in errors, bis die Lease verfügbar wird.
ACP-over-HTTP verwendet dieselben Request- und Response-Bodies über die Vendor-Methoden _qwen/sessions/archive und _qwen/sessions/unarchive. Die REST-Route-Tabelle mappt POST /sessions/archive und POST /sessions/unarchive für ACP-Transports auf diese Methoden.
Multi-Workspace-Live-Session-Routing
Wenn multi_workspace_sessions beworben wird, identifizieren Live-Session-Operationen ihren Workspace anhand der sessionId; Clients fügen der URL keinen Workspace-Selektor hinzu. Zusätzlich zu den bestehenden Owner-gerouteten Lifecycle-Operationen gilt dies für PATCH /session/:id/metadata, POST /session/:id/recap, POST /session/:id/generate, POST /session/:id/btw, POST /session/:id/mid-turn-message, GET /session/:id/mid-turn-messages, DELETE /session/:id/mid-turn-messages/:messageId, POST /session/:id/tasks/:taskId/cancel, POST /session/:id/goal/clear, POST /session/:id/continue, POST /session/:id/language, POST /session/:id/artifacts, DELETE /session/:id/artifacts/:artifactId, GET /session/:id/sources, POST /session/:id/sources und DELETE /session/:id/sources/:sourceId. Der Daemon routet jede Anfrage an die vertrauenswürdige Runtime, der die Live-Session gehört. Ein nicht vertrauenswürdiger nicht primärer Owner gibt 403 untrusted_workspace zurück, ein fehlender Live-Owner gibt 404 session_not_found zurück, und ein mehrdeutiger Owner schlägt fail-closed mit 500 ambiguous_session_owner fehl.
Diese Regel gilt nur für Live-Sessions und macht nicht jede Workspace-lose Session-Route Multi-Workspace-fähig. Persistierte oder archivierte Operationen verwenden ihre dokumentierten Workspace-qualifizierten Routen. POST /session/:id/branch, POST /session/:id/fork und POST /session/:id/cd bleiben absichtlich nur für Primary und geben non_primary_session_route_not_supported für nicht primäre Owner zurück.
Mid-Turn-Nachrichten
POST /session/:id/mid-turn-message akzeptiert { "message": "...", "messageId": "<optionale-message-id>" }. Eine erfolgreiche Aufnahme gibt { "accepted": true, "messageId": "<id>" } zurück und überträgt die Eigentümerschaft an den Daemon: Die Nachricht wird in den aktiven Turn drainiert oder in die normale Prompt-FIFO befördert, wenn die Session idle wird. Clients, die session_mid_turn_message_query verwenden, senden eine stabile messageId; Wiederholungen sind idempotent, solange sie gequeuet, pending oder in den begrenzten Versöhnungs-Ringen verbleiben. Eine volle Queue weist eine neue Anfrage zurück, ohne die Eigentümerschaft zu übernehmen. Neue Clients, die mit einem älteren Daemon verbunden sind, erkennen die fehlende Capability und behalten ihren lokalen Legacy-Fallback bei.
GET /session/:id/mid-turn-messages gibt die Daemon-eigene, sessionweite Queue zurück, zusammen mit den begrenzten settledMessageIds- und promotedMessageIds-Ringen. Settled-IDs wurden injiziert oder explizit gelöscht; promoted-IDs wurden in die normale Prompt-FIFO befördert. Eine ID in einem der beiden Ringe darf nicht erneut gesendet werden.
Wenn eine gequeuete Nachricht in den aktiven Turn drainiert wird, veröffentlicht der Daemon mid_turn_message_injected mit ausgerichteten messages- und messageIds-Arrays (sowie der promptId des laufenden Turns, wenn bekannt). Es ist ein transientes Dedup-Signal, kein Transkript-Element: Clients schließen Completion-Callbacks ab, die unter diesen Nachrichten-IDs registriert wurden, und entfernen alle lokalen Pending-Zeilen dafür. Ältere Daemons enthalten zusätzlich originatorClientId in der Payload. Ein verpasstes Echo wird über den Query oben aus dem Settled-Ring wiederhergestellt.
Wenn session_mid_turn_message_mutation beworben wird, kann ein angehängter Session-Client DELETE /session/:id/mid-turn-messages/:messageId aufrufen. Er entfernt die Nachricht entweder aus der Mid-Turn-Queue oder ihrem beförderten Pending-Prompt-Zustand; das Entfernen einer beförderten Nachricht, die bereits läuft, bricht diesen Turn ab, entsprechend der normalen Pending-Prompt-Entfernung. Daemon-eigene Queue-Additionen und -Entfernungen veröffentlichen die bestehenden pending_prompt_added- und pending_prompt_completed-Session-Events, sodass angehängte Clients beide autoritativen Queue-Snapshots aktualisieren. { "removed": false } bedeutet, dass die Nachricht bereits injiziert, abgeschlossen oder nicht gefunden wurde.
GET /session/:id/pending-prompts
Gibt den aktuell laufenden Prompt und die in der FIFO der Live-Session wartenden Prompts zurück. Die Anfrage darf X-Qwen-Client-Id enthalten; wenn vorhanden, muss es einen angehängten Client identifizieren.
{
"pendingPrompts": [
{
"promptId": "<prompt-id>",
"text": "Explain the failure",
"queuedAt": 1700000000123,
"state": "running",
"originatorClientId": "<client-id>"
}
]
}state ist running für den dispatchten Prompt und queued für wartende Prompts. content erscheint, wenn der Prompt strukturierte Inhalte wie Bilder enthält. Dies ist eine Live-Session-Owner-Route: 404 bedeutet kein Live-Owner und 503 bedeutet, dass der Owner vorübergehend nicht verfügbar ist. Ein nicht vertrauenswürdiger nicht primärer Owner gibt 403 untrusted_workspace zurück, und eine in mehr als einem Workspace lebende ID gibt 500 ambiguous_session_owner zurück. Es gibt kein dediziertes Capability-Tag; ältere Daemons geben 404 zurück. Die TypeScript-SDK-Methode ist getPendingPrompts().
POST /session/:id/prompt
Leitet einen Prompt an den Agenten weiter. Multi-Prompt-Caller werden pro Session in einer FIFO-Warteschlange gereiht (ACP garantiert einen aktiven Prompt pro Session).
Request:
{
"prompt": [{ "type": "text", "text": "What does src/main.ts do?" }],
"delivery": {
"kind": "channel",
"target": {
"channelName": "dingtalk",
"type": "user",
"id": "platform-user-id"
}
}
}delivery ist optional und erfordert die channel_delivery-Capability. Der
Daemon gibt weiterhin 202 {promptId,lastEventId} zurück, wenn der Prompt aufgenommen wird.
Nach einem erfolgreichen end_turn übermittelt die Session den sichtbaren finalen Text an den
bereits laufenden Channel Worker des exakten Workspaces. Die Payload ist nur der
letzte toolfreie Assistant-Antwortblock; Tool-Call-Einleitungen, Tool-Erzählungen,
veraltete Wiederholungen und frühere automatische Fortsetzungsblöcke werden
ausgeschlossen. Ein leerer oder nur-Whitespace-finaler erzeugt weiterhin ein korreliertes
channel_delivery_result mit status: "skipped", nachdem die Autorisierung verbraucht wurde,
kontaktiert aber keinen Worker. Delivery-Erfolg oder -Fehler kommt später über dasselbe
replaybare Event und ändert niemals turn_complete in turn_error. Cancellation,
Agent-Fehler und Token-Limit-Beendigung senden oder veröffentlichen kein Delivery-Ergebnis.
Validierung: prompt muss ein nicht leeres Array von Objekten sein. Andere Fehler geben 400 zurück, bevor die Bridge erreicht wird.
Response:
{ "promptId": "session-id########1", "lastEventId": 42 }Die 202-Antwort bestätigt die Aufnahme, nicht den Agent-Abschluss. Beobachte den
Session-SSE-Stream nach lastEventId und korreliere turn_complete oder
turn_error nach promptId.
turn_complete.data.stopReason trägt den ACP-StopReason, den der Agent zurückgegeben hat — end_turn, max_tokens, max_turn_requests, refusal oder cancelled. Der Daemon kann auch cancelled für einen Prompt emittieren, der abgebrochen wurde, ohne dass der Agent ihn ausgeführt hat, einschließlich Queued-Prompt-Entfernung, Caller-Disconnect oder Drain/Teardown; dieser Wert beweist nicht, dass der Agent den Prompt ausgeführt hat. Behandle das Feld als offenen String: es ist auf dem Wire als string typisiert, die ACP-Menge kann wachsen, und ein Client, der exhaustiv darüber switcht, wird beim nächsten Zuwachs brechen.
Zwei daemon-seitige Ergebnisse kommen nicht über dieses Feld an. Ein Turn, der innerhalb des Daemons fehlschlägt — Deadline-Ablauf, Teardown-Flush, Child-Crash — wird als turn_error-Event veröffentlicht, niemals als turn_complete-stopReason. Seine data trägt immer message; code ist nur vorhanden, wenn der Daemon den Fehler klassifiziert hat (Deadline-Ablauf → prompt_deadline_exceeded, Teardown-Flush → channel_closed, session_closed, session_killed oder daemon_shutdown), und der Frame für einen Prompt, der abgelehnt wurde, weil das ACP-Child mitten in der Anfrage gestorben ist, trägt weder code noch errorKind. Behandle beide als optional und verzweige über message.
Ein nach einem Neustart aus persistierter Historie wiederhergestellter Turn wird überhaupt nicht auf dem Stream erneut veröffentlicht; er erscheint in promptTerminals[] im POST /session/:id/load-Response-Body — als { terminal: "completed", stopReason: "reconstructed_from_transcript" }, wenn der persistierte Tail zeigt, dass der Turn abgeschlossen wurde, oder als { terminal: "interrupted", code: "daemon_lost" } mit keinem stopReason, wenn der Daemon mitten im Turn gestorben ist. Matche den Eintrag nach promptId und verzweige über terminal; promptTerminals wird aus der Response vollständig weggelassen, wenn das Ledger keine Nachweise für die Session enthält.
Wenn der HTTP-Client mitten im Prompt die Verbindung trennt, sendet der Daemon eine ACP-cancel-Benachrichtigung an den Agenten, wodurch der Prompt mit stopReason: "cancelled" beendet wird.
Wenn prompt_absolute_deadline beworben wird, kann deadlineMs die
konfigurierte Server-Deadline verkürzen. Ablauf emittiert ein korreliertes turn_error mit
code: "prompt_deadline_exceeded". Die Deadline entlässt den Caller, ohne den Agenten zu töten; wenn der Agent später abschließt, geben Turn-Status-Polls für diese promptId das abgeschlossene Transkript-Ergebnis zurück statt des Deadline-Fehlers.
POST /session/:id/cancel
Bricht den aktuell aktiven Prompt der Session ab. ACP-seitig ist dies eine Benachrichtigung, kein Request — der Agent bestätigt dies, indem er das aktive prompt() mit cancelled auflöst.
curl -X POST http://127.0.0.1:4170/session/$SID/cancel
# → 204 No ContentMulti-Prompt-Vertrag: Cancel betrifft nur den aktiven Prompt. Alle Prompts, die derselbe Client zuvor gepostet hat und die noch hinter dem aktiven in der Warteschlange stehen, werden weiterhin ausgeführt. Multi-Prompt-Queueing ist ein vom Daemon eingeführtes Verhalten (nicht in der ACP-Spezifikation); der Vertrag für Prompts in der Warteschlange lautet: “sie laufen weiter, es sei denn, du brichst sie einzeln ab oder beendest die Session über den Channel-Exit”.
Wenn Prompts in der Warteschlange in einem Multi-Client-Deployment unerwartet sind, stelle zunächst sicher,
ob die Caller eine Standard-Session mit sessionScope: "single" teilen. Für unabhängige
Unterhaltungen pro Thread erstelle Sessions mit sessionScope: "thread", sodass Prompts
nur innerhalb dieses Threads serialisiert werden.
DELETE /session/:id
Schließt eine aktive Session explizit. Erzwingt das Schließen, auch wenn andere Clients verbunden sind — bricht alle aktiven Prompts ab, löst ausstehende Berechtigungen als abgebrochen auf, veröffentlicht das session_closed-Event, schließt den EventBus und entfernt die Session aus den Daemon-Maps. Auf der Festplatte persistierte Sessions werden NICHT gelöscht — sie können über POST /session/:id/load neu geladen werden. Pre-flight caps.features.session_close.
curl -X DELETE http://127.0.0.1:4170/session/$SID
# → 204 No ContentIdempotent: Gibt 404 für unbekannte Sessions zurück. Das Error-Envelope verwendet code: "session_not_found"; ein gleichzeitiges Schließen kann code: "session_closing" zurückgeben, was Clients als denselben erfolgreichen Terminalzustand für diese Route behandeln können.
session_closed-Event. SSE-Subscriber erhalten ein terminalessession_closed-Event mit{ sessionId, reason: 'client_close', closedBy?: '<clientId>' }, bevor der Stream endet. SDK-Reducer behandeln dies identisch zusession_died(setztalive: false, löschtpendingPermissions).
PATCH /session/:id/metadata
Aktualisiert mutable Session-Metadaten. Pre-flight caps.features.session_metadata. Gruppierung und Pinning sind absichtlich nicht Teil dieser Route; verwende PATCH /session/:id/organization unter session_organization.
Request:
{
"displayName": "My Investigation Session",
"pr": {
"number": 123,
"url": "https://github.com/QwenLM/qwen-code/pull/123",
"state": "open"
}
}| Field | Required | Notes |
|---|---|---|
displayName | nein | String. Werte länger als 256 UTF-16 Code Units werden abgeschnitten, und der Schnitt ist nicht Surrogatpaar-bewusst, sodass ein Name, der mit einem Nicht-BMP-Zeichen endet, eine einzelne Surrogathälfte verlieren kann. Ein leerer oder nur-Whitespace-Wert wird mit 400 invalid_metadata abgelehnt; lass das Feld weg, um den Namen unverändert zu lassen. |
pr | nein | Bindet einen Pull Request. Erfordert eine positive Ganzzahl number, eine HTTP(S)-url mit höchstens 2.048 Zeichen ohne Steuerzeichen, und optionales state: open, merged oder closed. |
Response:
{
"sessionId": "<uuid>",
"displayName": "My Investigation Session",
"prs": [
{
"number": 123,
"url": "https://github.com/QwenLM/qwen-code/pull/123",
"state": "open"
}
]
}prs ist die effektive begrenzte Bindungshistorie und kann aktualisierte Issue-Links enthalten. Veröffentlicht ein session_metadata_updated-Event auf dem SSE-Stream der Session, das nur die Feldgruppe trägt, die sich geändert hat: eine Umbenennung emittiert displayName (plus titleSource, wenn der Name gesetzt wird) und lässt prs weg, während eine PR-Bindungsänderung prs emittiert und den aktuellen displayName echoet, wenn einer gesetzt ist. Behandle ein im Event fehlendes Feld als unverändert, nicht als gelöscht, und lies den 200-Body oder die Session-Liste erneut, wenn du die vollständigen Metadaten benötigst.
PATCH /session/:id/organization und PATCH /workspaces/:workspace/session/:id/organization
Aktualisiert den lokalen Session-Organisationszustand über das bestehende Mutations-Gate. Pre-flight caps.features.includes('session_organization'); die Plural-Route erfordert zusätzlich workspace_qualified_rest_core. Auf der Plural-Route wird :workspace zuerst als exakte registrierte Workspace-ID aufgelöst und dann als URL-kodiertes kanonisches absolutes CWD. Die ausgewählte Runtime muss vertrauenswürdig sein. Die Session-Existenz- und Nicht-Null-groupId-Validierung sind auf den aktiven persistierten, archivierten persistierten und Live-Session-Zustand und Gruppen-Store dieser Runtime beschränkt, ohne Fallback auf den Primary oder einen anderen Workspace. Die Legacy-Route bleibt nur für den primären Workspace.
Request:
{ "isPinned": true, "groupId": "018f..." }| Field | Required | Notes |
|---|---|---|
isPinned | nein | Boolean. true setzt pinnedAt, wenn es noch nicht gepinnt war; false löscht pinnedAt. |
groupId | nein | Benutzerdefinierte Gruppen-ID oder null für nicht gruppiert. Unbekannte Gruppen-IDs geben 404 { code: "group_not_found" } zurück. |
color | nein | Ein unterstütztes Session-Farb-Token, oder null zum Löschen der Session-Farbe. |
Response:
{
"sessionId": "<uuid>",
"groupId": "018f...",
"color": "blue",
"isPinned": true,
"pinnedAt": "2026-07-04T12:00:00.000Z",
"updatedAt": "2026-07-04T12:00:00.000Z"
}Dieser Zustand wird im Session-Organisations-Sidecar auf Projektebene unter dem Daemon-Runtime-Speicherverzeichnis gespeichert. Es handelt sich nicht um Transkript-Inhalte, aktualisiert nicht die mtime des Transkripts, wird nicht mit Transkripten exportiert und bleibt bei Archivierung/Unarchivierung erhalten.
POST /session/:id/heartbeat
Aktualisiert das Last-Seen-Bookkeeping des Daemons für diese Session. Langlebige Adapter (TUI/IDE/web) pingen dies in einem Intervall, sodass zukünftige Revocation-Policies (Wave 5 PR 24) tote Clients von inaktiven unterscheiden können.
Headers:
| Header | Required | Notes |
|---|---|---|
X-Qwen-Client-Id | nein | Gibt die vom Daemon ausgegebene ID aus POST /session zurück (Echo). Identifizierte Clients aktualisieren auch ihren pro-Client-Timestamp; anonyme Heartbeats aktualisieren nur die pro-Session-Watermark. Muss dieselbe [A-Za-z0-9._:-]{1,128}-Form erfüllen wie andernorts. |
Der Request-Body ist leer ({} ist in Ordnung – heute werden keine Felder gelesen).
Response:
{
"sessionId": "<sid>",
"clientId": "<cid>",
"lastSeenAt": 1700000000123
}clientId wird nur zurückgegeben (Echo), wenn eine vertrauenswürdige X-Qwen-Client-Id übermittelt wurde. lastSeenAt ist der daemon-seitige Date.now()-Epoch (ms), den die Bridge gespeichert hat.
Errors:
400—{ code: 'invalid_client_id' }, wenn der Header fehlerhaft formatiert ist (Header-Shape-Regel) oder wenn er eineclientIdenthält, die nicht für diese Session registriert ist (die Bridge wirftInvalidClientIdError, bevor ein Timestamp aktualisiert wird).404— unbekannte Session.
Capability-Gating: Pre-flight caps.features.client_heartbeat. Ältere Daemons geben für diesen Pfad 404 zurück.
POST /session/:id/model
Wechselt das aktive Modell innerhalb des aktuell an die Session gebundenen Model-Services. Serialisiert über die pro-Session Model-Change-Queue.
(Um den Service selbst zu wechseln – z. B. Alibaba ModelStudio vs. OpenRouter – übergib modelServiceId bei POST /session für eine neue Session. Stage 1 hat keine Live-Service-Switch-Route.)
Request:
{ "modelId": "qwen-staging" }Response: das Model-Switch-Ergebnis des ACP-Agenten, wortwörtlich weitergeleitet — der Daemon formt es nicht um, sodass die oberste Ebene keine modelId trägt. Lies die Switch-Details aus _meta.qwenModelSwitch.
Bei Erfolg wird model_switched an den SSE-Stream veröffentlicht. Bei Fehlschlag wird model_switch_failed veröffentlicht (sodass auch passive Subscriber den Fehlschlag sehen, nicht nur der Caller). Wettlauf (Race) gegen den Agent-Channel-Exit, sodass ein blockierter Child-Prozess den HTTP-Handler nicht blockieren kann. Ein erfolgreicher Wechsel zeichnet das Session-Modell auch nach bestem Wissen im Session-JSONL auf; wenn der Record geschrieben wird, versuchen Daemon-Load/Resume vor der Authentifizierung dieses Sessions-Modell wiederherzustellen. Wenn das aufgezeichnete Modell nicht mehr angewendet werden kann (Modell entfernt, Credentials nicht verfügbar), verwendet der Restore eine Registry-Route mit derselben ID, wenn eine existiert – für einen Runtime-Snapshot-Record kann das ein anderer Endpunkt als die aufgezeichnete Bindung sein – und fährt nur mit dem settings.model.name-Standard fort, wenn keine Route aufgelöst wird. settings.model.name wird weiterhin als Standard für neue Sessions aktualisiert.
POST /session/:id/recap
Capability-Tag: session_recap. Bridge → ACP extMethod qwen/control/session/recap.
Generiert eine einzeilige “Wo habe ich aufgehört”-Zusammenfassung der Session. Umhüllt die Core-Funktion generateSessionRecap (packages/core/src/services/sessionRecap.ts), die eine Side-Query gegen das schnelle Modell mit deaktivierten Tools, maxOutputTokens: 300 und einem strikten <recap>...</recap>-Ausgabeformat ausführt. Die Side-Query liest die bestehende GeminiClient-Chat-Historie der Session und fügt dieser nichts hinzu.
Der Request-Body wird ignoriert (sende {} oder leer). Non-strict Mutation-Gate – das Vorgehen spiegelt /session/:id/prompt wider (der Aufruf kostet Tokens, mutiert aber keinen Zustand). Es wird kein SSE-Event veröffentlicht.
Response (200):
{
"sessionId": "sess:42",
"recap": "Debugging the auth retry race. Next: add deterministic timing to the integration test."
}recap ist null (ein normales 200, kein Fehler), wenn:
- die Session noch weniger als zwei Dialog-Turns hat,
- die Side-Query keine extrahierbare
<recap>...</recap>-Payload zurückgegeben hat, - oder ein zugrunde liegender Modellfehler aufgetreten ist (der Core-Helper arbeitet nach dem Best-Effort-Prinzip und wirft nie).
Errors:
400 {code: 'invalid_client_id'}— fehlerhaft formatierterX-Qwen-Client-Id-Header.404— Session unbekannt.
Abbruch (Cancellation): keiner in v1. Die Route lauscht nicht auf HTTP-Client-Disconnects, kein AbortSignal wird in die Bridge durchgereicht, und der ACP-Child führt die Side-Query bis zum Abschluss aus, unabhängig davon, ob der Caller die Verbindung getrennt hat. Die einzigen Obergrenzen sind der 60s-Backstop-Timeout der Bridge (SESSION_RECAP_TIMEOUT_MS) und der Transport-Closed-Wettlauf gegen den ACP-Channel-Tod. Dies ist akzeptabel, da Recap kurz ist (einzelner Versuch, maxOutputTokens: 300, typischerweise ~1–5s); eine request-id-basierte Cancel-Ext-Methode kann in einer zukünftigen Version eine vollständige End-to-End-Abbruchlogik durchreichen, falls die Bandbreitenkosten dies jemals rechtfertigen.
POST /session/:id/generate
Capability-Tag: session_generation.
Führt anfrage-scopige Textgenerierung aus einem vom Aufrufer bereitgestellten Prompt aus. Die Anfrage liest oder mutiert keinen Konversationsverlauf und legt keine Tools offen. Sie bevorzugt das konfigurierte schnelle Modell und fällt auf das Hauptmodell der Session zurück, wenn das schnelle Modell fehlt oder nicht aufgelöst werden kann. Der Endpunkt ist aufgabenagnostisch; Übersetzung ist nur ein möglicher vom Aufrufer definierter Prompt.
Request:
{ "prompt": "Translate into Chinese: Hello" }Die Antwort ist text/event-stream. Der Server schreibt sofort einen initialen SSE-Kommentar, gefolgt von started, einem optionalen thinking-Fortschritts-Event, null oder mehr delta-Events und done. Das thinking-Event trägt keinen Reasoning-Inhalt. Ein Modellfehler nach Streaming-Start erzeugt ein error-Event; er wiederholt nicht mit einem anderen Modell. Prompts sind auf 32 KiB UTF-8-Text begrenzt. Das Trennen des HTTP-Clients annulliert die Generierungsanfrage.
Mutation: approval, tools, skills, init, MCP restart
Der Daemon legt fünf Mutations-Control-Routen offen, die es Remote-Clients ermöglichen, die Laufzeit-Haltung zu ändern, ohne die CLI des Daemon-Hosts zu berühren. Die Approval-Mode-Steuerung behält ihr nicht-striktes Kompatibilitäts-Gate bei. Tool-Toggle, Skill-Toggle, Workspace-Init und MCP-Restart verwenden das strikte Mutation-Gate: vertrauenswürdige Loopback-Primary-, Bearer-authentifizierte und gepaarte Local-Control-Anfragen passieren. Eine Token-lose Primary-Anfrage, die das Gate ohne Trusted-Loopback-Autorität erreicht, erhält 401 {code: 'token_required'}; fehlende oder ungültige konfigurierte Credentials und nicht gepaarte Local-Control-Credentials werden früher mit einfachem 401 Unauthorized abgelehnt. Alle fünf:
- Akzeptieren und stempeln den
X-Qwen-Client-Id-Header (PR 7 Audit-Chain). Wenn der Header eine vertrauenswürdige ID enthält, emittiert der DaemonoriginatorClientIdim entsprechenden SSE-Event, damit Cross-Client-UIs Echos eigener Mutationen unterdrücken können. - Akzeptieren und stempeln den
X-Qwen-Client-Id-Header (PR 7 Audit-Chain). Wenn der Header eine vertrauenswürdige ID enthält, emittiert der DaemonoriginatorClientIdim entsprechenden SSE-Event, damit Cross-Client-UIs Echos eigener Mutationen unterdrücken können. - Prüfen jede Per-Tag-Capability im Pre-Flight, bevor die Affordance verfügbar gemacht wird. Ein Daemon, dem eine Route fehlt, gibt
404zurück. Für die Skill-Settings-Routen kann das Fehlen eines Tags stattdessen bedeuten, dass dieselben Pfade den eingestellten katalogvalidierten Contract bedienen: Die Single-Target-Route kann HTTP404 skill_not_foundoder409 skill_not_toggleablezurückgeben, während die Batch-Route HTTP 200 mit katalogabgeleiteten Fehlern inerrors[]zurückgibt. Verwende Route-Probing nicht als Versionsprüfung.
Die Tool-Toggle-, Skill-Toggle-, Init- und MCP-Restart-Routen emittieren Workspace-scopige Events: Jeder aktive Session-SSE-Bus empfängt das Event, unabhängig davon, welche Session angehängt war, als die Mutation ausgelöst wurde. approval-mode emittiert ein Session-scopiges Event, da die Änderung lokal auf die Config einer einzelnen Session beschränkt ist.
POST /session/:id/approval-mode
Capability-Tag: session_approval_mode_control. Bridge → ACP extMethod qwen/control/session/approval_mode.
Ändere den Approval-Modus einer Live-Session. Der neue Modus landet sofort in der session-spezifischen Config des ACP-Childs. Einstellungen werden standardmäßig NICHT auf die Festplatte geschrieben – übergebe persist: true, um tools.approvalMode auch in die Workspace-Einstellungen zu schreiben.
Request:
{ "mode": "auto-edit", "persist": false }mode muss einer der folgenden Werte sein: 'plan' | 'default' | 'auto-edit' | 'auto' | 'yolo' (Spiegelung des ApprovalMode-Enums von Core; das SDK exportiert DAEMON_APPROVAL_MODES zur Laufzeitvalidierung). persist ist standardmäßig false.
Response (200):
{
"sessionId": "sess:42",
"mode": "auto-edit",
"previous": "default",
"persisted": false
}Fehler:
400 {code: 'invalid_approval_mode', allowed: [...]}— unbekannter Modus-Literal.400 {code: 'invalid_persist_flag'}—persistist nicht-boolean.403 {code: 'trust_gate', errorKind: 'auth_env_error'}— Der angeforderte Modus erfordert einen vertrauenswürdigen Ordner (privilegierte Modi in nicht vertrauenswürdigen Workspaces werden vonConfig.setApprovalModedes Cores abgelehnt).404— Session unbekannt.
SSE-Event (Session-scoped): approval_mode_changed mit {sessionId, previous, next, persisted, originatorClientId?}.
POST /workspace/tools/:name/enable
Capability-Tag: workspace_tool_toggle. Reine Datei-IO – kein ACP-Roundtrip.
Schalte einen Tool-Namen in der tools.disabled-Einstellungsliste des Workspaces um. Tools, die dort aufgeführt sind, werden gar nicht erst registriert (im Gegensatz zu permissions.deny, wo das Tool registriert bleibt, aber der Aufruf abgelehnt wird). Sowohl integrierte Tools als auch über MCP entdeckte Tools durchlaufen ToolRegistry.registerTool, was die Menge der deaktivierten Tools konsultiert.
⚠️ Namen müssen exakt mit dem vom Registry bereitgestellten Identifier übereinstimmen. Es findet keine Alias-Auflösung statt – die Route speichert den String aus dem Pfadparameter direkt in
tools.disabled, und das nächste ACP-Child vergleicht beim Registrieren mittool.name. Integrierte Tools verwenden ihren kanonischen Registry-Namen (snake_case-Verbform):run_shell_command,read_file,write_file,list_directory,glob,grep_search,web_fetchusw. – NICHT die Anzeige-Labels (Shell,Read,Write), die die CLI anzeigt. Über MCP entdeckte Tools verwenden die qualifizierte Formmcp__<server>__<name>(dies ist auch die Form, dietool_toggled-Events broadcasten und dieGET /workspace/mcpauflistet). Das Deaktivieren vonBashverhindert NICHT, dassrun_shell_commandin der nächsten Session registriert wird.
Live-ACP-Childs behalten bereits registrierte Tools – die Umschaltung wird erst beim Spawnen des nächsten ACP-Childs wirksam. Kombiniere dies mit POST /workspace/mcp/:server/restart (für MCP-Tools) oder der Erstellung einer neuen Session, um die Änderung im aktuellen Daemon wirksam zu machen.
Unbekannte Tool-Namen werden akzeptiert: Das vorzeitige Deaktivieren eines noch nicht installierten MCP-Tools ist ein legitimer Anwendungsfall.
Request:
{ "enabled": false }Response (200):
{ "toolName": "run_shell_command", "enabled": false }Fehler:
400 {code: 'invalid_tool_name'}— leerer Pfadparameter oder Pfadparameter überschreitet das Limit von 256 Zeichen.400 {code: 'invalid_enabled_flag'}—enabledfehlt oder ist nicht-boolean.
SSE-Event (Workspace-scoped): tool_toggled mit {toolName, enabled, originatorClientId?}.
POST /workspace/skills/:name/enable
Capability-Tag: workspace_skill_toggle. Die Workspace-qualifizierte Form ist POST /workspaces/:workspace/skills/:name/enable.
Aktualisiere die Workspace-Skill-Einstellungen für einen Namen, ohne den geladenen Skill-Katalog zu konsultieren. Der getrimmte Anfrage-Name wird an die Persistenz übergeben und in der Antwort zurückgegeben. Das Aktivieren eines beliebigen Namens zeichnet ein Workspace-skills.enabled-Opt-in auf, auch vor der Installation; das Deaktivieren entfernt dieses Opt-in und fügt einen Workspace-skills.disabled-Eintrag hinzu. Bestehende Einträge für nicht mehr geladene Skills werden beibehalten, und doppelte/Case-variante Einträge für das Ziel werden zusammengeführt. Ein Hard-skills.disabled-Eintrag, der von einem höheren Scope geerbt wurde, bleibt autoritativ für die effektive Verfügbarkeit, verhindert aber nicht, dass der Workspace-Scope seine eigene Deklaration aufzeichnet oder entfernt. Workspace-Deklarationen nehmen anderweitig an der üblichen skills.disabled > skills.enabled > skills.defaultDisabled-Auflösung teil und können höher-scope skills.defaultDisabled- oder skills.enabled-Einträge überschreiben.
Dies unterscheidet sich von der ACP-qwen/skills/setEnabled-Managed-Skill-Operation und dem disable-model-invocation-Frontmatter-Feld. Für einen aktiven Extension-Parent folgt die effektive Skill-Verfügbarkeit skills.disabled > skills.enabled > skills.defaultDisabled > Workspace-Internal-Override > Manifest-skillStates > enabled. Sowohl Hard- als auch Default-Deaktivierungen entfernen den Skill aus der Slash-Command/Modell-Verfügbarkeit und lehnen spätere Skill-Ausführung ab. disable-model-invocation: true hält die direkte Benutzer-Aufrufmöglichkeit verfügbar und verbirgt den Skill nur vor der Modellaufrufung.
Request:
{ "enabled": false }Response (200):
{
"skillName": "review",
"enabled": false,
"changed": true,
"activation": "applied",
"sessionsRefreshed": 2,
"sessionsFailed": 0
}activation spiegelt die Child-Liveness und jeden erforderlichen Refresh unabhängig von changed wider. Es ist applied, wenn ein ACP-Child live ist und jeder erforderliche Refresh erfolgreich ist, deferred, wenn kein Child zum Liveness-Check-Zeitpunkt live war oder eine geänderte Anfrage ihr Child/Session während des erforderlichen Refreshs verliert, und partial, wenn mindestens ein anderer erforderlicher Refresh fehlschlägt. Ein No-op kann daher applied oder deferred mit changed: false sein; wenn changed true ist und Activation deferred ist, wird die persistierte Deklaration verwendet, wenn ein Child startet. Busy Sessions sind in einem erforderlichen Refresh enthalten. Der Daemon lädt die Workspace-Einstellungen für das ACP-Child und jede aktive Session neu, benachrichtigt SkillManager-Consumer und pusht available_commands_update. Eine bereits an das Modell gesendete Anfrage wird nicht umgeschrieben; nachfolgende Validierung, Befehls-Snapshots und Modellkontexte verwenden den neuen Zustand. Wenn die Persistenz fehlschlägt, wird kein Refresh oder Event emittiert. Wenn ein Session-Refresh fehlschlägt, wird die committete Einstellung beibehalten. Wenn das Child pro-Session-Ergebnisse zurückgibt, sind die Session-Zahlen exakt. Wenn der Refresh-Control selbst vor der Rückgabe dieser Ergebnisse fehlschlägt, ist sessionsFailed: 1 eine konservative Untergrenze, die anzeigt, dass die Refresh-Anfrage fehlgeschlagen ist.
Fehler:
400 {code: 'invalid_skill_name'}— leerer Pfadparameter oder mehr als 256 Zeichen.400 {code: 'invalid_enabled_flag'}—enabledfehlt oder ist nicht-boolean.403 {code: 'untrusted_workspace'}— der ausgewählte Workspace ist nicht vertrauenswürdig.
Die Mutation verwendet das Workspace-scopige settings_changed-Event für jeden geänderten Key (skills.disabled und/oder skills.enabled); sie fügt keinen neuen Event-Typ hinzu. Jedes dieser Events enthält dasselbe mutation-Objekt: { id, kind: 'skill_toggle', skills: [{ name, enabled }], activation, sessionsRefreshed, sessionsFailed }. id korreliert jedes Settings-Event, das von einer Toggle-Anfrage erzeugt wird. skills listet die angeforderten Namen und angeforderten aktivierten Werte auf, deren Workspace-Settings-Deklarationen sich tatsächlich geändert haben; eine höher-scope Einstellung kann die effektive Verfügbarkeit unverändert lassen. Workspace-Skill-Status-Zellen enthalten optionale disabledReason: 'hard' | 'default' | 'inactive_extension'- und lockedScope: 'system' | 'user' | 'systemDefaults'-Felder.
POST /workspace/skills/enable
Capability-Tag: workspace_skill_batch_toggle. Die Workspace-qualifizierte Form ist POST /workspaces/:workspace/skills/enable.
Schalte bis zu 100 geladene Skills in einer Anfrage um; die Obergrenze zählt die rohen skillNames-Einträge vor der Deduplizierung. Namen werden getrimmt und case-insensitiv dedupliziert unter Beibehaltung der zuerst gesehenen Reihenfolge. Der Daemon konsultiert den geladenen Skill-Katalog nicht. Er wendet alle resultierenden Deklarationsänderungen in höchstens einem gesperrten Settings-Write an und aktualisiert, wenn sich etwas geändert hat, aktive Sessions einmal. Das Aktivieren zeichnet immer ein explizites Workspace-skills.enabled-Opt-in auf, einschließlich noch nicht installierter Namen, sodass es die interne Deaktivierung einer Extension überschreiben kann. Das Wiederholen einer identischen Deklaration bleibt ein No-op. Unerwartete Persistenz- oder Runtime-Generation-Fehler schlagen die gesamte Anfrage fehl.
Request:
{
"skillNames": ["review", "deploy", "missing"],
"enabled": false
}Response (200):
{
"enabled": false,
"activation": "applied",
"sessionsRefreshed": 2,
"sessionsFailed": 0,
"results": [
{
"skillName": "review",
"enabled": false,
"changed": true
},
{
"skillName": "deploy",
"enabled": false,
"changed": true
}
],
"errors": [
{
"skillName": "missing",
"code": "skill_not_found",
"error": "Skill not found: missing"
}
]
}Ziel-Fehler verwenden skill_not_found, skill_not_toggleable oder skill_inactive_extension. Fehlerhafte Anfragen geben HTTP 400 mit invalid_skill_names, invalid_skill_name oder invalid_enabled_flag zurück. Authentifizierung, Workspace-Trust, Client-Identität, unerwartete Persistenz-Fehler und Runtime-Generation-Fehler schlagen die gesamte Anfrage durch die Standard-Route-Gates fehl. errors bleibt für Wire-Kompatibilität in der Response und ist bei strukturell gültigen Namen leer. Batch-Level activation, sessionsRefreshed und sessionsFailed beschreiben die Child-Liveness und den einzigen Live-Session-Refresh, der von allen geänderten Ergebnissen geteilt wird. Ein Batch, in dem kein Ziel geändert wurde, kann dennoch applied antworten, wenn ein Child live ist, oder deferred, wenn keines existiert, passend zur Single-Skill-No-Op-Antwort – leite also tatsächlich Geändertes aus dem changed-Flag jedes Ergebnisses ab. Wenn mindestens ein Ziel sich ändert, emittiert der Daemon dieselben settings_changed-Mutations-Metadaten wie die Single-Skill-Route; jedes skills.disabled- / skills.enabled-Event von dieser Anfrage teilt eine mutation.id.
POST /workspace/init
Capability-Tag: workspace_init. Reine Datei-IO – kein ACP-Roundtrip, kein LLM-Aufruf.
Erstelle eine leere QWEN.md (oder den Workspace-context.fileName-Settings-Override) im primären Workspace-Root des Daemons. Rein mechanisch – für KI-gestütztes Füllen von Inhalten folge mit POST /session/:id/prompt.
Standardmäßig wird das Überschreiben verweigert, wenn die Zieldatei Nicht-Whitespace-Inhalte enthält. Dateien nur mit Whitespace werden als nicht vorhanden behandelt (entspricht dem lokalen /init-Slash-Command).
Request:
{ "force": false }Response (200):
{ "path": "/work/bound/QWEN.md", "action": "created" }action ist 'created' für Neuerstellungen, 'noop', wenn eine bestehende Whitespace-only-Datei unberührt blieb (kein Schreibvorgang), und 'overwrote', wenn force: true nicht-leere Inhalte ersetzt hat. Das workspace_initialized-SSE-Event spiegelt die Response-Action wider – Observer können nach action !== 'noop' filtern, um nur auf tatsächliche Änderungen auf der Festplatte zu reagieren.
Fehler:
400 {code: 'invalid_force_flag'}—forceist nicht-boolean.409 {code: 'workspace_init_conflict', path, existingSize}— Datei existiert mit Nicht-Whitespace-Inhalten undforcefehlt oder ist false. Der Body enthält den absoluten Pfad und die Größe (Bytes), damit SDK-Clients einen “N Bytes überschreiben?”-Prompt rendern können, ohne die Datei erneut zu staten.
SSE-Event (Workspace-scoped): workspace_initialized mit {path, action, originatorClientId?}.
POST /workspace/mcp/reload
Lade persistierte MCP-Einstellungen in die Workspace-Discovery-Konfiguration und jede
aktive Session neu. Die Workspace-qualifizierte Form ist
POST /workspaces/:workspace/mcp/reload.
Request-Body:
{ "forceReconnectAll": true }forceReconnectAll ist optional und standardmäßig false, erhält
inkrementelle Reconciliation. Wenn true, verbindet der Daemon jeden berechtigten
konfigurierten MCP-Server nach der Settings-Reconciliation neu. Alternativ übergib
forceReconnectWhich: ["server-a", "server-b"], um nur benannte Server neu zu verbinden.
Die Optionen schließen sich gegenseitig aus. Ein erzwungener Reconnect verursacht, dass jeder
Transport Credentials liest, die ein anderer lokaler Qwen-Code-Prozess in den Token-Speicher
geschrieben haben könnte; er startet keinen OAuth-Autorisierungs-Flow.
Die Route gibt 202 { "accepted": true } zurück; polle GET /workspace/mcp für
den endgültigen Verbindungsstatus. Ungültige Optionswerte geben 400 zurück.
POST /workspace/mcp/:server/restart
Capability-Tag: workspace_mcp_restart. Bridge → ACP extMethod qwen/control/workspace/mcp/restart.
Starte einen konfigurierten MCP-Server über McpClientManager.discoverMcpToolsForServer des ACP-Childs neu (Disconnect + Reconnect + Rediscover). Prüft vorab den Live-Budget-Snapshot aus dem Accounting von PR 14 v1, sodass ein Neustart in einem Budget-gesättigten Workspace einen Soft-Refusal zurückgibt, anstatt eine BudgetExhaustedError-Kaskade auszulösen.
Der Request-Body ist leer ({}). Der Pfadparameter ist der URL-kodierte Servername, wie er in der mcpServers-Konfiguration erscheint.
Response (200) – discriminated Union auf restarted:
{ "serverName": "docs", "restarted": true, "durationMs": 1234 }{
"serverName": "docs",
"restarted": false,
"skipped": true,
"reason": "budget_would_exceed"
}Gründe für Soft-Skips (alle geben 200 zurück):
reason | Meaning |
|---|---|
'in_flight' | Eine weitere Discovery / ein weiterer Neustart für diesen Server ist bereits im Gange. Die Route kehrt sofort zurück, anstatt auf das ursprüngliche Promise zu warten. Der Caller sollte nach einer kurzen Verzögerung erneut versuchen. |
'disabled' | Server ist konfiguriert, aber in excludedMcpServers aufgeführt. Vor dem Neustart wieder aktivieren. |
'budget_would_exceed' | Daemon ist auf --mcp-budget-mode=enforce gesetzt, der Zielserver befindet sich derzeit nicht in reservedSlots und der Live-Gesamtwert hat clientBudget erreicht. Der Caller sollte zuerst einen Slot freigeben. |
Fehler (non-2xx):
400 {code: 'invalid_server_name'}— leerer Pfadparameter.404— Servername nicht in dermcpServers-Konfiguration oder kein Live-ACP-Channel vorhanden (Neustart erfordert zwingend eine Live-McpClientManager-Instanz).500— interner Fehler (z. B.ToolRegistrynicht initialisiert).
SSE-Events (Workspace-scoped): mcp_server_restarted mit {serverName, durationMs, originatorClientId?} bei Erfolg; mcp_server_restart_refused mit {serverName, reason, originatorClientId?} bei Soft-Skip.
GET /session/:id/events (SSE)
Abonniere den Event-Stream der Session.
Headers:
Accept: text/event-stream
Last-Event-ID: 42 ← optional, replays from after id 42
X-Qwen-Event-Epoch: ... ← optional, paart den Cursor mit seiner Bus-Epoche
X-Qwen-Client-Id: ... ← optionale Client-Identität und diagnostische KorrelationQuery-Params:
| Param | Required | Notes |
|---|---|---|
maxQueued | nein | Obergrenze für den Live-Frame-Backlog pro Subscriber. Bereich [16, 2048], Standard 256. Replay-Frames, die beim Abonnieren forciert gepusht werden, sind von den Frame- und Byte-Obergrenzen ausgenommen; was sie tatsächlich verbraucht, sind Live-Events, die eintreffen, während der Subscriber noch einen großen Last-Event-ID: 0-Replay abarbeitet. Erhöhe diesen Wert für Cold-Reconnects, damit der Live-Tail nicht die Slow-Client-Warnung / Eviction auslöst, bevor der Consumer aufgeholt hat. Die Obergrenze für serialisierte Live-Bytes ist daemonseitig fest (Standard 2 MiB) und hat keinen Query-Parameter. Werte außerhalb des Bereichs / nicht-dezimal / vorhanden aber leer geben 400 invalid_max_queued zurück, bevor der SSE-Handshake geöffnet wird. Pre-Flight caps.features.slow_client_warning – alte Daemons ignorieren den Parameter stillschweigend. |
connectReason | nein | Vom Client gemeldeter diagnostischer Hinweis: initial, resume, prompt_restart, stream_end, transport_error, state_resync oder unknown. Ungültige Werte werden auf unknown normalisiert und lehnen den Handshake niemals ab. Der Daemon verwendet dieses Feld nicht für Auth, Replay, Eviction, Deduplizierung oder Stream-Ersetzung. |
previousStreamId | nein | UUID des vom Client gemeldeten vorherigen akzeptierten REST/SSE-Streams. Ungültige Werte werden ignoriert. Dies ist nur Best-Effort-Lineage und ändert niemals das Stream-Verhalten. |
Ein erfolgreicher Handshake enthält X-Qwen-SSE-Stream-Id: <uuid>. Browser-Gateways müssen diesen Response-Header beibehalten und über Access-Control-Expose-Headers offenlegen. Alte Daemons oder Intermediaries können ihn weglassen; Clients müssen normal fortfahren und Lineage als nicht verfügbar behandeln. Die ID identifiziert diese physische REST/SSE-Verbindung und korreliert ihren Daemon-Lifecycle, Queue-Diagnostik und Request-Trace.
Frame-Format. Die data:-Zeile ist das vollständige Event-Envelope, JSON-stringified in einer einzigen Zeile – {id?, v, type, data, originatorClientId?}. Das ACP-spezifische Payload (sessionUpdate, requestPermission-Argumente usw.) befindet sich im data-Feld des Envelopes; der eigene type des Envelopes entspricht der SSE-event:-Zeile.
id: 7
event: session_update
data: {"id":7,"v":1,"type":"session_update","data":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"…"}}}
id: 8
event: permission_request
data: {"id":8,"v":1,"type":"permission_request","data":{"requestId":"<uuid>","sessionId":"<sid>","toolCall":{...},"options":[...]}}
: heartbeat ← every 15s, no payload
event: client_evicted ← terminal frame, no id (synthetic)
data: {"v":1,"type":"client_evicted","data":{"reason":"queue_overflow","droppedAfter":42,"queueSize":256,"maxQueued":256,"queuedBytes":1800000,"maxQueuedBytes":2097152}}
event: client_evicted ← terminal frame for byte overflow, no id (synthetic)
data: {"v":1,"type":"client_evicted","data":{"reason":"queue_bytes_overflow","droppedAfter":43,"queueSize":1,"maxQueued":256,"queuedBytes":1900000,"maxQueuedBytes":2097152,"eventBytes":300000}}Die SSE-id:- / event:-Zeilen duplizieren envelope.id / envelope.type für EventSource-Kompatibilität. Raw-fetch-Consumer (wie parseSseStream des SDKs) lesen alles aus dem JSON-Envelope und ignorieren die SSE-Preamble-Zeilen.
| Ereignistyp | Trigger |
|---|---|
session_update | Jede ACP sessionUpdate-Benachrichtigung (LLM-Chunks, Tool-Aufrufe, Nutzung) |
permission_request | Agent hat um Tool-Genehmigung gebeten |
permission_resolved | Ein Client hat über POST /permission/:requestId für eine Berechtigung abgestimmt |
permission_partial_vote | (nur Consensus) Eine Stimme wurde erfasst, aber das Quorum ist noch nicht erreicht. Enthält {requestId, sessionId, votesReceived, votesNeeded, quorum, optionTallies}. Pre-Flight caps.features.permission_mediation. |
permission_forbidden | Eine Stimme wurde von der aktiven Richtlinie abgelehnt (designated-Mismatch, local-only Non-Loopback oder consensus-Voter nicht im Snapshot). Enthält {requestId, sessionId, clientId?, reason}. Pre-Flight caps.features.permission_mediation. |
model_switched | POST /session/:id/model erfolgreich |
model_switch_failed | POST /session/:id/model abgelehnt |
session_died | Agent-Child ist unerwartet abgestürzt. Terminal: Der SSE-Stream wird nach diesem Frame geschlossen; die Session ist aus byId entfernt. Subscriber sollten sich über POST /session neu verbinden, um eine neue Session zu erstellen. |
slow_client_warning | Subscriber-lokal: Live-Frame-Backlog oder Live-Serialized-Byte-Backlog ≥ 75 % voll. Non-terminal — der Stream läuft weiter; die Warnung ist eine Vorwarnung vor der Evakuierung. Enthält {queueSize, maxQueued, lastEventId, queuedBytes?, maxQueuedBytes?, threshold?}, wobei threshold frames, bytes oder frames_and_bytes ist. Wird EINMAL pro Überlauf-Episode ausgelöst; wird scharfgeschaltet, nachdem beide Werte wieder unter 37,5 % gefallen sind. Keine id (synthetisch). Pre-Flight caps.features.slow_client_warning. |
client_evicted | Subscriber-lokal: Queue-Überlauf. reason ist queue_overflow für das Live-Frame-Limit und queue_bytes_overflow für das Live-Serialized-Byte-Limit. Terminal: Der SSE-Stream wird nach diesem Frame geschlossen (keine id — synthetisch). Andere Subscriber auf derselben Session laufen weiter. |
stream_error | Daemon-seitiger Fehler beim Fan-out. Terminal: Der SSE-Stream wird nach diesem Frame geschlossen (keine id — synthetisch). |
Reconnect-Semantik:
- Sende
Last-Event-ID: <n>, um Ereignisse mitid > naus dem Session-Ring abzuspielen (Standardtiefe 8000, einstellbar überqwen serve --event-ring-size <n>). - Gap-Erkennung: Wenn
<n>älter ist als das älteste noch im Ring befindliche Ereignis, emittiert der Daemon einen ID-losenstate_resync_required-Frame, bevor das überlebende Suffix abgespielt wird. Das SDK latchtawaitingResync; Clients solltenPOST /session/:id/loadaufrufen und aus dem aktuellen begrenzten Replay-Snapshot-Fenster neu aufbauen. Dieser Snapshot kann selbst mithistory_truncatedbeginnen, wenn ältere In-Memory-Replay-Einträge verworfen wurden; dieser Marker ist informativ und darf keine weitere Resync-Schleife starten. - IDs sind pro Session monoton steigend, beginnend bei 1
- Synthetische Frames (
client_evicted,slow_client_warning,stream_error) lassen absichtlich dieidweg, damit sie keinen Sequenz-Slot für andere Subscriber verbrauchen
Backpressure:
- Die pro-Subscriber-Queue hat standardmäßig
maxQueued: 256Live-Items plus ein daemon-eigenes Live-Serialized-Byte-Limit von 2 MiB. Replay-Frames während des Reconnects,slow_client_warningundclient_evictedumgehen beide Limits. - Überschreibe nur das Frame-Limit über
?maxQueued=N(Bereich[16, 2048]) in der SSE-Anfrage. Es gibt absichtlich kein?maxQueuedBytes; Clients können das Daemon-Speicherbudget nicht erhöhen. - Wenn das Live-Frame-Backlog oder das Live-Byte-Backlog eines Subscribers 75 % Füllstand überschreitet, pusht der Bus zwangsweise einen
slow_client_warning-Synthetic-Frame an diesen Subscriber (einmal pro Überlauf-Episode; wird scharfgeschaltet, nachdem beide Werte wieder unter 37,5 % gefallen sind). Der Stream bleibt offen — die Warnung ist eine Vorwarnung, damit der Client die Queue schneller abarbeiten oder sich sauber trennen und neu verbinden kann. - Wenn das Live-Frame-Limit überläuft, gibt der Bus
client_evictedmitreason: "queue_overflow"aus. Wenn das Live-Byte-Limit überläuft, gibt erreason: "queue_bytes_overflow"aus. In beiden Fällen wird das Terminal-Frame zwangsgepusht und das Abonnement geschlossen.
POST /session/:id/permission/:requestId
Gib dieselbe unten dokumentierte Stimme ab, aber route sie über die Runtime, die
die genannte Live-Session besitzt. Neue Multi-Workspace-Integrationen sollten
diese Form statt der Legacy-prozessglobalen Route verwenden. Pre-Flight
caps.features.session_permission_vote.
Request-Body, Mediationsrichtlinien, Outcomes und die Erfolgsantwort sind
identisch mit POST /permission/:requestId. Der optionale
X-Qwen-Client-Id-Header wirkt bei designated- und consensus-Richtlinien mit.
Fehler verwenden stabile code-Werte wo angegeben; fehlerhafte Eingaben und ein
verlorener Pending-Request-Race können code weglassen:
400— ein fehlerhafter Vote-Body (keincode) oder eine ungültige Client-Identität (invalid_client_id), oderinvalid_option_idwenn die gewählte Option nicht angeboten wurde. Lies die angebotenen Optionen erneut, statt dieselbe Stimme erneut zu versuchen.403—permission_forbiddenwenn die aktive Richtlinie den Voter ablehnt, oderuntrusted_workspacewenn ein nicht primärer besitzender Workspace nicht vertrauenswürdig ist. Ein nicht vertrauenswürdiger Primary-Owner ist von dieser Trust-Prüfung ausgenommen und die Stimme kann akzeptiert werden.404—session_not_foundwenn kein Live-Owner existiert, oder keincodewenn die Anfrage nicht ausstehend ist.500—cancel_sentinel_collisionwenn dieallowedOptionIdsdes Agents das reservierte__cancelled__-Sentinel enthält, oderambiguous_session_ownerwenn mehr als ein Workspace die Session beansprucht.501—permission_policy_not_implementedfür eine Richtlinie, die dieser Build nicht implementiert.503—workspace_runtime_unavailablewenn die besitzende Runtime nicht verfügbar ist, oderdaemon_drainingwenn der Daemon keine Arbeit mehr annimmt.
Es wird niemals gegen die Primary-Bridge retried. Die TypeScript-SDK-Methode ist
respondToSessionPermission().
POST /permission/:requestId
Stimme über eine ausstehende permission_request ab. Die aktive Mediationsrichtlinie entscheidet, wer gewinnt:
| Richtlinie | Verhalten |
|---|---|
first-responder (Standard) | Jeder validierte Voter gewinnt; spätere Voter erhalten 404. Pre-F3-Baseline. |
designated | Nur der Prompt-Ursprungsclient (originatorClientId) entscheidet; Nicht-Ursprungsclients erhalten 403 permission_forbidden / designated_mismatch. Fällt bei anonymen Prompts auf First-Responder zurück. |
consensus | N von M Voter müssen zustimmen (Standard N = floor(M/2) + 1, überschreibbar über policy.consensusQuorum). Die erste Option, die N erreicht, gewinnt. Nicht-auflösende Stimmen erhalten 200 + permission_partial_vote-SSE-Frames. |
local-only | Nur Loopback-Voter entscheiden; Remote-Caller erhalten 403 permission_forbidden / remote_not_allowed. |
Die aktive Richtlinie wird in settings.json unter policy.permissionStrategy konfiguriert und auf /capabilities unter body.policy.permission angezeigt. Pre-Flight caps.features.permission_mediation (mit modes: [...]) für den Build-unterstützten Satz.
F3 (#4175): Multi-Client-Berechtigungskoordination. F3 hat die vier obigen Richtlinien hinzugefügt. Pre-F3-Daemons haben First-Responder hartcodiert; das Wire-Format bleibt Bit für Bit unverändert, wenn die konfigurierte Richtlinie
first-responderist. Neue Ereignisse (permission_partial_vote,permission_forbidden) sind additiv — alte SDKs sehen sie alsunrecognized_known_eventund ignorieren sie sicher.
Permission-Timeout (standardmäßig deaktiviert). Eine
permission_requestbleibt ausstehend, bis: (a) ein Client hier abstimmt, (b)POST /session/:id/cancelausgelöst wird, (c) der HTTP-Client, der den Prompt steuert, die Verbindung trennt (Mid-Prompt-Cancel löst ausstehende Berechtigungen alscancelledauf), (d) die Session beendet wird, (e) der Daemon herunterfährt oder (f) sein konfigurierter Timeout auslöst. Beim Auslösen des Timeouts wirdrequestPermissiondes Agents als{outcome: 'cancelled'}aufgelöst, der Audit-Ring zeichnet einenpermission.timeout-Eintrag auf, der Daemon-Stderr gibt einen einzeiligen Breadcrumb aus und der SSE-Bus verteilt das Standard-permission_resolved-Cancelled-Frame, damit Subscriber aufräumen können. Der gemeinsame Timeout ist konfigurierbar überBridgeOptions.permissionResponseTimeoutMsoderqwen serve --permission-response-timeout-ms. Sein Standardwert ist0, sodass sowohl gewöhnliche Berechtigungen als auchask_user_questionunbegrenzt auf eine menschliche Entscheidung warten. Voter-Cancellation, Session-Cancellation, Disconnect-Bereinigung und Daemon-Shutdown lösen ausstehende Interaktionen weiterhin als abgebrochen auf.
Anfrage:
{
"outcome": {
"outcome": "selected",
"optionId": "proceed_once"
}
}Ergebnisse:
{ "outcome": "selected", "optionId": "<one-of-the-options>" }— akzeptieren / ablehnen / proceed-once / usw., je nach den vom Agent angebotenen Auswahlmöglichkeiten{ "outcome": "cancelled" }— die Anfrage verwerfen (entspricht dem, wascancelSession/shutdownintern tun)
Antwort:
200 {}— deine Stimme wurde akzeptiert (aufgelöst ODER unter Consensus-Quorum erfasst)400— ein fehlerhafter Vote-Body (keincode),invalid_client_idoderinvalid_option_idwenn die gewählte Option nicht angeboten wurde; lies die angebotenen Optionen erneut, statt dieselbe Stimme erneut zu versuchen403 { "code": "permission_forbidden", "reason": "designated_mismatch" | "remote_not_allowed", "requestId", "sessionId" }— F3: Die aktive Richtlinie hat deine Stimme abgelehnt404 { "error": "..." }— dierequestIdist unbekannt (bereits aufgelöst, hat nie existiert oder Session wurde abgebaut)500 { "code": "cancel_sentinel_collision", ... }— F3: DieallowedOptionIdsdes Agents enthält das reservierte Sentinel'__cancelled__'; Verstoß gegen den Agent/Daemon-Vertrag501 { "code": "permission_policy_not_implemented", "policy": "<name>" }— F3 Forward-Compat: Ein Richtlinien-Literal ist im Schema gelandet, aber sein Mediator-Branch ist noch nicht gebaut (derzeit nicht erreichbar; für zukünftige Richtlinien reserviert)
Nach einer erfolgreichen Abstimmung sieht jeder verbundene Client permission_resolved mit derselben requestId und dem gewählten outcome. Unter consensus werden bei Zwischenabstimmungen zusätzlich permission_partial_vote-Events verteilt, bis das Quorum erreicht ist.
Auth-Device-Flow-Routen (Issue #4175 PR 21)
Der Daemon vermittelt einen OAuth 2.0 Device Authorization Grant (RFC 8628), sodass ein entfernter SDK-Client einen Login auslösen kann, dessen Tokens auf dem Dateisystem des Daemons landen – und nicht auf dem des Clients. Der Daemon pollt den IdP selbst; die einzige Aufgabe des Clients besteht darin, die Verifizierungs-URL + den User Code anzuzeigen und (optional) SSE für Abschluss-Events zu abonnieren.
Capability-Tag: auth_device_flow (wird immer advertised). Unterstützte Provider in
v1: qwen-oauth.
Der Qwen OAuth Free Tier wurde am 15.04.2026 eingestellt. Behandle qwen-oauth in
diesem Protokoll als den Legacy-v1-Provider-Identifier; neue Clients sollten
bevorzugt einen aktuell unterstützten Auth-Provider verwenden, sofern einer verfügbar ist.
Runtime-Lokalität. Der Daemon öffnet niemals einen Browser – selbst wenn er dazu in der Lage wäre. Der Client entscheidet, ob er open(verificationUri) lokal aufruft; auf einem Headless-Pod (dem kanonischen Mode-B-Deployment) öffnet der Benutzer die URL auf einem beliebigen Gerät, auf dem er einen Browser hat. Siehe docs/users/qwen-serve.md für die empfohlene UX.
Kein Token-Leakage in Events. auth_device_flow_started enthält nur {deviceFlowId, providerId, expiresAt}. User Code und Verifizierungs-URL werden Punkt-zu-Punkt im POST-201-Body und über GET /workspace/auth/device-flow/:id zurückgegeben; sie werden niemals per SSE broadcastet.
Pro-Provider-Singleton. Ein zweiter POST für denselben Provider, während ein Flow noch aussteht, ist ein idempotentes Take-over – er gibt den bestehenden Eintrag mit attached: true zurück, anstatt eine neue IdP-Anfrage zu starten.
POST /workspace/auth/device-flow
Striktes Mutations-Gate: Token-lose Trusted-Loopback-Primary-Anfragen passieren. Eine token-lose Primary-Anfrage, die das Gate ohne Trusted-Loopback-Autorität erreicht, erhält 401 token_required; fehlende oder ungültige konfigurierte Credentials und nicht gepaarte Local-Control-Credentials werden früher mit einfachem 401 Unauthorized abgelehnt.
Request:
{ "providerId": "qwen-oauth" }Response (201 für einen neuen Start, 200 für idempotentes Take-over):
{
"deviceFlowId": "fa07c61b-…",
"providerId": "qwen-oauth",
"status": "pending",
"userCode": "USER-1",
"verificationUri": "https://chat.qwen.ai/api/v1/oauth2/device",
"verificationUriComplete": "https://chat.qwen.ai/api/v1/oauth2/device?user_code=USER-1",
"expiresAt": 1700000600000,
"intervalMs": 5000,
"attached": false
}Errors:
400 unsupported_provider— unbekannteproviderId(Response enthältsupportedProviders)409 too_many_active_flows— Workspace-Cap (4) erreicht; einen mitDELETEabbrechen401 token_required— Strict-Gate hat eine token-lose Primary-Anfrage ohne Trusted-Loopback-Autorität abgelehnt502 upstream_error— IdP hat einen unerwarteten Fehler zurückgegeben
GET /workspace/auth/device-flow/:id
Liest den aktuellen Status. Ausstehende Einträge geben userCode/verificationUri/expiresAt/intervalMs zurück; terminale Einträge (5-Minuten-Gnadenfrist) lassen diese weg und zeigen stattdessen status + optionales errorKind/hint an.
Gibt 404 device_flow_not_found für unbekannte IDs und nach Ablauf der Gnadenfrist entfernte Einträge zurück.
DELETE /workspace/auth/device-flow/:id
Idempotentes Abbrechen:
- ausstehender Eintrag →
204+auth_device_flow_cancelledwird emitted - terminaler Eintrag →
204No-op (kein erneutes Emitting des Events) - unbekannte ID →
404
GET /workspace/auth/status
Snapshot der ausstehenden Flows + unterstützter Provider:
{
"v": 1,
"workspaceCwd": "/work/bound",
"providers": [],
"pendingDeviceFlows": [
{
"deviceFlowId": "fa07c61b-…",
"providerId": "qwen-oauth",
"expiresAt": 1700000600000
}
],
"supportedDeviceFlowProviders": ["qwen-oauth"]
}Device-flow SSE-Events
Fünf typisierte Events (Workspace-scoped, werden an jeden aktiven Session-Bus verteilt):
auth_device_flow_started{deviceFlowId, providerId, expiresAt}— POST erfolgreich; SDK sollte abonnieren (kein userCode enthalten, bei Bedarf per GET abrufen)auth_device_flow_throttled{deviceFlowId, intervalMs}— Daemon hat das Upstream-slow_downberücksichtigt; Clients, die GET pollen, sollten ihr Intervall entsprechend erhöhenauth_device_flow_authorized{deviceFlowId, providerId, expiresAt?, accountAlias?}— Credentials persistiert;accountAliasist ein Non-PII-Label (niemals E-Mail/Telefon)auth_device_flow_failed{deviceFlowId, errorKind, hint?}— terminal;errorKindist einer der Werteexpired_token | access_denied | invalid_grant | upstream_error | persist_failed.persist_failedist daemon-intern: Der IdP-Austausch war erfolgreich, aber der Daemon konnte die Credentials nicht dauerhaft speichern (EACCES / EROFS / ENOSPC). Der Benutzer sollte es erneut versuchen, sobald das zugrunde liegende Speicherproblem behoben ist.auth_device_flow_cancelled{deviceFlowId}— DELETE bei einem ausstehenden Eintrag erfolgreich
Nicht MCP-kompatibel. Die MCP-Authorization-Spec (2025-06-18) erfordert OAuth 2.1 + PKCE Auth-Code mit einem Redirect-Callback, was für Headless-Pod-Daemons nicht funktioniert. Die Device-Flow-Surface von Mode B ist daemon-privat – Clients, die auf MCP-konforme Server abzielen, sollten einen anderen Auth-Pfad verwenden.
Streaming-Wire-Format
Events werden als Standard-EventSource-Frames emittiert. Der Daemon schreibt pro Frame eine data:-Zeile (das JSON enthält nach JSON.stringify keine eingebetteten Newlines); der SDK-Parser unter packages/sdk-typescript/src/daemon/sse.ts verarbeitet sowohl dieses Format als auch die spezifikationskonforme Multi-data:-Form auf der Empfangsseite.
Error-Frames beim Streaming
Wenn der Bridge-Iterator beim Bedienen eines SSE-Subscribers eine Exception wirft, emittiert der Daemon ein terminales stream_error-Frame (keine id). Die data:-Zeile ist die vollständige Envelope (hat dieselbe Form wie jedes andere SSE-Frame in diesem Dokument); die eigentliche Fehlermeldung befindet sich unter envelope.data.error:
event: stream_error
data: {"v":1,"type":"stream_error","data":{"error":"<message>"}}Die Verbindung wird anschließend geschlossen.
Umgebungsvariablen
| Var | Zweck |
|---|---|
QWEN_SERVER_TOKEN | Bearer-Token. Wird beim Start um führende und nachfolgende Whitespaces bereinigt. |
Source-Layout
| Path | Zweck |
|---|---|
packages/cli/src/commands/serve.ts | yargs-Command + Flag-Schema |
packages/cli/src/serve/run-qwen-serve.ts | Listener-Lifecycle + Signal-Handling |
packages/cli/src/serve/server.ts | Express-App-Assembly, Middleware-Reihenfolge und verbleibende direkte Routen |
packages/cli/src/serve/routes/*.ts | Fokussierte Express-Route-Gruppen, einschließlich Session, SSE, Workspace-Auth, Workspace-Status und File-Routen |
packages/cli/src/serve/auth.ts | Bearer + Host-Allowlist + CORS-Deny |
packages/cli/src/serve/acp-session-bridge.ts | CLI-lokale Bridge-Kompatibilitäts-Fassade für Spawn-or-Attach, Session-FIFO und Permission-Registry |
packages/acp-bridge/src/status.ts | Read-only Daemon-Status-Wire-Types + ServeErrorKind + BridgeTimeoutError + mapDomainErrorToErrorKind |
packages/cli/src/serve/env-snapshot.ts | Pure Helper, der /workspace/env-Payloads aus dem process.*-Status erstellt, einschließlich Credential-Redaction |
packages/acp-bridge/src/eventBus.ts | Bounded Async Queue + Replay-Ring |
packages/sdk-typescript/src/daemon/DaemonClient.ts | TS-Client |
packages/sdk-typescript/src/daemon/sse.ts | EventSource-Frame-Parser |
integration-tests/cli/qwen-serve-routes.test.ts | 18 Cases, kein LLM |
integration-tests/cli/qwen-serve-streaming.test.ts | 3 Cases, echter qwen --acp-Child-Prozess, unterstützt vom lokalen Fake-OpenAI-Server (nur POSIX; wird unter Windows übersprungen) |