Skip to content

fix: 并发/重复调用导致目录卡出多个同名空壳《第二章》章卡 - #24

Merged
swjybky merged 1 commit into
swjybky:mainfrom
HXSLtim:fix/duplicate-chapter-card-creates
Oct 11, 2026
Merged

swjybky merged 1 commit into
swjybky:mainfrom
HXSLtim:fix/duplicate-chapter-card-creates

Conversation

@HXSLtim

@HXSLtim HXSLtim commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

问题

智能体一回合内重复(或并行子智能体同时)调用 create kind=chapter_card 时,目录里会一口气出现好几个重复的、空壳的同名章卡(例如多个《第二章》),全部是空正文。

根因(三层叠加)

  1. ID 永不冲突:allocateStableId 的种子是 ${bookId}:${runId}:${toolCallId}(long-agent-tools/shared.ts)。每次重复调用 toolCallId 都不同 → 每次都分配到新 ID,assertNewEntityId 的 already_exists 防线永远触发不了。
  2. 同 run 提案视图盲区:createProposalOverlay().remember()(long-agent-tools/proposal-overlay.ts)只记录 storyPlot.create 和两类 continuity 创建,不记录 chapter.create。于是同一 run 里第二次创建章卡时,loadIndex 返回的索引看不到第一次的待审章卡——预检通过、排序照常 +1,又形成一张新提案卡。父/子智能体共享 overlay 也一样中招。
  3. 校验层只查 ID 不查标题:apply-chapter.ts 的 chapter.create / chapter.update 只做 ID 唯一性校验,同卷同名完全放行。

执行层面 create 工具本身是 executionMode: "sequential"(且批次内任一 sequential 工具会使整批串行),所以"失控"并不是真并行 race,而是模型一回合重复发相同调用 + 上述三层缺口让每次重复都成功落卡。这与仓库既有约定一致——workspace.ts 提示词已要求"创建前必须排除目录中已经存在的同名章节",短篇通道 accept 时也有同名检查(draft-section-lane.ts),唯独长篇章卡路径漏了。

修复(两层)

contracts(apply-chapter.ts):chapter.create 与改名 chapter.update 增加同卷同名校验,冲突抛 already_exists。预检(previewLongWorkspaceOperations)与落盘(Core apply)共用这段逻辑,因此智能体工具预检、界面结构变更、Core 应用三条路径都被守住。

pi-runtime-adapter(proposal-overlay.ts):overlay 开始记录 chapter.create 并把它投影进同 run 的索引视图(plot.chapterCards + chapters)。同一 run 内(含父子智能体共享状态)的重复创建会在预检阶段立刻得到既有的友好提示:"未形成长篇结构变更提案:目标条目已经存在(already_exists)……不要重复提交相同参数",不再生成第二张提案卡。

行为边界

  • 同卷同名 → 拒绝(already_exists)
  • 同名但不同卷 → 仍允许
  • 同卷不同名 → 不受影响
  • 重命名成同卷已有标题 → 拒绝;保留原标题/改成新标题 → 不受影响

测试

  • 新增 long-agent-tools.duplicate-create.test.ts:同 run 重复创建被拒(含父子智能体共享 overlay 场景)、不同标题正常创建。
  • 新增 long-workspace-operations.chapter-title-uniqueness.test.ts:contracts 层同卷同名 create 拒绝、跨卷同名允许、重命名冲突拒绝/改新名允许。
  • 全量回归:contracts 57 文件 / 368 用例、pi-runtime-adapter 75 文件 / 521 用例、desktop 长篇 store 相关测试全部通过;pnpm typecheck、pnpm lint(含边界检查、i18n)、pnpm format:check 均通过。

A runaway agent turn (repeated parallel create calls or parent/child
subagents creating the same chapter) could stack several empty
same-named chapter cards in the manuscript directory, because:

- allocateStableId seeds ids per tool call, so every duplicate create
  got a fresh id and never collided;
- the same-run proposal overlay did not remember chapter.create, so
  preflight and id allocation never saw the pending chapter card;
- chapter.create/chapter.update only validated id uniqueness, never
  title uniqueness within a volume.

Fix in two layers:

- contracts: applyChapterOperation now rejects chapter.create and
  title-renaming chapter.update when the same volume already holds a
  chapter card with that title (already_exists). This guards every
  path: agent tool preflight, renderer structure mutations and the
  Core apply side.
- pi-runtime-adapter: the proposal overlay now remembers chapter.create
  operations and projects them into the same-run index view, so a
  repeated create inside one run (including across parent and child
  agents sharing the run state) fails preflight immediately with the
  existing friendly already_exists guidance instead of forming another
  proposal card.

Titles stay free across volumes, and distinct titles in one volume are
unaffected.
@swjybky
swjybky merged commit 86eddf0 into swjybky:main Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants