Skip to Content
EntwicklerhandbuchDaemonTyped Daemon Event Schema v1

Typed Daemon Event Schema v1

Übersicht

Jeder vom Daemon auf GET /session/:id/events ausgegebene SSE-Frame hat die Form { id, v, type, data, originatorClientId?, _meta? }. v: 1 ist die aktuelle EVENT_SCHEMA_VERSION. type stammt aus dem abgeschlossenen, versionsgebundenen DAEMON_KNOWN_EVENT_TYPE_VALUES-Set in packages/sdk-typescript/src/daemon/events.ts. Das _meta-Feld des Envelopes wird an der SSE-Schreibgrenze von formatSseFrame() in packages/cli/src/serve/routes/sse-events.ts gestempelt; siehe Metadaten auf Envelope-Ebene.

Das SDK stellt asKnownDaemonEvent(evt) bereit. Es gibt ein diskriminiertes KnownDaemonEvent für bekannte Event-Typen und undefined für andere Typen zurück. SDK-Consumer können dadurch Forward Compatibility handhaben, ohne ein gleichzeitiges SDK-Upgrade zu benötigen, wenn ein neuerer Daemon einen Event-Typ hinzufügt; der Session-Reducer erfasst diese als unrecognizedKnownEventCount.

Das Wire-Format befindet sich in ../qwen-serve-protocol.md. Diese Seite definiert den Payload-Vertrag für jedes Event.

Verantwortlichkeiten

  • Bereitstellung der Single Source of Truth für das Event-Vokabular (DAEMON_KNOWN_EVENT_TYPE_VALUES).
  • Bereitstellung eines typisierten Envelopes für jeden Event-Typ (DaemonEventEnvelope<TType, TData>).
  • Bereitstellung reiner Reducer (reduceDaemonSessionEvent, reduceDaemonAuthEvent), die einen Event-Stream in den SDK-View-State projizieren.
  • Broadcast des typed_event_schema Capability-Tags als Informationssignal. Wenn das Tag fehlt, fällt asKnownDaemonEvent dennoch auf unknown zurück.

Event-Vokabular

Gruppiert nach Domäne.

Core-Session

TypRichtungTriggerWichtige Payload-Felder
session_updateS->CJede ACP-sessionUpdate-Benachrichtigung: Agent-Text, Thought, Tool-Call oder PlansessionUpdate: string, content?: ... (opaque ACP-Form)
session_metadata_updatedS->CPATCH /session/:id/metadatasessionId, displayName?
session_diedS->C terminalchannel.exitedsessionId, reason, exitCode? | null, signalCode? | null
session_closedS->C terminalDELETE /session/:id oder programmatisches SchließensessionId, reason: 'client_close' | string, closedBy?
session_snapshotS->C syntheticSnapshot-Frame nach SSE-Attach / ReplaysessionId, currentModelId: string | null, currentApprovalMode: string | null, recordingDegraded: boolean
session_recording_degradedS->CDer Session-Transcript-Writer hat nach einem asynchronen Schreibfehler permanent gestopptsessionId, reason: 'write_failed'

Synthetic Frames auf Subscriber-Ebene

