跳转至

Data | 序列图(精选时序)

配合architecture-diagrams.md 看整体架构。 这里:聚焦"几个关键流程的时序"。


1. 完整 LLM 工具调用时序

sequenceDiagram
    autonumber
    actor U as User
    participant PI as PromptInput
    participant R as REPL
    participant S as AppState
    participant Q as query()
    participant QE as QueryEngine
    participant CTX as ToolUseContext
    participant PERM as canUseTool
    participant T as Tool (e.g. BashTool)
    participant API as Anthropic API

    U->>PI: 输入 "run npm test"
    PI->>R: onSubmit(text)
    R->>S: setState({ messages: [...prev, userMsg] })
    S-->>R: 通知 listeners
    R->>Q: for await (event of query(...))

    Q->>API: POST messages.stream
    API-->>Q: content_block_delta (text)
    Q-->>R: yield StreamEvent
    R->>S: 更新 assistantMessage
    S-->>R: re-render

    API-->>Q: content_block_start (tool_use)
    Q->>QE: record tool use

    API-->>Q: content_block_stop
    API-->>Q: message_stop (finish_reason: tool_use)
    Q->>Q: 准备执行工具

    Q->>PERM: canUseTool(tool, input, ctx)
    PERM->>CTX: checkPermission
    CTX-->>PERM: 'ask'
    PERM-->>Q: { behavior: 'ask' }
    Q-->>R: yield { type: 'permission_request' }

    R->>U: 弹 PermissionRequest
    U->>R: 批准
    R-->>Q: 继续

    Q->>T: tool.call(input, ctx)
    T->>T: validate(input)
    T->>T: checkPermission
    T-->>Q: yield { type: 'start' }

    Q-->>R: yield { type: 'tool_use' }
    R->>S: 追加 tool_use message
    S-->>R: re-render

    T->>T: spawn child_process
    loop 进程运行
        T-->>Q: yield { type: 'stdout', data }
        Q-->>R: 透传
        R->>S: 追加 progress message
    end

    T-->>Q: return ToolResult(content, exitCode)
    Q->>S: 追加 tool_result message
    Q->>Q: 循环继续

    Q->>API: POST messages.stream (第二轮)
    Note over Q,API: 现在 messages 包含<br/>user + assistant(tool_use) + tool_result

    API-->>Q: content_block_delta
    API-->>Q: message_stop (end_turn)
    Q-->>R: 循环结束

2. 启动期 prefetch 优化时序

sequenceDiagram
    autonumber
    participant Bun as Bun Runtime
    participant M as main.tsx
    participant Profiler
    participant MDM as startMdmRawRead
    participant KC as startKeychainPrefetch
    participant Init as init.ts
    participant Repl as replLauncher

    Bun->>M: 加载 main.tsx

    Note over M: 头部 18 行:<br/>import + 立即调用

    M->>Profiler: profileCheckpoint('main_tsx_entry')
    M->>MDM: startMdmRawRead()
    activate MDM
    MDM-->>MDM: spawn plutil/reg query
    Note right of MDM: 并行跑 ~135ms 导入时间

    M->>KC: startKeychainPrefetch()
    activate KC
    KC-->>KC: 读 macOS keychain
    Note right of KC: 并行跑 ~65ms

    M->>M: import 其余 100+ 模块
    Note over M: ~135ms 同步导入

    M->>Init: init()
    activate Init
    Init->>MDM: 等 plutil 完成
    deactivate MDM
    Init->>KC: 等 keychain 完成
    deactivate KC
    Init->>Init: 加载 settings + MCP
    Init-->>M: init() 完成
    deactivate Init

    M->>Repl: launchRepl()
    Repl->>Repl: <REPL /> mount
    Repl-->>Bun: 渲染第一帧

3. 异步权限检查时序

sequenceDiagram
    autonumber
    participant Q as query()
    participant CTX as ToolUseContext
    participant Rule as matchingRuleForInput
    participant R as REPL
    actor U as User
    participant Setting as ~/.claude/settings.json

    Q->>CTX: tool call
    CTX->>Rule: matchingRuleForInput(toolName, input)
    Rule->>Setting: read permissions
    Setting-->>Rule: { allow: [...], deny: [...], ask: [...] }
    Rule-->>CTX: PermissionRule

    alt rule = allow
        CTX-->>Q: continue
    else rule = deny
        CTX-->>Q: denial ToolResult
    else rule = ask or no rule
        CTX->>R: yield { type: 'permission_request' }
        R->>U: 弹 PermissionRequest Dialog

        alt 用户批准
            U->>R: 按 Enter
            R->>CTX: continue
        else 用户拒绝
            U->>R: 按 Esc
            R->>CTX: denial
        else 用户更新规则
            U->>R: "always allow this"
            R->>Setting: saveGlobalConfig({ allow: [...prev, newRule] })
            R->>CTX: continue
        end
    end

