Skip to content

feat(serve): add HTTP rewind endpoints for daemon/web-shell (issue #4514 T3.2)#4820

Merged
wenshao merged 5 commits into
daemon_mode_b_mainfrom
feat/serve-rewind-endpoint-v2
Jun 7, 2026
Merged

feat(serve): add HTTP rewind endpoints for daemon/web-shell (issue #4514 T3.2)#4820
wenshao merged 5 commits into
daemon_mode_b_mainfrom
feat/serve-rewind-endpoint-v2

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

Adds HTTP rewind endpoints (GET /session/:id/rewind/snapshots and POST /session/:id/rewind) to the daemon so web-shell and SDK clients can rewind a session's conversation and files to a previous turn — replacing the TUI-only interactive dialog.

Extends the existing rewindSession ACP extMethod to also accept a promptId parameter and perform file history rewind via FileHistoryService. Adds SessionBusyError (409) and InvalidRewindTargetError (400) error classes for structured HTTP error responses. Publishes a session_rewound SSE event for cross-client notification. SDK types, client methods, and event handling are included.

Supersedes #4817 (which targeted main instead of daemon_mode_b_main).

Why it's needed

Issue #4514 T3.2: /rewind is currently interactive-only (opens a TUI dialog). Web-shell and SDK clients have no way to rewind a session. The underlying FileHistoryService is pure filesystem (no git dependency), so this can be exposed over HTTP without a new checkpoint substrate.

Reviewer Test Plan

How to verify

  1. Start the daemon: npm run dev:daemon
  2. Create a session and send a few prompts that trigger file edits
  3. Call GET /session/$SID/rewind/snapshots — verify non-empty list with per-turn diff stats
  4. Call POST /session/$SID/rewind with a valid promptId from the list — verify files restored, conversation truncated, session_rewound event on SSE
  5. Call POST /session/$SID/rewind with an invalid promptId — verify 400
  6. Call POST /session/$SID/rewind while a prompt is running — verify 409

Evidence (Before & After)

N/A — new HTTP endpoints, no UI changes.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Local npm run dev:daemon + curl.

Risk & Scope

  • Main risk or tradeoff: Snapshot listing calls getDiffStats for all snapshots (up to 100) in Promise.all — acceptable for MVP but may need pagination for long sessions.
  • Not validated / out of scope: /restore (depends on gitService, planned for deprecation). Unrewind/unrevert not implemented per design decision.
  • Breaking changes / migration notes: Existing rewindSession extMethod callers (VSCode extension) are backward-compatible — old targetTurnIndex param still works when promptId is absent.

Linked Issues

Closes T3.2 of #4514.

中文说明

为 daemon 新增 HTTP rewind 端点(GET /session/:id/rewind/snapshotsPOST /session/:id/rewind),使 web-shell 和 SDK 客户端能将会话的对话和文件回退到之前的 turn,替代仅 TUI 可用的交互式对话框。

扩展已有的 rewindSession ACP extMethod 以支持 promptId 参数和通过 FileHistoryService 进行文件历史回退。新增 SessionBusyError(409)和 InvalidRewindTargetError(400)错误类用于结构化 HTTP 错误响应。成功后发布 session_rewound SSE 事件用于跨客户端通知。包含 SDK 类型、客户端方法和事件处理。

🤖 Generated with Qwen Code

Copilot AI review requested due to automatic review settings June 6, 2026 01:55
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @doudouOUC!

Template looks good ✓ — all required sections present, bilingual description, test plan included.

On direction: This is T3.2 from the tracked daemon capability gaps (#4514) — /rewind is currently TUI-only and web-shell/SDK clients genuinely have no way to rewind. Aligned with the session-management roadmap. The daemon_mode_b_main base branch is correct for this work. No concerns here.

On approach: The scope feels right and well-contained. Two HTTP endpoints (list snapshots + execute rewind), structured error classes (409 busy, 400 invalid target), SSE cross-client notification, and SDK plumbing — this mirrors the pattern used by other daemon endpoints (/recap, /btw, /branch). The backward-compatible rewindSession extMethod extension is a good call for existing VSCode consumers.

One observation: the Promise.all over up to 100 snapshots for diff stats (noted in the risk section) is fine for MVP but worth keeping on the radar. The supportedModes: ['interactive'] addition to rewindCommand.ts is a small but important detail — it correctly marks /rewind as interactive-only in the command registry.

No tests included in the 14 changed files — I'll check whether existing test coverage is sufficient during code review.

Moving on to code review. 🔍

中文说明

感谢 PR,@doudouOUC

模板完整 ✓ — 所有章节齐全,双语描述,包含测试计划。

方向: 这是 #4514 追踪的 daemon 能力缺口 T3.2 — /rewind 目前仅 TUI 可用,web-shell/SDK 客户端确实无法回退。与 session-management 路线图一致。目标分支 daemon_mode_b_main 正确,没有问题。

方案: 范围合理且聚焦。两个 HTTP 端点(列出快照 + 执行回退)、结构化错误类(409 busy、400 invalid target)、SSE 跨客户端通知和 SDK 接入 — 与其他 daemon 端点(/recap/btw/branch)的模式一致。rewindSession extMethod 的向后兼容扩展对现有 VSCode 消费者是好的做法。

一个观察:对最多 100 个快照的 Promise.all diff 统计(风险部分已提到)MVP 阶段可以接受,但需要持续关注。14 个变更文件中没有测试 — 代码审查时会检查现有测试覆盖是否充分。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR adds HTTP rewind endpoints to the daemon, enabling web-shell and SDK clients to rewind a session's conversation and files to a previous turn—previously only available via TUI interactive dialog. The implementation is well-structured, extending the existing rewindSession ACP extMethod with a promptId parameter and leveraging FileHistoryService for file restoration. The error handling is robust with new SessionBusyError (409) and InvalidRewindTargetError (400) classes, and cross-client notification via session_rewound SSE event is a nice touch.

🔍 General Feedback

  • Architecture: Clean separation of concerns—HTTP endpoints in server.ts delegate to bridge methods in acp-bridge, which forward to ACP agent handlers. The layered approach maintains consistency with existing patterns.
  • Type Safety: Comprehensive type definitions across bridgeTypes.ts, SDK types.ts, and events.ts ensure type-safe communication between layers.
  • Error Handling: Structured error taxonomy with SessionBusyError and InvalidRewindTargetError follows the established pattern in bridgeErrors.ts, enabling precise HTTP status code mapping.
  • Backward Compatibility: The rewindSession extMethod supports both legacy targetTurnIndex and new promptId parameters, preserving compatibility with existing VSCode extension callers.
  • Event Broadcasting: The session_rewound SSE event with originatorClientId tracking enables multi-client awareness—clients can update their UI when another client triggers a rewind.

🎯 Specific Feedback

🟡 High

  • packages/cli/src/acp-integration/acpAgent.ts:2153-2177 — The getRewindSnapshots handler calls getDiffStats for all snapshots in Promise.all without pagination or limiting. The PR body acknowledges this ("may need pagination for long sessions"), but for MVP, consider at least limiting to the last 100 snapshots to prevent memory/CPU spikes on long-running sessions:

    const results = await Promise.all(
      snapshots
        .slice(0, 100) // Add limit to prevent resource exhaustion
        .filter(...)
        .map(...)
    );
  • packages/acp-bridge/src/bridge.ts:3530-3536 — The session_rewound event is published without awaiting or error handling beyond the try-catch that swallows "bus closed". While the swallow is intentional, consider logging when the event bus is unexpectedly closed during a successful rewind for observability:

    } catch (err) {
      // Log for debugging: event bus closed during rewind notification
      debugLogger?.('session_rewound event publish failed: %s', err);
    }

🟢 Medium

  • packages/cli/src/serve/server.ts:2038-2043 — The POST /session/:id/rewind endpoint validates promptId but doesn't validate the format (e.g., contains the ######## separator). While the agent-side handler validates this, adding early validation at the HTTP boundary provides faster feedback:

    if (!promptId.includes('########')) {
      res.status(400).json({
        error: '`promptId` must be in format: <sessionId>########<turnIndex>',
        code: 'invalid_prompt_id_format',
      });
      return;
    }
  • packages/cli/src/acp-integration/acpAgent.ts:2957-2962 — The file rewind failure is silently swallowed with a comment "File history snapshot may not exist — non-fatal". While non-fatal, consider populating filesFailed with a sentinel value or logging when the snapshot is missing, so clients know file restoration was skipped:

    } catch (err) {
      // Log missing snapshot for observability
      debugLogger?.('FileHistoryService.rewind failed: %s', err);
      filesFailed.push('[snapshot unavailable]');
    }
  • packages/sdk-typescript/src/daemon/DaemonClient.ts:1158-1178 — The rewindSession method accepts an optional clientId, but the DaemonSessionClient.rewind method (line 297-300 in DaemonSessionClient.ts) hardcodes this.clientId. Consider documenting whether callers should ever pass a custom clientId or if the optional parameter should be removed from the public API.

🔵 Low

  • packages/acp-bridge/src/bridgeErrors.ts:435 — The InvalidRewindTargetError message says "compressed or does not exist". Consider clarifying what "compressed" means in this context (e.g., "pruned due to age" or "skipped turn") for better client-facing error messages.

  • packages/cli/src/ui/commands/rewindCommand.ts:17 — The supportedModes: ['interactive'] addition is correct but worth noting that this command now has dual paths: TUI dialog for interactive mode, HTTP endpoints for daemon mode. Consider adding a comment linking to the HTTP endpoints for discoverability.

  • packages/sdk-typescript/src/daemon/events.ts:1173-1183 — The isSessionRewoundData validator checks filesChanged is an array but doesn't validate element types. While TypeScript provides compile-time safety, adding runtime validation (every(item => typeof item === 'string')) would catch malformed SSE events from buggy daemon versions.

✅ Highlights

  • Error Kind Propagation: The mapping from agent-side RequestError with errorKind ('session_busy', 'invalid_rewind_target') to bridge-level typed errors (SessionBusyError, InvalidRewindTargetError) to HTTP status codes (409, 400) is exemplary—each layer preserves structured error semantics without text-matching.

  • PromptId Format Design: Using <sessionId>########<turnIndex> as a composite key is clever—it ties snapshots to their session namespace while preserving turn ordering. The 1-based to 0-based conversion logic (line 2925-2927) is well-commented.

  • SSE Event Design: The session_rewound event includes originatorClientId, enabling multi-client setups to distinguish "I triggered this rewind" from "another client rewound the session". This is critical for collaborative debugging scenarios.

  • Backward Compatibility: Supporting both targetTurnIndex (legacy) and promptId (new) in the same handler without breaking existing VSCode extension callers demonstrates thoughtful API evolution.

  • Retry-After Header: The SessionBusyError response includes Retry-After: 5 header, preventing clients from hammering the endpoint while a prompt is running. This is consistent with RestoreInProgressError handling.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class HTTP + SDK support for rewinding a daemon session to an earlier turn (conversation truncation + best-effort file history restore), enabling web-shell/SDK clients to replace the current TUI-only /rewind dialog workflow.

Changes:

  • Add daemon HTTP routes: GET /session/:id/rewind/snapshots and POST /session/:id/rewind, with structured 400/409 error mapping.
  • Extend ACP bridge + agent extMethods to list rewind snapshots, accept promptId, rewind session state, and emit a session_rewound SSE event.
  • Update TypeScript SDK types/clients/reducers to support new endpoints and event handling.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/sdk-typescript/src/daemon/types.ts Adds SDK result/snapshot types for rewind APIs.
packages/sdk-typescript/src/daemon/index.ts Re-exports new rewind types + session_rewound event types.
packages/sdk-typescript/src/daemon/events.ts Registers/parses session_rewound event and updates session view-state reducer.
packages/sdk-typescript/src/daemon/DaemonSessionClient.ts Adds per-session convenience wrappers for snapshot listing + rewind.
packages/sdk-typescript/src/daemon/DaemonClient.ts Adds HTTP client methods for rewind snapshot listing + rewind mutation.
packages/cli/src/ui/commands/rewindCommand.ts Marks /rewind as interactive-only (TUI dialog).
packages/cli/src/serve/server.ts Implements new HTTP rewind routes + HTTP mapping for new bridge error classes.
packages/cli/src/serve/capabilities.ts Advertises session_rewind capability.
packages/cli/src/serve/acpSessionBridge.ts Re-exports new rewind error classes for server error mapping.
packages/cli/src/acp-integration/acpAgent.ts Implements extMethods for snapshot listing + promptId-based rewind + errorKind mapping.
packages/acp-bridge/src/status.ts Adds extMethod names for rewind snapshots + rewind control.
packages/acp-bridge/src/bridgeTypes.ts Adds bridge request/response types for rewind APIs.
packages/acp-bridge/src/bridgeErrors.ts Adds SessionBusyError (409) and InvalidRewindTargetError (400).
packages/acp-bridge/src/bridge.ts Adds bridge methods for snapshot listing + rewind mutation + SSE session_rewound publish.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/sdk-typescript/src/daemon/events.ts
Comment thread packages/sdk-typescript/src/daemon/DaemonClient.ts
Comment thread packages/sdk-typescript/src/daemon/DaemonSessionClient.ts
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/sdk-typescript/src/daemon/events.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Copilot review response summary

# File Comment Action
1 events.ts:1373 isSessionRewoundData too permissive for targetTurnIndex Not taking — isFiniteNumber validates; negative values rejected upstream
2 DaemonClient.ts:1145 Missing SDK unit tests Deferred to follow-up
3 DaemonSessionClient.ts:296 Missing convenience wrapper tests Deferred to follow-up
4 server.ts:2020 Missing route-level tests Deferred to follow-up
5 events.ts:1730 Missing event reducer tests Deferred to follow-up

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional notes:

  • POST /session/:id/rewind is missing from the resolveDaemonTelemetryRoute regex at server.ts:284. Rewind is a destructive mutation — add rewind to the alternation group so it's tracked in daemon telemetry.
  • historyBeforeRewind is computed in acpAgent.ts but discarded by the bridge's RewindResponse type. If daemon clients won't need undo-after-rewind, consider skipping the snapshot capture in the daemon path to avoid unnecessary serialization cost.

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings:

  1. 2 test regressions in acpAgent.test.ts: "status ext methods expose workspace snapshots without secrets" (line 1132) and "status ext methods return error cells when workspace snapshots fail" (line 1258) — the test mock is missing settings.forScope() which the new code paths require. CI-blocking regressions.
  2. The nested unbounded Promise.all in the snapshot listing (acpAgent.ts:2165) fires getDiffStats for up to 100 snapshots × all tracked files with no cap — unlike getTurnDiff which uses MAX_TURN_DIFF_FILES = 500. Worst case risks EMFILE on large sessions.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

wenshao review response summary (8a7a113)

# Comment Action
1 [Critical] session_rewound missing filesFailed Fixed — added to event, SDK type, and guard
2 [Suggestion] Partial rewind on timeout Deferred — shared limitation with other ext methods, follow-up for rewindTimeoutMs
3 [Suggestion] File rewind errors silently swallowed Fixed — surfaces error reason in filesFailed
4 [Critical] Non-atomic rewind with silent catch Fixed — same as #3
5 [Suggestion] Missing SESSION_ID_RE Fixed — now uses regex validation
6 [Suggestion] SSE event omits filesFailed Fixed — same as #1
7 [Suggestion] initTimeoutMs too short Deferred — follow-up
8 resolveDaemonTelemetryRoute missing rewind Fixed — added to regex
9 historyBeforeRewind serialization cost Not taking — backward compat with VSCode extension callers

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/sdk-typescript/src/daemon/events.ts
@wenshao

wenshao commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Local Test Report — PR #4820

Branch: feat/serve-rewind-endpoint-v2daemon_mode_b_main
Environment: macOS Darwin 25.4.0 / Node v22.17.0 / npm 10.9.2

Summary

Check Result
Build (core → acp-bridge → cli) Pass
TypeCheck (tsc --noEmit) Pass
Lint (CLI) Pass
Lint (Core) Pass
acp-bridge tests (376) 376 passed
acpAgent tests (59) 57 passed, 2 failed (pre-existing)
server.test.ts (373) 361 passed, 12 failed
SDK build FAIL — bundle size exceeded

PR-Caused Issues (2)

1. SDK browser bundle size exceeded

Error: Browser daemon SDK bundle is 109280 bytes; expected <= 108544

The new rewind types and client methods (getRewindSnapshots, rewindSession, session_rewound SSE event types) push the browser bundle over the 106 KiB limit (108544 bytes). Actual size is 109280 bytes — 736 bytes over limit. The limit was bumped from 106 → 108 KiB in a separate recent PR (#4812), but this PR is based before that change. Options: rebase onto latest daemon_mode_b_main (which now includes the 108 KiB bump) and verify the new ceiling accommodates the rewind additions, or bump the limit further if needed.

2. Capability registry tests (3 failures)
Three createServeApp > serve capability registry tests fail because session_rewind is not included in the expected feature list arrays:

  • returns a fresh ordered registered feature list
  • advertises current-protocol features separately from the registry
  • marks every current feature with its historical v1 origin

The PR adds session_rewind: { since: 'v1' } to capabilities.ts but does not update the corresponding test assertions in server.test.ts.

Pre-Existing Issues

acpAgent.test.ts — 2 failures (known on daemon_mode_b_main):

  • status ext methods > status returns session data for known session
  • status ext methods > status returns null for unknown session
    Both fail due to Cannot read properties of undefined (reading 'catch') in acpAgent.ts:316 (shutdown handler teardown race).

server.test.ts — 9 runQwenServe afterEach timeouts (known flaky on daemon_mode_b_main):
All timeout at 10s in afterEach cleanup hook — these are infrastructure timeouts unrelated to this PR's changes:

  • accepts QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS above JS timer cap
  • does not mutate process.env when caller provides mcp budget options
  • preserves pre-existing process.env values
  • accepts QWEN_SERVER_TOKEN from the env when binding non-loopback
  • --max-connections Infinity treated as unlimited
  • --max-connections 100 sets the cap as supplied
  • case-insensitive loopback: --hostname Localhost / LOCALHOST does NOT require a token
  • strips brackets from [::1] before passing to app.listen()
  • rejects unbracketed host:port typo with a useful error

Verdict

Not merge-ready. Two actionable items must be addressed:

  1. Rebase or bump SDK browser bundle size limit to accommodate rewind additions
  2. Add session_rewind to capability registry test expected arrays

Tested by wenshao

Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/cli/src/serve/server.ts
Comment thread packages/cli/src/serve/server.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough local test report.

Issue 1 (SDK bundle size): Will rebase onto latest daemon_mode_b_main which includes the 108 KiB bump from #4812.

Issue 2 (capability registry tests): Already fixed in commit 5f73d06session_rewind added to both EXPECTED_STAGE1_FEATURES and EXPECTED_REGISTERED_FEATURES in server.test.ts.

Pre-existing failures: Confirmed — the 2 acpAgent teardown races and 9 runQwenServe afterEach timeouts are known on daemon_mode_b_main and unrelated to this PR.

doudouOUC added 5 commits June 6, 2026 17:56
 T3.2)

Expose session rewind as structured HTTP API so web-shell and SDK
clients can rewind a session's conversation and files to a previous
turn without relying on TUI-only dialog interaction.

API surface:
- GET /session/:id/rewind/snapshots — list rewindable turns with diff stats
- POST /session/:id/rewind — execute file restore + conversation truncation

Leverages the existing Session.rewindToTurn() for conversation
truncation and FileHistoryService.rewind() for file restore. Extends
the existing 'rewindSession' ACP extMethod to also support promptId
parameter and file history rewind.

Error handling:
- 409 SessionBusyError when a prompt is running
- 400 InvalidRewindTargetError when the target turn is compressed or
  does not exist
- 404 SessionNotFoundError for unknown sessions

Cross-client SSE event 'session_rewound' published on success with
originatorClientId for echo suppression.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Fixes from final audit:
- acpAgent.test.ts: add filesChanged/filesFailed to expected response,
  add newSession call before invalid-turnIndex test
- server.test.ts: add session_rewind to expected feature lists
- acpAgent.ts: make snapshot turnIndex 0-based (consistent with
  rewind response targetTurnIndex)
- server.ts: restore accidentally deleted comment on
  RestoreInProgressError handler

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
…to format errors

Two Codex review fixes:

1. After a rewind, Session.turn remains monotonic so promptId suffixes
   no longer correspond to actual turn positions. Use the snapshot's
   index in FileHistoryService.getSnapshots() instead of parsing the
   suffix — the array is always in sync with the current conversation.

2. Format validation errors (invalid prefix, non-numeric suffix) now
   carry errorKind: 'invalid_rewind_target' so the bridge maps them
   to 400 instead of falling through to 500.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
…acing, telemetry route

Fixes from wenshao's CHANGES_REQUESTED review:

1. Add filesFailed to session_rewound SSE event payload so
   subscribers can detect partial file restoration failures
2. Surface file rewind errors in filesFailed array instead of
   silently swallowing them
3. Add SESSION_ID_RE validation to sessionRewindSnapshots handler
4. Add 'rewind' to resolveDaemonTelemetryRoute regex
5. Update DaemonSessionRewoundData type and isSessionRewoundData
   guard to include filesFailed

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
…e extraction

wenshao R3 fixes:
1. Add debugLogger.error for file-history rewind failures so oncall
   has log breadcrumbs for partial-rewind incidents
2. Extract response fields once and reuse in both event + return
3. Fix rewound boolean: false when filesFailed is non-empty

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
@doudouOUC doudouOUC force-pushed the feat/serve-rewind-endpoint-v2 branch from 5747039 to bb69139 Compare June 6, 2026 09:56
@doudouOUC doudouOUC requested a review from wenshao June 6, 2026 10:01
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
@wenshao

wenshao commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

✅ Local Verification Report — PR #4820

feat(serve): add HTTP rewind endpoints for daemon/web-shell (issue #4514 T3.2)

Environment

  • macOS Darwin 25.4.0, Node.js v22.17.0, npm workspaces monorepo
  • Branch: feat/serve-rewind-endpoint-v2daemon_mode_b_main
  • Verified commits up to: bb69139 (debugLogger + deduplicate response extraction)

Build Verification

Step Result
npm run build (full monorepo) ✅ PASS (EXIT_CODE=0)
tsc --build (type-check) ✅ PASS (EXIT_CODE=0)

Test Results

Suite Result Details
ACP Bridge (packages/acp-bridge) ✅ 752/752 passed Full suite green
acpAgent.test.ts rewind tests ✅ 4/4 passed Happy path, invalid session, invalid turnIndex, missing session
acpAgent.test.ts full ✅ 54/56 passed 2 failures are pre-existing workspace snapshot tests
server.test.ts ⚠️ 13 failures 3 capability-registry count drift + 10 runQwenServe hook timeouts (see below)
CLI full suite ✅ EXIT_CODE=0 Pre-existing failures only (except capability-registry)

Capability Registry Count Drift (Minor Issue)

3 server.test.ts capability-registry tests fail: actual 53 features vs expected 50. The test arrays include session_rewind (added by this PR), but the expected count is 3 behind because other PRs merged to daemon_mode_b_main since this branch's rebase added capabilities not reflected in the test's expected list. Recommend rebasing onto latest daemon_mode_b_main and updating the expected feature arrays.

Pre-existing Failures (NOT introduced by this PR)

Test File Failures Cause
acpAgent.test.ts 2 Workspace snapshot shape mismatch (PR #4563 scope)
server.test.ts runQwenServe 10 Hook timeouts (10s) — local environment issue
server.test.ts entryIndex 1 Pre-existing
AuthDialog.test.tsx 7 UI test timeouts
useGeminiStream.test.tsx 2 handleFinishedEvent assertion
gemini.test.tsx 2 kitty protocol
Various .test.js stale copies Several Stale compiled files

Review Issues Status

# Issue Status
1 [Critical] session_rewound missing filesFailed ✅ Fixed (dc46c60) — event, SDK type, and type guard all include filesFailed
2 [Critical] Non-atomic rewind — silent error catch ✅ Fixed (dc46c60) — catch populates filesFailed with error reason
3 [Critical] Missing debugLogger in file-rewind catch ✅ Fixed (bb69139) — debugLogger.error() added
4 [Suggestion] Response fields extracted twice ✅ Fixed (bb69139) — deduplicated
5 [Suggestion] Missing SESSION_ID_RE validation ✅ Fixed (dc46c60) — uses SESSION_ID_RE.test()
6 [Suggestion] Partial rewind risk on bridge timeout Acknowledged — deferred (matches existing withTimeout pattern)
7 [Suggestion] Error classification uses fragile string matching Acknowledged — deferred (requires Session.ts changes out of scope)
8 [Suggestion] session_rewound has no UI normalizer case Acknowledged — deferred to web-shell integration PR

Code Review Summary

  • HTTP endpoints: Clean GET /session/:id/rewind/snapshots + POST /session/:id/rewind with promptId validation, proper sendBridgeError mapping
  • Bridge: rewindSession() with SessionBusyError (409) + InvalidRewindTargetError (400) error classes, session_rewound SSE event with filesFailed
  • ACP handler: promptIdturnIndex resolution via snapshot array position (not suffix parsing), SESSION_ID_RE validation, debugLogger.error() on file-rewind failure
  • SDK: DaemonSessionRewoundData type with filesFailed, isSessionRewoundData type guard, getRewindSnapshots()/rewindSession() client methods

Verdict

✅ PASS — Core rewind functionality works (4/4 acpAgent tests pass), all Critical review issues addressed, bridge + SDK types consistent. Minor issue: capability-registry test count is 3 behind (needs rebase). Pre-existing failures only in unrelated areas.


Verified by wenshao

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at HEAD bb69139a — APPROVE

PR: feat(serve): add HTTP rewind endpoints for daemon/web-shell (issue #4514 T3.2) — +474/-14, 16 files, 5 commits

Architecture assessment

Clean 3-layer design:

  • HTTP routes (server.ts): GET /session/:id/rewind/snapshots (status) + POST /session/:id/rewind (control, mutate({ strict: true }) guard)
  • Bridge (bridge.ts): ACP extMethod transport + errorKind-based error mapping + event bus publish
  • Agent (acpAgent.ts): Core rewind logic with file-history restoration + structured error propagation

Key correctness checks

  1. Error propagation chain — Agent throws RequestError with errorKind → bridge maps to typed Error class (SessionBusyError/InvalidRewindTargetError) → server maps to HTTP status (409/400). Verified: data.errorKind check in bridge.ts correctly unwraps the structured error from ACP child.

  2. turnIndex derivation from promptId — Uses snapshot array position (findIndex), NOT promptId suffix. Comment explains Session.turn is monotonic and doesn't reset on rewind. Correct design.

  3. Snapshot isolationsessionId + '########' prefix + /^\d+$/.test() ensures per-session snapshot filtering.

  4. File-history best-effortfhs.rewind(promptId, true) wrapped in try/catch, failures captured in filesFailed[] without blocking conversation rewind. debugLogger.error added (commit 5, addresses wenshao Critical).

  5. SDK event integrationisSessionRewoundData validates with isFiniteNumber (rejects NaN/Infinity), reducer increments rewindCount and merges originatorClientId. session_rewound added to DaemonControlEvent union.

  6. Capabilitiessession_rewind registered in capability registry. Telemetry regex updated.

wenshao review audit (3 CR → APPROVE)

Round Critical findings Resolution
1 filesFailed missing from session_rewound event Fixed in commit 4
2 Non-atomic rewind + silent error catch Addressed: debugLogger.error added, errorKind mapping complete
4 debugLogger in file-history catch block Fixed in commit 5

All Critical items resolved at HEAD. Remaining Suggestions (concurrent POST race, fragile string matching, rewound boolean collapse, test coverage gaps) are follow-up level — accepted by author and reviewer across rounds.

Follow-up items (non-blocking)

  • Test coverage: Copilot flagged missing SDK unit tests for getRewindSnapshots/rewindSession and server route tests for the new endpoints. acpAgent.test.ts only has mock field updates + session existence fix, no dedicated rewind endpoint tests.
  • Telemetry gap: GET /session/:id/rewind/snapshots (multi-segment path) doesn't match the existing single-segment regex pattern.
  • String matching for error classification: msg.includes('Cannot rewind while a prompt is running') is fragile to upstream message changes.

0 blocking findings. wenshao APPROVED at HEAD.

@wenshao wenshao merged commit 885f7a0 into daemon_mode_b_main Jun 7, 2026
12 checks passed
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
Co-authored-by: Jacob Richman <jacob314@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants