跳转至

Deep Dive | src/utils/attachments.ts 3997 行 — 上下文附件编排中心

重要性:⭐⭐⭐⭐(每次 LLM 调用前的上下文拼装中心——17+ 种附件类型并行组装) 真实位置src/utils/attachments.ts3997 行角色:在每次用户输入 / 工具调用时,并行组装 17+ 种"上下文附件"(@提及文件 / 计划模式 / 待办提醒 / MCP 指令 / nested memory / teammate 邮箱 等),拼成完整 prompt 关联topics/deep-dive-query-engine.md(消息循环入口)、topics/deep-dive-claude-api.md(最终 API 请求)


1. 文件全景

attachments.ts (3997 行, 64 函数, 17+ export type)
├── 行 1-220    :imports + 4 个 feature-gated 懒加载
├── 行 220-310  :6 个 attachment config 常量
│   ├── TODO_REMINDER_CONFIG
│   ├── PLAN_MODE_ATTACHMENT_CONFIG
│   ├── AUTO_MODE_ATTACHMENT_CONFIG
│   ├── RELEVANT_MEMORIES_CONFIG
│   └── VERIFY_PLAN_REMINDER_CONFIG
├── 行 295-740  :17+ Attachment 类型定义
│   ├── FileAttachment, CompactFileReferenceAttachment, PDFReferenceAttachment
│   ├── AlreadyReadFileAttachment, AgentMentionAttachment
│   ├── Hook* (6 变体)
│   ├── TeammateMailboxAttachment, TeamContextAttachment
│   └── Attachment = union of all above
├── 行 743-1005 :**getAttachments() 主函数**(~260 行)— 编排 20+ 种附件
├── 行 1005-1045:maybe() 辅助 — 单个附件失败不阻断
├── 行 1046-1100:getQueuedCommandAttachments + getAgentPendingMessageAttachments
├── 行 1103-1240:buildImageContentBlocks + getPlanMode* 系列
├── 行 1275-1410:getAutoMode* 系列
├── 行 1415-1490:getDateChangeAttachments + getUltrathinkEffortAttachment + getDeferredToolsDeltaAttachment
├── 行 1490-1590:getAgentListingDeltaAttachment + getMcpInstructionsDeltaAttachment
├── 行 1587-1700:getCriticalSystemReminderAttachment + getOutputStyleAttachment + getSelectedLinesFromIDE
├── 行 1656-1990:getDirectoriesToProcess + isInstructionsMemoryType + getNestedMemory* + getMemory*
├── 行 1990-2400:processAtMentionedFiles + processMcpResourceAttachments + processAgentMentions
├── 行 2400-2900:getChangedFiles + getDynamicSkill + getSkillListing + getSkill* 系列
├── 行 2900-3300:getTeammateMailboxAttachments + getTeamContextAttachment + getCompactionReminder
└── 行 3300-3997:getContextEfficiency + getCompanionIntro + ~30 个其他 helpers

核心洞察getAttachments() 是单一入口——所有附件在这里被"并行组装"。


2. 17+ Attachment 类型(行 295-740)

export type FileAttachment = { ... }
export type CompactFileReferenceAttachment = { ... }
export type PDFReferenceAttachment = { ... }
export type AlreadyReadFileAttachment = { ... }
export type AgentMentionAttachment = { ... }
export type AsyncHookResponseAttachment = { ... }
export type HookAttachment = { ... }
export type HookPermissionDecisionAttachment = { ... }
export type HookSystemMessageAttachment = { ... }
export type HookCancelledAttachment = { ... }
export type HookErrorDuringExecutionAttachment = { ... }
export type HookSuccessAttachment = { ... }
export type HookNonBlockingErrorAttachment = { ... }
export type Attachment = FileAttachment | ... | HookNonBlockingErrorAttachment
export type TeammateMailboxAttachment = { ... }
export type TeamContextAttachment = { ... }

17+ 种附件类型——覆盖所有需要在 LLM prompt 之前插入的"上下文块":

类别 附件类型
文件 FileAttachment, CompactFileReferenceAttachment, PDFReferenceAttachment, AlreadyReadFileAttachment
@ 提及 AgentMentionAttachment
Hooks Hook* (6 变体)
团队 TeammateMailboxAttachment, TeamContextAttachment