TypTriggerHinweise
client_evictedEventBus-Queue-Überlauf pro Subscriber. Keine idreason: 'queue_overflow' | 'queue_bytes_overflow' | string, droppedAfter?: number, queueSize?: number, maxQueued?: number, queuedBytes?: number, maxQueuedBytes?: number, eventBytes?: number; terminal nur für den aktuellen Subscriber, während die Session aktiv bleibt.
slow_client_warningLive-Frame-Backlog oder Live-Serialisierte-Bytes-Backlog >= 75%; force-pushed und hat keine idqueueSize, maxQueued, lastEventId, queuedBytes?, maxQueuedBytes?, threshold?: 'frames' | 'bytes' | 'frames_and_bytes'; wird erneut aktiviert, nachdem sowohl Frame- als auch Byte-Messungen unter 37,5 % fallen.
stream_errorSubscriberLimitExceededError oder ein anderer Route-Stream-Fehlererror: string; terminal für die Subscription.
state_resync_requiredsubscribe({lastEventId}) erkennt, dass der Daemon-Ring nicht mehr [lastEventId+1, earliestInRing-1] enthält oder der Client-Cursor aus einer vorherigen Bus-Epoche stammt. Force-pushed vor den verbleibenden Replay-Frames und hat keine id.reason: 'ring_evicted' | 'epoch_reset' | string, lastDeliveredId: number, earliestAvailableId: number. Dies ist ein Recovery-Signal, nicht terminal: der SSE-Stream bleibt offen und Replay- sowie Live-Frames werden fortgesetzt. Der SDK-Reducer setzt awaitingResync = true und überspringt Deltas, bis der Caller mit loadSession zurücksetzt.
history_truncatedPOST /session/:id/load gibt einen begrenzten Replay-Snapshot zurück, nachdem ältere In-Memory-Replay-Einträge verworfen wurden. Vorangestellt vor compactedReplay und hat keine id.reason: 'replay_window_exceeded', truncatedEvents: number, retainedEvents: number, maxBytes: number, truncatedTurns?: number, fullTranscriptAvailable: boolean. Dies ist ein Statusmarker, keine Resync-Aufforderung; Clients rendern ihn und setzen die Anwendung des behaltenen Replays fort.
replay_completeID-loses Sentinel, das ausgegeben wird, nachdem die Last-Event-ID-Replay-Schleife abgeschlossen ist, für sowohl sauberes Replay als auch Ring-Evicted-Pfade, selbst wenn data.replayedCount === 0. Keine idreplayedCount: number; ermöglicht es Consumern, die Catch-up-UI deterministisch ohne Timeout zu entfernen.

fullTranscriptAvailable ist ein boolesches Capability-Flag, kein literaler true-Typ. Aktuelle Daemons emittieren true, wenn /session/:id/transcript verwendet werden kann, um das persistierte Transkript seitenweise abzurufen; ältere oder eingeschränkte Daemons können false emittieren, und Clients sollten das begrenzte Replay normal weiter rendern.

Permissions (F3 + base)

TypRichtungTriggerWichtige Payload-Felder
permission_requestS->CAgent ruft requestPermission aufrequestId, sessionId, toolCall, options[]; das Envelope stempelt originatorClientId vom Prompt-Originator.
permission_resolvedS->CMediator hat entschiedenrequestId, outcome (ACP PermissionOutcome)
permission_already_resolvedS->CVote trifft ein, nachdem die Anfrage bereits entschieden wurderequestId, sessionId, outcome
permission_partial_voteS->Cconsensus-Policy erfasst einen nicht-finalen VoterequestId, sessionId, votesReceived, votesNeeded (>= 1), quorum, optionTallies: Record<string, number>, originatorClientId?
permission_forbiddenS->CPolicy lehnt einen Vote abrequestId, sessionId, clientId?, reason: 'designated_mismatch' | 'remote_not_allowed', originatorClientId?; anonyme Voter lassen clientId weg.

Models

TypRichtungPayload
model_switchedS->CsessionId, modelId
model_switch_failedS->CsessionId, requestedModelId, error: string

MCP-Guardrails (PR 14b + F2)

TypRichtungPayload
mcp_budget_warningS->CliveCount, reservedCount, budget, thresholdRatio: 0.75, mode: 'warn' | 'enforce', scope?: 'workspace' | 'session'
mcp_child_refused_batchS->CrefusedServers: [{ name, transport, reason: 'budget_exhausted' }], budget, liveCount, reservedCount, mode: 'enforce', scope?: 'workspace' | 'session'
mcp_server_restartedS->CserverName, durationMs, entryIndex? für F2-Multi-Entry-Pool-Restarts
mcp_server_restart_refusedS->CserverName, reason: 'budget_would_exceed' | 'in_flight' | 'disabled' | 'restart_failed', entryIndex?, details?. Der vierte Wert, restart_failed, transportiert einen zugrunde liegenden Hard-Failure für Multi-Entry-Pool-Restarts im Pool-Modus. MCP_RESTART_REFUSED_REASONS weist unbekannte Reasons zurück; ein älterer SDK-Reducer verwirft additive neue Reason-Werte stillschweigend, da parseDaemonEvent undefined zurückgibt. Liefere einen neuen Reason mit einem SDK aus, das ihn kennt.

