Skip to content

feat(daemon): add session artifact APIs#5895

Merged
chiga0 merged 79 commits into
QwenLM:mainfrom
chiga0:feat/add-aritifact-interface
Jul 3, 2026
Merged

feat(daemon): add session artifact APIs#5895
chiga0 merged 79 commits into
QwenLM:mainfrom
chiga0:feat/add-aritifact-interface

Conversation

@chiga0

@chiga0 chiga0 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR implements daemon session artifacts as a complete first usable capability. Agents and tools can now attach structured artifact metadata to tool results, hooks can surface additional artifacts after tool execution, daemon clients can list/add/remove session artifacts through HTTP and ACP bridge methods, and clients can subscribe to artifact change events for right-side artifact panels.

The implementation treats links as first-class artifacts. Generated business-resource URLs, published HTML pages, workspace files, images, videos, and other session outputs share the same envelope, with explicit kind, storage, source, status, locator, title, optional description, metadata, retention hint, and timestamps.

Custom artifacts are supported through two paths: model/tool-side registration via a built-in record-artifact tool, and client-side insertion through the session artifact add API. Published artifacts remain restricted to trusted internal publishers, so arbitrary clients and hooks can register external links or workspace resources but cannot claim published storage.

The daemon bridge now keeps a per-session artifact store with validation, stable identity-based upsert, idempotent remove, workspace path containment checks, URL safety checks, missing-file refresh, and a bounded in-memory retention policy. The TypeScript SDK exposes typed list/add/remove helpers plus the artifact_changed event so daemon consumers do not need to hand-roll vendor calls.

Why it's needed

Daemon clients need a stable way to display intermediate session products while an agent is working: files it wrote, links it generated, published previews, images, videos, and business system resource URLs that users can click immediately. Without a structured artifact channel, clients have to scrape text or infer outputs from tool calls, which is fragile and makes custom non-file artifacts awkward.

This keeps the first release intentionally bounded: it is session-scoped, in-memory, append/update/remove capable, evented, SDK-accessible, and safe enough for trial usage. Future persistence, pagination, richer preview metadata, and additional artifact kinds can be added without changing the existing v1 envelope or current endpoints.

Reviewer Test Plan

How to verify

Confirm that a successful artifact-producing tool result publishes artifact metadata to daemon clients, that GET /session/:id/artifacts returns the current envelope, that POST /session/:id/artifacts can insert a client-owned external URL artifact, that DELETE /session/:id/artifacts/:artifactId is idempotent, and that artifact_changed events are emitted for created/updated/removed artifacts.

Confirm that untrusted clients cannot register published artifacts, unsafe URLs and workspace path traversal are rejected, duplicate logical artifacts are updated rather than duplicated, and generated link artifacts appear in the same list as file artifacts.

Evidence (Before & After)

N/A for visual evidence. This is a daemon/API/SDK capability; behavior is covered by focused unit tests plus build and lint.

Tested on

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

Environment (optional)

Local Node/npm workspace on macOS.

Verified locally:

  • npm run build
  • npm run lint
  • cd packages/acp-bridge && npm run typecheck
  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts src/bridgeClient.test.ts
  • cd packages/core && npx vitest run src/tools/artifact/artifact-tool.test.ts src/tools/record-artifact.test.ts
  • cd packages/cli && npx vitest run src/acp-integration/session/emitters/ToolCallEmitter.test.ts
  • cd packages/sdk-typescript && npx vitest run test/unit/daemonEvents.test.ts test/unit/acpRouteTable.test.ts

Risk & Scope

  • Main risk or tradeoff: The first implementation is intentionally session-scoped and in-memory; daemon restart persistence and historical pagination are out of scope for this PR.
  • Not validated / out of scope: No UI artifact panel is included here, and no cross-process artifact persistence is added.
  • Breaking changes / migration notes: No breaking changes expected. Existing daemon/session flows continue to work; new fields, events, APIs, and SDK helpers are additive. The SDK browser bundle budget is raised slightly to account for the new typed daemon surface.

Linked Issues

N/A

中文说明

What this PR does

本 PR 将 daemon session artifacts 从设计推进为一套可试用的完整能力。agent 和 tool 可以在 tool result 上携带结构化 artifact 元数据,hook 可以在工具执行后补充 artifact,daemon client 可以通过 HTTP 和 ACP bridge 方法 list/add/remove session artifacts,并通过 artifact_changed 事件驱动右侧产物面板更新。

本实现把 link 作为一等 artifact。模型按规则拼出的业务资源 URL、发布后的 HTML 页面、工作区文件、图片、视频以及其他 session 中间产物都使用同一份 envelope,显式包含 kind、storage、source、status、locator、title、可选 description、metadata、保留提示和时间戳。

自定义 artifact 支持两条路径:模型或工具侧通过内置 record-artifact 工具登记,client 侧通过 session artifact add API 插入。published artifact 只允许可信内部发布方写入,因此普通 client 和 hook 可以登记 external link 或 workspace resource,但不能伪造 published storage。

Daemon bridge 现在维护按 session 隔离的 artifact store,支持校验、基于稳定 identity 的 upsert、幂等删除、workspace path containment 检查、URL 安全检查、缺失文件状态刷新以及有上限的内存保留策略。TypeScript SDK 提供类型化 list/add/remove helper 和 artifact_changed 事件,daemon 消费方不需要手写 vendor call。

Why it's needed

Daemon client 需要稳定机制展示 agent 工作过程中的中间产物:写出的文件、生成的 link、发布预览、图片、视频以及用户可以立即点击的业务系统资源 URL。没有结构化 artifact 通道时,client 只能解析文本或从 tool call 里推断输出,这既脆弱,也让非文件类自定义 artifact 很难接入。

这次首版边界保持克制:session-scoped、内存态、支持新增/更新/删除、有事件、有 SDK、也具备试用所需的安全边界。后续 persistence、pagination、更丰富的 preview metadata 和更多 artifact kind 都可以在当前 v1 envelope 与 endpoint 上继续增量扩展。

Reviewer Test Plan

How to verify

请确认 artifact-producing tool result 成功后 daemon client 能收到 artifact metadata,GET /session/:id/artifacts 能返回当前 envelope,POST /session/:id/artifacts 能插入 client-owned external URL artifact,DELETE /session/:id/artifacts/:artifactId 是幂等的,并且 created/updated/removed 都会产生 artifact_changed 事件。

请确认未受信 client 不能登记 published artifact,不安全 URL 和 workspace path traversal 会被拒绝,重复逻辑 artifact 会更新而不是重复插入,生成的 link artifact 与 file artifact 会出现在同一列表中。

Evidence (Before & After)

N/A。本 PR 是 daemon/API/SDK 能力,没有可视化面板变更;行为通过聚焦单测、build 和 lint 覆盖。

Tested on

OS Status
🍏 macOS ✅ 本地已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

Environment (optional)

macOS 本地 Node/npm workspace。

本地验证命令:

  • npm run build
  • npm run lint
  • cd packages/acp-bridge && npm run typecheck
  • cd packages/acp-bridge && npx vitest run src/sessionArtifacts.test.ts src/bridgeClient.test.ts
  • cd packages/core && npx vitest run src/tools/artifact/artifact-tool.test.ts src/tools/record-artifact.test.ts
  • cd packages/cli && npx vitest run src/acp-integration/session/emitters/ToolCallEmitter.test.ts
  • cd packages/sdk-typescript && npx vitest run test/unit/daemonEvents.test.ts test/unit/acpRouteTable.test.ts

Risk & Scope

  • Main risk or tradeoff: 首版实现故意限定为 session-scoped 和 in-memory;daemon 重启后的持久化与历史分页不在本 PR 范围内。
  • Not validated / out of scope: 本 PR 不包含 UI 产物面板,也不包含跨进程 artifact 持久化。
  • Breaking changes / migration notes: 预期无 breaking change。现有 daemon/session 流程不受影响;新增字段、事件、API 和 SDK helper 都是 additive。SDK browser bundle budget 略微上调,用于覆盖新增的类型化 daemon 能力。

Linked Issues

N/A

Security and compatibility notes

  • Artifact removal access model: client-created retained artifacts are owner-protected by client identity. Tool-sourced and hook-sourced artifacts are session-scoped outputs, so any authorized session client may remove them from the live artifact store. Removal only updates the live artifact index and emits an artifact removed event; it never deletes backing files, managed content, or external URLs.
  • Session metadata mutation: PATCH /session/:id/metadata now uses the same mutation/auth guard as other session mutation routes. On token-configured daemons, unauthenticated or mutation-denied metadata PATCH requests are rejected before displayName is changed. This closes a missing-auth gap and may require downstream clients to send the normal daemon auth token for metadata updates.

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Re-run of Stage 1 gate on the latest PR head (7e7f46).

Template looks good ✓ — all required headings present (What, Why, Reviewer Test Plan, How to verify, Evidence, Tested on, Risk & Scope, Linked Issues, 中文说明).

On direction: This PR adds a structured session artifact API to the daemon — agents, hooks, and clients can now register file/link/image/video artifacts that daemon clients display in artifact panels. This fills a genuine capability gap: without it, clients have to scrape tool output text to find intermediate products. The area is clearly within the project's daemon/session-management roadmap. No direct Claude Code CHANGELOG analog, but the existing ArtifactTool (HTML publish) and session event infrastructure are natural foundations to extend.

On approach: The scope is large (61 files, ~9.3K additions) but the additions are proportional to the feature surface: a new tool (record_artifact), a session-scoped in-memory store with validation/eviction, HTTP routes, ACP bridge methods, SSE events, SDK helpers, and comprehensive tests. The earlier rounds of review cleaned up the unrelated group-history-store.ts behavior change and formatting-only churn. The remaining diff is focused on the artifact feature.

The three-layer entry model (tool → hook → client POST) converging on a single SessionArtifactStore with identity-based dedup, source-aware eviction, workspace path containment, URL safety, and owner-scoped removal is well-designed. The v1 boundary (session-scoped, in-memory, no persistence) is explicit and reasonable.

Moving on to code review and testing. 🔍

中文说明

Stage 1 在最新 PR head (7e7f46) 上重新运行。

模板完整 ✓ — 所有必需标题齐全。

方向: 本 PR 为 daemon 添加结构化 session artifact API——agent、hook 和 client 可以注册文件/链接/图片/视频等 artifact,daemon 客户端在 artifact 面板中展示。这填补了真实的能力空白:没有它,客户端只能从工具输出文本中抓取中间产物。该领域明确属于项目的 daemon/session 管理路线图。Claude Code CHANGELOG 中没有直接对应项,但现有的 ArtifactTool(HTML 发布)和 session 事件系统是自然的扩展基础。

方案: 范围较大(61 个文件,约 9.3K 行新增),但新增量与功能面成正比:新工具(record_artifact)、session 级内存态 store(含验证/淘汰)、HTTP 路由、ACP bridge 方法、SSE 事件、SDK helper 和全面测试。早期审查已清理了无关的 group-history-store.ts 行为变更和纯格式化改动。剩余 diff 聚焦于 artifact 功能。

三层入口模型(tool → hook → client POST)汇聚到单一 SessionArtifactStore,具备基于 identity 的去重、按 source 的淘汰策略、workspace 路径包含检查、URL 安全检查和 owner 范围删除,设计合理。v1 边界(session 级、内存态、无持久化)明确且合理。

进入代码审查和测试 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Re-run of Stage 2 on the latest PR head (7e7f46).

Code Review

Independent baseline: I would have designed this with (1) a new record_artifact tool returning structured metadata, (2) a session-scoped in-memory store with validation and dedup, (3) HTTP + ACP bridge routes for CRUD, (4) SSE artifact_changed events, and (5) SDK typed helpers. The PR matches this baseline and exceeds it with published-artifact upgrade paths, source-aware eviction with per-source reservations, workspace status refresh with TTL batching, and cross-session injection protection.

Comparison with the diff:

The SessionArtifactStore in packages/acp-bridge/src/sessionArtifacts.ts is well-structured — the operation queue prevents race conditions, the identity-based dedup is deterministic, the eviction strategy (missing first → over-reserved source → non-retained → oldest) is sound, and the workspace status refresh with TTL + batch size limits prevents filesystem hammering.

The validation chain is thorough:

  • URL safety: rejects javascript:, data: with dangerous MIME types, embedded credentials, non-HTTP(S) schemes
  • Workspace containment: normalize() + realpath resolution catches symlink escapes
  • XSS markup detection in display fields (title, description, workspacePath, managedId)
  • Metadata size cap (4KB), key length limit (120), finite number check
  • ManagedId rejects path separators and absolute paths

The record_artifact tool correctly delegates to the daemon store for validation and doesn't duplicate logic. The ToolCallEmitter integration properly threads _meta.artifacts through the existing extension point.

The hook integration in toolHookTriggers.ts correctly collects artifacts from PostToolUse, PostToolUseFailure, and PostToolBatch hooks — the stop-path still preserves artifacts and additionalContext.

The ACP dispatch properly maps the three artifact methods (session/artifacts, session/artifacts/add, session/artifacts/remove) and routes SessionArtifactValidationError to INVALID_PARAMS with the field name.

No critical blockers found. The earlier review rounds have addressed the security concerns (cross-session injection, path-like managedId, unsafe display payloads in workspacePath/managedId, failed tool-call artifact ingestion before strip).

Testing — Local Verification

=== BUILD ===
Static assets synced -> dist/extension
Background/content build complete!

=== TYPECHECK ===
> @qwen-code/[email protected] typecheck
> tsc --noEmit
(no errors)

=== ACP-BRIDGE TESTS ===
 ✓ src/sessionArtifacts.test.ts (41 tests) 57ms
 ✓ src/bridgeClient.test.ts (42 tests) 32ms
 Test Files  2 passed (2)
      Tests  83 passed (83)

=== CORE TOOL TESTS ===
 ✓ src/tools/record-artifact.test.ts (13 tests) 61ms
 ✓ src/tools/artifact/artifact-tool.test.ts (18 tests) 245ms
 Test Files  2 passed (2)
      Tests  31 passed (31)

=== HOOK TESTS ===
 ✓ src/hooks/types.test.ts (15 tests) 7ms
 ✓ src/hooks/hookAggregator.test.ts (50 tests) 9ms
 ✓ src/core/toolHookTriggers.test.ts (62 tests) 16ms
 Test Files  3 passed (3)
      Tests  127 passed (127)

=== CLI TESTS ===
 ✓ src/acp-integration/session/emitters/ToolCallEmitter.test.ts (49 tests) 20ms
 Test Files  1 passed (1)
      Tests  49 passed (49)

