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

# Conflicts:
#	.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml
#	apps/web/tests/scaffold.ts
#	packages/session-persistence/session-checkpoint-policy/tests/crash-recovery.e2e.ts
#	packages/support/acp-snapshot/README.i18n.yaml
This commit is contained in:
Tianyi Cui
2026-07-27 06:31:17 +08:00
665 changed files with 11055 additions and 7377 deletions

View File

@@ -1,31 +0,0 @@
# Agent Note: Make the shared example base providerless
Status: rejected — superseded by [Extract example apps into packages](../../implemented/architecture/2026-06-20-extract-example-app-packages.md), which moves the spine into a `dsh-agent-spine-demo` bundle and deletes the `base*.yml` files, so there is no shared base YAML left to rename.
English | [中文](2026-06-20-providerless-example-base.zh.md)
## Problem
The examples had two shared base files: `examples/base-core.yml` was providerless, while `examples/base.yml` included that core plus the real `llm-deepseek` adapter. Snapshot replay needs the providerless core with `llm-replay`, because loading the real adapter without a key throws. The normal demos need the real adapter. The result was a naming inversion: the file named `base.yml` was not the reusable base for all examples, while the true base was `base-core.yml`.
The split was understandable, but it made every config explanation longer. It also led to awkward test setup like a keyless smoke test carrying a dummy API key so an adapter could boot even though the model is not called.
## Proposal
Rename the providerless core to `examples/base.yml` and make adapter selection explicit in each concrete example. The coding and ACP real configs add a tiny `llm-deepseek` include or local block; snapshot config adds `llm-replay`. Delete `examples/base-core.yml`.
The shared base should contain only provider-neutral services and tools: `llm`, sessions, system prompt, tools, agents, invariants, bash executor, and bash tool schemas. Anything that chooses a model provider belongs at the leaf config.
## Acceptance criteria
- `examples/base.yml` is providerless.
- `examples/base-core.yml` is deleted.
- Real demo configs explicitly add the DeepSeek adapter.
- Snapshot replay config includes the same providerless base and its replay adapter.
- The [examples README](../../../../examples/README.md), example-specific READMEs, and Agent Note references stop explaining "base = base-core plus adapter".
## What we give up
Real demos lose one layer of convenience: each must opt into the adapter. That is the right default for examples, because adapter choice is the variable part and providerless wiring is the shared product core.
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->

View File

@@ -1,31 +0,0 @@
# Agent Note: 使共享示例基础配置与提供方无关
Status: rejected — 已由[将示例应用提取到 packages 中](../../implemented/architecture/2026-06-20-extract-example-app-packages.md)取代;后者把主干移入 `dsh-agent-spine-demo` bundle 并删除 `base*.yml` 文件,因此已不存在可重命名的共享基础 YAML。
[English](2026-06-20-providerless-example-base.md) | 中文
## 问题
示例曾有两个共享基础文件:`examples/base-core.yml` 与提供方无关,而 `examples/base.yml` 在该核心基础上加入了真实的 `llm-deepseek` 适配器。快照回放需要与提供方无关的核心配合 `llm-replay` 使用,因为在没有密钥的情况下加载真实适配器会抛出异常。常规演示则需要真实适配器。结果是命名与实际含义倒挂:名为 `base.yml` 的文件并非所有示例可复用的基础,而真正的基础反倒是 `base-core.yml`
这种拆分可以理解,但它让每次解释配置都变得更冗长。它还导致了别扭的测试搭建方式,例如无密钥冒烟测试不得不携带一个虚拟 API key仅仅为了让适配器能启动——尽管模型根本不会被调用。
## 提案
将与提供方无关的核心重命名为 `examples/base.yml`,让适配器选择在每个具体示例中显式声明。编码和 ACPAgent Client Protocol真实配置添加一小段 `llm-deepseek` include 或本地块;快照配置添加 `llm-replay`。删除 `examples/base-core.yml`
共享基础应仅包含提供方无关的服务与工具:`llm`、会话、系统提示词、工具、agent智能体、不变式、bash 执行器和 bash 工具 schema。任何涉及模型提供方选择的内容都应放在叶子配置中。
## 验收标准
- `examples/base.yml` 与提供方无关。
- `examples/base-core.yml` 已删除。
- 真实演示配置显式添加 DeepSeek 适配器。
- 快照回放配置 include 同一个与提供方无关的基础,并加入其回放适配器。
- [examples README](../../../../examples/README.md)、各示例 README 及 Agent Noteagent 决策记录引用不再解释「base = base-core 加适配器」。
## 放弃了什么
真实演示失去了一层便利:每个演示都必须显式引入适配器。对于示例而言这是正确的默认行为,因为适配器选择是可变部分,而与提供方无关的接线才是共享的产品核心。
<!-- 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-07-13-stream-workflow-progress-through-tool-calls.md: 1b299ec323d32745cc504a90948b63a4dcaae64f
2026-07-13-stream-workflow-progress-through-tool-calls.zh.md: f84ff3bb22a19ed7ad2f9fc262a6702e254972ad

