Merge remote-tracking branch 'origin/master' into nih-imp-landstrip-xlink

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md
#	.agents/notes/implemented/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.zh.md
#	.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml
This commit is contained in:
Tianyi Cui
2026-07-27 06:36:55 +08:00
668 changed files with 11054 additions and 7377 deletions

View File

@@ -1,29 +0,0 @@
# Agent Note: Drop ACP session/load until resume has a product shape
Status: rejected — Zed is the current target ACP client, advertises and exercises load-capable sessions, and keeps pending-load state for concurrent `session/load`. The bridge should keep `session/load` and make the resume contract solid.
English | [中文](2026-06-20-drop-acp-session-load.zh.md)
## Problem
ACP advertises `loadSession: true` and implements `session/load` by injecting persistence into the bridge, validating cwd against stored metadata, reconstructing an agent from the persisted log, and replaying prior transcript updates to the client. That path has its own race handling, loading-id guard, replay presenter logic, and tests. It also depends on the canonical log retaining enough UI data to reconstruct old chunks and tool presentations.
Durable persistence remains foundational, but editor-visible resume is not yet a designed product flow. There is no session picker, no title/preview metadata, and no clear UX for failed or partial loads. The bridge is paying complexity for a feature that is exercised by tests, documentation, and the current target client's session model.
## Proposal
For now, ACP starts fresh sessions only. `initialize` advertises `loadSession: false` or omits the capability, and `session/load` is unsupported. Persistence remains available to the agent loop and tests; resume can still exist as a lower-level factory if another consumer needs it. The editor bridge should reintroduce `session/load` alongside a real session-selection UX and a stable load transcript contract.
## Acceptance criteria
- ACP no longer injects `sessionPersistence` solely for `session/load`.
- `initialize` does not advertise load support.
- The `session/load` handler, loading-id tracking, cwd preflight for loaded sessions, and load replay tests are removed.
- Snapshot fixtures no longer rely on load replay presentation.
- [ACP docs](../../../../packages/acp/acp/README.md) describe fresh-session support only.
## What we give up
An editor cannot reopen a prior persisted session through ACP. That is a real product feature, but the current implementation is ahead of the UX and ties the bridge to token-level log replay. Keeping persistence while dropping editor load narrows the bridge to the workflow it can currently present cleanly.
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -1,29 +0,0 @@
# Agent Note: 移除 ACPAgent Client Protocolsession/load直到恢复具备产品形态
Status: rejected — Zed 是当前目标 ACP 客户端,它声明并实际使用支持加载的会话,还为并发的 `session/load` 保留待加载状态。桥接层应保留 `session/load` 并巩固恢复契约。
[English](2026-06-20-drop-acp-session-load.md) | 中文
## 问题
ACP 声明 `loadSession: true` 并实现 `session/load`:向 bridge 注入持久化能力、校验 cwd 与存储元数据的一致性、从持久化日志重建 agent智能体并向客户端回放先前的 transcript文本记录更新。该路径有自己的竞态处理、loading-id 守卫、回放展示逻辑和测试。它还依赖规范日志保留足够的 UI 数据,以重建旧的分片和工具展示。
持久化仍然是基础能力,但编辑器可见的恢复尚未经过产品流程设计。目前没有会话选择器、没有标题/预览元数据也没有明确的加载失败或部分加载的用户体验。bridge 正在为一个仅被测试、文档和当前目标客户端的会话模型所使用的功能付出复杂度代价。
## 提案
当前阶段ACP 仅启动全新会话。`initialize` 声明 `loadSession: false` 或省略该能力,`session/load` 不予支持。持久化仍可供 agent loop智能体循环和测试使用如果其他消费方需要恢复仍可作为底层工厂存在。编辑器 bridge 应在具备真正的会话选择 UX 和稳定的 load transcript 契约后,再重新引入 `session/load`
## 验收标准
- ACP 不再注入 `sessionPersistence`;它原本仅供 `session/load` 使用。
- `initialize` 不再声明 load 支持。
- `session/load` handler、loading-id 追踪、已加载会话的 cwd 预检以及 load 回放测试均被移除。
- 快照 fixture测试前置数据不再依赖 load 回放展示。
- [ACP 文档](../../../../packages/acp/acp/README.md)仅描述全新会话的支持。
## 放弃的能力
编辑器无法通过 ACP 重新打开先前持久化的会话。这确实是一项产品功能,但当前实现超前于 UX 设计,且将 bridge 绑定到 token 级别的日志回放。保留持久化但移除编辑器 load可将 bridge 收窄到它当前能干净呈现的工作流。
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -1,6 +0,0 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-drop-acp-terminal-meta.md: 84b9028392f967e72f7b5585d013d669735631de
2026-06-20-drop-acp-terminal-meta.zh.md: 6ac7ba46bce20bcf2593ef422057e0562f8a1557