=== SDK TESTS ===
 ✓ test/unit/acpRouteTable.test.ts  (93 tests) 15ms
 ✓ test/unit/daemonEvents.test.ts  (98 tests) 23ms
 ✓ test/unit/DaemonSessionClient.test.ts (41 tests) 344ms
 ✓ test/unit/DaemonClient.test.ts  (198 tests) 1823ms
 Test Files  4 passed (4)
      Tests  430 passed (430)

=== SERVER ARTIFACT ROUTE TESTS ===
 ✓ src/serve/server.test.ts (582 tests | 575 skipped) 64ms
 Test Files  1 passed (1)
      Tests  7 passed | 575 skipped

TOTAL: 727 tests passed, 0 failed

CI: Test (ubuntu-latest, Node 22.x) = pass on this PR head. mac/win skipped per CI design (not failures).

The maintainer @wenshao also ran comprehensive end-to-end verification against the real daemon binary (29/29 assertions covering CRUD, dedup, security guards, owner-scoped removal, strict mutation gate, and live SSE events) — all green.

中文说明

代码审查

独立基线: 我会用以下方式设计:(1) 新的 record_artifact 工具返回结构化 metadata,(2) session 级内存态 store(含验证和去重),(3) HTTP + ACP bridge CRUD 路由,(4) SSE artifact_changed 事件,(5) SDK 类型化 helper。PR 与此基线匹配,并在 published artifact 升级路径、按 source 的淘汰策略(含 per-source 预留)、workspace 状态 TTL 批量刷新和跨 session 注入防护方面超越基线。

与 diff 的对比:

SessionArtifactStore 结构清晰——操作队列防止竞态,基于 identity 的去重是确定性的,淘汰策略(missing 优先 → 超额 source → 非 retained → 最旧)合理,workspace 状态刷新有 TTL + 批量大小限制防止文件系统过载。

验证链全面:URL 安全、workspace 包含、XSS markup 检测、metadata 大小限制、managedId 路径检查。record_artifact 工具正确委托 daemon store 验证。ToolCallEmitter 集成通过现有扩展点正确传递 _meta.artifacts。Hook 集成正确从 PostToolUse/PostToolUseFailure/PostToolBatch 收集 artifacts。

无关键阻塞项。 早期审查轮次已解决安全关切(跨 session 注入、path-like managedId、workspacePath/managedId 中的不安全显示 payload、失败 tool-call 的 artifact 在 strip 前摄入)。

测试——本地验证

总计 727 个测试通过,0 失败。Build 和 typecheck 均通过。CI ubuntu 通过。维护者 @wenshao 也对真实 daemon 二进制做了端到端验证(29/29 断言),全部通过。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Re-run of Stage 3 on the latest PR head (7e7f46).

Stepping back and looking at the whole picture:

This PR adds a well-designed session artifact API that fills a genuine capability gap in the daemon. The core SessionArtifactStore is clean and correct — the operation queue, identity-based dedup, source-aware eviction, workspace status refresh, and the validation chain (URL safety, workspace containment, XSS markup detection, metadata limits) are all solid. The integration across tool/hook/scheduler/bridge/SDK is well-executed with no dangling threads.

My independent baseline was simpler, but the PR's extras (published-artifact upgrade paths, per-source eviction reservations, PostToolBatch/PostToolUseFailure hook artifacts, cross-session injection protection, workspace status TTL batching) are justified additions for a production-ready v1, not over-engineering.

727 focused tests pass. Build and typecheck are clean. CI passes on ubuntu. The maintainer's own end-to-end verification (29/29 assertions against the real daemon binary) confirms the API behaves as documented.

The author has been highly responsive across multiple review rounds — fixing security gaps promptly, cleaning up unrelated changes, and leaving genuine design decisions (read-path refresh semantics, V1 attribution model, configurable quotas) explicitly open for maintainer discussion rather than making unilateral calls.

The one remaining open thread (same-batch overflow removed events) is a V1 contract decision, not a code defect.

Verdict: Code quality is high, tests are comprehensive, build is clean, maintainer has independently verified end-to-end behavior. Approving.

中文说明

整体审视:本 PR 添加了一套设计良好的 session artifact API,填补了 daemon 中真实的能力空白。核心 SessionArtifactStore 清晰正确——操作队列、基于 identity 的去重、按 source 的淘汰、workspace 状态刷新和验证链(URL 安全、workspace 包含、XSS markup 检测、metadata 限制)都很扎实。跨 tool/hook/scheduler/bridge/SDK 的集成执行到位,无悬挂线程。

我的独立基线更简单,但 PR 的额外内容(published artifact 升级路径、per-source 淘汰预留、PostToolBatch/PostToolUseFailure hook artifacts、跨 session 注入防护、workspace 状态 TTL 批量)对于生产就绪的 v1 是合理的补充,不是过度工程。

727 个聚焦测试通过。Build 和 typecheck 干净。CI ubuntu 通过。维护者自己的端到端验证(29/29 断言,针对真实 daemon 二进制)确认 API 行为与文档一致。

作者在多轮审查中响应积极——及时修复安全缺口、清理无关变更、将真正的设计决策(读路径刷新语义、V1 归属模型、可配置配额)明确留给维护者讨论。

唯一剩余的开放线程(同批次溢出 removed 事件)是 V1 契约决策,不是代码缺陷。

结论: 代码质量高,测试全面,构建干净,维护者已独立验证端到端行为。批准。

Qwen Code · qwen3.7-max

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

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

Thanks for the design doc. I think this needs a revision before we use it as an implementation reference.

Main points:

  1. Please de-identify the internal/business-specific examples before landing this in the open-source repo. The current doc mentions DataWorks / MaxCompute / dataworks.example.com / mcp__dataworks__get_table in several places. The underlying requirement is valid, but the public design should describe it generically, e.g. "internal data platform table detail URL", "scheduler task detail URL", or "resource dashboard URL".

  2. Please tighten the definition of link artifacts. I agree with the direction that ordinary links must not be auto-collected, but the rule should be stated as: a link artifact must come through an explicit artifact registration path with structured metadata. record_artifact is one such path, alongside ArtifactTool structured results, hookSpecificOutput.artifacts, and the client POST API. Assistant markdown links, web_fetch inputs, and shell/stdout URLs should stay out of the artifact store.

  3. Please narrow the file-artifact derivation rule. The current design says WRITE_FILE / EDIT / NOTEBOOK_EDIT all produce file artifacts, which would make normal source-code edits show up in the artifacts panel. That feels broader than the stated product semantics of "reusable/clickable/previewable/downloadable session outputs". A safer rule would be to only include files explicitly declared as artifacts, or generated output-like files such as reports, HTML, images, PDFs, notebooks, etc.

  4. Please make the v1 vs v1.1 acceptance boundary explicit. The top section marks hook artifacts and client POST as v1.1 or independent follow-ups, but the acceptance criteria later require both. That makes the implementation scope ambiguous.

@callmeYe

Copy link
Copy Markdown
Collaborator

@qwen-code-ci-bot one correction and one gap in the automated review:

  • The ${CLAUDE_PLUGIN_ROOT} note is probably not an issue. I checked the current codebase and qwen-code still supports substituting ${CLAUDE_PLUGIN_ROOT} in extension/hook configurations, so this should not be treated as a required change.
  • The bigger issues are semantic/documentation boundaries: the public design doc currently exposes DataWorks/MaxCompute-specific examples that should be de-identified for the open-source repo, and the artifact definition is too broad around default WRITE_FILE / EDIT / NOTEBOOK_EDIT derivation. Ordinary source edits should not automatically become session artifacts unless they are explicitly registered or clearly generated output artifacts.

