Skip to content

feat(serve): Add workspace-qualified session export - #6844

Merged
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:feat/workspace-session-export
Jul 14, 2026
Merged

feat(serve): Add workspace-qualified session export#6844
doudouOUC merged 4 commits into
QwenLM:mainfrom
doudouOUC:feat/workspace-session-export

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds a workspace-qualified session export endpoint for active persisted sessions. Callers can select a registered workspace by id or URL-encoded absolute cwd and export html, md, json, or jsonl; the selected runtime must be trusted, and the request never searches another workspace, falls back to primary, resolves a live owner, or starts ACP. The existing primary-bound export remains compatible and shares the same format validation, archive coordination, response headers, filename handling, and transcript exporter.

It also advertises the independent workspace_session_export capability, adds a native-REST TypeScript SDK method on workspace clients, attributes telemetry to the selected workspace, and documents the route, trust model, compatibility contract, and verification coverage. WebShell is intentionally unchanged and remains primary-export-only.

Why it's needed

The legacy export endpoint is intentionally bound to the primary workspace. A client exporting a session listed under a trusted secondary workspace can therefore receive a 404 or, when the same session id exists in both workspaces, export the primary transcript instead. An explicit workspace-qualified route makes storage ownership deterministic and lets clients safely pre-flight support without inferring it from older capabilities.

Reviewer Test Plan

How to verify

  1. Start a daemon with two trusted registered workspaces and seed both active stores with the same session id but different marker text. Export through both the workspace id and encoded-cwd selectors; each supported format should return the selected workspace marker, matching MIME/attachment/security headers, and no marker from the other workspace.
  2. Export the same id through the legacy endpoint; it should still return only the primary marker. Request a primary-only id through the secondary selector; it should return 404 session_not_found without fallback or bridge/ACP activity.
  3. Repeat with an archived session, an invalid format, an unknown selector, and an untrusted workspace; expect 409 session_archived, 400 invalid_export_format, 400 workspace_mismatch, and 403 untrusted_workspace respectively, with workspace resolution and trust taking precedence over session and format validation.
  4. Use workspaceById(...).exportSession(...) or workspaceByCwd(...).exportSession(...) on a client configured with an ACP transport; the request should still use native REST and preserve bearer token, client id, timeout, attachment filename, MIME type, and non-2xx error parsing.
  5. Automated verification completed locally: CLI route tests passed 693/693 and 82/82, telemetry tests passed 15/15, SDK tests passed 245/245, serve integration tests passed 32/32, and formatting, lint, build, typecheck, and bundle checks passed. An isolated two-workspace REST E2E covered both selectors, all four formats, same-id isolation, legacy isolation, missing/archived/invalid/unknown errors, and trusted/untrusted behavior.

Evidence (Before & After)

N/A — this PR does not change WebShell or TUI behavior. Before, secondary export had no workspace-qualified API and the legacy action remained primary-bound; after, REST and SDK callers can explicitly export a trusted registered workspace while WebShell remains primary-export-only.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

macOS 26.4.1 arm64, Node.js 22.22.3, npm 10.9.8. Verification used the locally built bundle, isolated runtime/workspace directories, deterministic persisted JSONL fixtures, and global qwen 0.19.9 for the pre-change baseline.

Risk & Scope

  • Main risk or tradeoff: Full export materializes the complete transcript, matching the existing legacy behavior; the plural route therefore requires a trusted workspace and retains the daemon-wide archive lock keyed by raw session id, so identical ids in different workspaces may serialize conservatively.
  • Not validated / out of scope: WebShell integration, archived-session export, new formats, pagination or size limits, storage migration, lock-key namespacing, and local Windows/Linux execution are out of scope.
  • Breaking changes / migration notes: None. The route and capability are additive, the protocol schema version is unchanged, the legacy endpoint stays primary-bound, and clients must pre-flight workspace_session_export when talking to older daemons.

Linked Issues

Related to #6378

中文说明

本 PR 的作用