Attachment 是 union——所有类型可统一处理。


3. 4 个 Feature-gated 懒加载(行 95-220)

const skillSearchModules = feature('EXPERIMENTAL_SKILL_SEARCH') ? require(...) : null;
const autoModeStateModule = feature('TRANSCRIPT_CLASSIFIER') ? require(...) : null;
const sessionTranscriptModule = feature('KAIROS') ? require(...) : null;

4 个懒加载模块: - EXPERIMENTAL_SKILL_SEARCH —— 实验性 skill 搜索 - TRANSCRIPT_CLASSIFIER —— auto mode 分类器 - KAIROS —— assistant 模式 - ANT-ONLY 段在各处散布


4. getAttachments() 主函数(行 743-1005)

这是整个文件的"心脏"——260 行,编排 20+ 种附件

4.1 4 步流程

export async function getAttachments(
  input: string | null,
  toolUseContext: ToolUseContext,
  ideSelection: IDESelection | null,
  queuedCommands: QueuedCommand[],
  messages?: Message[],
  querySource?: QuerySource,
  options?: { skipSkillDiscovery?: boolean },
): Promise<Attachment[]> {
  // 1. early return if disabled
  if (CLAUDE_CODE_DISABLE_ATTACHMENTS || CLAUDE_CODE_SIMPLE) {
    return getQueuedCommandAttachments(queuedCommands);
  }

  // 2. setup 1s abort controller
  const abortController = createAbortController();
  const timeoutId = setTimeout(ac => ac.abort(), 1000, abortController);
  const context = { ...toolUseContext, abortController };

  // 3. process user-input attachments (4 个)
  const userInputAttachments = input ? [
    maybe('at_mentioned_files', () => processAtMentionedFiles(input, context)),
    maybe('mcp_resources', () => processMcpResourceAttachments(input, context)),
    maybe('agent_mentions', () => processAgentMentions(input, ...)),
    // skill_discovery (feature-gated)
  ] : [];
  const userAttachmentResults = await Promise.all(userInputAttachments);

  // 4. process thread-level attachments (20+ 个) in parallel
  const allThreadAttachments = [
    maybe('queued_commands', () => getQueuedCommandAttachments(queuedCommands)),
    maybe('date_change', () => getDateChangeAttachments(messages)),
    maybe('ultrathink_effort', () => getUltrathinkEffortAttachment(input)),
    maybe('deferred_tools_delta', () => ...),
    maybe('agent_listing_delta', () => ...),
    maybe('mcp_instructions_delta', () => ...),
    // companion_intro (BUDDY)
    maybe('changed_files', () => getChangedFiles(context)),
    maybe('nested_memory', () => getNestedMemoryAttachments(context)),
    maybe('dynamic_skill', () => getDynamicSkillAttachments(context)),
    maybe('skill_listing', () => getSkillListingAttachments(context)),
    maybe('plan_mode', () => getPlanModeAttachments(messages, toolUseContext)),
    maybe('plan_mode_exit', () => getPlanModeExitAttachment(toolUseContext)),
    // auto_mode (TRANSCRIPT_CLASSIFIER)
    maybe('todo_reminders', () => ...),
    // teammate_mailbox + team_context (AgentSwarms)
    maybe('agent_pending_messages', () => ...),
    maybe('critical_system_reminder', () => ...),
    // compaction_reminder (COMPACTION_REMINDERS)
    // context_efficiency (HISTORY_SNIP)
  ];
  const threadAttachmentResults = await Promise.all(allThreadAttachments);

  // 5. flatten + dedup + return
  return [...userAttachmentResults, ...threadAttachmentResults].flat();
}

5 步: 1. early return —— bare/simple 模式只保留 queued commands 2. 1s abort —— 整个 getAttachments 最多跑 1s 3. user input attachments(4 种)—— 来自 input 字符串 4. thread attachments(20+ 种)—— 与 user 无关 5. flatten —— 合并所有结果

4.2 1s 总超时

const abortController = createAbortController();
const timeoutId = setTimeout(ac => ac.abort(), 1000, abortController);

