Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

/compact 与 /memory 分别管理「对话上下文生命周期」与「跨 session 持久记忆」:compact 是 local 命令,调用 services/compact/ 压缩 transcript;memory 是 local-jsx 命令,打开 MemoryFileSelector 编辑 CLAUDE.md 系列文件。compact 在 BRIDGE_SAFE_COMMANDS 白名单内;memory 需本地编辑器。本章对比两条路径的 call 签名、与 compact 服务的边界、以及 session memory 优先策略。

学完本章你应该能

  • 说明 compact index.ts 的 isEnabled 与 supportsNonInteractive
  • 追踪 compact.ts call 的三段 fallback:session memory → reactive → legacy
  • 解释 getMessagesAfterCompactBoundary 与 snip 消息投影
  • 描述 memory.tsx 的文件创建与 editFileInEditor 流程
  • 理解 compact 结果如何经 processSlashCommand 变为新 messages

核心概念(先读懂这些)

compact 命令 vs services/compact 服务

commands/compact/compact.ts 是薄 orchestration:调 trySessionMemoryCompaction、microcompactMessages、compactConversation、reactiveCompact。services/compact/compact.ts 含 fork 摘要核心。读 autocompact 阈值应去 services 章;读 /compact 用户参数与 displayText 读本章。

custom instructions 跳过 session memory 路径

trySessionMemoryCompaction 不支持 custom instructions;用户 /compact focus on API changes 直接走 traditional/reactive 路径。无参 /compact 优先尝试廉价 session memory compact。

memory 文件与 getMemoryFiles 缓存

call 入口 clearMemoryFileCaches + await getMemoryFiles() 避免 Suspense fallback flash。Memory 路径来自 utils/claudemd 扫描 project + user + local 层级 CLAUDE.md。

建议学习步骤

  1. 阅读源码块 A:compact index.ts
  2. 阅读源码块 B:compact call 主流程
  3. 阅读源码块 C:compactViaReactive
  4. 阅读源码块 D:buildDisplayText 与 getCacheSharingParams
  5. 阅读源码块 E:memory index 与 call
  6. 阅读源码块 F:MemoryCommand 文件编辑
  7. 对照 processSlashCommand compact 分支

常见误区

注意

DISABLE_COMPACT env 使 isEnabled false,命令从池移除

注意

abort 时统一 "Compaction canceled." 文案

注意

memory writeFile wx flag:EEXIST 忽略,保留已有内容

在架构中的位置

两条命令解决不同痛点:

命令问题写入目标
/compactcontext window 将满替换 messages[],插入 compact boundary
/memory跨 session 记住偏好~/.claude/CLAUDE.md、项目 CLAUDE.md 等
/compact [instructions]
  → processSlashCommand local 分支
  → compact.ts call(args, context)
  → { type:'compact', compactionResult, displayText }
  → buildPostCompactMessages → setMessages

/memory
  → local-jsx → MemoryFileSelector → editFileInEditor
  → onDone system 消息(路径 + $EDITOR 提示)

Autocompact(services)与 manual /compact 共用 compactConversation;manual 可带 customInstructions。

/compact:index.ts 元数据

commands/compact/index.ts:

  • type: 'local' — 非 JSX,直接 call
  • supportsNonInteractive: true — SDK/headless 可调用
  • isEnabled: !isEnvTruthy(process.env.DISABLE_COMPACT)
  • argumentHint: '<optional custom summarization instructions>'
  • description 说明保留 summary 清除 history

DISABLE_COMPACT 与 DISABLE_AUTO_COMPACT 不同:前者关掉命令注册(含 manual),后者仅关 autocompact(services 层)。

compact 在 BRIDGE_SAFE_COMMANDS — mobile 可 mid-session 压缩。