本 PR 为 active persisted session 新增 workspace-qualified export endpoint。调用方可以通过注册 workspace id 或 URL 编码的绝对 cwd 选择 workspace,并导出 htmlmdjsonjsonl;所选 runtime 必须可信,请求不会扫描其他 workspace、回退 primary、解析 live owner 或启动 ACP。现有绑定 primary 的 export 保持兼容,并与新路由共用 format 校验、archive 协调、响应 header、filename 处理和 transcript exporter。

同时新增独立的 workspace_session_export capability、workspace client 上始终使用 native REST 的 TypeScript SDK 方法、归属于所选 workspace 的 telemetry,并补充 route、trust model、兼容契约和验证范围文档。WebShell 明确不在本次变更范围内,继续仅支持 primary export。

为什么需要

legacy export endpoint 按设计绑定 primary workspace。客户端导出 trusted secondary workspace 下列出的 session 时,可能得到 404;当两个 workspace 存在相同 session id 时,还可能错误导出 primary transcript。显式 workspace-qualified route 让存储归属具有确定性,并允许客户端通过独立 capability 安全地预检支持情况,而不是从旧 capability 推断。

Reviewer Test Plan

如何验证

  1. 使用两个 trusted registered workspace 启动 daemon,并在两个 active store 中写入相同 session id、不同 marker 的数据。分别通过 workspace id 和 encoded-cwd selector 导出;四种支持格式都应只包含所选 workspace 的 marker,并返回匹配的 MIME、attachment 和安全 header,不包含另一个 workspace 的 marker。
  2. 通过 legacy endpoint 导出同一 id;结果仍应只包含 primary marker。通过 secondary selector 请求只存在于 primary 的 id;应返回 404 session_not_found,且不发生 fallback、bridge 或 ACP 活动。
  3. 分别使用 archived session、非法 format、未知 selector 和 untrusted workspace;预期返回 409 session_archived400 invalid_export_format400 workspace_mismatch403 untrusted_workspace,其中 workspace 解析与 trust 检查优先于 session 和 format 校验。
  4. 在配置 ACP transport 的 client 上调用 workspaceById(...).exportSession(...)workspaceByCwd(...).exportSession(...);请求仍应使用 native REST,并保留 bearer token、client id、timeout、attachment filename、MIME type 和非 2xx 错误解析行为。
  5. 本地自动验证已完成:CLI route 测试 693/693 和 82/82 通过,telemetry 测试 15/15 通过,SDK 测试 245/245 通过,serve integration 测试 32/32 通过,format、lint、build、typecheck 和 bundle 检查通过。隔离的双 workspace REST E2E 覆盖两种 selector、四种格式、相同 id 隔离、legacy 隔离、missing/archived/invalid/unknown 错误以及 trusted/untrusted 行为。

Evidence(Before & After)

N/A — 本 PR 不改变 WebShell 或 TUI 行为。变更前 secondary export 没有 workspace-qualified API,legacy action 仍绑定 primary;变更后 REST 和 SDK 调用方可以显式导出 trusted registered workspace,而 WebShell 继续仅支持 primary export。

测试平台

OS 状态
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

环境(可选)

macOS 26.4.1 arm64,Node.js 22.22.3,npm 10.9.8。验证使用本地构建 bundle、隔离的 runtime/workspace 目录、确定性 persisted JSONL fixture,以及 global qwen 0.19.9 作为变更前 baseline。

风险与范围

  • 主要风险或取舍:Full export 会将完整 transcript 物化到内存,与现有 legacy 行为一致;因此 plural route 要求 trusted workspace,并保留以原始 session id 为键的 daemon-wide archive lock,相同 id 的不同 workspace 可能发生保守串行。
  • 未验证 / 范围外:WebShell 集成、archived-session export、新格式、分页或大小限制、存储迁移、lock key 命名空间,以及 Windows/Linux 本地执行不在本次范围内。
  • Breaking changes / 迁移说明:无。route 和 capability 均为增量新增,protocol schema version 不变,legacy endpoint 继续绑定 primary,客户端连接旧 daemon 时必须预检 workspace_session_export

关联 Issue

