修复:引用名内部空白统一折成 -,粘贴解析不再多插一枚芯片
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m21s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m53s
Project CI / Frontend tests (pull_request) Successful in 2m4s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m19s
Project CI / Native shell tests (pull_request) Successful in 5m13s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m46s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m16s

resourceReferences.ts 新增共享 normalizeMentionName:内部空白折 `-`、裁掉首尾,token 层幂等再折一次
素材显示名(resourceDisplayName)、Skill 名(目录读入与 toReference / mentionToken)、附件名(导入映射与 toReference / mentionToken)统一过这一份口径
resourceDisplayName 去掉 `|| asset.id` 兜底:引用名假定非空,不再回退 resourceId
前缀重叠 bug 由此收口:`@hero` 不再命中 `@hero-v2`(后一个字符不是空白),粘贴只剩正确的那一枚芯片
新增用例:素材 / Skill / 附件三类归一化口径、前缀重叠回归、Skill 目录名带空白、附件名带空白;把 normalizeMentionName 变异成恒等函数后新增用例全红
顺带修掉 referenceSourceProviders 用例里重命名遗留的 provider.match 断言与 skill provider 注释里的 match
同步 CONTEXT.md 术语、功能说明、实施计划与决策记录
This commit is contained in:
2026-09-23 10:40:21 +08:00
parent 072bf691d9
commit 5e36b61534
10 changed files with 184 additions and 19 deletions
@@ -41,3 +41,12 @@
已完成:`buildContentFromPastedText` 与规则矩阵单测(命中 / 未命中 / 相邻中文 / 扩展名 / 大小写 / 全角 / 同名歧义 / 重复出现 / 换行 / 与显示口径互为逆运算);provider 的 `fuzzyLookup` / `lookup` 及用例;输入区 `PASTE_COMMAND` 接管与 5 条集成用例(粘贴重建芯片、未命中保持字面、纯文本走默认导入、Lexical 负载让位、Skill 冷启动保持字面且敲过 `$` 后可解析);文档同步。
未做(本次范围外):斜杠命令 `/` 解析、拖拽文本(drop)、附件 / 运行画面区域 / 文件路径 / URL / 剪贴板图片的解析、复制侧 `text/plain` 形态调整、扩展安装卸载后的目录即时失效。
## 追加执行状态(2026-09-23):前缀重叠按「引用名无空白」收口
自动评审留下的唯一破坏性项(`@hero``@hero v2` 互为前缀时粘贴会多插一枚短名芯片)不改反解析,改为把不变量前移到引用名:
- `resourceReferences.ts` 新增共享 `normalizeMentionName(value)`(内部空白折 `-`、裁首尾),素材显示名(`resourceDisplayName`)、Skill 名(目录读入与 `toReference` / `mentionToken`)、附件名(导入映射与 `toReference` / `mentionToken`)与两个 token 投影(`chatReferenceMentionToken``directCodexContentToPromptText`)统一过这一份;token 层重复归一化是幂等的。
- 名称里不再做 `resourceId` 兜底(`resourceDisplayName` 去掉 `|| asset.id`);假定引用名非空。
- 代价与取舍:`hero v2``hero-v2` 归一化后同名时走既有的「同名多候选按文本保留」;改动前生成的旧文本里的 `@hero v2` 不再解析。
- 验证:`normalizeMentionName` / 显示名 / token 口径单测 + 前缀重叠回归用例(归一化后粘贴只剩正确的那一枚芯片)+ provider 两处用例;把 `normalizeMentionName` 变异成恒等后新增用例全红。
@@ -1,5 +1,15 @@
# 决策记录
## 2026-09-23 引用名不允许空白:素材 / Skill / 附件共用 `normalizeMentionName`
- 背景:自动评审发现 `buildContentFromTextTokens` 在前缀重叠时会多插一枚芯片——素材显示名 `hero``hero v2` 并存时,粘贴 `看 @hero v2 这一版` 得到 `[chip hero]` + `[chip hero-v2]`(短名先按 index 平局抢位,长名成了补到末尾的孤儿)。根因不是匹配算法,而是**引用名自己带空白**:token 的边界规则是「前后为空白或行首行尾」,`@hero␠``@hero v2` 内部也算一次合法命中。
- 决策:把不变量前移到引用名——`@显示名` / `$名称` / `@附件名` 的名字内部不允许空白,统一经共享 `normalizeMentionName(value)`(内部空白折成 `-`、裁掉首尾)处理。落点是名字的产生处:`resourceDisplayName()`、Skill 目录读入与 `toReference` / `mentionToken`、附件导入映射与 `toReference` / `mentionToken`,外加两个 token 投影(`chatReferenceMentionToken``directCodexContentToPromptText`)——token 层幂等再折一次,「token 里没有空白」就是不变量本身的性质,不依赖上游数据干净。
- 决策(不兜底):引用名假定非空,不做 `resourceId` 之类的兜底;`resourceDisplayName` 原来的 `|| asset.id` 一并去掉。
- 原因:不改反解析是因为粘贴解析与润色回包共用 `buildContentFromTextTokens`,改匹配算法要冒回归润色的风险;而「名字里带空白的 token」本来就无法手敲(候选触发器 `allowWhitespace: false`,空格处菜单就关),显示口径与输入口径早就不一致。折成 `-` 之后 token 自带边界:`@hero` 不会命中 `@hero-v2`(后一个字符是 `-`,不是空白),前缀重叠不可能再发生。
- 影响范围:`apps/ai-game-creator-shell/src/features/project-workspace/{resourceReferences.ts,reference-source/{skillReferenceProvider.ts,attachmentReferenceProvider.ts}}``apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directCodexTurnAttachments.ts``apps/ai-game-creator-shell/tests/{resourceReferences.test.ts,referenceSourceProviders.test.ts}``CONTEXT.md``docs/【功能说明】AGC聊天素材引用-2026-09-08.md``docs/project-memory/plans/【实施计划】引用粘贴解析-2026-09-22.md`
- 代价(已接受):归一化可能撞名(`hero v2``hero-v2` 同名),走既有的「同名多候选一律按文本保留」——不认错,但两者都成不了芯片;改动前生成的旧文本(历史回合 prompt、旧气泡)里的 `@hero v2` 不再解析,重试 / 润色回填时那条引用会退化成末尾孤儿(内容不丢、位置可能不对)。
- 验证方式:`normalizeMentionName``resourceDisplayName``chatReferenceMentionToken` 的口径单测;「空白折 `-` 后 token 自带边界、前缀重叠只剩正确芯片」的回归用例;Skill 目录名带空白与附件名带空白的 provider 用例;把 `normalizeMentionName` 变异成恒等函数后以上新增用例全部变红。另跑受影响用例、`npm run typecheck``npm run check:encoding``git diff --check`
## 2026-09-22 引用粘贴解析:只认显示口径的 token,宁可不成芯片也不能认错
- 背景:引用输入区的 `@` / `$` 只由 `LexicalTypeaheadMenuPlugin` 的逐字敲击触发,粘贴走 Lexical 默认路径(`text/plain` → 纯文本),所以从用户消息气泡复制回来的 `@显示名` / `$名称` 粘进来就是死文本;同时 chip 的 `text/plain` 是占位符,复制出去再粘回来必然丢引用(气泡显示文本才是完整 token 的形态)。
@@ -1,6 +1,6 @@
# AGC 聊天素材引用
更新时间:2026-09-22
更新时间:2026-09-23
AGC 聊天输入框支持以结构化引用标记当前项目已登记素材,并提供 Codex 风格的 Skill 提及。输入 `@` 会按素材名称、资源 ID 和类型过滤候选项;输入 `$` 会按当前 DirectProject 可用 Skill 名称过滤候选项;也可以点击输入框右侧的 `@` 按钮打开素材选择面板。
@@ -42,6 +42,15 @@ AGC 聊天输入框支持以结构化引用标记当前项目已登记素材,
- 候选来源按「模糊 / 精确」分两个口:菜单走 `fuzzyLookup(query)`(包含匹配 + 截断到候选上限),粘贴解析走 `lookup()`(就绪的全量候选,不模糊、不截断)。
- Skill 目录是应用级异步读取,仍然只在用户第一次敲出 `$` 时读:冷启动时粘贴 `$名称` 就按字面文本保留(粘贴不会为了解析去提前读盘,也不会等待目录),用户敲过一次 `$` 之后粘贴即可重建芯片。
## 引用名的空白不变量(2026-09-23)
引用的名字(素材显示名、Skill 名、附件名)同时就是它在正文里的 token,所以**名字内部不允许出现空白**:内部空白统一经共享的 `normalizeMentionName` 折成 `-``hero v2.png``@hero-v2``my skill``$my-skill`),首尾空白照旧裁掉。
- 归一化落在名字的产生处:`resourceDisplayName()`(素材显示名由文件名派生)、Skill 目录读入与 `toReference` / `mentionToken`、附件导入映射与 `toReference` / `mentionToken`、以及两个 token 投影(`chatReferenceMentionToken``directCodexContentToPromptText`)。token 层再折一次是幂等的,因此「token 里绝不会有空白」是这一层的性质,不依赖上游数据干净。
- 为什么必须有这条不变量:token 的边界规则是「前后为空白或行首行尾」。名字里带空白时,`@hero v2` 会被反解析切成 `@hero` + 文本 `v2`,短名字抢先命中,真正的引用反而被当成孤儿补到正文末尾(粘贴回填会多出一枚错芯片)。折成 `-` 之后 token 自带边界,`@hero` 不会命中 `@hero-v2`(后一个字符是 `-`)。
- 名字里不做 `resourceId` 之类的兜底:引用名假定非空。
- 已存在的老文本(本次改动前生成的回合 prompt、历史气泡)里的 `@hero v2` 不再解析——粘贴时按字面保留,重试 / 润色回填时该引用会退化成末尾孤儿。内容不丢,位置可能不对。
## 拖拽引用(2026-09-21
除了 `@` 输入与「引用」按钮,资源卡还支持**拖到对话**:在资源画布上按住一张卡拖到右侧 Agent 对话栏,松手即把这次拖动真正参与位移的那批素材整批 `@` 进输入框(框选多选后拖任意一张 = 整批引用;拖未选中的卡 = 只引用它自己)。