Mutationskontrolle (Wave 4 PR 16+17)

TypRichtungPayload
memory_changedS->CFile-Speicher: scope: 'workspace' | 'global', filePath, mode, bytesWritten; Managed Memory: scope: 'managed', source, taskId, touchedScopes
agent_changedS->Cchange: 'created' | 'updated' | 'deleted', name, level: 'project' | 'user'
approval_mode_changedS->CsessionId, previous, next, persisted: boolean
tool_toggledS->CtoolName, enabled; betrifft den nächsten ACP-Child-Spawn und mutiert nicht bereits laufende Sessions.
settings_changedS->CWorkspace-Einstellungen erfolgreich geschrieben. Payload enthält key; value, scope und Skill-Toggle-mutation sind optional.
settings_reloadedS->CDaemon-Workspace-Service hat Einstellungen neu eingelesen. Payload ist offen.
trust_change_requestedS->CworkspaceCwd, desiredState: 'trusted' | 'untrusted', reason?
workspace_initializedS->Cpath, action: 'created' | 'overwrote' | 'noop', originatorClientId?
github_setup_completedS->CreleaseTag, readmeUrl, secretsUrl?, workflows: [{path, status, sizeBytes?, error?}], gitignore: {path, status, added?, error?}

memory_changed umfasst auch sitzungslose Managed-Memory-Tasks. Für diese Payloads ist scope "managed", source ist einer der Werte "workspace_memory_remember", "workspace_memory_forget" oder "workspace_memory_dream", taskId ist die ID des Tasks in der Warteschlange und touchedScopes listet die geänderten Managed-Memory-Scopes auf ("user" und/oder "project"). Es wird kein Event ausgelöst, wenn ein Remember/Forget/Dream-Task abgeschlossen wird, ohne den Managed Memory zu verändern.

Auth-Device-Flow (PR 21)

Diese Events sind auf den Workspace bezogen (workspace-keyed), nicht auf die Session. Der Session-Reducer behandelt sie als No-Ops; reduceDaemonAuthEvent projiziert sie in den State auf Workspace-Ebene.

TypRichtungPayload
auth_device_flow_startedS->CdeviceFlowId, providerId, expiresAt
auth_device_flow_throttledS->CdeviceFlowId, intervalMs
auth_device_flow_authorizedS->CdeviceFlowId, providerId, expiresAt?, accountAlias?
auth_device_flow_failedS->CdeviceFlowId, errorKind, hint?
auth_device_flow_cancelledS->CdeviceFlowId

MCP Runtime Mutation

TypRichtungTriggerWichtige Payload-Felder
mcp_server_addedS->CServer zur Laufzeit über POST /workspace/mcp/servers hinzugefügtname, transport, replaced, shadowedSettings, toolCount, originatorClientId
mcp_server_removedS->CServer zur Laufzeit entferntname, wasShadowingSettings, originatorClientId

Extensions-Lifecycle

TypRichtungTriggerWichtige Payload-Felder
extensions_changedS->CHintergrund-Installation/Aktualisierung von Extensions abgeschlossen oder Statusänderungrefreshed, failed, status?: 'installed' | 'enabled' | 'disabled' | 'updated' | 'uninstalled' | 'failed', source?, name?, version?, error?

Mid-Turn Message Injection