View File

@@ -1,43 +0,0 @@
# Agent Note: Stream workflow progress through tool calls
Status: rejected — ACP is automation-only; live workflow presentation needs a human-interface owner and a fresh design.
English | [中文](2026-07-13-stream-workflow-progress-through-tool-calls.zh.md)
## Problem
The workflow engine intentionally emits balanced `workflow/*` observation events for run, phase, narration, and child-agent progress, but no production consumer presents them. Editors therefore show one pending workflow tool card until the final result even while the engine already reports which phase is active, what the script logged, and which children started or settled. The [dynamic-workflows decision](../../implemented/feature/2026-07-05-dynamic-workflows.md) explicitly reserves ACP progress UI for this event stream.
Making `dsh-acp` listen to workflow events directly would invert the capability boundary: the generic UI bridge would depend on an optional workflow package and special-case one tool name. The tool pipeline already owns the routing facts a live update needs—agent and call id—but exposes only pure pending/final presenters, so a long-running tool has no provider-neutral way to report transient UI state between them.
## Proposal
Add a live progress channel to `dsh-tools`. The registry-owned `ToolExecution` gains `reportProgress(view): boolean`, where `view` is a detached provider-neutral generic progress snapshot containing an optional replacement title and UI-facing content blocks. Progress cannot change the call's args-derived card tag, kind, raw input, locations, terminal intent, or diff intent; it updates only the live title/content within the presentation chosen up front. While the execution is active, the method validates and snapshots the view, then dispatches a contained, agent-scoped `tools/progress` observation carrying the authoritative execution identity and snapshot. Once final-result processing begins it returns `false` and emits nothing, so a late asynchronous reporter cannot overwrite a terminal card. Observer exceptions are logged and cannot fail the tool.
`dsh-acp` consumes `tools/progress` generically. It resolves the execution's agent through its existing agent-to-session map and emits an in-progress `tool_call_update` for the same call id. Because reporting is available only inside the tool execution pipeline, the durable `tool/call` and its ACP `tool_call` always precede the first update; closing the reporter before `tools/result` ensures no progress update follows the completed/failed card. Progress is live UI state rather than model input or durable history: session replay continues to reconstruct the pending and final cards from `tool/call` and `tool/result` without replaying transient updates.
`dsh-tool-workflow` becomes the first producer. Each tool execution installs a compact event capture before calling `ctx.workflows.start()`, because a valid engine may emit progress synchronously inside `start()`. Until the call returns, the capture reduces observed events into candidate states keyed by `WorkflowRunInfo.id`; it then selects the returned `WorkflowRun.id`, discards other candidates, reports the accumulated snapshot, and routes later matching events directly. If `start()` throws, the capture is disposed and its candidates are dropped. This preserves engine swappability without adding observer correlation to `WorkflowStartRequest` or requiring progress to wait until `start()` returns.
The reducer consumes the existing start, phase, log, agent-start, agent-end, and end events, reporting a replacement snapshot with the current phase, latest log line, active child labels, and completed/failed/cancelled counts. It does not accumulate a narration transcript; settled children leave the active set and become counters. `workflow/end`, tool settlement, or plugin disposal removes the reducer entry and event capture. The six workflow events, their metadata, paired child lifecycle, run handle, cancellation channels, and observer containment remain unchanged; third-party observers can continue consuming them directly.
Update the tool execution/presentation docs, generated event and API catalogs, workflow package docs, and the workflow data-structure catalog. ACP integration coverage must exercise the real workflow tool and worker seam with a scripted model boundary; the primary ACP snapshot suite adds one workflow-progress scenario because this changes the editor-facing transcript.
## Alternatives considered
**Delete the workflow observation surface.** Rejected in [the collapse-workflow simplification](../../rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md): the events and their balanced lifecycle are intentional, and the missing piece is a consumer.
**Teach ACP about workflows directly.** This could map `WorkflowRunInfo` to a session and card, but it would make the generic bridge depend on an optional capability and bypass the rule that tools own presentation intent. A tool-progress channel solves the same routing problem for every long-running tool.
**Persist every progress update as a session event.** That would make live narration replayable, but it would permanently enlarge logs with state whose authoritative durable outcome is already the tool call/result pair. If resumable workflow progress becomes a product requirement, it needs a workflow-journaling design rather than UI snapshots disguised as durable facts.
## Acceptance criteria
- `ToolExecution.reportProgress()` is registry-owned, agent-scoped, snapshotting, observer-contained, and returns `false` without dispatch after terminal processing starts.
- ACP routes progress to the correct call in the correct live session; concurrent workflows in different sessions cannot cross-talk, and no `tool_call_update` appears before its `tool_call` or after its terminal update.
- Workflow progress shows the current phase, latest log line, active children, and outcome counts while preserving all existing `workflow/*` events and run semantics; a seam test engine that emits start, phase, log, child, and end events synchronously inside `start()` loses none of that reducer state.
- Cancellation, worker death, tool failure, session close, and plugin disposal release reducer state; replay emits only the durable pending/final card pair.
- Unit, workflow integration, ACP integration, snapshot, typecheck, coverage, doc-sync, module-graph, build, and hygiene gates pass.
## Risks
This adds a public live-progress method and event to the tool seam, so implementations must keep the active/terminal boundary exact and detach snapshots before observers see them. The pre-start capture can briefly observe unrelated workflow runs, so it holds only compact candidate state keyed by run id and drops every non-matching candidate as soon as `start()` returns. A workflow can emit many progress changes; the bounded reducer avoids transcript growth but still sends one UI update per meaningful event after correlation. If measured clients need coalescing, it must be a defaulted validated bridge configuration rather than a hardcoded throttle. Transient progress intentionally disappears on replay, so the final tool result remains the only durable workflow card content.

