docs(daemon): refresh daemon docs for recent PRs (wave 2) - #5954
Conversation
- Add Last-Event-ID client reconnect guide (10-event-bus, 13-sdk-daemon-client) - Add cross-connection vote routing section (04-permission-mediation) - Add new capability tags: daemon_status, workspace_permissions, workspace_trust, workspace_github_setup, workspace_voice, workspace_voice_transcription, voice_transcribe (11-capabilities-versioning) - Add new event types: trust_change_requested, github_setup_completed, extensions_changed, mid_turn_message_injected (09-event-schema) - Fix _meta.serverTimestamp source description (09-event-schema, 10-event-bus) - Fix async function* syntax in SDK example (13-sdk-daemon-client) - Sync event/capability counts across all docs (43->47 events, 67->75 tags)
Design document for the sessionless workspace remember API proposed in PR #5884. Covers API endpoints, task lifecycle, implementation details, events, error handling, and SDK integration. Status: Proposed (not yet merged).
|
Thanks for the docs refresh, @doudouOUC! Template has a minor deviation — the PR body uses a "Summary" heading instead of the template's "What this PR does" / "Why it's needed" sections, and is missing "Risk & Scope" and "Tested on". For a docs-only PR these are all reasonable to skip, so not a blocker — just noting it for awareness. On direction: keeping developer docs in sync with merged code changes is clearly aligned with the project's needs. The wave-based cadence is a clean way to batch these updates. No concerns. On approach: the scope feels right for a periodic refresh. The changes are focused — count bumps (43→47 events, 67→75 capabilities, 10→13 conditional tags), client integration guides for
The new design doc ( Moving on to code review. 🔍 中文说明感谢贡献,@doudouOUC! 模板有轻微偏差——PR 使用了 "Summary" 标题而非模板要求的 "What this PR does" / "Why it's needed",也缺少 "Risk & Scope" 和 "Tested on" 部分。不过对于纯文档 PR 这些都是合理的,不构成阻塞问题,仅提醒注意。 方向:保持开发者文档与已合并代码的同步,完全符合项目需求。按 wave 批量更新是个干净的做法。无顾虑。 方案:范围对于定期刷新来说合理。改动聚焦——数量更新(43→47 事件、67→75 能力、10→13 条件标签)、
新增的设计文档( 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewReviewed all 8 changed files against the source code. No critical issues found. Counts verified against live code (all match):
Cross-connection vote routing (new section in New event type entries — Capability tag additions — the 8 new tags ( SDK reconnect examples ( Design doc ( TestingThis is a docs-only PR (8 markdown files, 0 source code changes). All verification was done by cross-referencing documentation claims against the actual TypeScript source:
No tmux-based real-scenario testing applies — there is no behavioral change to test. 中文说明代码审查审查了全部 8 个变更文件,对照源代码检查。未发现关键问题。 数量验证(全部匹配):
跨连接投票路由( SDK 重连示例 — 设计文档 — 429 行的结构化设计文档,正确标记为 Proposed/尚未合并。 测试这是纯文档 PR(8 个 markdown 文件,0 源代码变更)。所有验证通过将文档声明与 TypeScript 源代码交叉比对完成。无 tmux 真实场景测试适用——没有可测试的行为变更。 — Qwen Code · qwen3.7-max |
|
Clean docs refresh — ships what it says on the tin. Every count (47 events, 75 capabilities, 13 conditional, 37 UI types) verified against live code via runtime execution and static analysis. The No code changes, no risk of regression, no scope creep. The minor template deviations (heading names, missing "Tested on" table) are sensible for a docs-only PR and don't warrant blocking. Approving. ✅ 中文说明干净的文档刷新——名副其实。 所有数量(47 事件、75 能力、13 条件、37 UI 类型)已通过运行时执行和静态分析对照实际代码验证。 无代码变更、无回归风险、无范围蔓延。模板的轻微偏差(标题名称、缺少"Tested on"表格)对纯文档 PR 是合理的,不构成阻塞理由。 批准。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
| set of primitives that turn daemon events into transcript blocks: | ||
|
|
||
| - `normalizeDaemonEvent(evt)` maps the 43 known daemon wire events into 37 UI-friendly `DaemonUiEventType` values; unmodeled or malformed events normalize to `debug`. | ||
| - `normalizeDaemonEvent(evt)` maps the 47 known daemon wire events into 37 UI-friendly `DaemonUiEventType` values; unmodeled or malformed events normalize to `debug`. |
There was a problem hiding this comment.
[Suggestion] The wire event count was correctly updated from 43 → 47 on this line, but the DaemonUiEventType count is still 37. The actual DaemonUiEventType union in packages/sdk-typescript/src/daemon/ui/types.ts has 42 members. Since this PR's purpose is to refresh counts to match the current codebase, this is a missed update.
The same stale "37" appears in 4 other locations: 14-cli-tui-adapter.md:9, 14-cli-tui-adapter.md:44, 14-cli-tui-adapter.md:120, and 00-index.md:138.
| - `normalizeDaemonEvent(evt)` maps the 47 known daemon wire events into 37 UI-friendly `DaemonUiEventType` values; unmodeled or malformed events normalize to `debug`. | |
| - `normalizeDaemonEvent(evt)` maps the 47 known daemon wire events into 42 UI-friendly `DaemonUiEventType` values; unmodeled or malformed events normalize to `debug`. |
— qwen3.7-max via Qwen Code /review
| ## Overview | ||
|
|
||
| Every SSE frame emitted by the daemon on `GET /session/:id/events` has the shape `{ id, v, type, data, originatorClientId?, _meta? }`. `v: 1` is the current `EVENT_SCHEMA_VERSION`. `type` comes from the closed, version-pinned `DAEMON_KNOWN_EVENT_TYPE_VALUES` set in `packages/sdk-typescript/src/daemon/events.ts`; the current set has 43 known event types. The envelope `_meta` field is stamped at the SSE write boundary by `formatSseFrame()` in `packages/cli/src/serve/routes/sse-events.ts`; see [Envelope-level metadata](#envelope-level-metadata). | ||
| Every SSE frame emitted by the daemon on `GET /session/:id/events` has the shape `{ id, v, type, data, originatorClientId?, _meta? }`. `v: 1` is the current `EVENT_SCHEMA_VERSION`. `type` comes from the closed, version-pinned `DAEMON_KNOWN_EVENT_TYPE_VALUES` set in `packages/sdk-typescript/src/daemon/events.ts`; the current set has 47 known event types. The envelope `_meta` field is stamped at the SSE write boundary by `formatSseFrame()` in `packages/cli/src/serve/routes/sse-events.ts`; see [Envelope-level metadata](#envelope-level-metadata). |
There was a problem hiding this comment.
[Suggestion] This overview paragraph still says _meta is "stamped at the SSE write boundary by formatSseFrame()". However, the detailed _meta.serverTimestamp section (line 209) was correctly updated to say EventBus.publish() stamps it, with formatSseFrame() as a fallback only. The overview and the detailed section now contradict each other within the same file.
The same stale description also appears in 00-index.md:128 ("_meta.serverTimestamp stamped at SSE write boundary").
| Every SSE frame emitted by the daemon on `GET /session/:id/events` has the shape `{ id, v, type, data, originatorClientId?, _meta? }`. `v: 1` is the current `EVENT_SCHEMA_VERSION`. `type` comes from the closed, version-pinned `DAEMON_KNOWN_EVENT_TYPE_VALUES` set in `packages/sdk-typescript/src/daemon/events.ts`; the current set has 47 known event types. The envelope `_meta` field is stamped at the SSE write boundary by `formatSseFrame()` in `packages/cli/src/serve/routes/sse-events.ts`; see [Envelope-level metadata](#envelope-level-metadata). | |
| Every SSE frame emitted by the daemon on `GET /session/:id/events` has the shape `{ id, v, type, data, originatorClientId?, _meta? }`. `v: 1` is the current `EVENT_SCHEMA_VERSION`. `type` comes from the closed, version-pinned `DAEMON_KNOWN_EVENT_TYPE_VALUES` set in `packages/sdk-typescript/src/daemon/events.ts`; the current set has 47 known event types. The envelope `_meta` field is stamped by `EventBus.publish()` when the event enters the bus, with `formatSseFrame()` providing a fallback for synthetic frames; see [Envelope-level metadata](#envelope-level-metadata). |
— qwen3.7-max via Qwen Code /review
| | `workspace_initialized` | S->C | `path, action: 'created' \| 'overwrote' \| 'noop', originatorClientId?` | | ||
| | Type | Direction | Payload | | ||
| | ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `memory_changed` | S->C | `scope: 'workspace' \| 'global', filePath, mode: 'append' \| 'replace', bytesWritten` | |
There was a problem hiding this comment.
[Suggestion] The memory_changed payload column still shows scope: 'workspace' | 'global'. The new design doc (docs/design/daemon-workspace-remember.md, section 6) introduces a third scope value 'managed' with a different payload shape (source, taskId, touchedScopes instead of filePath, mode, bytesWritten). The event schema — the authoritative event contract — was not updated.
SDK developers building a reducer or TypeScript discriminated union on memory_changed.scope using this doc as the source of truth would miss the 'managed' case.
Consider updating the payload column to include 'managed', or adding a forward-compatibility note referencing the design doc.
— qwen3.7-max via Qwen Code /review
|
|
||
| ```ts | ||
| async function resilientSubscribe(session: DaemonSessionClient) { | ||
| const MAX_RETRIES = 10; |
There was a problem hiding this comment.
[Suggestion] Two issues in this retry example that developers will copy verbatim:
-
attempt = 0resets on every event, not on successful connection. A flapping connection that delivers one event then drops resets the counter each time, so backoff never progresses beyondBASE_DELAY_MSandMAX_RETRIESis effectively meaningless. The loop retries forever at ~1s intervals. -
No random jitter. Pure exponential backoff (
BASE_DELAY_MS * 2 ** attempt) without jitter causes synchronized reconnection storms when many clients disconnect simultaneously (e.g., daemon restart).
Suggested fix for both:
let connectionAttempt = 0;
for (let totalRetries = 0; totalRetries < MAX_RETRIES; totalRetries++) {
try {
connectionAttempt = 0; // reset on successful connect, not per-event
for await (const event of session.events()) {
handleEvent(event);
}
break;
} catch (err) {
const delay = BASE_DELAY_MS * 2 ** Math.min(connectionAttempt, 5)
* (0.5 + Math.random() * 0.5); // add jitter
await new Promise((r) => setTimeout(r, delay));
connectionAttempt++;
}
}— qwen3.7-max via Qwen Code /review
| // Handle ring-eviction gap. | ||
| if (event.type === 'state_resync_required') { | ||
| // State is stale — reload full session state. | ||
| await client.loadSession(sessionId); |
There was a problem hiding this comment.
[Suggestion] After loadSession rebuilds full state on state_resync_required, the cursor variable is not reset. The surviving replay events that follow in the stream will still be applied on top of the freshly loaded state, potentially causing duplicate event application.
Consider resetting the cursor after loadSession to suppress further replay processing in this iteration:
if (event.type === 'state_resync_required') {
await client.loadSession(sessionId);
cursor = undefined; // prevent double-application of replay events
continue;
}— qwen3.7-max via Qwen Code /review
| preventing concurrent writes to the managed memory filesystem. | ||
| - **Hidden** — the forked agent runs with `name: 'managed-auto-memory-remember'` | ||
| and is invisible to the session list. | ||
| - **Capability-advertised** — `workspace_memory_remember` in the daemon's |
There was a problem hiding this comment.
[Suggestion] This states workspace_memory_remember will appear in the daemon's /capabilities response, but this tag does not exist in SERVE_CAPABILITY_REGISTRY (packages/cli/src/serve/capabilities.ts) and is not listed in 11-capabilities-versioning.md. The design doc advertises a capability that clients cannot currently feature-detect.
Consider adding a note that this tag must be registered alongside the implementation, or explicitly marking it as deferred.
— qwen3.7-max via Qwen Code /review
| ### `memory_changed` (scope: `managed`) | ||
|
|
||
| Published on the daemon SSE event stream (`GET /session/:id/events`) as a | ||
| `memory_changed` event with `scope: 'managed'` when a remember task completes |
There was a problem hiding this comment.
[Suggestion] This says the event is published on GET /session/:id/events and "clients subscribed to the per-session event stream receive this notification" — but the remember task is sessionless (no session is created or loaded). It's unclear which session's SSE stream carries this event:
- Is it broadcast to all active sessions in the workspace?
- Only to the session matching the originator?
- Dropped if no session is active?
SDK integrators won't know where to subscribe. Consider clarifying the delivery scope explicitly, e.g.: "Published to all active SSE subscribers in the same workspace" or "broadcast on a workspace-level event channel."
— qwen3.7-max via Qwen Code /review
Summary
Refreshes daemon developer documentation to cover recent PRs merged in the past two weeks (2026-06-13 to 2026-06-27).
Changes
Developer docs updates
10-event-bus.mdand SDK reconnect examples to13-sdk-daemon-client.md(covers PR feat(daemon,sdk): resumable /acp session stream (Last-Event-ID) + opt-in SDK transports export #5852 resumable SSE)04-permission-mediation.mdcovering connection-qualified IDs, authorization rules, loopback evaluation, and JSON-RPC vote format (covers PR fix(daemon): resolve ACP permission votes across connections #5912)New design document
Reviewer Test Plan
11-capabilities-versioning.mdmatches 75 listed tags09-event-schema.mdmatches 47 listed events00-index.md,10-event-bus.md,13-sdk-daemon-client.md,14-cli-tui-adapter.mdall reference 47 events consistentlydaemon-workspace-remember.mdheader says Proposed/not-yet-merged