源码引用: src/commands/compact/index.ts · 第 1–15 行(共 16 行)

   1| import type { Command } from '../../commands.js'
   2| import { isEnvTruthy } from '../../utils/envUtils.js'
   3| 
   4| const compact = {
   5|   type: 'local',
   6|   name: 'compact',
   7|   description:
   8|     'Clear conversation history but keep a summary in context. Optional: /compact [instructions for summarization]',
   9|   isEnabled: () => !isEnvTruthy(process.env.DISABLE_COMPACT),
  10|   supportsNonInteractive: true,
  11|   argumentHint: '<optional custom summarization instructions>',
  12|   load: () => import('./compact.js'),
  13| } satisfies Command
  14| 
  15| export default compact

/compact:call 主流程

compact.ts export call 步骤:

  1. messages = getMessagesAfterCompactBoundary(messages) — REPL snip 保留 UI scrollback 但 compact 模型不应看到已 snip 内容
  2. 空 messages → throw 'No messages to compact'
  3. customInstructions = args.trim()
  4. 若无 customInstructions → trySessionMemoryCompaction 成功则:
    • clear getUserContext cache
    • runPostCompactCleanup
    • notifyCompaction(PROMPT_CACHE_BREAK_DETECTION)
    • markPostCompaction + suppressCompactWarning
    • return compact result + buildDisplayText
  5. reactiveCompact?.isReactiveOnlyMode() → compactViaReactive
  6. 否则 legacy:microcompactMessages → compactConversation(fork 摘要)
  7. setLastSummarizedMessageId(undefined) — legacy 替换全部 UUID
  8. catch:abort → canceled;精确匹配 NOT_ENOUGH / INCOMPLETE 错误码

返回 LocalCommandResult type:'compact',processSlashCommand 负责 apply。

源码引用: src/commands/compact/compact.ts · 第 40–137 行(共 288 行)

  40| export const call: LocalCommandCall = async (args, context) => {
  41|   const { abortController } = context
  42|   let { messages } = context
  43| 
  44|   // REPL keeps snipped messages for UI scrollback — project so the compact
  45|   // model doesn't summarize content that was intentionally removed.
  46|   messages = getMessagesAfterCompactBoundary(messages)
  47| 
  48|   if (messages.length === 0) {
  49|     throw new Error('No messages to compact')
  50|   }
  51| 
  52|   const customInstructions = args.trim()
  53| 
  54|   try {
  55|     // Try session memory compaction first if no custom instructions
  56|     // (session memory compaction doesn't support custom instructions)
  57|     if (!customInstructions) {
  58|       const sessionMemoryResult = await trySessionMemoryCompaction(
  59|         messages,
  60|         context.agentId,
  61|       )
  62|       if (sessionMemoryResult) {
  63|         getUserContext.cache.clear?.()
  64|         runPostCompactCleanup()
  65|         // Reset cache read baseline so the post-compact drop isn't flagged
  66|         // as a break. compactConversation does this internally; SM-compact doesn't.
  67|         if (feature('PROMPT_CACHE_BREAK_DETECTION')) {
  68|           notifyCompaction(
  69|             context.options.querySource ?? 'compact',
  70|             context.agentId,
  71|           )
  72|         }
  73|         markPostCompaction()
  74|         // Suppress warning immediately after successful compaction
  75|         suppressCompactWarning()
  76| 
  77|         return {
  78|           type: 'compact',
  79|           compactionResult: sessionMemoryResult,
  80|           displayText: buildDisplayText(context),
  81|         }
  82|       }
  83|     }
  84| 
  85|     // Reactive-only mode: route /compact through the reactive path.
  86|     // Checked after session-memory (that path is cheap and orthogonal).
  87|     if (reactiveCompact?.isReactiveOnlyMode()) {
  88|       return await compactViaReactive(
  89|         messages,
  90|         context,
  91|         customInstructions,
  92|         reactiveCompact,
  93|       )
  94|     }
  95| 
  96|     // Fall back to traditional compaction
  97|     // Run microcompact first to reduce tokens before summarization
  98|     const microcompactResult = await microcompactMessages(messages, context)
  99|     const messagesForCompact = microcompactResult.messages
 100| 
 101|     const result = await compactConversation(
 102|       messagesForCompact,
 103|       context,
 104|       await getCacheSharingParams(context, messagesForCompact),
 105|       false,
 106|       customInstructions,
 107|       false,
 108|     )
 109| 
 110|     // Reset lastSummarizedMessageId since legacy compaction replaces all messages
 111|     // and the old message UUID will no longer exist in the new messages array
 112|     setLastSummarizedMessageId(undefined)
 113| 
 114|     // Suppress the "Context left until auto-compact" warning after successful compaction
 115|     suppressCompactWarning()
 116| 
 117|     getUserContext.cache.clear?.()
 118|     runPostCompactCleanup()
 119| 
 120|     return {
 121|       type: 'compact',
 122|       compactionResult: result,
 123|       displayText: buildDisplayText(context, result.userDisplayMessage),
 124|     }
 125|   } catch (error) {
 126|     if (abortController.signal.aborted) {
 127|       throw new Error('Compaction canceled.')
 128|     } else if (hasExactErrorMessage(error, ERROR_MESSAGE_NOT_ENOUGH_MESSAGES)) {
 129|       throw new Error(ERROR_MESSAGE_NOT_ENOUGH_MESSAGES)
 130|     } else if (hasExactErrorMessage(error, ERROR_MESSAGE_INCOMPLETE_RESPONSE)) {
 131|       throw new Error(ERROR_MESSAGE_INCOMPLETE_RESPONSE)
 132|     } else {
 133|       logError(error)
 134|       throw new Error(`Error during compaction: ${error}`)
 135|     }
 136|   }
 137| }