1s 总超时——保护主线程。maybe() helper 接受 abort 信号。

4.3 feature-gated 条件数组

...(feature('BUDDY') ? [
  maybe('companion_intro', () => getCompanionIntroAttachment(messages)),
] : []),
...(feature('TRANSCRIPT_CLASSIFIER') ? [
  maybe('auto_mode', () => getAutoModeAttachments(messages, toolUseContext)),
  maybe('auto_mode_exit', () => getAutoModeExitAttachment(toolUseContext)),
] : []),

5+ 个 feature flag 控制哪些附件被收集: - BUDDY - TRANSCRIPT_CLASSIFIER - COMPACTION_REMINDERS - HISTORY_SNIP - AgentSwarms(全局 helper)


5. maybe() 辅助 — 单个附件失败不阻断(行 1005)

async function maybe<A>(label: string, f: () => Promise<A[]>): Promise<A[]> {
  try {
    return await f();
  } catch (e) {
    logError(e);
    return [];
  }
}

5 行函数——整个架构的关键

  • label —— 附件名(用于 logging)
  • f —— 异步生成附件的函数
  • try/catch —— 任何失败返回 [](不阻断其他附件)
  • logError —— 记录错误

为什么关键: - 20+ 个附件并行计算 - 任何一个失败(disk I/O 错 / 网络超时 / 解析错)不影响其他 - 附件系统永远返回部分结果,不是"全或无"


6. 各类附件详解

6.1 User-input attachments(4 种)

名称 触发 实现
at_mentioned_files input 中有 @path/to/file processAtMentionedFiles 读文件
mcp_resources input 中有 @server:resource processMcpResourceAttachments
agent_mentions input 中有 @agent-name processAgentMentions
skill_discovery turn 0 + feature('EXPERIMENTAL_SKILL_SEARCH') prefetch.getTurnZeroSkillDiscovery

@ 提及语法: - @src/foo.ts —— 提到文件 - @server:resource —— MCP 资源 - @agent-name —— 调用 sub-agent

skipSkillDiscovery 注释

Without this gate, a 110KB SKILL.md fires ~3.3s of chunked AKI queries on every skill invocation

性能关键:skill SKILL.md 内容被当 input 传入——若不跳过,每次 skill 调用都触发 ~3.3s 搜索。

6.2 6 种 Delta attachments

"Delta" = 计算与上次相比的变化

Delta 计算
deferred_tools_delta 工具列表变化
agent_listing_delta agent 列表变化
mcp_instructions_delta MCP 服务器指令变化
changed_files 文件变化(IDE 通知)
nested_memory nested CLAUDE.md 变化
date_change 日期跨天

Delta 模式:避免每次重发不变的上下文。

6.3 Plan Mode / Auto Mode 附件

maybe('plan_mode', () => getPlanModeAttachments(messages, toolUseContext)),
maybe('plan_mode_exit', () => getPlanModeExitAttachment(toolUseContext)),
// auto_mode (TRANSCRIPT_CLASSIFIER)
maybe('auto_mode', () => getAutoModeAttachments(messages, toolUseContext)),
maybe('auto_mode_exit', () => getAutoModeExitAttachment(toolUseContext)),

Plan mode 附件: - 提醒用户当前是 plan mode - 跟踪 plan mode 停留 turn 数(防止忘记退出) - 退出附件(当用户说"开始实现"时)

Auto mode 附件(ANT-ONLY):类似 plan mode 但自动模式。

6.4 Todo/Task 提醒

maybe('todo_reminders', () =>
  isTodoV2Enabled()
    ? getTaskReminderAttachments(messages, toolUseContext)
    : getTodoReminderAttachments(messages, toolUseContext),
),

两种实现: - isTodoV2Enabled —— 新的 task 系统 - 否则 —— 老 todo 系统

配置常量(行 254-310): - TODO_REMINDER_CONFIG —— 提醒频率 - PLAN_MODE_ATTACHMENT_CONFIG —— plan 模式提醒 - AUTO_MODE_ATTACHMENT_CONFIG —— auto 模式提醒

6.5 Teammate / Swarm 附件

