Deep Dive | src/utils/attachments.ts 3997 行 — 上下文附件编排中心¶
重要性:⭐⭐⭐⭐(每次 LLM 调用前的上下文拼装中心——17+ 种附件类型并行组装) 真实位置:
src/utils/attachments.ts(3997 行) 角色:在每次用户输入 / 工具调用时,并行组装 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 effortgetCompactionReminderAttachment—— 压缩提醒getContextEfficiencyAttachment—— 上下文效率getCompanionIntroAttachment—— Companion 介绍(BUDDY)
7. buildImageContentBlocks — 图片处理(行 1103)¶
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 总体超时¶
AbortController 传给每个附件函数——可以提前取消正在做的 I/O。
8.4 Feature-gated 数组展开¶
模式:
- 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 配置化附件¶
配置驱动——不写死 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. 阅读建议¶
- 看 17+ Attachment 类型(行 295-740)—— 知道有什么附件
- 看 getAttachments 主函数(行 743-1005)—— 编排逻辑
- 看 maybe() 辅助(行 1005)—— 失败隔离
- 看 1-2 个具体附件实现(
getPlanModeAttachments/getDateChangeAttachments) - 跳过 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. 阅读清单¶
- ✅ 看 17+ Attachment 类型(行 295-740)
- ✅ 看 getAttachments 主函数(行 743-1005)
- ✅ 看 maybe() 辅助(行 1005)
- ✅ 看 1-2 个具体附件(
getDateChangeAttachments/getPlanModeAttachments) - 📌 跳过 2000+ 行的 processXxx 内部
- 📌 对照 topics/deep-dive-query-engine.md 看附件怎么被消费
16. 练习任务¶
- 数 maybe() 调用(grep)—— 看共 24+ 个附件
- 数 feature('X')(grep)—— 看附件系统的 DCE 范围
- 画 getAttachments 流程图 —— 4 个 user input + 20+ thread attachments
- 找一个 delta 附件(如
getDeferredToolsDeltaAttachment)—— 解读它怎么算"变化" - 手写一个 maybe() 风格附件(~30 行)—— 失败隔离 + 1s abort
- 思考:附件系统如果改成 async generator 模式,会怎样?