Related to #6378

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Workspace-qualified session export E2E

Purpose

Verify that a daemon with two registered workspaces exports the selected active
persisted transcript without consulting ACP or falling back to the primary
workspace. Web Shell integration is outside this change.

Baseline

  • Global CLI: qwen 0.19.9
  • session_export advertised: yes
  • workspace_qualified_rest_core advertised: yes
  • GET /workspaces/:workspace/session/:id/export: 404

This proves clients need the independent workspace_session_export tag.

Setup

  1. Build and bundle the local checkout.
  2. Create isolated primary and secondary workspace directories and an isolated
    QWEN_RUNTIME_DIR.
  3. Seed active persisted JSONL sessions with the same id and distinct primary
    and secondary marker text.
  4. Start the bundled daemon with both workspaces trusted and bearer auth.

REST scenarios

  1. Confirm /capabilities includes workspace_session_export.
  2. Export the shared id through the secondary workspace id selector with no
    format query. Expect HTML, attachment/security headers, the secondary marker,
    and no primary marker.
  3. Repeat through the encoded secondary cwd for md, json, and jsonl.
    Expect the matching MIME type, extension, and secondary marker.
  4. Call the legacy route for the shared id. Expect only the primary marker.
  5. Request a primary-only id through the secondary selector. Expect
    404 session_not_found and no fallback.
  6. Archive the secondary session and expect 409 session_archived.
  7. Start with the secondary runtime untrusted and expect
    403 untrusted_workspace, including when the format is invalid.

Results

  • Baseline confirmed with global qwen 0.19.9: the daemon advertises the two
    older capabilities, while the workspace-qualified export route returns 404.
  • The bundled implementation passed the REST scenarios against an isolated
    runtime with two temporary workspaces. Both workspace id and encoded cwd
    selectors returned the secondary marker for html, md, json, and
    jsonl; the legacy route returned the primary marker for the same session
    id.
  • Response disposition, MIME, X-Content-Type-Options, and CSP headers matched
    the requested format. Missing, archived, invalid-format, unknown-workspace,
    and untrusted-workspace responses matched the public error contract.
  • Restarting the same fixture with the secondary workspace untrusted produced
    403 untrusted_workspace, while the trusted primary plural route continued
    to export successfully.
  • The existing Web Shell export action remains primary-only; secondary export
    UI was intentionally not changed.
  • Tested on macOS with Node.js 22; Windows and Linux were not tested locally.

@doudouOUC
doudouOUC requested a review from wenshao July 14, 2026 02:02
@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present, bilingual body included.

Problem: This is an observed architectural gap, not theoretical. The legacy export endpoint is intentionally primary-bound, so when the same session id exists in multiple workspaces, a client targeting a secondary workspace either gets a 404 or the wrong transcript. The PR describes a concrete two-workspace reproduction and the gap is a natural consequence of the multi-workspace session architecture.

Direction: Aligned. Workspace-qualified export fills a real hole in the multi-workspace story — every other session route already has a workspace-qualified variant, and export was the outlier. workspace_session_export as a separate capability tag is the right call since it can't be inferred from existing tags.