TypRichtungTriggerWichtige Payload-Felder
mid_turn_message_injectedS->CWeb-Shell oder Remote-Client hat Nachrichten über POST /session/:id/inject in einen laufenden Turn injiziertsessionId, messages: string[], originatorClientId?; Consumer MÜSSEN originatorClientId mit ihrer eigenen ID vergleichen, bevor sie Deduplizierungen vornehmen.

Turn-Lifecycle / Assistant Pushes

TypRichtungTriggerWichtige Payload-Felder
prompt_cancelledS->CPrompt wurde über die explizite cancelSession-Route oder durch SSE-Disconnect des Originators abgebrochenEnvelope versieht den abbrechenden Client mit dem originatorClientId-Stempel. Dies bedeutet “Abbruch angefordert”, nicht “Abbruch bestätigt”. Peer-Subscriber erfahren dadurch, dass der Prompt beendet wurde.
turn_completeS->CEin Turn wurde erfolgreich abgeschlossensessionId, stopReason, promptId?, branchPoint?. promptId verknüpft mit nicht-blockierenden Prompt-Antworten (202). Eligible completed turns include branchPoint: { assistantRecordUuid, checkpointUuid }.
turn_errorS->CEin Turn ist fehlgeschlagensessionId, message, code?, promptId?; derselbe promptId-Korrelationsmechanismus.
session_rewoundS->CPOST /session/:id/rewind war erfolgreichsessionId, promptId, targetTurnIndex, filesChanged[], filesFailed[], originatorClientId?
session_branchedS->CLegacy-Kompatibilitäts-Event; der aktuelle Branch-Endpunkt gibt sein Ergebnis direkt zurück und veröffentlicht dieses Event nichtsourceSessionId, newSessionId, displayName, originatorClientId?. Reader behalten Support für ältere Producer bei.
followup_suggestionS->CACP-Child hat Ghost-Text-Follow-up-Vorschläge nach end_turn generiert, weitergeleitet über session-spezifisches SSEsessionId, suggestion, promptId; der Wire überträgt nur Vorschläge, bei denen getFilterReason()===null ist. Clients rendern sie als Ghost-Text für Input-Platzhalter und invalidieren sie beim nächsten sendPrompt.
user_shell_commandS->CBenutzer hat einen Shell-Befehl über POST /session/:id/shell gestartet; an andere Subscriber in derselben Session verteiltsessionId, command, shellId, originatorClientId?. Es gibt noch keine typisierte DaemonXxxData-Schnittstelle; asKnownDaemonEvent gibt undefined zurück und der UI-Normalizer parst es ad hoc.
user_shell_resultS->CErgebnis des obigen Shell-BefehlssessionId, shellId, exitCode, output, aborted. Gleicher Hinweis zum ad-hoc-Parsing wie bei user_shell_command.

Architektur

AspektQuelleHinweise
EVENT_SCHEMA_VERSION = 1packages/acp-bridge/src/eventBus.tsWird in jedem Frame gesendet.
DAEMON_KNOWN_EVENT_TYPE_VALUESpackages/sdk-typescript/src/daemon/events.tsAbgeschlossene Liste mit 53 Typen.
DaemonEventEnvelope<TType, TData>events.tsGenerische Envelope.
DaemonKnownEventTypeevents.tstypeof DAEMON_KNOWN_EVENT_TYPE_VALUES[number].
Payload-Typen pro Eventevents.tsDie meisten Event-Typen haben eine DaemonXxxData-Schnittstelle; user_shell_* wird derzeit ad hoc vom UI-Normalizer geparst.
asKnownDaemonEvent(evt)events.tsGibt KnownDaemonEvent | undefined zurück.
reduceDaemonSessionEvent(state, evt)events.tsProjiziert in DaemonSessionViewState.
reduceDaemonAuthEvent(state, evt)events.tsProjiziert in DaemonAuthState.
isWorkspaceScopedBudgetEvent(evt)events.tsErkennt F2 scope: 'workspace'.

DaemonSessionViewState

