功能:引用输入区支持粘贴解析 @显示名 / $名称 #473
Reference in New Issue
Block a user
Delete Branch "feat/smart-paste"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
-,粘贴解析不再多插一枚芯片- [x] 1. 前缀重叠的 token 会多插一个 chip(原 review 第 1 条 / bug / medium,已修:
5e36b6153)你的裁决(2026-09-23):不动反解析,改为 enforce「引用名内部不允许空白」——素材 / Skill / 附件三类引用名统一走共享
normalizeMentionName(内部空白折-),不做resourceId兜底、假定名字非空。落地见本节末尾。当前实现
buildContentFromPastedText(resourceReferences.ts)先按「某个 token 在文本里出现几次」把候选展开成ContentTokenCandidate[](同一 token 出现 N 次就放 N 条),再交给buildContentFromTextTokens定位。而
buildContentFromTextTokens的定位规则是:每行从 cursor 起找「仍未被消费、且 index 最小」的候选,平局时按候选数组顺序取第一个;扫完后没有任何候选被消费掉的话,会作为 orphan 追加到正文末尾
(那是润色回包的语义:引用与附件绝不凭空消失)。
问题(已实测复现,不是推测)
当两个候选的 token 互为前缀且短 token 后面正好是空白时(例如素材显示名
hero与hero v2),短 token 也会在
@hero v2里命中一次边界(@hero␠的空白算合法边界)。粘贴看 @hero v2 这一版:即:短 token 先按 index 平局抢到位置 0,长 token 无处可放,被当成 orphan 追加到末尾——
多出一个不该有的
hero芯片,且顺序与原文不符。这违反「宁可不成芯片也不能认错」的口径。推荐修复(两条路线,必须先定口径)
(同一起点命中多个候选时取最长者、并跳过被它覆盖的短候选),并且只投真正选中的那一条;
顺带给
buildContentFromTextTokens加一个「不追加 orphan」的入参(或给粘贴单独一个插入原语),让「未消费候选补到末尾」只保留给润色回包。代价:动到与润色共用的函数,需要回归润色用例。
实现只需在
buildContentFromPastedText里丢掉「与前缀关系有关的组」,不碰共享函数。代价:素材名叫
hero时,只要项目里同时存在hero v2,@hero的粘贴解析也会一起失效。我倾向 A(长名字是用户真实输入,短名字不该抢位;orphan 追加在粘贴场景本来就是错的),
但「谁赢」和「要不要动共享反解析」是你的口径,所以没动代码。
落地(
5e36b6153)normalizeMentionName加在resourceReferences.ts,落点是名字产生处(resourceDisplayName、Skill 目录读入与toReference/mentionToken、附件导入映射与toReference/mentionToken)与两个 token 投影(chatReferenceMentionToken、directCodexContentToPromptText),token 层幂等再折一次;resourceDisplayName去掉|| asset.id。于是
@hero在@hero-v2里面后面跟的是-、不再是空白 → 不命中,前缀重叠不可能发生;buildContentFromTextTokens与润色回包零改动。代价:hero v2与hero-v2归一化后同名时走既有的「同名多候选一律按文本保留」(不认错,但两者都成不了芯片);本次改动前生成的旧文本(历史回合 prompt、旧气泡)里的@hero v2不再解析,重试 / 润色回填时那条引用会退化成末尾孤儿(内容不丢、位置可能不对)。验证:新增素材 / Skill / 附件三类归一化口径用例、前缀重叠回归用例(归一化后粘贴只剩正确的那一枚芯片)、Skill 目录名带空白与附件名带空白的 provider 用例;把
normalizeMentionName变异成恒等函数后以上新增用例全红。- [x] 2. 选区兜底在三处重复(原 review 第 2 条 / maintainability / low,已修:
9e50947d0)当前实现
「选区缺失、或选区指向已被重建掉的节点时回落到草稿末尾」这段判断,原本在
insertReferences、insertText、$insertContentAtSelection里各写了一份(三份逐字重复)。问题
策略是三处复制的:任何一处改成「插到光标原文处」或加一条新的失效条件,另外两处会悄悄漂移,
表现是同一份草稿在不同入口(
@面板 / 资源卡拖拽 / 粘贴 / 润色回填)落点不一致。修复
抽出
$selectionOrRootEnd()(ResourceReferenceInput.tsx:307):统一取「当前可用选区,失效则回落到草稿末尾」,三处调用点改为调用它(净减 14 行)。行为逐字不变,仅去重;其余几处
selectEnd()是「整根替换后把光标放到末尾」的另一种意图,没有并入。
验证:输入区 39 条 + 画布生成面板引用 19 条用例通过;
tsc、eslint、prettier 通过。- [x] 3. 解析不出的引用 part 被静默丢掉(原 review 第 3 条 / bug / low,已修:
14f11e77a)当前实现
$insertContentAtSelection原本只在referenceFromPart(providers, part)返回引用时插入 chip,返回
null时什么都不做。而 token 已经在上一层的buildContentFromPastedText里被摘成 part 了。问题
这是一条潜在的数据丢失路径:一旦某个 provider 的
toReference在插入那一刻解析不出来(资产被删、将来出现异步 provider),这段内容会彻底消失——用户看到的是粘贴少了一段字,
既没提示也无法从正文找回,与函数自身「token 之外的每个字符照原样保留」的不变量自相矛盾。
今天的 4 个内置 provider 在同一 tick 内都能同步解析,所以尚未触发。
修复
补
else分支:退回该 part 按 provider 口径的 token 文本(mentionTokenFromPart)插入,字符不丢。新增用例:自定义 provider 的
lookup()有候选、toReference()返回null,断言正文保持看 @hero 一眼且不产生芯片。变异验证:去掉该兜底后用例变红(正文变成看 一眼),恢复即绿。验证:输入区 40 条(新增 1 条)+ provider 用例通过;
tsc、eslint、prettier 通过。- [x] 4. 重命名后注释里的 provider 成员名过期(Agent 自查发现,非原评审条目,已修:
072bf691d)当前实现
buildContentFromPastedText的文档注释写着「候选由宿主注入的 provider 枚举(ReferenceProvider.candidates)」,但
ec1f20702已经把candidates()改名成lookup(),注释没跟上;而且这句话把「谁去查候选」记在了这个纯函数头上——实际是输入区在
PASTE_COMMAND里调provider.lookup(),再把清单当参数传进来。问题
ReferenceProvider.candidates已不存在,注释指着一个被删掉的成员。不影响运行,但属于「看着像对的」的一类过期注释:下一个人按注释去找那个 API 会找不到,也会误判这个函数的职责边界。
修复
改成「候选来自调用方传入的引用清单(输入区给的是
ReferenceProvider.lookup()此刻就绪的全量候选)」,只有注释变化,无行为变化。验证:prettier、
check:encoding、git diff --check通过;输入区相关3 个测试文件 69 条用例通过。
自动评审(引用粘贴解析,第二轮)处理结果 · 2026-09-23 · 分支 feat/smart-paste
已由我改掉并逐项提交(一个问题一个提交;工作区只剩你自己的 .env 改动):
4e90465c4素材显示名词干为空(.env/.gitignore)时回退 asset.id(原第 1 项,含决策记录校正与功能说明同步)3f9c698a6粘贴解析跳过只有触发符的退化 token(原第 4 项)3391ecf7d粘贴接管改为「插入真的发生之后」才 preventDefault,插入为空时放行默认粘贴(原第 3 项前半)c9bb5a93f归一化撞名的代码注释与功能说明(原第 2 项前半)原第 3 项后半现在的实现:$insertContentAtSelection 的兜底链是 provider 的 mentionToken → contentPartText(资源落 @resourceId、Skill 落 $名称、附件落 @附件名、运行区域落 @标签,与出站文本同一份渲染),只有「连通用文本形态都拿不到」(只剩 input_text 这一种,函数前面已经单独处理)才是空。单测覆盖四类 part 的文本形态,输入区集成用例覆盖「provider 的 toReference 与 mentionToken 都答不出来」时粘贴仍按文本落下;去掉兜底即变红。
评审里一处描述与现状不符:原第 3 项说的「取不到选区时粘贴文字静默丢失」不可达——选区缺失或节点已卸载时 $selectionOrRootEnd() 都回落到 $getRoot().selectEnd(),而 Lexical 的 selectEnd() 恒返回 RangeSelection(空根走 select())。顺序仍按建议加固了,因为它把「绝不静默丢字」从约定变成结构。
下面这项需要你拍板,我没有动代码:
现状:名字统一过 normalizeMentionName(内部空白折
-),hero v2.png与hero-v2.png这类名字会折成同一个 token@hero-v2。生成候选菜单(reference-source/resourceReferenceProvider.ts 的 fuzzyLookup / lookup)只按名字过滤,不做去重或冲突标记;ResourceReferenceInput 的候选项标签直接取 chatReferenceMentionToken。问题:两条候选在菜单里显示完全相同的
@hero-v2标签,用户无从分辨要选哪一条;从气泡复制的@hero-v2也重建不成芯片——buildContentFromPastedText 见到「同一个 token 对应多条引用身份」就整段按文本保留(bucket.length !== 1),粘回来只是死文本。不认错这一点是对的,但菜单侧没有任何提示。建议(需要产品取舍,所以留给你):两种走法都是产品可见行为变化——(a) 菜单侧做冲突检测,撞名的候选只保留一条或给非命中那条加区分配角(会改变「哪些素材能被 @ 到」);(b) 名字派生时保底唯一化(例如撞名时把 asset.id 片段接在名字后面),代价是显示名会带出 id,且要处理改名前后的稳定性。只做提示不做处理(例如菜单里标一句「同名」)是最小的第三选项。选 (a)/(b) 时要补 provider 用例。
@3f9c698a67评审结论:通过(Approve)
范围:
feat/smart-paste→master,引用输入区粘贴解析(@显示名/$名称→ 原位重建 chip),15 个文件,+1257/-101。已验证
resourceReferences.test.ts/referenceSourceProviders.test.ts/resourceReferenceInput.test.tsx共 79 条通过。apps/ai-game-creator-shelltsc 通过;npm run check:encoding、git diff --check通过。preventDefault;Lexical 负载、纯文本、图片粘贴放行默认导入,ranSync排队场景的处理有注释说明。mentionTokenOrText一条兜底链贯穿粘贴插入 / 润色回写 / 初始草稿,消灭了「认不出的引用静默消失」的例外分支。normalizeMentionName把「引用名无空白」前移到名字产生处,token 层幂等再折一次;.env/.gitignore词干为空回退asset.id有回归用例。match→fuzzyLookup/lookup改名干净,src 下无旧名残留;ADR 修订节、功能说明、CONTEXT.md、决策记录同步完整。非阻塞 nit(可顺手修,不影响合并)
tests/resourceReferenceInput.test.tsx:467,498,873三处测试桩写的是fuzzy_lookup(snake_case),类型上是fuzzyLookup。该属性在这些用例里不会被消费、测试目录也不在 typecheck 的 include 里,所以运行时与 CI 都发现不了;建议改成fuzzyLookup或删掉,避免误导后来者照着抄。已知取舍(文档已声明,认可)
hero v2与hero-v2归一化撞名只在解析侧兜住,菜单侧不做冲突检测。@hero v2不再解析(内容不丢、位置可能退化)。$名称按字面保留。上一版评审正文乱码,作废重发
评审结论:通过(Approve)
范围:
feat/smart-paste→master,引用输入区粘贴解析(@显示名/$名称原位重建 chip),15 个文件,+1257/-101。已验证
resourceReferences.test.ts/referenceSourceProviders.test.ts/resourceReferenceInput.test.tsx共 79 条通过。apps/ai-game-creator-shelltsc 通过;npm run check:encoding、git diff --check通过。preventDefault;Lexical 负载、纯文本、图片粘贴放行默认导入,ranSync排队场景的处理有注释说明。mentionTokenOrText一条兜底链贯穿粘贴插入 / 润色回写 / 初始草稿,消灭了「认不出的引用静默消失」的例外分支。normalizeMentionName把「引用名无空白」前移到名字产生处,token 层幂等再折一次;.env/.gitignore词干为空回退asset.id有回归用例。match改名fuzzyLookup/ 新增lookup干净,src 下无旧名残留;ADR 修订节、功能说明、CONTEXT.md、决策记录同步完整。非阻塞 nit(可顺手修,不影响合并)
tests/resourceReferenceInput.test.tsx:467,498,873三处测试桩写的是fuzzy_lookup(snake_case),类型上是fuzzyLookup。该属性在这些用例里不会被消费、测试目录也不在 typecheck 的 include 里,所以运行时与 CI 都发现不了;建议改成fuzzyLookup或删掉,避免误导后来者照着抄。已知取舍(文档已声明,认可)
hero v2与hero-v2归一化撞名只在解析侧兜住,菜单侧不做冲突检测。@hero v2不再解析(内容不丢、位置可能退化)。$名称按字面保留。fuzzy_lookupmethods from test ReferenceProviders for cleanup