4. 上下文压缩时序

sequenceDiagram
    autonumber
    participant Q as query()
    participant Calc as calculateTokenWarningState
    participant AC as autoCompact
    participant Group as grouping
    participant LLM as LLM (small)
    participant S as AppState
    participant CC as postCompactCleanup

    Q->>Q: 累计 messages 的 token
    Q->>Calc: calculateTokenWarningState(total, max)
    Calc-->>Q: 'ok' / 'warning' / 'critical' / 'auto_compact'

    alt state != 'auto_compact'
        Q->>Q: 继续正常流程
    else state = 'auto_compact'
        Q->>AC: runAutoCompact(messages)
        AC->>Group: groupMessagesForCompaction(messages)

        Group->>Group: 按时间分组
        Group->>Group: 评分(user-related, recent, tool-heavy)
        Group-->>AC: [{ messages, priority, tokens }]

        AC->>AC: 选低分组的 N 个
        AC->>LLM: 摘要(用 haiku 小模型)
        LLM-->>AC: summary

        AC->>AC: buildPostCompactMessages(origMsgs, summary)
        Note over AC: 加 compact_boundary 标记
        AC-->>Q: newMessages

        Q->>S: setState({ messages: newMessages })
        S-->>Q: 通知 listeners
        S->>CC: postCompactCleanup (onChange hook)
        CC->>CC: 清 fileStateCache
        CC->>CC: 清 toolResultCache
        CC->>CC: 清理 orphan attachments

        Q->>Q: 继续下一轮 LLM 调用
    end

5. MCP 工具调用时序

sequenceDiagram
    autonumber
    actor U as User
    participant R as REPL
    participant T as MCPTool
    participant Mgr as MCPConnectionManager
    participant Conn as MCPConnection
    participant T2 as Transport (stdio/HTTP/InProcess)
    participant Server as External MCP Server

    U->>R: "在 GitHub 创建 issue"
    R->>T: mcp__github__create_issue
    T->>T: 拆解 { server: 'github', tool: 'create_issue' }
    T->>Mgr: callTool('github', 'create_issue', input)

    Mgr->>Conn: get('github')
    Conn-->>Mgr: MCPConnection

    alt transport = InProcess
        Conn->>T2: request('tools/call', { name, arguments })
        T2->>Server: 直接调方法
        Server-->>T2: ToolResult
    else transport = stdio
        Conn->>T2: 写 JSON-RPC 到 stdin
        T2->>Server: spawn 进程
        Server-->>T2: 写 JSON-RPC 到 stdout
    else transport = HTTP+SSE
        Conn->>T2: POST /tools/call
        T2->>Server: HTTP request
        Server-->>T2: SSE 响应
    end

    T2-->>Conn: ToolResult
    Conn-->>Mgr: ToolResult
    Mgr-->>T: ToolResult
    T-->>R: yield ToolResult

6. 主题切换时序

sequenceDiagram
    autonumber
    actor U as User
    participant R as REPL
    participant TP as ThemeProvider
    participant G as GlobalConfig
    participant Files as ~/.claude/settings.json

    U->>R: /config → 选主题
    R->>TP: setPreviewTheme('light')
    TP->>TP: ThemeContext.themeSetting = 'light'
    TP-->>R: re-render with 'light'
    R->>U: 显示新主题

    alt 用户确认
        U->>R: Enter 确认
        R->>TP: savePreview()
        TP->>G: saveGlobalConfig({ theme: 'light' })
        G->>Files: 写文件
        Files-->>G: ok
    else 用户取消
        U->>R: Esc 取消
        R->>TP: cancelPreview()
        TP->>TP: themeSetting = 原值
    end

7. 错误重试时序