reduceDaemonSessionEvent füllt diesen View-State. CLI-TUI-Adapter, DaemonChannelBridge und die VS Code IDE konsumieren ihn. Wichtige Felder:

  • alive: boolean - wird nach einem Terminal-Frame (session_died, session_closed, client_evicted, stream_error) auf false gesetzt.
  • currentModelId?: string - aus model_switched.
  • displayName?: string - aus session_metadata_updated.
  • recordingDegraded: boolean - sticky Session-Recording-State von session_recording_degraded; ein expliziter session_snapshot.recordingDegraded-Wert ist maßgebend.
  • pendingPermissions: Record<string, DaemonPermissionRequestData> - offene Requests, gekeyed nach requestId; bereinigt durch permission_resolved / permission_already_resolved.
  • lastSessionUpdate?: DaemonSessionUpdateData - neuestes session_update.
  • lastModelSwitchFailure?: DaemonModelSwitchFailedData - aus model_switch_failed.
  • terminalEvent? - rohes Terminal-Event.
  • streamError?: DaemonStreamErrorData - neueste stream_error-Payload.
  • unrecognizedKnownEventCount, lastUnrecognizedKnownEvent? - Event wurde von asKnownDaemonEvent erkannt, aber der Reducer hat noch keinen dedizierten State dafür.
  • droppedPermissionRequestCount, lastDroppedPermissionRequestId? - fehlerhafter Permission-Request konnte nicht in die Pending-Map aufgenommen werden.
  • unmatchedPermissionResolutionCount, lastUnmatchedPermissionResolutionId? - Permission-Resolution hatte keinen passenden Pending-Request.
  • slowClientWarningCount, lastSlowClientWarning? - aus slow_client_warning.
  • mcpBudgetWarningCount, lastMcpBudgetWarning? - aus mcp_budget_warning.
  • mcpChildRefusedBatchCount, lastMcpChildRefusedBatch? - aus mcp_child_refused_batch.
  • lastWorkspaceMutation?, lastWorkspaceMutationType? - aus memory_changed / agent_changed.
  • approvalMode?, approvalModeChangedCount, lastApprovalModeChange? - aus approval_mode_changed.
  • toolToggleCount, lastToolToggle? - aus tool_toggled.
  • workspaceInitCount, lastWorkspaceInit? - aus workspace_initialized.
  • mcpRestartCount, lastMcpRestart? - aus mcp_server_restarted.
  • mcpRestartRefusedCount, lastMcpRestartRefused? - aus mcp_server_restart_refused.
  • settings_changed / settings_reloaded - werden von asKnownDaemonEvent erkannt; der Session-Reducer pflegt keine dedizierten View-State-Felder dafür und UIs behandeln sie üblicherweise als Refresh-Signale.
  • permissionVoteProgress: Record<string, DaemonPermissionPartialVoteData> - Fortschritt des Consensus-Votings.
  • forbiddenVotes: DaemonPermissionForbiddenData[], forbiddenVoteCount - von der Policy abgelehnte Vote-Datensätze, begrenzt auf 32.
  • awaitingResync: boolean - gesetzt durch state_resync_required; bereinigt, wenn der Consumer den View-State zurücksetzt.
  • resyncRequiredCount, lastResyncRequired? - Resync-Observability.
  • lastFollowupSuggestion?: DaemonFollowupSuggestionData - neuester Follow-up-Vorschlag, gepusht vom Daemon.
  • lastTurnComplete?: DaemonTurnCompleteData - neuester erfolgreicher Turn-Abschluss.
  • lastTurnError?: DaemonTurnErrorData - neuester Turn-Fehler.
  • rewindCount, lastRewind?, lastBranch? - neueste Rewind-/Branch-Events.

DaemonAuthState

Ein Eintrag pro providerId, gesteuert durch auth_device_flow_*. Jeder Flow legt { deviceFlowId, status, providerId, expiresAt?, lastThrottleIntervalMs?, lastError? } offen.

Flow

Producer-Seite

Consumer-Seite (SDK)

Metadaten auf Envelope-Ebene