compactViaReactive 与 hooks

REACTIVE_COMPACT feature 下 compactViaReactive:

并发 executePreCompactHooks(trigger:'manual')与 getCacheSharingParams — 注释:hooks 子进程与 system prompt 构建互不依赖。

流程:

  • setSDKStatus('compacting')、onCompactProgress 事件
  • reactiveCompactOnPromptTooLong 带 mergedInstructions
  • 失败 reason 映射到标准 ERROR_MESSAGE_*
  • 成功:setLastSummarizedMessageId(undefined)、runPostCompactCleanup、suppressCompactWarning
  • 合并 pre/ post hook userDisplayMessage

finally 恢复 streamMode、compact_end、SDKStatus null。

与 autocompact reactive 路径共享 outcome 语义,但 manual /compact 显式 trigger:'manual'。

源码引用: src/commands/compact/compact.ts · 第 139–228 行(共 288 行)

 139| async function compactViaReactive(
 140|   messages: Message[],
 141|   context: ToolUseContext,
 142|   customInstructions: string,
 143|   reactive: NonNullable<typeof reactiveCompact>,
 144| ): Promise<{
 145|   type: 'compact'
 146|   compactionResult: CompactionResult
 147|   displayText: string
 148| }> {
 149|   context.onCompactProgress?.({
 150|     type: 'hooks_start',
 151|     hookType: 'pre_compact',
 152|   })
 153|   context.setSDKStatus?.('compacting')
 154| 
 155|   try {
 156|     // Hooks and cache-param build are independent — run concurrently.
 157|     // getCacheSharingParams walks all tools to build the system prompt;
 158|     // pre-compact hooks spawn subprocesses. Neither depends on the other.
 159|     const [hookResult, cacheSafeParams] = await Promise.all([
 160|       executePreCompactHooks(
 161|         { trigger: 'manual', customInstructions: customInstructions || null },
 162|         context.abortController.signal,
 163|       ),
 164|       getCacheSharingParams(context, messages),
 165|     ])
 166|     const mergedInstructions = mergeHookInstructions(
 167|       customInstructions,
 168|       hookResult.newCustomInstructions,
 169|     )
 170| 
 171|     context.setStreamMode?.('requesting')
 172|     context.setResponseLength?.(() => 0)
 173|     context.onCompactProgress?.({ type: 'compact_start' })
 174| 
 175|     const outcome = await reactive.reactiveCompactOnPromptTooLong(
 176|       messages,
 177|       cacheSafeParams,
 178|       { customInstructions: mergedInstructions, trigger: 'manual' },
 179|     )
 180| 
 181|     if (!outcome.ok) {
 182|       // The outer catch in `call` translates these: aborted → "Compaction
 183|       // canceled." (via abortController.signal.aborted check), NOT_ENOUGH →
 184|       // re-thrown as-is, everything else → "Error during compaction: …".
 185|       switch (outcome.reason) {
 186|         case 'too_few_groups':
 187|           throw new Error(ERROR_MESSAGE_NOT_ENOUGH_MESSAGES)
 188|         case 'aborted':
 189|           throw new Error(ERROR_MESSAGE_USER_ABORT)
 190|         case 'exhausted':
 191|         case 'error':
 192|         case 'media_unstrippable':
 193|           throw new Error(ERROR_MESSAGE_INCOMPLETE_RESPONSE)
 194|       }
 195|     }
 196| 
 197|     // Mirrors the post-success cleanup in tryReactiveCompact, minus
 198|     // resetMicrocompactState — processSlashCommand calls that for all
 199|     // type:'compact' results.
 200|     setLastSummarizedMessageId(undefined)
 201|     runPostCompactCleanup()
 202|     suppressCompactWarning()
 203|     getUserContext.cache.clear?.()
 204| 
 205|     // reactiveCompactOnPromptTooLong runs PostCompact hooks but not PreCompact
 206|     // — both callers (here and tryReactiveCompact) run PreCompact outside so
 207|     // they can merge its userDisplayMessage with PostCompact's here. This
 208|     // caller additionally runs it concurrently with getCacheSharingParams.
 209|     const combinedMessage =
 210|       [hookResult.userDisplayMessage, outcome.result.userDisplayMessage]
 211|         .filter(Boolean)
 212|         .join('\n') || undefined
 213| 
 214|     return {
 215|       type: 'compact',
 216|       compactionResult: {
 217|         ...outcome.result,
 218|         userDisplayMessage: combinedMessage,
 219|       },
 220|       displayText: buildDisplayText(context, combinedMessage),
 221|     }
 222|   } finally {
 223|     context.setStreamMode?.('requesting')
 224|     context.setResponseLength?.(() => 0)
 225|     context.onCompactProgress?.({ type: 'compact_end' })
 226|     context.setSDKStatus?.(null)
 227|   }
 228| }

displayText 与 cache 参数

buildDisplayText 组装用户可见 dim 文本:

  • 前缀 "Compacted"
  • 非 verbose 时提示 ctrl+o(toggleTranscript)展开完整摘要
  • 附加 hook userDisplayMessage
  • getUpgradeMessage('tip') 上下文升级提示(1M 等)

getCacheSharingParams 为 compact fork 构建与主 thread 一致的 cache 上下文:

  • getSystemPrompt + buildEffectiveSystemPrompt
  • getUserContext / getSystemContext parallel
  • forkContextMessages 传入摘要 Agent

这保证 compact 子 Agent 看到与主 loop 对齐的 tool 列表与 system prompt,摘要质量稳定。

源码引用: src/commands/compact/compact.ts · 第 230–287 行(共 288 行)

 230| function buildDisplayText(
 231|   context: ToolUseContext,
 232|   userDisplayMessage?: string,
 233| ): string {
 234|   const upgradeMessage = getUpgradeMessage('tip')
 235|   const expandShortcut = getShortcutDisplay(
 236|     'app:toggleTranscript',
 237|     'Global',
 238|     'ctrl+o',
 239|   )
 240|   const dimmed = [
 241|     ...(context.options.verbose
 242|       ? []
 243|       : [`(${expandShortcut} to see full summary)`]),
 244|     ...(userDisplayMessage ? [userDisplayMessage] : []),
 245|     ...(upgradeMessage ? [upgradeMessage] : []),
 246|   ]
 247|   return chalk.dim('Compacted ' + dimmed.join('\n'))
 248| }
 249| 
 250| async function getCacheSharingParams(
 251|   context: ToolUseContext,
 252|   forkContextMessages: Message[],
 253| ): Promise<{
 254|   systemPrompt: SystemPrompt
 255|   userContext: { [k: string]: string }
 256|   systemContext: { [k: string]: string }
 257|   toolUseContext: ToolUseContext
 258|   forkContextMessages: Message[]
 259| }> {
 260|   const appState = context.getAppState()
 261|   const defaultSysPrompt = await getSystemPrompt(
 262|     context.options.tools,
 263|     context.options.mainLoopModel,
 264|     Array.from(
 265|       appState.toolPermissionContext.additionalWorkingDirectories.keys(),
 266|     ),
 267|     context.options.mcpClients,
 268|   )
 269|   const systemPrompt = buildEffectiveSystemPrompt({
 270|     mainThreadAgentDefinition: undefined,
 271|     toolUseContext: context,
 272|     customSystemPrompt: context.options.customSystemPrompt,
 273|     defaultSystemPrompt: defaultSysPrompt,
 274|     appendSystemPrompt: context.options.appendSystemPrompt,
 275|   })
 276|   const [userContext, systemContext] = await Promise.all([
 277|     getUserContext(),
 278|     getSystemContext(),
 279|   ])
 280|   return {
 281|     systemPrompt,
 282|     userContext,
 283|     systemContext,
 284|     toolUseContext: context,
 285|     forkContextMessages,
 286|   }
 287| }

/memory:index 与 call

commands/memory/index.ts 最小定义:

const memory: Command = {
  type: 'local-jsx',
  name: 'memory',
  description: 'Edit Claude memory files',
  load: () => import('./memory.js'),
}

无 immediate、无 argumentHint — 纯菜单交互。

memory.tsx call:

  1. clearMemoryFileCaches()
  2. await getMemoryFiles() — prime 缓存
  3. return <MemoryCommand onDone={onDone} />

Suspense 包裹 MemoryFileSelector;await getMemoryFiles 避免首次打开 fallback flash。

源码引用: src/commands/memory/index.ts · 第 1–10 行(共 11 行)

   1| import type { Command } from '../../commands.js'
   2| 
   3| const memory: Command = {
   4|   type: 'local-jsx',
   5|   name: 'memory',
   6|   description: 'Edit Claude memory files',
   7|   load: () => import('./memory.js'),
   8| }
   9| 
  10| export default memory

源码引用: src/commands/memory/memory.tsx · 第 83–89 行(共 103 行)

  83|           />
  84|         </React.Suspense>
  85| 
  86|         <Box marginTop={1}>
  87|           <Text dimColor>
  88|             Learn more: <Link url="https://code.claude.com/docs/en/memory" />
  89|           </Text>

MemoryCommand:编辑流程

handleSelectMemoryFile(memoryPath) 异步:

  1. 若路径在 claude config home 下 → mkdir recursive
  2. writeFile(memoryPath, '', { flag: 'wx' }) 创建空文件;EEXIST 忽略
  3. editFileInEditor(memoryPath) — spawn $VISUAL/$EDITOR
  4. 构建 editorHint(显示使用的 env var)
  5. onDone Opened memory file at ${relativePath} + hint,display:'system'

Dialog title="Memory" color="remember";底部 Link 指向 docs memory 页。

handleCancel → 'Cancelled memory editing' system 消息。

依赖 utils/claudemd getMemoryFiles、components/memory/MemoryFileSelector 列表 UI。