So I do not think this is ready to merge as-is despite being docs-only.

@callmeYe

Copy link
Copy Markdown
Collaborator

Additional note after checking Codex's current implementation for artifact-like outputs:

  1. Codex keeps ordinary file changes separate from reusable artifacts. Regular edits are modeled as file-change / patch-history items, while generated images have a dedicated image-generation item with a saved path. That is a useful precedent for this design: WRITE_FILE / EDIT should not automatically become session artifacts. File changes and reusable deliverables should stay separate unless the file is explicitly registered as an artifact or is clearly a generated output such as a report, HTML page, image, PDF, or notebook.

  2. Codex also uses managed storage for generated images ($CODEX_HOME/generated_images/<session>/<call>.png) rather than requiring every generated output to live under the workspace. This doc should clarify the v1 boundary: either v1 only supports workspace-relative file artifacts, or it should introduce a daemon-managed session artifact storage area for generated outputs such as screenshots, generated images, and temporary reports. In the latter case, the API should expose only the managed artifact reference/path, not arbitrary temp paths.

ytahdn
ytahdn previously approved these changes Jun 26, 2026

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

Code Review

Documentation-only PR. The design document is well-structured and covers the API shape, data model, artifact sources, safety boundaries, implementation phases, and acceptance criteria clearly.

The concern about internal vendor-specific examples (DataWorks/MaxCompute references) is already raised in existing comments. No new high-confidence issues found beyond what's already being discussed.


Generated by Claude Code

@chiga0

chiga0 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

设计审计意见:建议先收敛后再作为实现基准

我从 qwen-code 当前 daemon/tool/hook 架构、Claude Code / Codex 的 artifact-like 输出设计、以及企业级 CloudAgent 的治理视角看了一轮。结论是:方向正确,但当前文档还不适合作为后续实现 PR 的稳定基准。建议继续调整后再合并。

总体判断

这条主线是合理的:

  • session_artifacts capability 做 feature gate。
  • 提供 GET /session/:id/artifacts snapshot API。
  • 通过现有 session SSE 增加 artifact_changed 增量事件。
  • ToolResult.artifacts / ArtifactTool / hook / client POST 最终汇入同一个 SessionArtifactStore
  • 普通 markdown link、web_fetch URL、shell stdout URL 不自动进入 artifact store。

这些方向与现有 daemon 的 EventBus、capabilities、SDK known event、ToolCallEmitter _meta 结构基本吻合,可实施性是有的。

但文档里有几处关键语义和边界需要收紧,否则实现后 artifacts 面板会很快变成“文件变更列表 + URL 收藏夹”,而不是用户可复用、可预览、可分享的 session outputs。

主要问题

1. 不应把普通 WRITE_FILE / EDIT / NOTEBOOK_EDIT 自动提升为 artifact

文档目前在多处把写入 workspace 文件视为默认 artifact,例如:

  • 表格中写着“写入的 workspace 文件:是,因为 agent 明确生成/修改”。
  • Section 6.1 说 WRITE_FILEEDITNOTEBOOK_EDIT 成功执行后派生 file artifact。
  • 测试计划和验收标准也要求 write_file/edit/notebook_edit 自动生成 artifact。

这个定义过宽。普通源码编辑、测试修复、配置文件修改,本质上是 file change / patch history / git diff,不是 artifact。否则一个正常 coding session 里每次编辑都会污染右侧产物区,UI 噪声很高,200 artifact cap 也会被普通代码修改快速填满。

更安全的规则应该是:

  • 普通 source edit 继续走现有 file-change/diff 通道,不进入 artifact store。
  • artifact 只来自显式登记,或非常明确的“生成型输出文件”,例如 report、HTML、PDF、image、video、audio、xlsx、docx、pptx、notebook 等。
  • 即使是 notebook,也要区分“编辑已有 notebook 源文件”和“生成给用户查看/下载的 notebook artifact”。

Claude Code 的 artifacts 是显式发布的交互页面;Codex 也把普通文件变更和生成图片/非代码产物分在不同 item/side panel 语义里。这个边界值得借鉴。

2. workspace path 与 managed artifact storage 的边界不清

文档说:

  • path 只对 workspace 内文件对外展示,且必须是 workspace-relative path。
  • workspace 外 path 不作为 file artifact 暴露。
  • 但又把现有 ArtifactTool 作为 v1 核心来源。

这里和 qwen-code 当前实现有张力。现有 ArtifactTool 的 local publisher 会把发布结果写到 ~/.qwen/artifacts/{id}/index.html,并返回 file:// 或远端 URL;这不是 workspace-relative path。Codex 也有类似分层:生成图片默认落在 $CODEX_HOME/generated_images/...,如果要作为项目资产引用,再复制/移动进 workspace。

建议显式引入 storage/location 语义,而不是只用 path?: string

storage?: 'workspace' | 'managed' | 'external_url' | 'published';
workspacePath?: string;   // 只用于 workspace 内相对路径
managedId?: string;       // daemon/qwen-home 托管产物引用
url?: string;             // 外部或发布 URL

v1 可以选择简单路线:只支持 workspace artifact + published link。或者正式支持 daemon-managed artifact storage。但无论哪种,都不应该把本机绝对路径或 qwen home path 当作普通 path 直接暴露给 daemon client。

3. v1 / v1.1 的验收边界前后不一致

顶部说 v1.1 或同批小增量才做:

  • hookSpecificOutput.artifacts
  • POST /session/:id/artifacts
  • DaemonSessionClient.addArtifact()

但后面的测试计划和验收标准又把 hook 注入、client POST、SDK add artifacts 都列为“实现完成后至少满足”。这会让后续实现 PR 范围不清。

建议把 v1 明确收敛为:

  1. ToolArtifact + ToolResult.artifacts?
  2. ArtifactTool structured artifact metadata
  3. SessionArtifactStore
  4. BridgeClient 消费 _meta.artifacts
  5. GET /session/:id/artifacts
  6. artifact_changed
  7. SDK list/event 类型

record_artifact 是否放 v1 要单独讨论,因为它会新增 model-visible tool,有 token 和 tool-choice 成本。hook output 和 client POST 更适合作为 v1.1 独立 PR。

4. record_artifact 默认注册需要谨慎

record_artifact 对 skill/agent.md 场景很方便,尤其是“根据业务资源 ID 拼详情页 URL”这种需求。但它是一个模型可见 tool,默认注册会增加所有 session 的工具面和 prompt/selection 成本。

可选方案:

  • feature-gated,只在 daemon artifacts capability 开启时注册;
  • skill/extension 明确声明需要时注册;
  • 或先不做 model-facing tool,优先通过 ToolResult.artifacts、ArtifactTool、client/hook 注入验证产品闭环。

如果保留,建议文档明确:这是“显式登记入口”,不用于扫描聊天文本,不用于普通参考链接,不做网络请求,不打开 URL。

5. 企业级 CloudAgent 治理字段需要预留

对于企业级 Agent,Artifacts 不是只有 UI 列表问题,还涉及审计、权限、保留策略和泄露控制。Claude Code artifacts 文档里有组织级开关、RBAC、retention、audit log、Compliance API;Codex 企业文档也强调 Analytics/Compliance API 用于审计与调查。

qwen-code v1 不必实现完整企业治理,但设计至少要预留字段或边界:

  • createdBy / clientId / sourceId
  • extensionId / hookName / toolCallId / toolName
  • createdAt / updatedAt / 可选 expiresAt
  • visibility 或明确 v1 只有 session-local visibility
  • sensitivity / metadata 不得包含 secret、signed URL、cookie、token
  • 明确 audit event 是否由现有 daemon event/logging 体系承载

否则后续企业版本会很难从一个泛化的 metadata map 里补治理语义。

6. 公开文档里的企业/厂商特定例子需要去标识化

文档中多处出现 DataWorks、MaxCompute、mcp__dataworks__get_tabledataworks.example.commaxcompute_table 等例子。需求本身合理,但作为 open-source repo 的公开设计文档,建议改成泛化描述,例如:

  • internal data platform table detail URL
  • scheduler task detail URL
  • resource dashboard URL
  • internal lineage/trace page
  • mcp__data_platform__get_table

这样能保留设计意图,同时避免把公共方案绑定到内部业务语境。

建议的收敛定义

建议把 artifact 定义改成:

Session 中被显式登记的、用户可复用/预览/下载/分享的结构化产物引用。普通源码变更不是 artifact;源码变更属于 file change / diff / patch history。

对应采集规则:

  • 明确进入:ToolResult.artifactsArtifactTool 发布结果、显式 record_artifact、hook/client 显式注入。
  • 条件进入:生成型文件输出,且由工具结果或显式 metadata 标记为 artifact。
  • 明确不进入:普通 EDIT / WRITE_FILE 源码修改、assistant markdown link、shell stdout URL、web_fetch input URL、grep/glob/read_file 结果。

是否有更好的方案

我倾向于一个更小的 v1:

  1. 先做 ToolResult.artifacts 到 daemon artifact store 的闭环。
  2. 让现有 ArtifactTool 返回结构化 metadata。
  3. 提供 GET /session/:id/artifactsartifact_changed
  4. SDK 只先做 list + event narrowing。
  5. 明确 v1 artifact 是 session-local、in-memory、无历史恢复。
  6. 后续再分 PR 做 record_artifact、hook 注入、client POST、managed storage、历史恢复、预览和企业治理。

这样既能验证 UI/协议价值,也避免一开始就把普通文件编辑、业务链接、hook、client mutation、managed storage 全部揉在一起。

结论

我建议这份设计先不要作为实现参考合并。核心方向可以保留,但需要至少改掉以下四点:

  1. 去掉普通 WRITE_FILE / EDIT / NOTEBOOK_EDIT 自动进入 artifacts 的规则。
  2. 明确 workspace artifact 与 daemon-managed / published artifact 的存储边界。
  3. 收紧 v1 / v1.1 验收范围。
  4. 去标识化企业特定示例,并预留企业治理字段/审计边界。

这些改完后,方案会更接近 Claude Code / Codex 这类 agent 产品中对 artifacts 的成熟边界:Artifacts 是“被声明的产物”,不是“所有 agent 动过的东西”。

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Downgraded from Request Changes to Comment: self-PR.

— qwen3.7-max via Qwen Code /review

@chiga0
chiga0 dismissed stale reviews from ytahdn and qwen-code-ci-bot via eea3cfc June 30, 2026 03:33
@chiga0

chiga0 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Updated the design doc to address the requested changes.

Summary of the revision:

  • De-identified public examples: removed DataWorks / MaxCompute / dataworks-specific references and replaced them with generic internal data platform / scheduler / resource dashboard examples.
  • Tightened artifact semantics: ordinary WRITE_FILE / EDIT / NOTEBOOK_EDIT source edits no longer automatically become artifacts; artifacts are explicit session outputs or clearly generated deliverables.
  • Split scope: v1 now focuses on ToolResult.artifacts, ArtifactTool metadata, SessionArtifactStore, GET /session/:id/artifacts, artifact_changed, and SDK list/event types. record_artifact, hook artifacts, client POST, SDK addArtifact, and managed storage are moved to v1.1 / follow-up PRs.
  • Added storage/location model: storage, workspacePath, managedId, url, and rules for workspace vs managed vs external_url vs published artifacts.
  • Added enrichment rules from ToolArtifact input to DaemonSessionArtifact output, including id/source/timestamps/default kind/default storage/status/audit fields.
  • Removed unreachable idempotencyKey identity path and specified workspacePath / managedId / URL identity normalization.
  • Added validation details for URL parsing, userinfo stripping/rejection, path traversal, symlink escape, metadata limits, and hook/client/record_artifact shared validation.
  • Added eviction notification behavior and v1 in-memory lifecycle limitations.
  • Expanded implementation notes for hookAggregator, toolHookTriggers, coreToolScheduler, PostToolBatch artifacts, and qwen/notify/session/artifact-event payload.
  • Split test plan and acceptance criteria into v1 and v1.1, and added security plus cross-package integration coverage.

Local validation: git diff --check passed, and the document was scanned for internal/vendor-specific examples and stale idempotency/path wording.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

对设计文档的一轮审查。

说明:首轮审查跑在本 PR 较早的修订上,其中关于 artifact 生命周期的几处主要问题——裁剪不发 removed 事件、kind 必填但无默认、idempotencyKey 去重无对应输入字段、missing 状态无迁移机制、POST 响应为空对象、normalizedUrl 无归一化规则——在当前 head 中均已修复,这部分补得很到位,生命周期与去重语义现在基本闭环。

以下是针对当前版本仍存在的 3 点工程质量建议,均非阻塞:

@chiga0

chiga0 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up adjustment based on product-scope feedback: the document no longer presents v1 and v1.1 as separate capability versions.

The updated framing is:

  • V1 is one complete session artifacts capability, including ToolResult.artifacts, ArtifactTool metadata, SessionArtifactStore, GET /session/:id/artifacts, artifact_changed, SDK list/event support, record_artifact, hook artifacts, client POST /session/:id/artifacts, and SDK add support.
  • The document still uses Phase A-F, but only as an engineering implementation order. These phases are not separate product versions and not separate capability definitions.
  • Acceptance criteria now describe the complete V1 capability, including all explicit registration paths.

This keeps the reviewer-requested semantic boundaries while avoiding a fragmented design.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

针对最新 head (ff82501) 的二轮审计。上一轮关于 removed 事件、kind 默认、idempotencyKeymissing 迁移、POST 响应、URL 归一化、枚举跨包重复、双摄入通道、stat 热路径的问题都已修复——这版改得很扎实,Phase A-F 的拆分也比之前的 v1/v1.1 划分更清楚。

这轮聚焦当前版本仍存在的问题,共 10 条,聚成三类根因:(1) URL 归一化把「identity」和「存储/可点击 URL」混为一谈(丢 fragment + 排序 query 既造成去重误判/数据丢失,也会让点击跳到错误页);(2) artifact 采集被「工具成功」门控,且 batch 通道在 daemon 主会话上是死路(失败路径与 batch hook 产物静默丢失);(3) 事件契约不闭环(eviction 的 removed、status 惰性翻转都到不了 SSE 客户端)。前 8 条是正确性/可实施性问题,后 2 条是约定/简洁性。逐条见 inline。

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

Review Summary

设计文档整体结构清晰、覆盖面广。以下是几个需要关注的问题:

  • Critical: SessionArtifactInput 类型在 §5.3 被多次引用但全文未定义;dedup 的 last-write-wins 不将 source 纳入 identity,存在跨来源元数据覆写风险
  • Suggestion: title/description 缺少长度和字符约束;ArtifactChangedResult 返回类型未定义;Phase C 重复编号

— qwen3.7-max via Qwen Code /review

@chiga0

chiga0 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up update for the latest design audit.

