REPL 查询管线深度分析

涵盖 QueryGuard 状态机、四个核心函数的职责划分、isActive 语义、dispatching 存在的必要性


一、QueryGuard 状态机

1.1 状态定义

arduino 复制代码
idle         → 空闲,无查询执行
dispatching  → 已预约槽位,异步处理输入中(图片 resize、附件提取、bash/slash 命令执行)
running      → 查询正在执行中(API 调用 + 工具循环)

1.2 状态转换全集

scss 复制代码
idle ────────────────── tryStart() ─────────────────► running   (直接提交,无队列)
idle ────────────────── reserve() ──────────────────► dispatching  (经队列处理器)
dispatching ─────────── tryStart() ─────────────────► running   (输入处理完,确认要调 API)
dispatching ─────────── cancelReservation() ────────► idle      (本地命令,不需要调 API)
dispatching ─────────── forceEnd() ─────────────────► idle      (用户取消)
running ─────────────── end(gen) ───────────────────► idle      (查询正常完成)
running ─────────────── forceEnd() ─────────────────► idle      (用户取消,递增加法)
running ─────────────── tryStart() ─────────────────► null      (并发,拒绝)

1.3 各场景的时序

场景一:正常提交------队列空闲时用户按回车

scss 复制代码
onSubmit → handlePromptSubmit → executeUserInput
  reserve()                       idle → dispatching
  processUserInput()              [await 图片resize、附件...]
    └─ 此间隙 isActive=true,并发提交入队
  onQuery()
    tryStart()                    dispatching → running
    onQueryImpl()
      for await...of query()      [await 流式响应]
    end(gen)                      running → idle
  finally → cancelReservation()   空操作(已是 idle)

场景二:本地斜杠命令(/model、/theme)

scss 复制代码
onSubmit → executeUserInput
  reserve()                       idle → dispatching
  processSlashCommand()
    [await] 模型列表、UI 渲染...
  返回 shouldQuery=false, 无消息
  cancelReservation()             dispatching → idle
  setToolJSX / clearToolJSX

场景三:用户取消

scss 复制代码
onCancel()
  forceEnd()                      running → idle
  ++generation                    ← 废弃当前查询
  resetLoadingState()

  ── 被取消的 onQuery finally ──
  end(staleGeneration)            ← 返回 false(代数不匹配),跳过清理

场景四:并发提交

scss 复制代码
用户第一次提交 → reserve() → dispatching
                   ↓
用户第二次提交 → isActive=true → enqueue(),返回
                   ↓
第一次提交 → tryStart() → running → end() → idle
                   ↓
useQueueProcessor → isActive=false → dequeue → 第二次提交执行

二、四个核心函数的职责划分

2.1 handlePromptSubmit --- 编排器

文件 : utils/handlePromptSubmit.ts

makefile 复制代码
输入: 用户原始字符串 + 元数据(mode、pastedContents...)
输出: 无(副作用)
本质: 编排层,不做消息转换也不做 API 调用

职责顺序:

scss 复制代码
1. 路径选择:
   ├─ queuedCommands 已存在 → 直接 executeUserInput
   └─ 直接输入:
        a. 过滤已删除引用的图片
        b. 检查空输入 / exit / quit
        c. 展开 [Pasted text #N]
        d. 检查 immediate 命令 → 执行 JSX
        e. 检查 isActive → 入队
        f. 构造 QueuedCommand → executeUserInput

2. executeUserInput(内部函数):
   a. queryGuard.reserve()
   b. AsyncLocalStorage 上下文(workload 传播)
   c. forEach command → processUserInput()
   d. 文件历史快照
   e. 有消息 → onQuery();无消息 → cancelReservation()
   f. 处理 nextInput 链式调用

3. finally:cancelReservation() + 清除 placeholder

2.2 processUserInput --- 转换器

文件 : utils/processUserInput/processUserInput.ts

makefile 复制代码
输入: 已展开的字符串 + 上下文
输出: { messages: Message[], shouldQuery, allowedTools, ... }
本质: 用户输入 → Message[] 的转换

职责顺序:

scss 复制代码
1. 显示 placeholder

2. processUserInputBase:
   a. 图片处理(content block 的图片→resize,粘贴的图片→并行 resize)
   b. bridgeOrigin 安全命令检查
   c. Ultraplan 关键词检测
   d. 附件提取(IDE 选择、todos、diff)
   e. 模式分发:
      ├─ bash → processBashCommand()
      ├─ /xxx → processSlashCommand()
      └─ 纯文本 → processTextPrompt()
   f. 返回消息数组 + 控制参数

3. UserPromptSubmit hooks:
   for await...of executeUserPromptSubmitHooks()
   ├─ blockingError → 阻止查询
   ├─ preventContinuation → 阻止查询
   ├─ additionalContexts → 追加附件
   └─ hook_success → 追加结果消息

2.3 onQuery --- 控制器

文件 : REPL.tsx:2855

makefile 复制代码
输入: Message[](已转换好的消息)
输出: 无(副作用)
本质: 并发安全外壳 + 生命周期管理

职责:

scss 复制代码
1. queryGuard.tryStart()
   ├─ 成功 → 继续
   └─ 失败(并发)→ 入队,返回

2. try:
   a. 重置计时器、追加消息、清空流式状态
   b. mrOnBeforeQuery() → onBeforeQuery()
   c. onQueryImpl()

3. finally(代数匹配时):
   a. resetLoadingState()
   b. mrOnTurnComplete()
   c. 通知 bridge 客户端
   d. 捕获 API 指标 → turn 耗时消息
   e. 自动恢复(取消后无有意义响应时回滚)

2.4 query --- 引擎

文件 : query.ts:219async generator

makefile 复制代码
输入: 完整消息数组 + 系统提示 + 上下文
输出: AsyncGenerator<StreamEvent | Message | ..., Terminal>
本质: API 调用 + 工具执行循环

职责queryLoop 的每次迭代):

scss 复制代码
1. yield stream_request_start

2. 上下文压缩链:
   a. applyToolResultBudget()
   b. snipCompactIfNeeded()
   c. microcompact()
   d. contextCollapse 投影
   e. autoCompact()

3. API 调用:
   a. fetchStream() → SSE
   b. yield 每个事件

4. 工具循环:
   a. collectToolResults()
   b. runTools() → 执行工具
   c. continue → 回到步骤 1
   d. terminal → return

5. 反采样 hooks

6. 继续判定 → return Terminal

2.5 对比总结

维度 handlePromptSubmit processUserInput onQuery query
本质 编排器 转换器 控制器 引擎
是否操作 DOM/UI 是(清输入框、弹通知) 是(placeholder) 是(setMessages、spinner)
是否调 API 否(hook 除外)
有无 await 有(processUserInput) 有(resize、bash) 有(onQueryImpl) 有(SSE、工具)
异常处理 finally → cancelReservation 错误阻断返回 generation 隔离 + finally error → retry / return
产出 副作用 Message[] void(副作用) StreamEvent[]

三、isActive 语义详解

3.1 定义

ts 复制代码
get isActive(): boolean {
  return this._status !== 'idle'
}

3.2 真值表

_status isActive 含义
idle false 可安全出队执行新查询
dispatching true 正在处理输入(异步),不要并发
running true 正在执行查询,不要并发

3.3 所有读取点

位置 判断目的 期望值
handlePromptSubmit.ts:251 并发守卫:已有查询时入队 true→入队
handlePromptSubmit.ts:313 同上(下游路径) true→入队
REPL.tsx:904 useSyncExternalStore 订阅驱动 UI ---
REPL.tsx:3010 取消后自动恢复 必须是 false
REPL.tsx:3184 是否将命令视为 immediate true 才允许
REPL.tsx:3999 回调守卫 true→跳过
REPL.tsx:4868,4873 Ultraplan 延迟写入 false→写入
useQueueProcessor.ts:49 队列等待 false→出队

3.4 在 UI 层的体现

REPL.tsx:904 通过 useSyncExternalStore 订阅:

tsx 复制代码
const isQueryActive = React.useSyncExternalStore(
  queryGuard.subscribe,
  queryGuard.getSnapshot,
);
const isLoading = isQueryActive || isExternalLoading;

isLoading 直接驱动 <SpinnerWithVerb /> 的显示/隐藏。


四、为什么需要 dispatching 状态?

4.1 核心矛盾

processUserInput 内部有 await 调用 (图片 resize、bash 执行、附件提取),且执行完后不确定是否真的需要调 API (本地命令 /model 就不需要)。

4.2 只有 idle + running 的替代方案及其缺陷