View File

@@ -1,43 +0,0 @@
# Agent Note: 通过工具调用流式传输工作流进度
Status: rejected — ACP 仅面向自动化;实时工作流展示需要一个面向人类界面的归属方和全新设计。
[English](2026-07-13-stream-workflow-progress-through-tool-calls.md) | 中文
## 问题
工作流引擎有意为 run、phase、narration 和子 agent智能体进度发出成对的 `workflow/*` observation 事件,但目前没有生产消费方呈现这些事件。因此,编辑器在最终结果返回之前只显示一张 pending 状态的工作流工具卡片,尽管引擎已经报告了当前活跃的 phase、脚本日志内容以及哪些子 agent 已启动或已结束。[动态工作流决策](../../implemented/feature/2026-07-05-dynamic-workflows.md)明确将 ACPAgent Client Protocol进度 UI 保留给这一事件流。
如果让 `dsh-acp` 直接监听工作流事件,就会反转能力边界:通用的 UI 桥接层将依赖一个可选的工作流包package并对一个工具名做特殊处理。工具流水线已经拥有实时更新所需的路由信息agent 和 call id但只暴露了纯粹的 pending/final 展示器,因此长时间运行的工具没有提供方无关的方式在二者之间报告瞬态 UI 状态。
## 提案
`dsh-tools` 添加一条实时进度通道。注册表所有的 `ToolExecution` 新增 `reportProgress(view): boolean`,其中 `view` 是一个独立的、提供方无关的通用进度快照,包含可选的替换标题和面向 UI 的内容块。进度不能更改调用的 args 派生卡片标签、kind、原始输入、locations、terminal intent 或 diff intent它只更新在最初选定的展示方式内的实时标题/内容。当执行处于活跃状态时,该方法校验并快照 view然后分发一个受限的、agent 作用域的 `tools/progress` observation携带权威的执行标识与快照。一旦 final-result 处理开始,方法返回 `false` 且不再分发,因此迟到的异步报告者无法覆盖终态卡片。观察者异常会被记录日志,不会导致工具失败。
`dsh-acp` 以通用方式消费 `tools/progress`。它通过既有的 agent 到会话映射解析执行所属的 agent并为同一 call id 发出 in-progress 的 `tool_call_update`。由于报告仅在工具执行流水线内可用,持久化的 `tool/call` 及其 ACP `tool_call` 始终先于第一条 update`tools/result` 之前关闭报告者,确保进度更新不会出现在 completed/failed 卡片之后。进度是实时 UI 状态,而非模型输入或持久历史:会话回放继续从 `tool/call``tool/result` 重建 pending 与 final 卡片,无需重放瞬态更新。
`dsh-tool-workflow` 成为第一个生产者。每次工具执行在调用 `ctx.workflows.start()` 之前安装一个紧凑的事件捕获器,因为合法的引擎可能在 `start()` 内部同步发出进度。在调用返回之前,捕获器将观察到的事件按 `WorkflowRunInfo.id` 归约为候选状态;随后选取返回的 `WorkflowRun.id`,丢弃其他候选,报告累积的快照,并将后续匹配事件直接路由。如果 `start()` 抛出异常,捕获器被 dispose资源释放其候选状态被丢弃。这在不向 `WorkflowStartRequest` 添加观察者关联、也不要求进度等到 `start()` 返回的前提下,保持了引擎的可替换性。
归约器消费既有的 start、phase、log、agent-start、agent-end 和 end 事件,报告一个替换快照,包含当前 phase、最新日志行、活跃子 agent 标签以及 completed/failed/cancelled 计数。它不累积 narration transcript文本记录已结束的子 agent 离开活跃集合,变为计数器。`workflow/end`、工具结算或插件 dispose 移除归约器条目和事件捕获器。六种工作流事件及其元数据、成对的子 agent 生命周期、run handle、取消通道和观察者隔离保持不变第三方观察者可继续直接消费这些事件。
更新工具执行/展示文档、生成的事件与 API 目录、工作流包文档以及工作流数据结构目录。ACP 集成覆盖率必须使用脚本化的模型边界测试真实的工作流工具和 worker seam主 ACP 快照套件新增一个 workflow-progress 场景,因为这改变了面向编辑器的 transcript。
## 曾考虑的替代方案
**删除工作流 observation 表面。** 在[折叠工作流简化提案](../../rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md)中被否决:这些事件及其成对生命周期是有意设计的,缺少的是消费方。
**让 ACP 直接了解工作流。** 这可以将 `WorkflowRunInfo` 映射到会话和卡片,但会使通用桥接层依赖一个可选能力,并绕过「工具拥有展示意图」的规则。工具进度通道为每个长时间运行的工具解决了相同的路由问题。
**将每条进度更新持久化为会话事件。** 这会使实时 narration 可回放,但会用一种状态永久膨胀日志,而该状态的权威持久结果已经是工具调用/结果对。如果可恢复的工作流进度成为产品需求,需要一个工作流日志化设计,而非伪装成持久事实的 UI 快照。
## 验收标准
- `ToolExecution.reportProgress()` 由注册表所有、agent 作用域、快照化、观察者隔离,且在终态处理开始后返回 `false` 而不分发。
- ACP 将进度路由到正确的实时会话中的正确调用;不同会话中的并发工作流不能串扰,且 `tool_call_update` 不会出现在其 `tool_call` 之前或终态更新之后。
- 工作流进度显示当前 phase、最新日志行、活跃子 agent 和结果计数,同时保留所有既有 `workflow/*` 事件和 run 语义;一个在 `start()` 内部同步发出 start、phase、log、child 和 end 事件的 seam 测试引擎不会丢失任何归约器状态。
- 取消、worker 死亡、工具失败、会话关闭和插件 dispose 释放归约器状态;回放仅发出持久的 pending/final 卡片对。
- 单元测试、工作流集成测试、ACP 集成测试、快照、类型检查、覆盖率、doc-sync文档同步门禁、module-graph、构建和 hygiene 门禁全部通过。
## 风险
本提案向工具 seam 添加了一个公开的实时进度方法和事件,因此实现方必须精确维护 active/terminal 边界并在观察者看到快照之前将其分离。pre-start 捕获器可能短暂观察到无关的工作流 run因此它仅按 run id 持有紧凑的候选状态,并在 `start()` 返回后立即丢弃所有不匹配的候选。一个工作流可能发出大量进度变更;有界归约器避免了 transcript 增长,但在关联完成后仍会为每个有意义的事件发送一条 UI 更新。如果经测量的客户端需要合并更新,这必须是一个带默认值的、经过校验的桥接配置,而非硬编码的节流。瞬态进度在回放时有意消失,因此最终工具结果仍是唯一持久的工作流卡片内容。

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-07-04-generate-agent-note-index-tables.md: 6e5221f018942a0629f30b6e6f22cedfb9f4145e
2026-07-04-generate-agent-note-index-tables.zh.md: f8ebcd51933b3ad91e0197fc71c0d8aae568bbcf