Addressed in commit 31af48164:

  • Split public clickable url from internal identityUrl; URL identity now preserves fragment and query order.
  • Made artifact identity source-scoped and froze provenance fields after first insert to avoid cross-source overwrite.
  • Added SessionArtifactInput and DaemonSessionArtifactMutationResult definitions; POST now returns changes[] so eviction removals are observable.
  • Removed workspaceCwd from the public envelope and removed V1 fields without behavior (visibility, sensitivity, expiresAt, sourceId).
  • Clarified managed artifacts are not locally statted in V1; only workspace artifacts use status cache.
  • Switched retention from FIFO to updatedAt-based eviction.
  • Clarified failed-tool hook artifacts can still be ingested, while failed tool-result artifacts are not collected.
  • Clarified daemon ACP main session does not currently support PostToolBatch artifacts unless a real callsite is added.
  • Added title/description constraints, hookAggregator concat requirements, shared hook collection helper guidance, and Phase C-1/C-2/C-3 numbering.

All currently visible review threads have been individually replied to and resolved; unresolved review thread count is now 0.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

针对最新 head (31af481) 的三轮审计。上一轮的 10 条(URL identity 拆分、batch 死通道、失败路径 hook、provenance、POST changes[]、status 翻转发事件、裁剪策略、managed stat、workspaceCwd、预留字段)都已修复,改得很彻底。

这轮聚焦本次改动引入的一致性/边界问题,共 10 条、去重后 8 类,集中在新的「source 纳入 identity」模型、updatedAt LRU、GET 副作用、changes[]/事件契约上。需要特别说明:其中【裁剪策略】和【GET 发事件】两条,是上一轮我的建议引入的反向 failure mode——属于真实的设计张力,我在对应 inline 里给了更平衡的解法,而非简单翻案。 逐条见下。

@chiga0

chiga0 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up update for the newest audit round.

Addressed in commit 1027016e:

  • Defined qwen/notify/session/artifact-event as ACP extNotification, including sender constraints and BridgeClient demux behavior.
  • Made record_artifact a single-artifact tool call with structured llmContent; validation failure produces no artifact.
  • Removed the undefined trim/resync event option; V1 eviction now only emits per-artifact removed changes.
  • Removed source from identity. Identity is now resource-location based, while sources are tracked in provenance[].
  • Rejected inputs with multiple locator fields instead of guessing identity precedence.
  • Added trust-based display-field merge rules and canonical provenance selection.
  • Replaced pure updatedAt LRU with retention class + lastSeenAt, so low-trust high-frequency artifacts cannot evict high-trust manual/published artifacts.
  • Made GET status refresh silent; reads no longer broadcast SSE updates.
  • Removed the single bridge mutation path; POST uses addSessionArtifacts() with one input.
  • Normalized example artifact ids to the 12-character spec.

All currently visible review threads have been replied to and resolved; unresolved review thread count is now 0.

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

针对最新 head (1027016) 的四轮审计。上一轮 8 条全部修复,且解法很漂亮——identity 移除 source、改用 provenance[] + 信任级别 + retention class,把「跨源重复 / 覆盖 / 审计」三件事一次理清。

本轮聚焦这个新引入的 provenance / 信任级别 / retention 子系统自身的边界,共发现 9 处 spec 未闭合(8 CONFIRMED + 1 cleanup),都在 §7。

一个元层面的观察:这个信任子系统是为修上一轮而加的,功能上更强,但它本身正在产生一条边界 case 的长尾(下面 9 条大多源于它)。其中最根因的是【信任级别无法从 source 推导】——DaemonSessionArtifactSource 只有 tool|hook|client 三值,而信任级别有四级(ArtifactTool 与 record_artifact 都是 source='tool'),没有 toolName→level 映射,整个覆盖/retention 规则在运行时无法落地。建议要么补齐这套映射 + 边界规则,要么考虑 V1 用更简的策略(identity 合并 + keep-first-writer + 简单 quota),把完整信任分级推到后续。逐条见 inline。

@chiga0

chiga0 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

设计文档审计总账(4 轮)

session-artifacts-daemon-api-implementation-design.md 做了 4 轮逐版审计(head 演进 eea3cfc → ff82501 → 31af481 → 1027016)。本评论汇总进展,逐条细节见下方四条 inline review。整体趋势:硬伤(死通道、数据丢失、事件不闭环)已基本清完,文档主体健全;当前剩余集中在新引入的 provenance/信任级别子系统的边界,以及一个 V1 复杂度的设计取舍。

✅ 已修复(27 项)

首版已自查(6)removed 事件、kind 默认值、idempotencyKey 字段、missing 状态迁移、POST 响应、URL 归一化规则。

第 1 轮(3)kind 枚举跨 core/sdk 重复 → 共享单一来源;batch 第二摄入通道 → 统一 ingestArtifacts();GET stat-on-read → status cache+TTL。

第 2 轮(10):URL identity/可点击 URL 拆分(保留 fragment+query 顺序);batch hook daemon 主会话死通道 → 明确 V1 不声明;失败路径 hook 被吞 → 独立收集;provenance last-write-wins → 冻结;POST 单值返回 → changes[];status 翻转发事件;createdAt-FIFO → updatedAt LRU;managed stat → managed/URL 不 stat;workspaceCwd 泄露 → 删除;speculative 字段 → 删除。

第 3 轮(8):source 纳入 identity 过度修正 → 移除、改 provenance[]+信任级别;多定位字段 → 拒绝;updatedAt LRU 保护 spam → retention class+lastSeenAt;GET 副作用 → 不发事件、最终一致;artifacts_trimmed 未登记 → 删除;同批 changes[] 定义;单数 addSessionArtifact 删除;id 长度统一 12 位。

⏳ 当前待决(9 项,head 1027016,集中在 §7)