方案 A:不保留,processUserInput 之后才 tryStart()

vbnet 复制代码
executeUserInput:
  processUserInput()           // [await] 没有任何并发保护!
    └─ 图片resize期间用户又提交一次
       → 第二个 processUserInput 并发执行!

缺陷 : processUserInput 内的所有 await 都是裸奔的,并发提交会导致消息乱序或状态错乱。

方案 B:processUserInput 之前就 tryStart()

csharp 复制代码
executeUserInput:
  tryStart()                 // idle → running, gen++
  processUserInput()         // 可能返回 shouldQuery=false
  发现是 /model
  end()                      // running → idle

缺陷 1 --- 语义混淆running 意味着"执行查询中",/model 只是本地 UI 从未调 API,两者不应共享同一状态。

缺陷 2 --- 代数污染tryStart() 递增 _generationend(gen) 依赖代数匹配做取消安全:

scss 复制代码
gen=1  正常查询 A → tryStart() → running → [处理中]
gen=2  /model → tryStart() → running → end(2) → idle
gen=3  正常查询 B → tryStart() → running → [用户取消]
gen=4  用户重新提交 C → tryStart() → running
查询 B 的 stale finally → end(3) → 代数不匹配 → 安全跳过 ✓

每次 /model /theme 都跑一遍 tryStart→end代数被快速消耗,竞态保护的信号噪声比下降。极端情况:一小时内运行 50 次本地命令 → gen 跳到 52,但实际真正需要保护的查询只有几次。

缺陷 3 --- forceEnd 副作用forceEnd() 必须 ++generation。如果本地命令走了 end() 正常释放路径还好,但如果有取消操作必须 forceEnd,每次都会污染代数。

4.3 dispatching 方案的优势

scss 复制代码
        reserve()                    tryStart()
idle ──────────────► dispatching ──────────────► running ──end()──► idle
   ▲                     │                          ↑
   │   cancelReservation │   语义: "在输入处理中"     │
   │   ◄─────────────────┘                           │
   │   不涉及代数                                    │
   │   没有副作用                                    │
   │                                                │
   │   forceEnd() ──── 涉及代数递增 ────────────────┘
特性 reserve() / cancelReservation() tryStart() / end()
涉及 generation
可以被 forceEnd() 清除
清除后是否需要代数匹配
语义 "我在处理输入" "我在执行查询"
典型路径 /model、/theme、/compact 正常对话、tool 循环

dispatching 是一个轻量级预约状态

  • 只负责在异步输入处理期间挡住并发
  • 不需要代数保护
  • 可以无副作用地通过 cancelReservation() 释放
  • 使用成本极低

running 是重量级执行状态

  • 需要代数机制处理取消+重新提交的竞态
  • end() 通过代数匹配确保只有当前查询能执行清理
  • forceEnd() 通过递增代数"废弃"旧查询的 pending finally 块

两者职责不同,分开设计的核心原因就是:"我在处理输入"和"我在执行查询"是两件完全不同的事,不应该共享同一个状态。

相关推荐
解决问题2 小时前
REPL.tsx 源码逐行分析
claude
青山如墨雨如画3 小时前
【Claude】Win11电脑下VSCode环境中Claude+Deepseek的报错及解决方法记录日志
vscode·aigc·claude·authropic
云安全助手14 小时前
2026年企业级Claude中转服务深度评测:安全、稳定与速度的终极答案
人工智能·安全·claude·ai大模型
yaocheng的ai分身15 小时前
【转载】Scaling Managed Agents:将“大脑”和“手”解耦
claude
xcLeigh16 小时前
聚合AI工具KULAAI:GPT、Claude、Gemini、DeepSeek热门模型一键使用
人工智能·gpt·claude·gemini·deepseek·聚合ai·kulaai
DO_Community18 小时前
为AI编程降本!OpenCode 原生支持 DigitalOcean 推理路由器
智能路由器·ai编程·claude
Bigger19 小时前
mini-cc 的 MCP 协议:给 AI 装个 USB-C 接口
人工智能·ai编程·claude
ZzT19 小时前
Claude Code 把编排写进代码:Dynamic Workflows 详解
claude
创世宇图20 小时前
Claude Opus 4.8 深度实测:动态多 Agent 协同、Effort Control 与幻觉抑制的工程化解析
ai·llm·agent·claude·ai工程化