View File

@@ -1,31 +0,0 @@
# Agent Note: Drop ACP terminal `_meta` rendering
Status: rejected — removing only Zed terminal metadata was rejected while ACP remained an editor bridge; automation-only ACP later removed the whole editor projection.
English | [中文](2026-06-20-drop-acp-terminal-meta.zh.md)
## Problem
The former ACP editor bridge implemented a Zed-specific terminal-card convention through `_meta.terminal_info`, `_meta.terminal_output`, and `_meta.terminal_exit`. The current [render-intent decision](../../implemented/architecture/2026-07-02-tool-render-intent-union.md) preserves the underlying rule that bash execution belongs in the harness and terminal cards are display-only. The later [automation-only ACP decision](../../implemented/simplification/2026-07-23-acp-automation-only-protocol.md) removes the `_meta` projection, bridge state, capability negotiation, terminal ids, special update mapping, text fallback tests, and exit-pill parsing from ACP. TUI and the Web host/client runtime retain the tagged presentation contract, while ACP no longer renders editor cards.
At proposal time, the fallback path already existed: render the tool call and completed output as normal ACP content blocks. Non-Zed clients relied on that path, but the Zed terminal card was a target-client feature rather than speculative decoration.
## Proposal
Ignore `clientCapabilities._meta.terminal_output` and render bash results through the plain ACP content path. Keep execution agent-side through `dsh-bash`; only the display-specific terminal metadata is removed. A terminal card can return later if ACP standardizes agent-executed terminals or if the product decides Zed-specific display is worth the maintenance cost.
This proposal is narrower than [collapsing tool-owned UI presentation](2026-06-20-generic-tool-rendering.md): it keeps generic `presentCall`/`presentResult` if those survive, but removes the terminal sub-shape and `_meta` mapping.
## Acceptance criteria
- ACP no longer reads or stores `_meta.terminal_output` capability state.
- `TerminalRendering`, terminal ids, terminal cwd resolution, and `_meta.terminal_*` update mapping disappear from `@deepseek-ai/dsh-acp`.
- `ToolTerminal` disappears from `@deepseek-ai/dsh-tools`, or is unused and deleted with the presentation cleanup.
- Bash result presentation no longer parses exit status for terminal pills.
- The [automation-only ACP decision](../../implemented/simplification/2026-07-23-acp-automation-only-protocol.md) later removes ACP terminal cards and absorbs their execution-ownership rationale.
## What we give up
Under this proposal, Zed users would lose the dedicated terminal card: no cwd header, terminal display, or exit pill. They would still see the command and output as plain content. That was a reasonable simplification to consider while the ACP bridge was unreleased and the `_meta` keys were a convention rather than a standard.
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -1,31 +0,0 @@
# Agent Note: 移除 ACPAgent Client Protocol终端 `_meta` 渲染
Status: rejected — 在 ACP 仍是编辑器桥接层时,仅移除 Zed 终端元数据的方案被否决;后续仅面向自动化的 ACP 则移除了整个编辑器投影。
[English](2026-06-20-drop-acp-terminal-meta.md) | 中文
## 问题
原 ACP 编辑器桥接层通过 `_meta.terminal_info``_meta.terminal_output``_meta.terminal_exit` 实现了一套 Zed 特有的终端卡片约定。当前的 [render-intent 决策](../../implemented/architecture/2026-07-02-tool-render-intent-union.md)保留了底层规则bash 执行属于 harnessterminal 卡片只用于展示。后续的[仅面向自动化 ACP 决策](../../implemented/simplification/2026-07-23-acp-automation-only-protocol.md)从 ACP 中移除了 `_meta` 投影、桥接状态、能力协商、终端 id、特殊 update 映射、文本回退测试和 exit-pill 解析。TUI 与 Web 宿主/客户端运行时保留带标签的展示契约,而 ACP 不再渲染编辑器卡片。
本提案提出时,回退路径已经存在:将工具调用和完成输出渲染为普通 ACP 内容块。当时,非 Zed 客户端依赖这条路径,但 Zed 终端卡片是目标客户端的功能特性,而非推测性装饰。
## 提案
忽略 `clientCapabilities._meta.terminal_output`,通过纯 ACP 内容路径渲染 bash 结果。执行仍由 agent 侧的 `dsh-bash` 完成;仅移除展示相关的终端元数据。如果 ACP 日后标准化了 agent 执行的终端,或产品决定 Zed 特有展示值得其维护成本,终端卡片可以再回来。
本提案比[收拢工具自有 UI 展示](2026-06-20-generic-tool-rendering.md)更窄:如果通用的 `presentCall`/`presentResult` 保留,本提案不影响它们,只移除终端子形态与 `_meta` 映射。
## 验收标准
- ACP 不再读取或存储 `_meta.terminal_output` 能力状态。
- `TerminalRendering`、终端 id、终端 cwd 解析与 `_meta.terminal_*` update 映射从 `@deepseek-ai/dsh-acp` 中消失。
- `ToolTerminal``@deepseek-ai/dsh-tools` 中消失,或在展示清理中因未使用而删除。
- Bash 结果展示不再为终端 pill 解析退出状态。
- [仅面向自动化 ACP 决策](../../implemented/simplification/2026-07-23-acp-automation-only-protocol.md)后来移除了 ACP 终端卡片,并吸收了其中有关执行归属的决策依据。
## 放弃的内容
如果采用本提案Zed 用户会失去专用终端卡片:没有 cwd 头部、终端展示或 exit pill。但他们仍会以纯内容形式看到命令和输出。当时 ACP 桥接层尚未发布,且 `_meta` 键只是约定而非标准;在这种情况下,考虑这项简化是合理的。
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -1,6 +0,0 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-drop-unused-session-lineage.md: 605f1949999435b24404e0c5a72320416303ae52
2026-06-20-drop-unused-session-lineage.zh.md: 981f44189f4b8f11f513261db7094afdc650dffa