# 类别 问题 建议
1 根因/可实施 信任 4 级无法从 3 值 source 推导(ArtifactTool 与 record_artifact 都是 source='tool' toolName/storage→level 映射
2 数据丢失 200 上限被高信任占满时,新低信任 artifact 无驱逐目标、静默丢弃 定义拒绝事件 / 低信任配额
3 正确性 lastSeenAt=max(provenance) 被低信任 upsert 抬高 → 同 class 内挤掉真高信任项 排序时间戳限本 class 来源
4 效率 provenance[] 无界增长(key 含 per-call toolCallId 设上限 / toolCallId 移出 key
5 安全 metadata 低信任可注入新 key;合并后大小不再受 4KB 限制 禁注入 + 合并后重校验大小
6 一致性 展示字段「同信任级别、不同 key」未定义,且与 line 861 canonical 冲突 保留首次写入者
7 可实施 managedId identity 无归一化规则(不同于 workspacePath/url) 补 trim/大小写规则
8 cleanup 顶层 canonical 字段与 provenance[] 重复存储,易漂移 序列化时派生

(第 9 项为上表 #5 拆出的「合并后超 4KB」子项,已并入 #5。)

🔀 一个 V1 复杂度的设计取舍

待决 #1 引出一个根因层面的问题:为闭合上一轮而引入的信任级别 + retention class 子系统,本身正在产生一条边界 case 的长尾(本轮 9 条多源于它)。两条路径供权衡:

  • A. 继续硬化:补全 level 映射、provenance[] 上限、metadata 重校验、starvation 处理等 —— 可闭合,但 V1 复杂度持续上升。
  • B. V1 简化:identity 合并 + keep-first-writer + 简单 per-source quota,把完整四级信任分级推到后续 PR —— 边界面显著缩小,更贴合 AGENTS.md 的 Simplicity First。

建议倾向 B:当前待决项多为该子系统的长尾,对 V1 而言收益与复杂度不太成正比;但若产品确有跨来源信任分级的近期需求,则按 A 把规则补齐即可。

@wenshao

wenshao commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

wenshao
wenshao previously approved these changes Jul 3, 2026

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

Review Summary

Deterministic analysis: TypeScript + ESLint clean (0 findings). All 354 tests pass.

Findings: 3 critical, 7 suggestions

Critical

  1. Metadata injection into trusted published artifactsmergeArtifact always calls mergeMetadata regardless of source. A client POST with a URL matching an existing published artifact's identity can inject new metadata keys while the artifact's source remains 'tool'.

  2. getInitialWorkspaceStatus swallows filesystem errors — The catch block discards the original error (EACCES, EIO, ETIMEDOUT) and throws a generic message. The underlying cause is neither logged nor attached to the error.

  3. Published upgrade leaves stale Map key and incorrect public id — After a workspace → published upgrade, the stored artifact's id is never recomputed. The Map entry stays keyed by the old workspace-derived id, causing every subsequent upsert to miss the Map.get() fast path and forcing the O(n) linear scan.

Suggestion

  1. refreshWorkspaceStatus ignores onError parameter — catch block always preserves old status
  2. handleArtifactEvent doesn't check inFlightRestoreIds — hook artifacts dropped during session restore
  3. Eviction sourceCounts includes newly-created artifacts — inflates per-source counts above reservation thresholds
  4. Stored URLs preserve single quotes — XSS risk if rendered in single-quoted HTML attributes
  5. Duplicated validation logic between record-artifact.ts and sessionArtifacts.ts — future divergence risk
  6. mergeArtifact allocates two throwaway objects per upsert for change detection
  7. Eviction loop O(n²) via redundant linear scans

Comment thread packages/acp-bridge/src/sessionArtifacts.ts
Comment thread packages/acp-bridge/src/sessionArtifacts.ts
Comment thread packages/acp-bridge/src/sessionArtifacts.ts Outdated
Comment thread packages/acp-bridge/src/sessionArtifacts.ts
Comment thread packages/acp-bridge/src/bridgeClient.ts Outdated
Comment thread packages/acp-bridge/src/sessionArtifacts.ts
Comment thread packages/acp-bridge/src/sessionArtifacts.ts
@wenshao

wenshao commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

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

@chiga0
chiga0 dismissed stale reviews from qwen-code-ci-bot and wenshao via dee8d9a July 3, 2026 08:28

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

Overall

Well-designed, well-bounded v1 implementation. Security checks (URL validation, workspace path containment, published storage permission, client field allowlist) are solid. The enqueue serialization, eviction strategy, and identity-based upsert logic are all correct. Test coverage is thorough.

Two items to fix, one design suggestion for future.


Issues

1. Copyright header mismatch in record-artifact.ts

packages/core/src/tools/record-artifact.ts has:

Copyright 2025 Google LLC

While other new files in this PR (e.g. packages/acp-bridge/src/sessionArtifacts.ts) correctly use:

Copyright 2026 Qwen Team

If this file is newly authored for the Qwen project (not carried over from upstream), the header should be unified.

2. Spurious whitespace diff in config.ts

Line 1481 changes */ */ (one less space). This is a no-op that pollutes git blame. Consider reverting.


Non-blocking suggestion

Type literal drift risk

ToolArtifactKind (core), DaemonSessionArtifactKind (acp-bridge), and KnownDaemonSessionArtifactKind (SDK) are three independent string literal unions that must stay in sync manually. Consider defining a single const tuple in core as the source of truth in a future iteration, so adding a new kind (e.g. dataset) cannot accidentally miss one package.

@doudouOUC doudouOUC 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 — well-designed, well-bounded v1 implementation.

Strengths:

  • Security checks are thorough: URL validation (no javascript:/data: schemes), workspace path containment with symlink escape detection, published storage restricted to trusted publishers, client field allowlist via explicit copy.
  • enqueue serialization handles errors correctly — rejected promises don't block the queue, errors propagate to callers.
  • Eviction strategy with source-based soft reservations (tool:100, client:50, hook:50) is fair and won't starve any source.
  • Identity-based stable IDs (SHA-256 of session + identity key) give correct upsert semantics.
  • Test coverage is comprehensive (~2800 lines across store, bridge client, record-artifact, SDK).

Minor nits (non-blocking):

  1. packages/core/src/tools/record-artifact.ts copyright header says "2025 Google LLC" while other new files use "2026 Qwen Team" — worth unifying if this is new Qwen-authored code.
  2. config.ts L1481 has a whitespace-only diff ( */ */) that will show up in git blame.

Future suggestion:
The three independent kind/storage string literal unions (core ToolArtifactKind, bridge DaemonSessionArtifactKind, SDK KnownDaemonSessionArtifactKind) could be consolidated into a single source-of-truth const tuple in core to prevent drift when new kinds are added.

@chiga0
chiga0 dismissed callmeYe’s stale review July 3, 2026 08:57

two approvals

@chiga0
chiga0 added this pull request to the merge queue Jul 3, 2026
Merged via the queue into QwenLM:main with commit 9658dcc Jul 3, 2026
36 of 49 checks passed

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

Review in progress — inline comments incoming.

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

test


app.patch(
'/session/:id/metadata',
mutate({ strict: true }),

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.

test pos 141

app.patch(
'/session/:id/metadata',
mutate({ strict: true }),
withMutableSession('PATCH /session/:id/metadata', (req, res, sessionId) => {

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.

test pos 142

'/session/:id/metadata',
mutate({ strict: true }),
withMutableSession('PATCH /session/:id/metadata', (req, res, sessionId) => {
const body = safeBody(req);

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.

test pos 143

mutate({ strict: true }),
withMutableSession('PATCH /session/:id/metadata', (req, res, sessionId) => {
const body = safeBody(req);
const clientId = parseClientIdHeader(req, res);

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.

test pos 144

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

/review — PR #5895 feat(daemon): add session artifact APIs

61 files · +9,493/−89 · 9 parallel review agents · 15 findings (13 verified high-confidence)

Deterministic checks: tsc ✓ · eslint ✓ · 2,270 tests pass

Critical (5)

  • C1 XSS: hasUnsafeDisplayPayload regex misses data:application/xhtml+xml
  • C2 SOURCE_RESERVATIONS hardcoded {tool:100,client:50,hook:50} don't scale with maxArtifacts
  • C3 inputBatchLimit() = maxArtifacts * 2 allows single batch to evict ALL pre-existing artifacts
  • C4 Dropped artifacts silently lost in non-strict mode (only writeStderrLine, no structured event)
  • C5 Security-critical validation duplicated byte-for-byte across record-artifact.ts and sessionArtifacts.ts

Suggestions (8)

  • S1 mergeArtifact discards title/description for non-published merges
  • S2 remove() silently returns empty changes on ownership mismatch
  • S3 PATCH /session/:id/metadata gained mutate({strict:true}) — potential breaking change
  • S4 list() blocks on refreshWorkspaceStatuses() fs I/O
  • S5 Sequential fs I/O during batch normalization
  • S6 SSE session_update published before artifact upsert completes
  • S7 file: URLs accepted for trusted publishers with no workspace containment
  • S8 Symlink escape detection creates ghost artifacts (deletes workspacePath but keeps artifact in store)

— qwen3.7-max via Qwen Code /review


function hasUnsafeDisplayPayload(value: string): boolean {
return (
/<\s*\/?[a-z!]|&(?:#[0-9]+|#x[0-9a-f]+|[a-z][a-z0-9]+);|javascript\s*:|data\s*:\s*(?:text\/(?:html|javascript)|application\/javascript|image\/svg\+xml)/i.test(

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.

[Critical · Security] hasUnsafeDisplayPayload regex misses data:application/xhtml+xml.

The current regex blocks data:text/html, data:text/javascript, data:application/javascript, and data:image/svg+xml — but data:application/xhtml+xml is not matched. XHTML data URIs can execute JavaScript and are rendered by browsers, making this an XSS vector for artifact display fields (title, description) that pass through isDisplayField validation.

Add application\/xhtml\+xml to the alternation:

Suggested change
/<\s*\/?[a-z!]|&(?:#[0-9]+|#x[0-9a-f]+|[a-z][a-z0-9]+);|javascript\s*:|data\s*:\s*(?:text\/(?:html|javascript)|application\/javascript|image\/svg\+xml)/i.test(
/<\s*\/\?[a-z!]|&(?:#[0-9]+|#x[0-9a-f]+|[a-z][a-z0-9]+);|javascript\s*:|data\s*:\s*(?:text\/(?:html|javascript)|application\/(?:javascript|xhtml\+xml)|image\/svg\+xml)/i.test(

— qwen3.7-max via Qwen Code /review


export type DaemonSessionArtifactStatus = 'available' | 'missing';

const SOURCE_RESERVATIONS: Record<DaemonSessionArtifactSource, number> = {

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.

[Critical · Correctness] SOURCE_RESERVATIONS is hardcoded as {tool:100, client:50, hook:50} (sum=200) regardless of maxArtifacts.

If maxArtifacts is set to 50 (below default 200), the reservations alone consume 4× the total capacity, effectively starving other sources. Conversely, if maxArtifacts is 1000, reservations cap each source at 5-10% of capacity — likely too conservative.

Consider scaling reservations proportionally:

const base = this.maxArtifacts;
const reservations = {
  tool: Math.floor(base * 0.5),
  client: Math.floor(base * 0.25),
  hook: Math.floor(base * 0.25),
};

— qwen3.7-max via Qwen Code /review

this.maxArtifacts = options.maxArtifacts ?? 200;
}

inputBatchLimit(): number {

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.

[Critical · Correctness] inputBatchLimit() returns maxArtifacts * 2 (default 400), but evictOverflow excludes createdInThisBatch from eviction candidates.

This means a batch of 400 incoming artifacts can cause eviction of ALL 200 pre-existing artifacts, since only pre-existing artifacts are candidates for eviction and the store overflows by 200. The batch effectively nuked the entire prior state.

Consider capping the input batch at maxArtifacts (not 2×), or including newly-created artifacts in eviction candidates when the overflow exceeds the batch size.

— qwen3.7-max via Qwen Code /review

}
const message =
error instanceof Error ? error.message : String(error);
writeStderrLine(

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.

[Critical · Correctness] In non-strict mode, dropped artifacts are only logged via writeStderrLine. The caller receives no structured signal that their artifact was rejected.

Clients submitting artifacts via POST /session/:id/artifacts will see a successful response for the batch, but individual artifacts that failed validation are silently lost. The changes array in the response won't include them.

Consider including a dropped array in the response, or emitting an artifact_changed event with change: 'dropped' so callers can track rejected artifacts.

— qwen3.7-max via Qwen Code /review

);
}

function hasControlCharacter(

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.

[Critical · Code Quality] hasControlCharacter, hasUnsafeDisplayPayload, and isDisplayField are duplicated byte-for-byte between this file and packages/acp-bridge/src/sessionArtifacts.ts.

This is security-critical validation (XSS filtering, control character detection). If a vulnerability is found in one copy and fixed, the other copy may be missed — creating a drift risk where one entry point is patched but the other remains exploitable.

Extract these functions into a shared module (e.g., packages/core/src/tools/artifact/validation.ts) that both files import. This ensures security fixes propagate automatically.

— qwen3.7-max via Qwen Code /review

}

async list(): Promise<SessionArtifactsEnvelope> {
return this.enqueue(async () => {

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 · Performance] list() calls await this.refreshWorkspaceStatuses() on every invocation, which performs sequential fs stat calls for artifacts with stale lastStatAt timestamps.

For sessions with many file artifacts, this adds latency to every GET /session/:id/artifacts call. Consider:

  1. Lazy refresh — only stat artifacts that the client actually displays
  2. Background refresh — decouple the stat refresh from the list response
  3. Cache TTL extension — increase WORKSPACE_STATUS_REFRESH_TTL_MS for stable workspaces

— qwen3.7-max via Qwen Code /review

): Promise<SessionArtifactMutationResult> {
return this.enqueue(async () => {
const normalizedResults: NormalizedArtifact[] = [];
for (const input of inputs) {

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 · Performance] normalizeForStore is awaited sequentially in the batch loop. Each artifact goes through normalizeForStore which may involve fs.access / fs.stat calls for workspace path validation.

Consider parallelizing with Promise.all or Promise.allSettled:

const normalizedResults = await Promise.allSettled(
  input.map((raw, idx) =>
    this.normalizeForStore(raw, options.sources[idx] ?? 'tool', options),
  ),
);

This can significantly reduce latency for large batches with many file artifacts.

— qwen3.7-max via Qwen Code /review

trustedPublisher: isTrustedArtifactToolUpdate(params, updateMeta),
});
}
}

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 · Correctness] The SSE session_update event is published (line ~644) before upsertAndPublishArtifacts completes (this line). Clients consuming artifact_changed events may see the session_update referencing artifacts that haven't been indexed yet.

Consider awaiting upsertAndPublishArtifacts before publishing the session_update, or emitting artifact events inline within the upsert so ordering is preserved.

— qwen3.7-max via Qwen Code /review

if (
parsed.protocol !== 'http:' &&
parsed.protocol !== 'https:' &&
!(allowFile && parsed.protocol === 'file:')

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 · Security] normalizeArtifactUrl accepts file: URLs when allowFile is true (trusted publishers). However, there's no workspace containment check on file: URLs — a trusted publisher could reference /etc/passwd or any file on the system.

Consider validating that file: URLs resolve to paths within the workspace:

if (allowFile && parsed.protocol === 'file:') {
  const filePath = fileURLToPath(parsed);
  if (!filePath.startsWith(workspaceCwd)) {
    throw new SessionArtifactValidationError('file: URL must stay inside workspace', 'url');
  }
}

— qwen3.7-max via Qwen Code /review

if (status.escaped) {
artifact.status = 'missing';
artifact.sizeBytes = undefined;
delete artifact.workspacePath;

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 · Correctness] When symlink escape is detected, workspacePath is deleted and status is set to 'missing', but the artifact remains in the store. This creates a "ghost" artifact — it appears in list() responses but has no path, no size, and no way to resolve its content.

Consider either:

  1. Removing the artifact from the store entirely and emitting a removed change event
  2. Adding a distinct status like 'escaped' so clients can differentiate from genuinely missing files
  3. Documenting this as intentional behavior

— qwen3.7-max via Qwen Code /review

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

mini test


export type DaemonSessionArtifactStatus = 'available' | 'missing';

const SOURCE_RESERVATIONS: Record<DaemonSessionArtifactSource, number> = {

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.

mini test C2

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. Qwen review exited with status 1. See workflow logs.

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.

9 participants