源码引用: src/commands/memory/memory.tsx · 第 14–82 行(共 103 行)

  14| 
  15| function MemoryCommand({
  16|   onDone,
  17| }: {
  18|   onDone: (
  19|     result?: string,
  20|     options?: { display?: CommandResultDisplay },
  21|   ) => void
  22| }): React.ReactNode {
  23|   const handleSelectMemoryFile = async (memoryPath: string) => {
  24|     try {
  25|       // Create claude directory if it doesn't exist (idempotent with recursive)
  26|       if (memoryPath.includes(getClaudeConfigHomeDir())) {
  27|         await mkdir(getClaudeConfigHomeDir(), { recursive: true })
  28|       }
  29| 
  30|       // Create file if it doesn't exist (wx flag fails if file exists,
  31|       // which we catch to preserve existing content)
  32|       try {
  33|         await writeFile(memoryPath, '', { encoding: 'utf8', flag: 'wx' })
  34|       } catch (e: unknown) {
  35|         if (getErrnoCode(e) !== 'EEXIST') {
  36|           throw e
  37|         }
  38|       }
  39| 
  40|       await editFileInEditor(memoryPath)
  41| 
  42|       // Determine which environment variable controls the editor
  43|       let editorSource = 'default'
  44|       let editorValue = ''
  45|       if (process.env.VISUAL) {
  46|         editorSource = '$VISUAL'
  47|         editorValue = process.env.VISUAL
  48|       } else if (process.env.EDITOR) {
  49|         editorSource = '$EDITOR'
  50|         editorValue = process.env.EDITOR
  51|       }
  52| 
  53|       const editorInfo =
  54|         editorSource !== 'default'
  55|           ? `Using ${editorSource}="${editorValue}".`
  56|           : ''
  57| 
  58|       const editorHint = editorInfo
  59|         ? `> ${editorInfo} To change editor, set $EDITOR or $VISUAL environment variable.`
  60|         : `> To use a different editor, set the $EDITOR or $VISUAL environment variable.`
  61| 
  62|       onDone(
  63|         `Opened memory file at ${getRelativeMemoryPath(memoryPath)}\n\n${editorHint}`,
  64|         { display: 'system' },
  65|       )
  66|     } catch (error) {
  67|       logError(error)
  68|       onDone(`Error opening memory file: ${error}`)
  69|     }
  70|   }
  71| 
  72|   const handleCancel = () => {
  73|     onDone('Cancelled memory editing', { display: 'system' })
  74|   }
  75| 
  76|   return (
  77|     <Dialog title="Memory" onCancel={handleCancel} color="remember">
  78|       <Box flexDirection="column">
  79|         <React.Suspense fallback={null}>
  80|           <MemoryFileSelector
  81|             onSelect={handleSelectMemoryFile}
  82|             onCancel={handleCancel}

processSlashCommand 如何应用 compact

local 命令 compact 返回后,processSlashCommand:

  1. 识别 result.type === 'compact'
  2. buildPostCompactMessages(compactionResult, ...) 构造新 transcript
  3. resetMicrocompactState — processSlashCommand 统一调用(reactive 路径注释说明)
  4. setMessages 替换;可能 enqueue 后续 query

Compact boundary 消息类型 isCompactBoundaryMessage 供 session JSONL loader 修复 parentUuid。

/memory 不修改 messages 结构,仅 system 消息记录操作;真正内容在 CLAUDE.md,下轮 query getUserContext 读取注入 system。

与 services/SessionMemory/ 交叉:session memory compact 写结构化记忆;/memory 编辑源文件——二者互补。

源码引用: src/utils/processUserInput/processSlashCommand.tsx · 第 14–16 行(共 1263 行)

  14|   getCommandName,
  15|   hasCommand,
  16|   type PromptCommand,

本章小结与延伸

/compact 缩上下文,/memory 扩持久知识。回到 command-registry 查命令池如何注册二者。 继续学习:

  • command-registry
  • services compact 章
Prev
mcp-commands · /mcp 服务器管理
Next
Ink 渲染管线 · Screen 与终端输出