Zusätzlich zum data-Payload jedes Events versieht der Daemon zwei Felder auf Envelope-Ebene mit einem Timestamp.

_meta.serverTimestamp - Daemon-Uhr

EventBus.publish() in packages/acp-bridge/src/eventBus.ts versieht _meta.serverTimestamp mit einem Timestamp, wenn das Event den Bus betritt. Der BridgeEvent-Typ enthält _meta?: Record<string, unknown>, sodass interne Daemon-Consumer _meta bei jedem über den Bus veröffentlichten Event sehen. formatSseFrame() in packages/cli/src/serve/routes/sse-events.ts stellt nur für synthetische Frames (z. B. stream_error), die EventBus.publish umgehen, einen Fallback-Timestamp bereit.

{ "id": 47, "v": 1, "type": "session_update", "data": { ... }, "_meta": { "serverTimestamp": 1716287345123 } }

Der Merge behält alle vorhandenen _meta-Keys aus dem Input-Event bei ({...input._meta, serverTimestamp: Date.now()}). Producer können zusätzliche _meta-Keys auf Envelope-Ebene anhängen; EventBus.publish führt diese mit dem Timestamp zusammen, anstatt sie zu überschreiben.

Warum das wichtig ist: Multi-Client-UIs, die relative Zeiten rendern oder Transcript-Blöcke sortieren, sollten die Serverzeit anstelle der lokalen Uhr des jeweiligen Browsers/Tabs/Smartphones verwenden. Server-Timestamps halten die Reihenfolge clientübergreifend konsistent.

SDK-Zugriff: bevorzuge event._meta?.serverTimestamp. Kompatibilitätspfade können auch event.serverTimestamp oder event.data._meta.serverTimestamp abfragen. Vermische nicht das ACP-Payload data._meta mit dem Daemon-Envelope _meta.

originatorClientId

Events, die durch einen Request mit einer registrierten X-Qwen-Client-Id ausgelöst werden, können dieses Feld mit einem Timestamp versehen. Siehe 08-session-lifecycle.md.

Tool-Call _meta (Provenance / serverId)

Dies ist getrennt vom Envelope-_meta: ACP-session/update-Payloads können ihr eigenes _meta in event.data._meta tragen. ToolCallEmitter (packages/cli/src/acp-integration/session/emitters/tool-call-emitter.ts) versieht bei emitStart, emitResult und emitError zwei Felder mit Werten:

FeldTypAuflösungsregel
provenance'builtin' | 'mcp' | 'subagent'ToolCallEmitter.resolveToolProvenance: subagentMeta gewinnt mit subagent; Tool-Name, der mcp__<server>__<tool> entspricht, wird auf mcp gemappt; alles andere wird auf builtin gemappt.
serverIdstring nur wenn provenance === 'mcp'Heuristisch extrahiert aus mcp__<serverId>__<tool>.

Der vorhandene _meta.toolName-Anzeigename bleibt erhalten. Die UI verwendet diese Felder, um Builtin-/MCP-Server-/Subagent-Badges zu rendern, ohne den Tool-Namen erneut parsen zu müssen.

SDK-Reducer-Verhalten

reduceDaemonSessionEvent(state, evt) in packages/sdk-typescript/src/daemon/events.ts projiziert den Stream in DaemonSessionViewState. Die Resync-bezogenen Felder sind:

  • awaitingResync: boolean - wird durch state_resync_required gesetzt; der Caller setzt es zurück, typischerweise nachdem POST /session/:id/load den View-State zurückgesetzt hat.
  • resyncRequiredCount: number - Observability-Counter.
  • lastResyncRequired?: DaemonStateResyncRequiredData - neuestes Payload.

Während awaitingResync = true ist, überspringt der Reducer die Delta-Anwendung und lässt nur das abgeschlossene RESYNC_PASSTHROUGH_TYPES-Set zu:

Passthrough-TypWarum er während des Resyncs weiterhin angewendet wird
state_resync_requiredSeltener zweiter Resync sollte lastResyncRequired / resyncRequiredCount aktualisieren.
session_diedTerminales Stream-Signal muss während des Resyncs sichtbar bleiben.
session_closedWie oben.
client_evictedWie oben.
stream_errorWie oben.
session_snapshotFull-State-Authoritative-Frame; sicher während des Resyncs anzuwenden.
session_recording_degradedSticky-Safety-Signal, unabhängig vom Transkript-Delta-State.

lastEventId schreitet auch während des Resyncs monoton durch advanceLastEventId(base) fort. Nachdem der Caller zurückgesetzt und awaitingResync gelöscht hat, richten sich nachfolgende Deltas am korrekten Cursor aus.

reduceDaemonAuthEvent projiziert Device-Flow-Events konzeptionell in Workspace-level Auth-State-Einträge in der Form {deviceFlowId, status, providerId, expiresAt?, lastThrottleIntervalMs?, lastError?}. Im Code speichert der Reducer status, errorKind, hint, intervalMs, lastSeenEventId, authorizedExpiresAt und accountAlias auf DaemonDeviceFlowReducerState; die Daemon-Event-Payloads selbst behalten die oben aufgeführten Formen pro Event bei.

State und Forward-Compatibility

  • Füge einen bekannten Event-Typ hinzu, indem du an DAEMON_KNOWN_EVENT_TYPE_VALUES anhängst. Alte SDKs geben für nicht erkannte Event-Typen über den Fallback-Pfad undefined zurück und inkrementieren unrecognizedKnownEventCount; neue SDKs verlassen sich auf die Discriminated Union.
  • Das Hinzufügen optionaler Felder zu einem bestehenden Payload ist sicher, da Payloads offen sind ({ [key: string]: unknown }).
  • Das Ändern der Form eines bestehenden Payloads ist ein Breaking Change und muss EVENT_SCHEMA_VERSION erhöhen sowie einen kompatiblen Capability-Tag wie caps.features.typed_event_schema_v2 bekannt geben.
  • id ist pro Session monoton. Synthetische Frames auf Subscriber-Ebene (client_evicted, slow_client_warning, stream_error, state_resync_required, replay_complete, session_snapshot) haben absichtlich keine ID, damit andere Subscriber keine Lücken sehen.
  • originatorClientId befindet sich auf dem Envelope und nicht in data. F3-Partial-Vote-/Forbidden-Payloads führen es auch über mergeOriginator in data zusammen, damit View-State-Consumer den Envelope nicht vorhalten müssen.

Abhängigkeiten

Konfiguration

  • Immer beworben: typed_event_schema, mcp_guardrail_events und permission_mediation (mit unterstützten Policy-Modi).
  • Keine Env-Var oder Flag steuert direkt das Schema selbst. QWEN_SERVE_NO_MCP_POOL=1 ändert den MCP-Event-scope von 'workspace' zu absent oder 'session'.

Einschränkungen und bekannte Limits

  • Sechs synthetische Frame-Typen haben absichtlich keine id; SDK-Code darf nicht davon ausgehen, dass jedes Event eine ID hat.
  • permission_partial_vote erscheint nur unter consensus. permission_forbidden erscheint unter designated, consensus und local-only, aber nicht unter first-responder.
  • mcp_child_refused_batch erscheint nur im mode: 'enforce'; der warn-Modus lehnt nie ab.
  • auth_device_flow_*-Events sind nicht session-keyed. Verwende bei der Konsumierung über DaemonSessionClient dafür reduceDaemonAuthEvent anstatt des Session-Reducers.

Referenzen

  • packages/sdk-typescript/src/daemon/events.ts
  • packages/acp-bridge/src/eventBus.ts (EVENT_SCHEMA_VERSION)
  • packages/cli/src/serve/capabilities.ts (typed_event_schema, mcp_guardrail_events, permission_mediation)
  • Wire-Referenz: ../qwen-serve-protocol.md
Last updated on