View File

@@ -1,38 +0,0 @@
# Agent Note: Generate the Agent Note index tables
Status: rejected — a centralized generated list is merge-prone and adds little discovery value
English | [中文](2026-07-04-generate-agent-note-index-tables.zh.md)
## Problem
Per-lifecycle/per-class tables would list facts that are fully derivable: an Agent Note's path encodes lifecycle and class, its filename encodes the first-proposed date, and its H1 carries the title. A hand-maintained copy of those facts would also be a high-contention docs hotspot because concurrent Agent Note branches append rows to the same few lines. [The classification Agent Note](../../implemented/process/2026-06-20-agent-note-classification.md) makes the tree itself authoritative.
## Proposal
Keep the curated prose and generate the list as a fully generated `.agents/notes/INDEX.md`. A shared `scripts/agent-note-index.ts` module would own both the tree walker and the renderer. Two thin consumers would share it:
- `scripts/gen-agent-note-index.ts` (`pnpm run gen-agent-note-index`) would rewrite INDEX.md in full from the tree.
- `scripts/verify-agent-note-classification.ts` would check structure and assert that the committed INDEX.md byte-matches a fresh render.
Adding, moving, or deleting an Agent Note would mean editing the Agent Note file and running the generator.
## Alternatives considered
### Why not marker-delimited regions inside README.md?
Marker-delimited tables inside README.md would mix generated and curated text, requiring splice mechanics and protection for the surrounding contract. A dedicated generated file would at least keep those concerns separate.
### Why not the verifier-only model?
It catches mistakes but still makes every proposal edit a shared hotspot in a hand-maintained table. The author has already named and placed the file, so the index copy adds no information. This is the same hand-list-versus-derivation judgment the [package-inventory proposal](../../proposed/process/2026-06-20-discover-package-inventory.md) applies to tsconfig references and knip stanzas.
## Consequences
- The generated file would be explicit and contain no curated region.
- A malformed or missing H1 would be a hard error because the H1 supplies each row title.
- Concurrent branches would still modify the same committed artifact, even if conflicts could be resolved by rerunning the generator.
## Related
The implemented [no-index decision](../../implemented/process/2026-07-19-remove-generated-agent-note-index.md) keeps the tree and repository search as the discovery mechanisms.