sequenceDiagram
    autonumber
    participant Q as query()
    participant WR as withRetry
    participant API as Anthropic API
    participant Cat as categorizeRetryableAPIError

    Q->>WR: withRetry(async () => streamApi(...))
    WR->>API: POST messages.stream
    API-->>WR: 429 Too Many Requests

    WR->>Cat: categorizeRetryableAPIError(err)
    Cat-->>WR: RetryableError(429, 'rate_limited')

    alt 重试次数 < MAX
        WR->>WR: sleep(backoff(attempt))
        Note over WR: 指数退避<br/>attempt 1: 1s<br/>attempt 2: 2s<br/>attempt 3: 4s
        WR->>API: POST messages.stream (重试)
    else 重试次数 >= MAX
        WR-->>Q: throw MaxRetriesExceeded
    end

    API-->>WR: 200 OK + stream
    WR-->>Q: AsyncGenerator<StreamEvent>

8. BashTool 安全检查时序

sequenceDiagram
    autonumber
    actor U as User
    participant R as REPL
    participant T as BashTool
    participant CS as commandSemantics
    participant PV as pathValidation
    participant BS as bashSecurity
    participant DCW as destructiveCommandWarning
    participant SUS as shouldUseSandbox
    participant PR as PermissionRequest
    participant OS as OS (sandbox-exec / bwrap)

    U->>R: "rm -rf /tmp/old.log"
    R->>T: call(input={ command: 'rm -rf /tmp/old.log' }, ctx)

    T->>CS: classifyCommand
    CS-->>T: { hasSideEffects: true, writesFiles: true }

    T->>PV: validatePath('/tmp/old.log', ctx)
    PV-->>T: ok

    T->>BS: checkCommandSecurity
    BS->>BS: 解析 AST
    BS-->>T: { safe: false, reason: 'rm -rf' }

    T->>DCW: getDestructiveWarning
    DCW-->>T: { severity: 'medium' }

    T->>SUS: shouldUseSandbox
    SUS-->>T: true

    T-->>R: yield { type: 'permission_request' }
    R->>U: 弹 PermissionRequest (含警告)
    U->>R: 批准
    R-->>T: continue

    T->>OS: spawn('rm -rf /tmp/old.log', { sandbox: profile })
    activate OS
    OS-->>T: stdout/stderr
    T-->>R: yield { type: 'stdout', data }
    R->>U: 实时显示
    OS-->>T: exit code 0
    deactivate OS
    T-->>R: return ToolResult

9. 多 Agent 协调时序

sequenceDiagram
    autonumber
    actor U as User
    participant L as Leader
    participant Coord as coordinator
    participant T1 as Teammate1
    participant T2 as Teammate2
    participant M as Mailbox

    U->>L: "让 2 个 agent 并行做 X 和 Y"
    L->>Coord: spawn(team=[T1, T2])
    activate Coord
    Coord->>T1: sendMessage(do X)
    activate T1
    Coord->>T2: sendMessage(do Y)
    activate T2

    par T1
        T1->>M: post progress
    and T2
        T2->>M: post progress
    end

    L->>M: poll inbox
    M-->>L: [progress1, progress2, ...]

    alt T1 完成
        T1-->>Coord: done
        Coord->>M: notify
    end

    L->>M: poll inbox
    M-->>L: [T1 done, T2 progress]

    T2-->>Coord: done
    deactivate T2
    Coord->>M: notify
    deactivate Coord
    deactivate T1

    L->>M: poll inbox
    M-->>L: [T1 done, T2 done]
    L->>U: 合并结果

10. REPL 状态订阅时序

sequenceDiagram
    autonumber
    participant C as Component
    participant USES as useSyncExternalStore
    participant Store as AppState store
    participant OnChg as onChange hook
    participant Disk as ~/.claude/

    C->>USES: useAppState(selector)
    USES->>Store: subscribe(listener)
    USES->>Store: getState()
    Store-->>USES: currentState
    USES-->>C: selectedState

    Note over C,Disk: 后续 setState

    Store->>Store: setState(updater)
    Store->>OnChg: onChange({ new, old })
    OnChg->>Disk: saveGlobalConfig(new)
    Store->>USES: notify listeners
    USES->>Store: getState()
    Store-->>USES: newState
    USES-->>C: newSelectedState (if changed)
    C->>C: re-render

关键洞察

1. 时序图暴露"系统骨架"

看时序图 = 看谁先谁后,比看代码快 10 倍

2. 关键模式:"yield 透传 + 拦截"

所有跨层通信都是"上层 yield,下层 for-await 透传"。

3. 错误处理的统一性

不管什么错误,都先分类categorizeRetryableAPIError 风格),再决定重试 / 跳过 / 抛

4. 权限决策的"显式"

权限检查是显式函数调用canUseTool),不是装饰器/注解。
便于测试和审计