View File

@@ -1,31 +0,0 @@
# Agent Note: Drop unused session lineage metadata
Status: rejected — `parentSession` is part of the documented fork/sub-agent seam and is already preserved by the agent/session resume path. The field is future-facing, but it is not accidental dead state.
English | [中文](2026-06-20-drop-unused-session-lineage.zh.md)
## Problem
`SessionHeader.parentSession` records the session a new session was forked from. It is defined in `dsh-session`, preserved by persistence backends, copied through resume, documented as lineage metadata, and covered by round-trip tests. The repo has no production fork UI or sub-agent flow that reads it. The planned sub-agent/fork seam is still a TODO, so the field is currently stored future shape.
The cost is small per file but broad across the format: every backend schema and metadata serializer preserves a value that no completed feature reads yet. Because the header is an on-disk contract, even a placeholder field becomes something future refactors must either maintain, migrate, or deliberately break.
## Proposal
Remove `parentSession` from `SessionHeader` until a real fork/resume feature needs lineage. Forking can still seed a new session with prior events if such an API exists, but the durable parent pointer should be introduced alongside the feature that reads it and the UX that explains it.
If lineage returns, decide then whether it belongs in the immutable header, a session graph index, or a first-class event. The current field should not pre-commit that design.
## Acceptance criteria
- `SessionHeader` contains version, id, createdAt, and optional cwd only.
- JSONL and SQLite metadata schemas stop storing parent-session ids.
- Resume and list APIs no longer round-trip `parentSession`.
- Docs and tests remove fork-lineage claims that are not backed by a production consumer.
- The session format version, backend schema versions, and recorded fixtures are refreshed as needed; non-current stored data is rejected per the pre-release format policy, with no migration path.
## What we give up
The codebase loses a ready-made lineage hook for future fork/sub-agent UX. That is intentional. The field is easy to reintroduce when the feature exists, and the unreleased stance lets the format change without migrations.
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -1,31 +0,0 @@
# Agent Note: 移除未使用的会话血缘元数据
Status: rejected — `parentSession` 是已记录的 fork/subagent seam 的一部分,并已由 agent智能体/会话恢复路径保留。该字段面向未来,但并非意外遗留的死状态。
[English](2026-06-20-drop-unused-session-lineage.md) | 中文
## 问题
`SessionHeader.parentSession` 记录新会话从哪个会话 fork 而来。它在 `dsh-session` 中定义,被持久化后端保留,在恢复流程中复制,作为血缘元数据被文档记录,并有往返测试覆盖。然而仓库中没有任何生产环境的 fork UI 或 subagent 流程读取它。计划中的 subagent/fork seam 仍是 TODO因此该字段目前只是预存的未来形状。
单个文件的成本虽小,但在格式层面影响面广:每个后端 schema 和元数据序列化器都在保留一个尚无已完成功能读取的值。由于 header 是磁盘契约,即使是占位字段也会成为未来重构必须维护、迁移或有意打破的东西。
## 提案
移除 `parentSession`,使其不再属于 `SessionHeader`,直到真正的 fork/恢复功能需要血缘信息时再引入。如果存在相应 APIfork 仍然可以用先前事件来初始化新会话,但持久化的父指针应当与读取它的功能和解释它的 UX 一同引入。
如果血缘信息回归,届时再决定它应放在不可变 header 中、会话图索引中,还是作为一等事件。当前字段不应预先锁定那个设计。
## 验收标准
- `SessionHeader` 仅包含 version、id、createdAt 和可选的 cwd。
- JSONL 与 SQLite 元数据 schema 不再存储父会话 id。
- 恢复与列表 API 不再往返传递 `parentSession`
- 文档和测试移除没有生产消费方支撑的 fork 血缘声明。
- 会话格式版本、后端 schema 版本与记录的 fixture测试前置数据按需刷新按预发布格式策略非当前版本的存储数据将被拒绝不提供迁移路径。
## 放弃了什么
代码库失去了一个为未来 fork/subagent UX 预备的现成血缘钩子。这是有意为之。该字段在功能存在时很容易重新引入,而未发布的立场允许格式变更无需迁移。
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-04-prune-unimplemented-subagent-vocabulary.md: 890aca31f09f97ab6d9bf7c00f738d894695d9ad
2026-07-04-prune-unimplemented-subagent-vocabulary.zh.md: 7837759604a30ee8f58d922bb5f55f6730d1ddcb
2026-07-04-prune-unimplemented-subagent-vocabulary.md: 276e832af695acbcf70103def8b51fb8c6e1033f
2026-07-04-prune-unimplemented-subagent-vocabulary.zh.md: 1cb835ff26e407223646d1c92a78c7fc42c9e564