View File

@@ -1,38 +0,0 @@
# Agent Note: 生成 Agent Note 索引表
Status: rejected — 集中生成的列表容易产生合并冲突,且几乎不增加发现价值
[English](2026-07-04-generate-agent-note-index-tables.md) | 中文
## 问题
按生命周期和分类划分的表格只会列出完全可推导的事实Agent Noteagent 决策记录的路径编码其生命周期和分类文件名编码首次提出日期H1 承载标题。手工维护这些事实的副本还会成为高冲突文档热点,因为并发的 Agent Note 分支会向相同的几行追加条目。[分类 Agent Note](../../implemented/process/2026-06-20-agent-note-classification.md) 将目录树本身定为权威来源。
## 提案
保留策展文本,并将列表生成为完全生成的 `.agents/notes/INDEX.md`。共享的 `scripts/agent-note-index.ts` 模块将同时负责目录树遍历器和渲染器。两个轻量消费方会共用它:
- `scripts/gen-agent-note-index.ts``pnpm run gen-agent-note-index`)将根据目录树完整重写 INDEX.md。
- `scripts/verify-agent-note-classification.ts` 将检查结构,并断言已提交的 INDEX.md 与新鲜渲染结果逐字节一致。
添加、移动或删除 Agent Note 时,只需编辑 Agent Note 文件并运行生成器。
## 曾考虑的替代方案
### 为什么不在 README.md 中使用标记分隔区域?
README.md 中由标记分隔的表格会混合生成内容与策展文本,因而需要拼接机制并保护周围的契约。专用生成文件至少能将这些关注点分开。
### 为什么不采用纯校验器模式?
它能捕获错误,但每次提案编辑仍然要在手工维护的表格中触碰共享热点。作者已经命名并放置了文件,因此索引副本不增加任何信息。这与[package清单提案](../../proposed/process/2026-06-20-discover-package-inventory.md)对 tsconfig 引用和 knip 配置段所做的手写列表与推导之间的判断相同。
## 后果
- 生成文件将是显式的,且不包含任何策展区域。
- H1 格式错误或缺失将是硬错误,因为 H1 为每一行提供标题。
- 即使可以通过重新运行生成器解决冲突,并发分支仍会修改同一个已提交产物。
## 相关
已落地的[不建立索引决策](../../implemented/process/2026-07-19-remove-generated-agent-note-index.md)保留目录树和仓库搜索作为发现机制。

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-session-load.md: bae71ba2968bbb10503619e764694a6712572efd
2026-06-20-drop-acp-session-load.zh.md: cdf49039e889f8528f488b53ad01cc13eab2b9d6

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-providerless-example-base.md: 2f41476a487775e6f9da2f113efe566e44786ff3
2026-06-20-providerless-example-base.zh.md: e767d6b3a35dcfd52194f8a59edc496b86414b6e
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: 31c925cd7bfe21e2020ae8bd3ba8f9e2b0398641
2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: 097ba6c879a9eab7ae25f9a3020c403380842014
2026-07-26-dependency-swaps-rejected-by-nih-audit.md: b1815967698f24cffc4abb07da364fa8a008cb6c
2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: 7f04ed15461f004e61c1de5729b2235b1a35b0ba

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 is a different question — see the [landstrip evaluation proposal](../../proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md).)
- **`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 层级是另一个问题——见 [landstrip 评估提案](../../proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)。)
- **以 `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 同步,不是现在。
## 曾考虑的替代方案