...(isAgentSwarmsEnabled() ? [
  ...(querySource === 'session_memory' ? [] : [
    maybe('teammate_mailbox', async () => getTeammateMailboxAttachments(toolUseContext)),
  ]),
  maybe('team_context', async () => getTeamContextAttachment(messages ?? [])),
] : []),

Swarm 模式: - teammate_mailbox —— 其他 teammate 的来信 - team_context —— 当前团队上下文

session_memory 跳过注释

Skip teammate mailbox for the session_memory forked agent. It shares AppState.teamContext with the leader, so isTeamLead resolves true and it reads+marks-as-read the leader's DMs as ephemeral attachments, silently stealing messages that should be delivered as permanent turns.

细节:session_memory agent 共享 leader 的 teamContext——若不跳过,会默默偷走本应作为永久 turn 投递的消息。

6.6 Hooks 6 种变体

HookPermissionDecisionAttachment      // 权限决定
HookSystemMessageAttachment           // 系统消息
HookCancelledAttachment               // 取消
HookErrorDuringExecutionAttachment    // 执行中错
HookSuccessAttachment                 // 成功
HookNonBlockingErrorAttachment         // 非阻塞错

6 种——每种对应 hook 生命周期的不同阶段。

6.7 其他

  • getCriticalSystemReminderAttachment —— 关键系统提示
  • getOutputStyleAttachment —— 输出风格
  • getSelectedLinesFromIDE —— IDE 选中的代码
  • getDateChangeAttachments —— 跨天
  • getUltrathinkEffortAttachment —— ultrathink effort
  • getCompactionReminderAttachment —— 压缩提醒
  • getContextEfficiencyAttachment —— 上下文效率
  • getCompanionIntroAttachment —— Companion 介绍(BUDDY)

7. buildImageContentBlocks — 图片处理(行 1103)

async function buildImageContentBlocks(...) {
  // 行 1103
  // 读图片 → token 预算检查 → base64 编码
}

3 步: 1. 读图片(readImageWithTokenBudget) 2. Token 预算检查 3. 输出 base64


8. 关键设计模式

8.1 附件并行计算

const userInputAttachments = [...];  // 4 个 maybe
const userAttachmentResults = await Promise.all(userInputAttachments);

const allThreadAttachments = [...];  // 20+ 个 maybe
const threadAttachmentResults = await Promise.all(allThreadAttachments);

两批 Promise.all: - 第一批:4 个 user-input 附件 - 第二批:20+ 个 thread 附件 - 全部并行

8.2 失败隔离(maybe)

每个附件用 maybe() 包装——任何一个失败不影响其他

8.3 1s 总体超时

setTimeout(ac => ac.abort(), 1000, abortController);

AbortController 传给每个附件函数——可以提前取消正在做的 I/O。

8.4 Feature-gated 数组展开

...(feature('BUDDY') ? [maybe(...)] : []),

模式: - feature() 编译时门控 - 数组 spread 在编译后只剩"真"分支 - DCE 友好 + 运行时安全

8.5 Delta 模式

很多附件是 delta 计算——只报告变化部分(不是重发整个列表): - deferred_tools_delta —— 工具变化 - agent_listing_delta —— agent 变化 - mcp_instructions_delta —— MCP 指令变化

节省 token——避免每轮都重发相同信息。

8.6 跨 agent 处理

isMainThread = !toolUseContext.agentId 区分主线程 vs subagent。

callSite(推测): - 'attachments_main' —— 主线程 - 'attachments_subagent' —— subagent

可能用于遥测行为差异

8.7 session_memory 特殊处理

querySource === 'session_memory' 跳过 teammate_mailbox。

注释详细解释原因——session_memory agent 共享 leader 的 teamContext。

8.8 配置化附件

export const TODO_REMINDER_CONFIG = { ... }
export const PLAN_MODE_ATTACHMENT_CONFIG = { ... }

配置驱动——不写死 turn 数 / 阈值。


9. 复杂度分析

维度 数字
总行数 3997
函数数 64
Attachment 类型 17+
getAttachments 中 maybe() 数 24+
Feature flags 5+ (BUDDY, TRANSCRIPT_CLASSIFIER, COMPACTION_REMINDERS, HISTORY_SNIP, EXPERIMENTAL_SKILL_SEARCH)
总超时 1s