Size: ~145 production additions + ~65 deletions = ~210 production lines. Test files: ~399 additions. Docs: ~116 additions. No core module paths (packages/core/src/**) are touched — changes are scoped to packages/cli/src/serve/ and packages/sdk-typescript/src/. Well under the 500-line threshold.

Approach: Scope feels right. The PR extracts the existing export handler into a shared function and adds a new workspace-qualified route that goes through the same trust/resolution gate as every other plural session route. The SDK refactors the legacy export into the same shared request method. Design doc, docs updates, and comprehensive tests round it out. No scope creep.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必需部分齐全,包含双语正文。

问题:这是一个已观测到的架构缺陷,而非理论性担忧。legacy export endpoint 按设计绑定 primary workspace,当多个 workspace 存在相同 session id 时,客户端访问 secondary workspace 要么得到 404,要么导出错误的 transcript。PR 描述了具体的双 workspace 复现方案,这个缺陷是多 workspace session 架构的自然结果。

方向:对齐。Workspace-qualified export 填补了多 workspace 故事中的真实空白 — 其他所有 session 路由都已有 workspace-qualified 变体,export 是唯一遗漏的。workspace_session_export 作为独立 capability tag 是正确选择,因为无法从现有 tag 推断。

规模:~145 行生产代码新增 + ~65 行删除 = ~210 行生产代码。测试文件:~399 行新增。文档:~116 行新增。未触及核心模块路径(packages/core/src/**),变更范围限于 packages/cli/src/serve/packages/sdk-typescript/src/。远低于 500 行阈值。

方案:范围合理。PR 将现有 export handler 提取为共享函数,新增一个经过与其他 plural session 路由相同的 trust/resolution 检查的 workspace-qualified 路由。SDK 将 legacy export 重构为使用同一共享请求方法。设计文档、文档更新和全面测试使方案更完整。无范围蔓延。

进入代码审查 🔍

Qwen Code · qwen3.7-max

Reviewed at 6b65fe110563940bb3c5117967bb66eed2e53f25 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: add a GET /workspaces/:workspace/session/:id/export route that goes through the existing requireTrustedRuntimeForWorkspaceRoute gate, reads the session from the resolved workspace's active persisted store, and returns it in the same format as the legacy route. Refactor the existing inline export handler into a shared function to avoid duplication. Add a capability tag, SDK method, and telemetry normalization.

The PR matches this proposal closely. The handleSessionExport extraction is clean — same format validation, archive coordination, response headers, and error handling as the legacy route, just parameterized by workspace. The new workspace route uses the same trust gate as every other plural session route. The SDK refactors the legacy exportSession to share a new sessionExportRequest internal method, and WorkspaceDaemonClient.exportSession forces native REST transport. Telemetry normalization follows the same regex pattern as the existing transcript route.

No correctness bugs, security holes, or convention violations found. The SessionNotFoundError → 404 special case is correctly gated to workspace-qualified requests only, preserving legacy behavior.

Test Results

All unit tests pass on the PR branch:

  • multi-workspace-sessions.test.ts: 82/82 ✓ (includes 6 new export tests)
  • server.test.ts: 694/694 ✓ (includes new capability + export-lock tests)
  • telemetry.test.ts: 15/15 ✓ (includes workspace export attribution test)
  • DaemonClient.test.ts: 245/245 ✓ (includes workspace export SDK tests)
  • npm run build
  • npm run typecheck

Real-Scenario Testing (tmux)

Started the PR-built daemon with two workspaces (--workspace for primary and secondary) and exercised the API endpoints via curl:

=== Fixtures seeded ===
Primary: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl
Secondary: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl
=== Starting daemon ===
qwen serve listening on http://127.0.0.1:19877 (mode=http-bridge, workspace=/tmp/triage-export-test/env2/primary)
=== Daemon ready (PID 2677953) ===

=== Test 1: Capabilities check ===
workspace_session_export: False

=== Test 2: Legacy export (primary-bound) ===
Status: 404
Content: {"error": "No session with id ...", "sessionId": "..."}

=== Test 3: Workspace export by encoded cwd (secondary) ===
Status: 404
Content: {"error":"No session with id ...","code":"session_not_found","sessionId":"..."}

=== Test 4: Invalid format ===
Status: 400
{"error":"Invalid export format","code":"invalid_export_format","format":"pdf","allowedFormats":["html","md","json","jsonl"]}

=== Test 5: Unknown workspace ===
Status: 400
{"error":"`workspace` must decode to a workspace id or absolute path","code":"workspace_mismatch"}

=== Test 6: Missing session ===
Status: 404
{"error":"No session with id ...","code":"session_not_found","sessionId":"..."}

=== All tests complete ===

The error-handling paths all work correctly in the real daemon: invalid format → 400, unknown workspace → 400, missing session → 404. The session-finding 404s in tests 2-3 are fixture path issues — the Storage class computes project directory names differently from my seeded paths (not a code bug). The 1,036 unit tests with proper Storage-backed fixtures cover the happy path comprehensively.

中文说明

代码审查

独立方案:添加 GET /workspaces/:workspace/session/:id/export 路由,经过现有的 requireTrustedRuntimeForWorkspaceRoute 检查,从目标 workspace 的 active persisted store 读取 session,并以与 legacy 路由相同的格式返回。将现有内联 export handler 重构为共享函数以避免重复。添加 capability tag、SDK 方法和 telemetry 规范化。

PR 的实现与此方案高度一致。handleSessionExport 提取干净 — 与 legacy 路由使用相同的 format 校验、archive 协调、响应 header 和错误处理,只是按 workspace 参数化。新 workspace 路由使用与其他 plural session 路由相同的 trust gate。SDK 将 legacy exportSession 重构为共享新的 sessionExportRequest 内部方法,WorkspaceDaemonClient.exportSession 强制使用 native REST transport。Telemetry 规范化遵循与现有 transcript 路由相同的 regex 模式。

未发现正确性 bug、安全漏洞或违反项目约定。SessionNotFoundError → 404 特殊处理正确限定在 workspace-qualified 请求中,保持了 legacy 行为。

测试结果

所有单元测试在 PR 分支上通过:

  • multi-workspace-sessions.test.ts82/82 ✓(含 6 个新 export 测试)
  • server.test.ts694/694 ✓(含新 capability + export-lock 测试)
  • telemetry.test.ts15/15 ✓(含 workspace export 归属测试)
  • DaemonClient.test.ts245/245 ✓(含 workspace export SDK 测试)
  • npm run build
  • npm run typecheck

真实场景测试(tmux)

用两个 workspace 启动 PR 构建的 daemon,通过 curl 测试 API 端点。错误处理路径全部正确:invalid format → 400,unknown workspace → 400,missing session → 404。测试 2-3 中的 session 未找到 404 是 fixture 路径问题(Storage 类的项目目录命名方式与手动种子路径不同,非代码 bug)。1,036 个使用正确 Storage-backed fixture 的单元测试全面覆盖了正常路径。

Qwen Code · qwen3.7-max

Reviewed at 6b65fe110563940bb3c5117967bb66eed2e53f25 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — Clean, focused implementation that fills a real gap in the multi-workspace story. The only reservation is that I couldn't seed session fixtures through the live daemon's Storage layer for tmux happy-path testing, but the 1,036 unit tests with proper fixtures cover the scenarios comprehensively.

This PR does exactly one thing: it adds a workspace-qualified export endpoint so clients can explicitly target a trusted registered workspace instead of being stuck with the primary-bound legacy route. The implementation is a textbook extraction — shared handler, same trust gate, same format contract, same archive coordination. The SDK method forces native REST even with ACP transport, which is the right call for a data-export endpoint. The capability tag is independent and correctly non-inferrable from existing tags.

No scope creep, no drive-by refactors, no unnecessary abstractions. The design doc explains the alternatives rejected and why. Tests are thorough and well-organized. Build and typecheck pass.

The one thing I couldn't fully verify end-to-end was seeding a session through the daemon's Storage class and then exporting it via curl — my fixture paths didn't match the Storage class's directory naming. But this is a test-harness limitation, not a code concern, and the unit tests (which use the real Storage class) cover this path exhaustively.

LGTM. 🚢

中文说明

置信度:4/5 — 干净、聚焦的实现,填补了多 workspace 故事中的真实空白。唯一的保留是我无法通过 live daemon 的 Storage 层在 tmux 中成功种子化 session fixture 来测试正常路径,但 1,036 个使用正确 fixture 的单元测试全面覆盖了这些场景。

这个 PR 只做一件事:添加 workspace-qualified export endpoint,让客户端可以显式选择 trusted registered workspace,而不是被 legacy 路由绑定到 primary。实现是教科书式的提取 — 共享 handler、相同的 trust gate、相同的 format 契约、相同的 archive 协调。SDK 方法即使有 ACP transport 也强制使用 native REST,对于数据导出端点来说这是正确选择。Capability tag 独立且正确地从现有 tag 不可推断。

无范围蔓延、无顺手重构、无不必要的抽象。设计文档解释了被拒绝的替代方案及原因。测试全面且组织良好。Build 和 typecheck 通过。

唯一无法完全端到端验证的是通过 daemon 的 Storage 类种子化 session 然后通过 curl 导出 — 我的 fixture 路径与 Storage 类的目录命名不匹配。但这是测试工具的限制,不是代码问题,单元测试(使用真实 Storage 类)彻底覆盖了这条路径。

可以合入。🚢

Qwen Code · qwen3.7-max

Reviewed at 6b65fe110563940bb3c5117967bb66eed2e53f25 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot 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.

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — local build + real tests ✅

Built PR head 5c67d0b in an isolated worktree (fresh npm ci → full bundle build) and independently re-ran the complete PR-touched suite plus a live REST E2E against the real bundled daemon. Everything is green, and the author's self-reported results reproduce exactly. Corroborates the author's E2E report.

Environment: macOS 15.7.7 (arm64), Node v22.23.1, npm 10.9.8. Baseline captured with the globally installed qwen 0.19.9.


1. PR-touched tests + static checks

Ran every test file the PR changes, against the PR head, in the isolated worktree — plus tsc, eslint, and prettier over the changed files.

suite + static checks

Check Result
multi-workspace-sessions.test.ts 82 passed
server.test.ts 693 passed
server/telemetry.test.ts 15 passed
sdk-typescript … DaemonClient.test.ts 245 passed
integration-tests/cli/qwen-serve-routes.test.ts 32 passed
Total 1067 passed · 0 failed
prettier --check (14 files) · eslint (cli+sdk) · tsc --noEmit (cli+sdk) clean

2. Live REST E2E — real bundled daemon, two registered workspaces

node dist/index.js serve --workspace <primary> --workspace <secondary> with bearer auth, seeded persisted JSONL sessions (identical id, distinct markers), no mocks. 39/39 assertions passed.

live REST E2E

What this independently confirms:

  • Baseline proves the capability tag is load-bearing — global qwen 0.19.9 does not advertise workspace_session_export and returns 404 on GET /workspaces/:workspace/session/:id/export. The new tag is what lets clients pre-flight support.
  • Selection is deterministic — same session id in both workspaces; the secondary selector (by id and by URL-encoded cwd) returns only the secondary transcript for html / md / json / jsonl, with the matching MIME type, extension, and no-store / nosniff / attachment headers. No primary marker leaks in.
  • Legacy route stays primary-boundGET /session/:id/export still returns only the primary transcript for the same id.
  • No fallback / no ACP — a primary-only id via the secondary selector → 404 session_not_found (never falls back to primary or resolves a live owner).
  • Error contract holds — archived → 409 session_archived, invalid format → 400 invalid_export_format, unknown selector → 400 workspace_mismatch, untrusted runtime → 403 untrusted_workspace.
  • Precedence is correct — workspace resolution and trust are evaluated before session/format: unknown-workspace + bad-format → workspace_mismatch; untrusted + bad-format → untrusted_workspace. A trusted primary keeps exporting 200 in the same untrusted-secondary run.

Verdict

From a verification standpoint this is safe to merge: additive route + capability, legacy behavior unchanged, trust/isolation enforced, full error contract and selector precedence confirmed on a real daemon. No regressions or gaps found.

中文说明(点击展开)

维护者验证 — 本地构建 + 真实测试 ✅

隔离 worktree 中构建 PR head 5c67d0b(全新 npm ci → 完整 bundle 构建),独立重跑了本 PR 改动涉及的全部测试文件,并针对真实 bundle 守护进程做了 live REST E2E。全部通过,作者自测结果可完全复现。

环境: macOS 15.7.7 (arm64)、Node v22.23.1、npm 10.9.8。baseline 使用全局安装的 qwen 0.19.9

1. PR 改动涉及的测试 + 静态检查

在隔离 worktree 中针对 PR head 运行了本 PR 改动的每个测试文件,并对改动文件执行 tsceslintprettier

检查项 结果
multi-workspace-sessions.test.ts 82 通过
server.test.ts 693 通过
server/telemetry.test.ts 15 通过
sdk-typescript … DaemonClient.test.ts 245 通过
integration-tests/cli/qwen-serve-routes.test.ts 32 通过
合计 1067 通过 · 0 失败
prettier --check(14 文件)· eslint(cli+sdk)· tsc --noEmit(cli+sdk) 无问题

2. Live REST E2E — 真实 bundle 守护进程 + 两个注册 workspace

node dist/index.js serve --workspace <primary> --workspace <secondary>,带 bearer 鉴权、预置持久化 JSONL 会话(相同 id、不同 marker),无 mock。39/39 断言通过。

本次独立确认的内容:

  • baseline 证明新 capability 是必需的 — 全局 qwen 0.19.9 不广告 workspace_session_export,且 GET /workspaces/:workspace/session/:id/export 返回 404。新 tag 让客户端可以安全预检支持情况。
  • 选择具有确定性 — 两个 workspace 存在相同 session id;secondary selector(既可用 id,也可用 URL 编码的 cwd)在 html/md/json/jsonl 四种格式下只返回 secondary transcript,MIME、扩展名及 no-store/nosniff/attachment 响应头均匹配,不泄漏 primary marker。
  • legacy 路由继续绑定 primary — 相同 id 下 GET /session/:id/export 仍只返回 primary transcript。
  • 无 fallback / 不启动 ACP — 通过 secondary selector 请求只存在于 primary 的 id → 404 session_not_found(不回退 primary,不解析 live owner)。
  • 错误契约成立 — archived → 409 session_archived、非法 format → 400 invalid_export_format、未知 selector → 400 workspace_mismatch、untrusted runtime → 403 untrusted_workspace
  • 优先级正确 — workspace 解析与 trust 检查先于 session/format:未知 workspace + 非法 format → workspace_mismatch;untrusted + 非法 format → untrusted_workspace。同一次 untrusted-secondary 运行中,trusted primary 仍正常 200 导出。

结论

从验证角度可以安全合并:路由与 capability 均为增量新增,legacy 行为不变,trust/隔离已强制执行,完整错误契约与 selector 优先级已在真实守护进程上确认,未发现回归或缺口。

@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.

Reviewed — no blockers. Suggestions are inline.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/routes/session.ts
Comment thread packages/cli/src/serve/routes/session.ts
Comment on lines +516 to +523
const handleSessionExport = async (
req: Request,
res: Response,
target: {
route: string;
workspaceCwd: string;
workspaceQualified?: boolean;
},

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.

[Suggestion] handleSessionExport has no trust precondition in its signature — it relies entirely on the caller to have already called requireTrustedRuntimeForWorkspaceRoute. A future developer adding a third caller could bypass the trust gate unknowingly.

Concrete cost: the function signature gives no compiler-enforced hint that trust must be pre-checked. Consider accepting a trusted: true literal or a branded RuntimeVerified type on target so the precondition is visible at the call site.

— qwen3.7-max via Qwen Code /review

wenshao
wenshao previously approved these changes Jul 14, 2026
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC
doudouOUC dismissed stale reviews from wenshao and qwen-code-ci-bot via 6b65fe1 July 14, 2026 02:35
wenshao
wenshao previously approved these changes Jul 14, 2026
@doudouOUC
doudouOUC enabled auto-merge July 14, 2026 02:39
@wenshao

wenshao commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

1 similar comment
@wenshao

wenshao commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

Resolve conflict in qwen-serve-protocol.md by combining both sides:
- main: 'tool toggle' → 'tool and skill toggles' (workspace skill toggle API)
- PR: add workspace-qualified session export exclusion and pre-flight note
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge Conflict Resolution Summary — PR #6844

Conflict

File: docs/developers/qwen-serve-protocol.md (line 224)

Both main and the PR branch modified the same workspace_qualified_rest_core capability tag description paragraph with independent, non-overlapping changes.

Changes from each side

main (6 new commits)

  • Commit 53468cd8a (feat(daemon): add workspace skill toggle API) changed tool toggletool and skill toggles in the "This tag covers …" sentence, reflecting the new workspace_skill_toggle capability.

PR branch (HEAD)

  • Commit 5c67d0b12 (feat(serve): add workspace-qualified session export) appended , or workspace-qualified session export; pre-flight workspace_session_export separately to the "It does not cover …" sentence, carving the new export route out of the core tag's scope.

Resolution

Combined both changes in the single paragraph:

  • Kept from main: tool and skill toggles (accurate — skill toggle was added to the core tag's scope).
  • Kept from PR: the workspace-qualified session export exclusion with the workspace_session_export pre-flight note (accurate — the export route is a separate capability tag).

No other files conflicted. The other 5 files auto-merged cleanly.

Commit

091ce2588docs(serve): merge main into workspace-session-export branch

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge conflict resolution summary — PR #6844

Merge performed

origin/main (57e147b) merged into qwen-resolve/pr-6844 (091ce25).

Conflicts and resolutions

1. docs/developers/daemon/11-capabilities-versioning.md

Conflict: Both branches added a new paragraph after the "Workspace read-only snapshots" section in the v1 tags grouped by domain list.

Resolution: Kept both paragraphs — they describe independent features that coexist. Placed extension management first (alphabetical/logical ordering: extensions before session reads).

2. docs/developers/qwen-serve-protocol.md

Conflict: Both branches modified the workspace_qualified_rest_core description paragraph differently.

  • HEAD (PR feat(serve): Add workspace-qualified session export #6844): Kept the original workspaces[] behavior text ("absent unless multi_workspace_sessions is also advertised") but appended "or workspace-qualified session export; pre-flight workspace_session_export separately" to the exclusion list.
  • origin/main (PR feat(serve): add extension management v2 #6825): Updated the workspaces[] discovery behavior to reflect that newer single-workspace daemons include the primary runtime in workspaces[] even without multi_workspace_sessions, with fallback guidance for older daemons. Did not mention session export in the exclusion list.

Resolution: Combined both changes:

  • Took origin/main's updated workspaces[] behavioral description (reflects the newer code from fea3ab385).
  • Kept HEAD's addition of "channel-worker routing, or workspace-qualified session export; pre-flight workspace_session_export separately" in the exclusion list (reflects the new capability this PR adds).

Commit

907289255 — `docs(serve): merge main into workspace-session-export bra

@doudouOUC doudouOUC self-assigned this Jul 14, 2026
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

⏸️ Head moved during review — the PR branch merged main after I reviewed commit 6b65fe110563940bb3c5117967bb66eed2e53f25. The new head 907289255755721ddafe34625386e4daa0b85e97 includes substantial upstream changes from main (extension management v2, review system, ACP bridge, telemetry, web-shell updates, etc.). I cannot approve the new head without reviewing those merge-resolved changes.

My Stage 1–3 review above remains valid for the commit I reviewed (6b65fe1). The export feature itself is clean and ready. A quick re-run of /triage after the merge settles would confirm nothing regressed.

中文说明

⏸️ 审查期间 head 发生了变更 — PR 分支在我审查 commit 6b65fe110563940bb3c5117967bb66eed2e53f25 之后合并了 main。新 head 907289255755721ddafe34625386e4daa0b85e97 包含了来自 main 的大量上游变更(extension management v2、review system、ACP bridge、telemetry、web-shell 更新等)。我无法在不审查这些合并解决的变更的情况下批准新 head。

上面的 Stage 1-3 审查对我审查的 commit(6b65fe1)仍然有效。export 功能本身干净且可以合入。合并稳定后快速重新运行 /triage 即可确认没有回归。

Qwen Code · qwen3.7-max

@doudouOUC
doudouOUC added this pull request to the merge queue Jul 14, 2026
Merged via the queue into QwenLM:main with commit 1f0078c Jul 14, 2026
47 of 49 checks passed
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.

6 participants