View File

@@ -19,9 +19,9 @@ Remove `outputSchema`/`structured`, `toolFilter`, `sendMessage`, and `resume` fr
**Keep** `depthLimit`/`maxDepth` and capability checks. The in-process backend enforces the limit, although the shipping tool does not yet set it. Recursion is a known seam risk, so the appropriate follow-up is to supply a tool default rather than delete working enforcement.
Adjacent surface examined and deliberately left alone: `SubagentService.getProvider()`/`list()` have test-harness consumers only, but the [prune-dead-seam-methods implementation note](../../implemented/simplification/2026-06-20-prune-dead-seam-methods.md) records precisely this shape being removed from the bash executor and reverted — a test harness IS a consumer for a one-line accessor over an already-tracked map. `SubagentRunEndInfo.lastAssistantMessage` is a recorded keep (the [subagent-observe-enrich Agent Note](../../implemented/feature/2026-06-30-subagent-observe-enrich.md)'s review dropped `agentType` and kept it deliberately, as the only final-message channel for out-of-process children); its currently-unwired bridge forwarding is a gap to close or a consumer to document, not surface for this Agent Note to cut.
Adjacent surface examined and deliberately left alone: `SubagentService.getProvider()`/`list()` have test-harness consumers only, but the [prune-dead-seam-methods implementation note](../../archived/simplification/2026-06-20-prune-dead-seam-methods.md) records precisely this shape being removed from the bash executor and reverted — a test harness IS a consumer for a one-line accessor over an already-tracked map. `SubagentRunEndInfo.lastAssistantMessage` is a recorded keep (the [subagent-observe-enrich Agent Note](../../archived/feature/2026-06-30-subagent-observe-enrich.md)'s review dropped `agentType` and kept it deliberately, as the only final-message channel for out-of-process children); its currently-unwired bridge forwarding is a gap to close or a consumer to document, not surface for this Agent Note to cut.
This is the seam-vocabulary echo of [prune dead methods from the persistence seam](../../implemented/simplification/2026-06-20-prune-dead-seam-methods.md): members every implementation must declare for nobody — weaker even, since here zero implementations exist.
This is the seam-vocabulary echo of [prune dead methods from the persistence seam](../../archived/simplification/2026-06-20-prune-dead-seam-methods.md): members every implementation must declare for nobody — weaker even, since here zero implementations exist.
## Alternatives considered

View File

@@ -19,9 +19,9 @@ Status: rejected — 延后的能力词汇(`outputSchema`/`structured`、`tool
**保留** `depthLimit`/`maxDepth` 与能力检查。进程内后端已强制执行该限制,尽管当前发布的工具尚未设置它。递归是已知的 seam 风险,因此恰当的后续工作是提供一个工具默认值,而非删除正在工作的强制逻辑。
审视过但有意不动的相邻接口面:`SubagentService.getProvider()`/`list()` 仅有测试 harness 消费方,但 [prune-dead-seam-methods 实现说明](../../implemented/simplification/2026-06-20-prune-dead-seam-methods.md)恰好记录了这种形态从 bash 执行器中被移除后又被回退的经过——对于一个基于已跟踪 map 的单行访问器而言,测试 harness 就是消费方。`SubagentRunEndInfo.lastAssistantMessage` 是一个已记录的保留项([subagent 观测/丰富化 Agent Note](../../implemented/feature/2026-06-30-subagent-observe-enrich.md)的评审删除了 `agentType` 但有意保留了它,因为它是进程外子 agent智能体唯一的最终消息通道它当前未接通的桥接转发是一个待补的缺口或待记录的消费方不是本 Agent Note 要裁剪的接口面。
审视过但有意不动的相邻接口面:`SubagentService.getProvider()`/`list()` 仅有测试 harness 消费方,但 [prune-dead-seam-methods 实现说明](../../archived/simplification/2026-06-20-prune-dead-seam-methods.md)恰好记录了这种形态从 bash 执行器中被移除后又被回退的经过——对于一个基于已跟踪 map 的单行访问器而言,测试 harness 就是消费方。`SubagentRunEndInfo.lastAssistantMessage` 是一个已记录的保留项([subagent 观测/丰富化 Agent Note](../../archived/feature/2026-06-30-subagent-observe-enrich.md)的评审删除了 `agentType` 但有意保留了它,因为它是进程外子 agent智能体唯一的最终消息通道它当前未接通的桥接转发是一个待补的缺口或待记录的消费方不是本 Agent Note 要裁剪的接口面。
这是[从持久化 seam 裁剪死方法](../../implemented/simplification/2026-06-20-prune-dead-seam-methods.md)在 seam 词汇层面的回响:每个实现都必须为无人声明的成员,甚至更弱,因为这里连一个实现都没有。
这是[从持久化 seam 裁剪死方法](../../archived/simplification/2026-06-20-prune-dead-seam-methods.md)在 seam 词汇层面的回响:每个实现都必须为无人声明的成员,甚至更弱,因为这里连一个实现都没有。
## 曾考虑的替代方案

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-drop-acp-session-load.md: bae71ba2968bbb10503619e764694a6712572efd
2026-06-20-drop-acp-session-load.zh.md: cdf49039e889f8528f488b53ad01cc13eab2b9d6
2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md: 475fd632cd4f75c966d4693e049edd48a1301992
2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md: 47b20fdb237ab52aecba6b7df20dbd25eeb1649e

View File

@@ -0,0 +1,37 @@
# Agent Note: Use node:timers/promises for hand-rolled cancellable sleeps
Status: rejected — implementation (PR #679) falsified the parity premise: vitest's fake clock does not intercept `node:timers/promises`, so the swap costs deterministic fast tests for ~10 deleted lines
English | [中文](2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md)
## Problem
Three packages hand-roll promise-wrapped timers that the `node:timers/promises` builtin already provides, while other packages (`dsh-llm-mock-server` `pause()`, `dsh-lsp-local`, `dsh-acp-snapshot`) already use the builtin — so the hand-rolled copies are also a consistency gap:
- `packages/llm/llm-retry/src/index.ts` `cancellableDelay()` (~14 lines): `new Promise` + `setTimeout` + manual abort-listener add/remove, resolving `true` on elapse and `false` on abort, consumed once for the backoff wait.
- `packages/workflow/workflow-workerthread/src/host.ts` `sleep()` (~7 lines): promise-wrapped unref'd `setTimeout` used as the dispose-grace bound.
- `packages/pty/pty-local/src/session.ts` `delay()` (~4 lines): bare promise-wrapped `setTimeout` used in polling/teardown waits.
## Proposal
Replace all three with `import { setTimeout } from 'node:timers/promises'`:
- llm-retry: `try { await setTimeout(delayMs, undefined, { signal }); /* retry */ } catch { /* abort → fail */ }` — with a signal, the promise rejects only with the abort error, and a pre-aborted signal rejects immediately; behavior is identical, including timer clearing on abort. The empty `catch` names the abort rejection per the repo's empty-catch rule.
- workflow-workerthread: `setTimeout(ms, undefined, { ref: false })` — exact semantics including not holding the event loop open.
- pty-local: `import { setTimeout as delay } from 'node:timers/promises'` — identical signature, call sites unchanged.
No dedicated tests pin the helpers themselves; the packages' behavior suites keep passing.
## Alternatives considered
- **`p-timeout`/`p-defer` style packages.** Rejected: the builtin covers both call sites exactly; an external package for a one-line await is negative-net.
- **Leave them.** Rejected only weakly — the cost is small, but the repo already uses the builtin idiom elsewhere, and two hand-rolled variants of a builtin invite a third.
## Acceptance criteria
- None of the three packages defines a promise-wrapped `setTimeout` helper; all import from `node:timers/promises`.
- The `llm-retry`, `workflow-workerthread`, and `pty-local` test suites pass unchanged (behavioral parity).
## Risks
Essentially none: no model-visible output, no platform concerns, no new dependency. The llm-retry rewrite changes a boolean-returning helper into try/catch control flow — a local readability judgment the implementing PR makes.

View File

@@ -0,0 +1,37 @@
# Agent Note: 用 node:timers/promises 替代手写的可取消休眠
Status: rejected — 实现PR #679证伪了行为等价前提vitest 的假时钟不拦截 `node:timers/promises`,这次替换用确定性的快速测试换来约 10 行删除,得不偿失
[English](2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.md) | 中文
## 问题
三个包package手写了 promise 包装的定时器,而 `node:timers/promises` 内置模块早已提供同等能力;其他包(`dsh-llm-mock-server``pause()``dsh-lsp-local``dsh-acp-snapshot`)已经在使用该内置模块,因此这些手写副本同时也是一处一致性缺口:
- `packages/llm/llm-retry/src/index.ts``cancellableDelay()`(约 14 行):`new Promise` + `setTimeout` + 手动添加/移除 abort 监听器,计时走完时 resolve 为 `true`、被中止时 resolve 为 `false`,仅在退避等待处消费一次。
- `packages/workflow/workflow-workerthread/src/host.ts``sleep()`(约 7 行promise 包装、已 unref 的 `setTimeout`,用作 dispose资源释放宽限的时间上界。
- `packages/pty/pty-local/src/session.ts``delay()`(约 4 行):朴素的 promise 包装 `setTimeout`,用于轮询与拆除等待。
## 提案
`import { setTimeout } from 'node:timers/promises'` 替换这三处实现:
- llm-retry`try { await setTimeout(delayMs, undefined, { signal }); /* retry */ } catch { /* abort → fail */ }`。传入 signal 后,该 promise 只会以 abort 错误拒绝,已提前中止的 signal 则立即拒绝;行为完全一致,包括中止时清除定时器。按仓库的空 catch 规则,这个空 `catch` 注明其吞下的是 abort 拒绝。
- workflow-workerthread`setTimeout(ms, undefined, { ref: false })`,语义完全等价,包括不会让事件循环保持存活。
- pty-local`import { setTimeout as delay } from 'node:timers/promises'`,签名完全相同,调用点无需改动。
没有专属测试固定这些辅助函数本身;各包的行为测试套件继续通过。
## 曾考虑的替代方案
- **`p-timeout`/`p-defer` 一类的包。** 不予采纳:内置模块恰好精确覆盖这些调用点;为一行 await 引入外部包是负收益。
- **维持现状。** 不予采纳,但理由较弱:成本确实很小,但仓库其他地方已经在用这一内置惯用法,而同一内置能力存在两个手写变体,就会招来第三个。
## 验收标准
- 这三个包都不再各自定义 promise 包装的 `setTimeout` 辅助函数,而是都从 `node:timers/promises` 导入。
- `llm-retry``workflow-workerthread``pty-local` 的测试套件原样通过(行为等价)。
## 风险
基本没有风险不涉及模型可见的输出没有平台顾虑也不新增依赖。llm-retry 的改写把一个返回布尔值的辅助函数变成 try/catch 控制流,这是一项局部可读性判断,由实施 PRPull Request裁量。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-26-dependency-swaps-rejected-by-nih-audit.md: 92ecfeef2deb7f6cca6e99b4e2de7571bc974548
2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: 76f9c012e8986fc8b527cab263f875983bd72609
2026-07-26-dependency-swaps-rejected-by-nih-audit.md: 9737eb7965c1224c984070dd1766db23e7bbdcf4
2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: 04f79efed523ce498e6ac7079360aa0129c8b5f0

View File

@@ -69,7 +69,7 @@ Adopt the following dependency swaps. Rejected — per-item evidence below; a fu
- **`prebuildify`/`node-gyp-build` for the landlock launcher packaging**: inapplicable — those load `.node` addons via dlopen; the launcher ships a standalone exec'd static binary, and per-platform `optionalDependencies` *is* the ecosystem convention for binaries.
- **Replacing the Landlock launcher itself with `@landstrip/landstrip`**: fails the security-invariant test — the launcher is a ~300-line reviewable C file with byte-pinned provenance that already migrated away from a Rust dependency; a single-maintainer LGPL Rust binary set is a larger audit surface with weaker provenance. (The unbuilt Windows rung was weighed separately and also [rejected](../feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md) — landstrip is not battle-tested.)
- **`hatch-nodejs-version` for Python release versioning**: roughly LOC-neutral (a custom metadata hook replaces the regex), inverts the recorded decision that the dev sentinel never determines a release version, and puts a single-maintainer build plugin in the release supply chain.
- **YAML consolidation (`js-yaml` vs `yaml`)**: the repo carries both parsers, with the `!!js` tag defined three times on js-yaml (vendored include, app-boot, apps/cli) and twice on `yaml` (sdk-telemetry's `ScalarTag`, sdk-helper's comment-preserving Document editing). The direction is forced — js-yaml cannot replace `yaml` (sdk-helper needs the Document API) — but migrating the js-yaml sites cannot retire the library either (the vendored include pins it) and would put two parsers in charge of one dialect that must agree exactly, against the [personal-config note](../../implemented/feature/2026-07-20-dsh-cli-personal-config.md)'s deliberate load-only-copy parity. Deletable: ~2025 lines of duplicate tag definitions and two `@types/js-yaml` entries. The consolidation moment is a future include sync, not now.
- **YAML consolidation (`js-yaml` vs `yaml`)**: the repo carries both parsers, with the `!!js` tag defined four times on js-yaml (vendored include, app-boot, apps/cli, `scripts/verify-cordis-config.ts`) and twice on `yaml` (sdk-telemetry's `ScalarTag`, sdk-helper's comment-preserving Document editing). The direction is forced — js-yaml cannot replace `yaml` (sdk-helper needs the Document API) — but migrating the js-yaml sites cannot retire the library either (the vendored include pins it) and would put two parsers in charge of one dialect that must agree exactly, against the [personal-config note](../../implemented/feature/2026-07-20-dsh-cli-personal-config.md)'s deliberate load-only-copy parity. Deletable: ~2025 lines of duplicate tag definitions and two `@types/js-yaml` entries. The consolidation moment is a future include sync, not now.
## Alternatives considered

View File

@@ -69,7 +69,7 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门
- **以 `prebuildify`/`node-gyp-build` 承担 landlock 启动器打包**:不适用——那些工具通过 dlopen 加载 `.node` addon这个启动器交付的是独立 exec 的静态二进制,而按平台划分的 `optionalDependencies` 恰恰*就是*二进制分发的生态惯例。
- **以 `@landstrip/landstrip` 替换 Landlock 启动器本身**:未通过安全不变式检验——启动器是一个约 300 行、可完整评审、来源逐字节锁定的 C 文件,且早已从一个 Rust 依赖迁移出来;单一维护者的 LGPL Rust 二进制集合是更大的审计面加更弱的来源保障。(尚未构建的 Windows 层级经单独权衡后同样被[驳回](../feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)——landstrip 未经实战检验。)
- **以 `hatch-nodejs-version` 承担 Python 发布版本号**:代码行数大致持平(一个自定义 metadata 钩子换掉那个正则却反转了「dev 哨兵值绝不决定发布版本」这条记录在案的决策,还把一个单一维护者的构建插件放进发布供应链。
- **YAML 归一(`js-yaml``yaml`**:仓库同时携带两个解析器,`!!js` 标签在 js-yaml 上定义了vendor 收录的 include、app-boot、apps/cli`yaml` 上定义了两次sdk-telemetry 的 `ScalarTag`、sdk-helper 的保留注释式 Document 编辑。方向是被迫的——js-yaml 无法取代 `yaml`sdk-helper 需要 Document API——但迁移 js-yaml 各调用点也退休不了这个库vendor 收录的 include 锁定了它),还会让两个解析器共管一种必须完全一致的方言,违背[个人配置决策](../../implemented/feature/2026-07-20-dsh-cli-personal-config.md)刻意的「仅加载副本」对等性。可删除的:约 2025 行重复标签定义和两条 `@types/js-yaml` 条目。归一的时机是未来某次 include 同步,不是现在。
- **YAML 归一(`js-yaml``yaml`**:仓库同时携带两个解析器,`!!js` 标签在 js-yaml 上定义了vendor 收录的 include、app-boot、apps/cli`scripts/verify-cordis-config.ts`),在 `yaml` 上定义了两次sdk-telemetry 的 `ScalarTag`、sdk-helper 的保留注释式 Document 编辑。方向是被迫的——js-yaml 无法取代 `yaml`sdk-helper 需要 Document API——但迁移 js-yaml 各调用点也退休不了这个库vendor 收录的 include 锁定了它),还会让两个解析器共管一种必须完全一致的方言,违背[个人配置决策](../../implemented/feature/2026-07-20-dsh-cli-personal-config.md)刻意的「仅加载副本」对等性。可删除的:约 2025 行重复标签定义和两条 `@types/js-yaml` 条目。归一的时机是未来某次 include 同步,不是现在。
## 曾考虑的替代方案