10. 性能特征

10.1 总时间

理想 < 50ms(disk 命中缓存) 最坏 1000ms(abort 兜底) 通常 100-300ms(多次 disk 读)

10.2 瓶颈

  • 多个 disk I/O(CLAUDE.md, memory files)
  • MCP 资源拉取(网络)
  • Skill 搜索(多次查询)

10.3 优化手段

  • 1s abort
  • 并行
  • 失败隔离
  • Delta(避免重发)

11. 与其他文件的关系

attachments.ts
  ├──→ processAtMentionedFiles / processMcpResourceAttachments / processAgentMentions
  ├──→ plan mode / auto mode helpers
  ├──→ memory / CLAUDE.md / nested memory
  ├──→ skill / dynamic skill helpers
  ├──→ teammate mailbox / team context
  ├──→ MCP 指令 / agent 列表 / tool delta
  └──→ 17+ attachment types

getAttachments 是"拼 prompt 的工厂"——每次 LLM 调用都跑一次。


12. 关键洞察

12.1 附件是"提示工程"的实现

每个附件都是一个 prompt 注入策略: - @file —— 显式附件 - plan_mode —— 行为约束 - todo_reminders —— 注意力引导 - mcp_instructions_delta —— 工具更新

附件 = 商业产品的"提示魔法"

12.2 maybe() 是"优雅降级"的典范

整个附件系统设计为永远返回部分结果——任何一个失败都不阻断其他。

12.3 1s 总体超时保护主线程

AbortController 传给每个附件——避免 5+10s 的"卡死 prompt"。

12.4 Delta 是 token 经济

很多附件计算与上次相比的变化——节省大量 token。

12.5 session_memory 跳过 mailbox = 细粒度 bug 修复

注释详细解释——session_memory agent 共享 leader teamContext,不跳过会"偷消息"。

12.6 skipSkillDiscovery = 110KB 性能炸弹修复

110KB SKILL.md 触发 ~3.3s 搜索——加了 skip flag。

这种"为性能加的 gate"在文件里有多处——是真实性能调优的痕迹。

12.7 Feature-gated 数组展开

...(feature('X') ? [maybe(...)] : []) 是这个文件最常见的模式


13. 阅读建议

  1. 看 17+ Attachment 类型(行 295-740)—— 知道有什么附件
  2. 看 getAttachments 主函数(行 743-1005)—— 编排逻辑
  3. 看 maybe() 辅助(行 1005)—— 失败隔离
  4. 看 1-2 个具体附件实现getPlanModeAttachments / getDateChangeAttachments
  5. 跳过 processAtMentionedFiles(行 ~2000)—— 细节太多

14. 与其他深度拆解的关系

文件 关系
query.ts / QueryEngine getAttachments 拼 prompt
claude.ts 把 Attachment 序列化到 API 请求
plans.ts plan mode 文件读写
claudemd.ts CLAUDE.md / memory file 处理
tasks.ts todo / task reminder

15. 阅读清单

  1. ✅ 看 17+ Attachment 类型(行 295-740)
  2. ✅ 看 getAttachments 主函数(行 743-1005)
  3. ✅ 看 maybe() 辅助(行 1005)
  4. ✅ 看 1-2 个具体附件(getDateChangeAttachments / getPlanModeAttachments
  5. 📌 跳过 2000+ 行的 processXxx 内部
  6. 📌 对照 topics/deep-dive-query-engine.md 看附件怎么被消费

16. 练习任务

  1. 数 maybe() 调用(grep)—— 看共 24+ 个附件
  2. 数 feature('X')(grep)—— 看附件系统的 DCE 范围
  3. 画 getAttachments 流程图 —— 4 个 user input + 20+ thread attachments
  4. 找一个 delta 附件(如 getDeferredToolsDeltaAttachment)—— 解读它怎么算"变化"
  5. 手写一个 maybe() 风格附件(~30 行)—— 失败隔离 + 1s abort
  6. 思考:附件系统如果改成 async generator 模式,会怎样?