# Agent Note：行内代码文件提及可打开其命名的文件

Status: implemented

[English](2026-08-07-web-inline-file-mentions.md) | 中文

> 范围：把收尾消息正文中的行内代码 token 链接到本轮产出的文件。不在范围内：识别普通正文中的路径、链接本轮未产出的文件，以及流式或轮次中途消息里的提及。

## 问题

产物行列出了一轮的输出，但收尾消息通常也会在正文里*点名*文件——以行内代码形式，如 `` `deepseek-homepage.html` ``——而这个提及是死文本。读者的视线先落在句子上；可点击的交互却在下面一行。

## 决定

**正文提及只在与产出文件对得上时才成为链接。**[产物行的决定](2026-07-31-web-workspace-file-links.md)否决过"把收尾消息链接化"，理由是渲染不能依赖模型把路径写得可识别；这一点不变。产物行仍是权威的、不依赖正文的记录。本特性只是给同一份 `locations` 词表增加第二个消费者：`producedFileMentions` 按精确路径解析行内代码 token，或当 token 恰好是且仅是一条产出路径的 basename 时解析。两条路径共享的 basename 保持死文本而不猜测，命名了本轮没写过的文件的 token 同样保持死文本——提及链接永远不会 404。

**渲染器不持有词表，提供方是 deliverables 插件。**`MarkdownText` 接受可选的 `MarkdownFileMentions` 解析器，对行内代码 token 询问它——URL 提升优先于解析器，且绝不在锚点内部（按钮不能嵌套在链接里）。什么算文件名的决定藏在 ui-conversation 经 `ctx.get` 触达的可选 `chatFileMentions` service 背后：ui-deliverables 在其 turn-tail chain 注册项旁提供该 service，因此 cordis.yml 中的一行同时把产物行和正文链接组合进来或去掉，ui-primitives 不引入任何会话概念。提及只作用于已定稿的渲染——流式缓存不能烘进可能过期的 handler，而且词表在轮次收尾前并不最终。消费方按收尾 seq 而非不断增长的 transcript 记忆化解析器，因此已定稿消息的缓存解析在流式追加中得以保留。

## 考虑过的替代方案

- **对全部正文跑路径形状的正则**——会把随口提到的 `package.json` 和从未写过的示例都链接上；每个误报都是一次打开空无或错误文件的点击。词表方案不可能产生死链。
- **链接后缀匹配（子目录列表里把 `out/index.html` 写作 `index.html`）**——暂缓；精确路径加唯一 basename 已覆盖观察到的收尾消息形态，之后放宽匹配器不会破坏这道 seam。
- **在 ui-primitives 里对传入的路径列表做解析**——把匹配策略放进通用渲染器，其他消费方会被动继承。解析器约定让策略留在持有者手里。
- **经由 turn-tail chain 传递词表**——chain 是消息下方的渲染派发；提及要装饰的是消息内部的 markdown，只有抵达 MarkdownText 的数据才做得到。可选 service 就是那条数据通路，它的缺席即关闭态。

## 后果

提及与产物行是同一事实的两个交互面（两者都以完整路径作 `title`）；提及本身采用 markdown 样式表的锚点语言——静止为链接蓝、悬停出下划线——因为静止下划线在 code 胶囊里会压住等宽字的下伸部。`apps/web/tests/produced-file-mentions.e2e.ts` 用构造的写入轮 seed 钉住组装后的行为：唯一 basename 成链，歧义与未知 token 保持死文本；它不驱动点击，沿用产物行的克制（opener 会启动真实应用）。轮次中途叙述里的提及即使命名了本轮后来产出的文件也保持死文本，因为词表只挂在收尾消息上。窗口前插的边界——从轮次中途开始的窗口后来补入了同轮更早的写入——只会让提及在重挂载前暂不成链，绝不会错链。
