概述

agent-session.ts(3159 行)是 Pi Coding Agent 的核心枢纽,位于 F:\Pi\packages\coding-agent\src\core\。它是所有运行模式(interactive、print、rpc)共享的 AgentSession 类,封装了 agent 生命周期的完整管理逻辑。

一句话定位:AgentSession 是所有模式在 pi-agent-core 之上叠加的会话管理层 — 连接了 agent 循环、扩展系统、会话持久化、模型管理、compaction、自动重试等所有主要子系统。

核心职责

职责领域说明
Agent 生命周期管理 prompt 发送、流式响应、消息持久化
扩展系统桥接事件转发到 ExtensionRunner,处理扩展注册的工具/命令
会话管理消息持久化、会话切换、分支导航、压缩
模型管理模型选择、切换、thinking level 控制
Compaction自动/手动上下文压缩,overflow 恢复
自动重试可配置指数退避重试(429/500/网络错误)
Bash 执行命令执行、结果记录、流式输出
消息队列steering/followUp 消息的排队与消费

架构位置与依赖关系

数据流层次(按代码执行顺序)

user message: /skill:name args 扩展
    ↓ (text expansion)
AgentSession.prompt()
    ↓ (input event → skill → template → streaming? → auth → message build)
_runAgentPrompt()
    ↓
agent.prompt() / agent.continue()
    ↓ (agent-core 内部循环:LLM → 工具 → LLM ...)
_handleAgentEvent() ←── agent event 回调
    ↓
_emitExtensionEvent()  →  ExtensionRunner
    ↓
_emit()                →  AgentSessionEventListener
    ↓
sessionManager.appendMessage() → 会话持久化

外部依赖全景

agent-session.ts
├── @earendil-works/pi-agent-core    (Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel)
├── @earendil-works/pi-ai/compat     (AssistantMessage, ImageContent, Model, TextContent, streamSimple, isContextOverflow, clampThinkingLevel 等)
├── node:fs / node:path              (文件读写)
│
├── ./extensions/index.ts            (ExtensionRunner, 扩展事件类型, wrapRegisteredTools)
├── ./extensions/runner.ts           (emitSessionShutdownEvent)
├── ./session-manager.ts             (SessionManager, BranchSummaryEntry, CompactionEntry, SessionHeader)
├── ./model-registry.ts              (ModelRegistry)
├── ./settings-manager.ts            (SettingsManager)
├── ./resource-loader.ts             (ResourceLoader, ResourceExtensionPaths)
│
├── ./compaction/index.ts            (compact, prepareCompaction, shouldCompact, estimateContextTokens 等)
├── ./bash-executor.ts               (executeBashWithOperations, BashResult)
├── ./messages.ts                    (CustomMessage, BashExecutionMessage)
├── ./system-prompt.ts               (buildSystemPrompt, BuildSystemPromptOptions)
├── ./prompt-templates.ts            (expandPromptTemplate, PromptTemplate)
├── ./slash-commands.ts              (SlashCommandInfo)
├── ./source-info.ts                 (SourceInfo, createSyntheticSourceInfo)
├── ./tools/index.ts                 (createAllToolDefinitions)
├── ./tools/bash.ts                  (BashOperations, createLocalBashOperations)
├── ./tools/tool-definition-wrapper.ts (createToolDefinitionFromAgentTool)
│
├── ./auth-guidance.ts               (formatNoApiKeyFoundMessage, formatNoModelSelectedMessage)
├── ./defaults.ts                    (DEFAULT_THINKING_LEVEL)
├── ./export-html/index.ts           (exportSessionToHtml, ToolHtmlRenderer)
├── ./export-html/tool-renderer.ts   (createToolHtmlRenderer)
├── ./keybindings.ts                 (KeybindingsConfig)
├── ./diagnostics.ts                 (ResourceDiagnostic)
│
├── ../utils/frontmatter.ts          (stripFrontmatter)
├── ../utils/paths.ts                (resolvePath)
├── ../utils/sleep.ts                (sleep)
└── ../modes/interactive/theme/theme.ts  (getThemeByName, theme)

类结构总览

AgentSession
├── 构造函数 ──── _buildRuntime() → 初始化工具注册表、扩展系统
├── 公开方法
│   ├── prompt()                → 主入口:处理用户输入
│   ├── steer() / followUp()    → 流式状态下的消息排队
│   ├── sendCustomMessage()     → 扩展发送自定义消息
│   ├── sendUserMessage()       → 扩展发送用户消息
│   ├── setModel() / cycleModel()           → 模型管理
│   ├── setThinkingLevel() / cycleThinkingLevel()  → Thinking level
│   ├── compact()               → 手动压缩
│   ├── navigateTree()          → 会话树导航
│   ├── exportToHtml/Jsonl()    → 导出
│   ├── executeBash()           → Bash 执行
│   ├── subscribe()             → 事件订阅
│   ├── bindExtensions()        → 扩展绑定
│   └── dispose()               → 资源清理
├── 内部方法
│   ├── _handleAgentEvent()     → agent event 回调枢纽
│   ├── _emitExtensionEvent()   → 转发到 ExtensionRunner
│   ├── _runAgentPrompt()       → agent.prompt() + post-run 处理循环
│   ├── _handlePostAgentRun()   → agent 停止后的处理(重试/compaction/队列)
│   ├── _checkCompaction()      → 压缩检查(overflow + threshold)
│   ├── _runAutoCompaction()    → 自动压缩执行
│   ├── _prepareRetry()         → 指数退避重试
│   └── _buildRuntime()         → 初始化工具注册表和扩展运行器
├── Getter 属性
│   ├── model / isStreaming / thinkingLevel / state
│   ├── sessionFile / sessionId / sessionName
│   ├── isCompacting / isRetrying / isBashRunning
│   ├── pendingMessageCount / activeToolNames
│   └── autoCompactionEnabled / autoRetryEnabled
└── 内部状态
    ├── _steeringMessages / _followUpMessages / _pendingNextTurnMessages
    ├── _compactionAbortController / _autoCompactionAbortController
    ├── _retryAbortController / _retryAttempt
    ├── _bashAbortController / _pendingBashMessages
    └── _toolRegistry / _toolDefinitions / _toolPromptSnippets

逐段解读

段 1:类型定义与常量(第 1-123 行)

ParsedSkillBlock 与 parseSkillBlock()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export interface ParsedSkillBlock {
  name: string;
  location: string;
  content: string;
  userMessage: string | undefined;
}

export function parseSkillBlock(text: string): ParsedSkillBlock | null {
  const match = text.match(
    /^<skill name="([^"]+)" location="([^"]+)">\n([\s\S]*?)\n<\/skill>(?:\n\n([\s\S]+))?$/
  );
  if (!match) return null;
  return { name: match[1], location: match[2], content: match[3], userMessage: match[4]?.trim() || undefined };
}

功能:从用户消息中解析 <skill> XML 块。正则匹配四个捕获组:name、location、content 和可选的 userMessage。用于将技能文件的内容嵌入用户消息上下文。

AgentSessionEvent — 自定义事件类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
export type AgentSessionEvent =
  | Exclude<AgentEvent, { type: "agent_end" }>
  | { type: "agent_end"; messages: AgentMessage[]; willRetry: boolean }
  | { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
  | { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" }
  | { type: "compaction_end"; reason: ...; result: ...; aborted: boolean; willRetry: boolean; errorMessage?: string }
  | { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
  | { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
  | { type: "session_info_changed"; name: string | undefined }
  | { type: "thinking_level_changed"; level: ThinkingLevel };

语法要点Exclude<T, U>:从联合类型 T 中排除匹配 U 的成员。这里排除 agent-core 的 agent_end 类型,用自定义的(带 willRetry 字段)替换。

设计:新增的事件类型(compaction_start/endauto_retry_start/endqueue_update 等)是 AgentSession 层独有的,agent-core 不知道它们的存在。

AgentSessionConfig — 构造函数配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
export interface AgentSessionConfig {
  agent: Agent;                              // agent-core 的 Agent 实例
  sessionManager: SessionManager;            // 会话持久化管理器
  settingsManager: SettingsManager;          // 设置管理器
  cwd: string;
  scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>; // --models 标志
  resourceLoader: ResourceLoader;
  customTools?: ToolDefinition[];
  modelRegistry: ModelRegistry;
  initialActiveToolNames?: string[];
  allowedToolNames?: string[];               // 工具 allowlist
  excludedToolNames?: string[];              // 工具 denylist
  baseToolsOverride?: Record<string, AgentTool>;
  extensionRunnerRef?: { current?: ExtensionRunner };
  sessionStartEvent?: SessionStartEvent;
}

依赖注入:AgentSession 通过构造函数接收所有依赖,不做服务定位。这让它在不同运行模式(interactive、print、rpc)下可独立实例化,每个模式提供不同的实现。


段 2:类构造函数与初始化(第 259-340 行)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
export class AgentSession {
  // ~30 个私有状态字段
  private _steeringMessages: string[] = [];
  private _followUpMessages: string[] = [];
  private _pendingNextTurnMessages: CustomMessage[] = [];
  private _compactionAbortController: AbortController | undefined;
  private _retryAbortController: AbortController | undefined;
  private _bashAbortController: AbortController | undefined;
  // ...

  constructor(config: AgentSessionConfig) {
    // 1. 保存配置
    this.agent = config.agent;
    this.sessionManager = config.sessionManager;
    // ...
    // 2. 订阅 agent 事件
    this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
    // 3. 安装工具钩子(beforeToolCall / afterToolCall)
    this._installAgentToolHooks();
    // 4. 构建运行时(工具注册表、ExtensionRunner、系统提示)
    this._buildRuntime({ activeToolNames: this._initialActiveToolNames, includeAllExtensionTools: true });
  }

_installAgentToolHooks() — 工具拦截钩子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private _installAgentToolHooks(): void {
  this.agent.beforeToolCall = async ({ toolCall, args }) => {
    const runner = this._extensionRunner;
    if (!runner.hasHandlers("tool_call")) return undefined;
    return await runner.emitToolCall({ type: "tool_call", toolName: toolCall.name, toolCallId: toolCall.id, input: args as Record<string, unknown> });
  };

  this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
    const runner = this._extensionRunner;
    if (!runner.hasHandlers("tool_result")) return undefined;
    const hookResult = await runner.emitToolResult({ type: "tool_result", toolName: toolCall.name, ... });
    if (!hookResult) return undefined;
    return { content: hookResult.content, details: hookResult.details, isError: hookResult.isError ?? isError };
  };
}

设计要点:钩子只安装一次,但每次调用时读取 this._extensionRunner 的最新值。这意味着扩展重载后新 runner 自动生效,无需重新安装钩子。


段 3:事件系统(第 340-580 行)

事件订阅

1
2
3
4
subscribe(listener: AgentSessionEventListener): () => void {
  this._eventListeners.push(listener);
  return () => { const i = this._eventListeners.indexOf(listener); if (i !== -1) this._eventListeners.splice(i, 1); };
}

模式:返回取消函数(类似 DOM addEventListener 返回的移除函数)。每个 listener 独立管理。

_handleAgentEvent() — 事件处理枢纽

收到 AgentEvent
    │
    ├── Phase 1: 队列管理 — 从 steer/followUp 队列移除已消费消息
    │
    ├── Phase 2: 扩展系统 — _emitExtensionEvent() 转发
    │
    ├── Phase 3: 自定义 listener — _emit() 含 willRetry 增强
    │
    └── Phase 4: 消息持久化 — sessionManager.appendMessage()
                     └── 如果是 assistant → 重试计数器管理

_replaceMessageInPlace() — 原地替换消息

1
2
3
4
5
6
private _replaceMessageInPlace(target: AgentMessage, replacement: AgentMessage): void {
  if (target === replacement) return;
  const targetRecord = target as unknown as Record<string, unknown>;
  for (const key of Object.keys(targetRecord)) delete targetRecord[key];
  Object.assign(targetRecord, replacement);
}

语法要点 — 可变对象替换:当扩展的 message_end handler 修改消息时,使用此方法将新属性覆盖到原对象上,保持所有引用同步。删除现有属性后再 Object.assign,确保删除旧值中没有的属性。

_emitExtensionEvent() — 事件映射桥

AgentEvent                          ExtensionEvent
──────────                          ──────────────
agent_start      ──────────────────  agent_start
agent_end        ──────────────────  agent_end
turn_start       ───→ TurnStartEvent
turn_end         ───→ TurnEndEvent
message_start    ───→ MessageStartEvent
message_update   ───→ MessageUpdateEvent
message_end      ───→ MessageEndEvent(+ 替换消息)
tool_execution_start ──→ ToolExecutionStartEvent
tool_execution_update ──→ ToolExecutionUpdateEvent
tool_execution_end   ──→ ToolExecutionEndEvent

段 4:Prompt 处理流程(第 580-800 行)

prompt() — 7 步处理管道

用户输入 text
    │
Step 1: 扩展命令?(/command) → 直接执行,返回
    │
Step 2: input 事件给扩展 → "handled" 返回 / "transform" 修改文本
    │
Step 3: 展开 /skill:name 和提示模板
    │
Step 4: 流式中?→ 排队(steer/followUp),返回
    │
Step 5: 认证检查(模型 + API key)→ 无则抛错
    │
Step 6: 预 prompt compaction → 有则 agent.continue()
    │
Step 7: before_agent_start 事件 + 构建消息 → _runAgentPrompt()

_runAgentPrompt() / _handlePostAgentRun() — 后处理链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private async _runAgentPrompt(messages): Promise<void> {
  try {
    await this.agent.prompt(messages);
    while (await this._handlePostAgentRun()) {
      await this.agent.continue();
    }
  } finally {
    this._flushPendingBashMessages();
  }
}

private async _handlePostAgentRun(): Promise<boolean> {
  // 1. 重试检查 → 是则准备重试,返回 true
  if (this._isRetryableError(msg) && await this._prepareRetry(msg)) return true;
  // 2. 压缩检查 → 是则执行压缩,返回 true(overflow 会继续重试)
  if (await this._checkCompaction(msg)) return true;
  // 3. 队列检查 → extension 在 agent_end 中排队了消息
  return this.agent.hasQueuedMessages();
}

关键设计:每次 agent.continue() 后都重新跑整个后处理链。重试 → 压缩 → 队列消费,直到三者都不满足才结束循环。


段 5:Steer / FollowUp 消息队列(第 800-960 行)

          prompt("text")   isStreaming=true
                │
          ┌─────┴──────┐
          │ streaming  │
          │ Behavior?  │
          └─────┬──────┘
          ┌─────┴───────┐
          ▼             ▼
     steer()        followUp()
        │               │
        ▼               ▼
agent.steer()    agent.followUp()
        │               │
        ▼               ▼
 中断当前 LLM     等待工具链完成
 立即处理          LLM 空闲时消费
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
async steer(text: string, images?: ImageContent[]): Promise<void> {
  if (text.startsWith("/")) this._throwIfExtensionCommand(text); // 命令不能排队
  let expandedText = this._expandSkillCommand(text);
  expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
  await this._queueSteer(expandedText, images);
}

private async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {
  this._steeringMessages.push(text);
  this._emitQueueUpdate();
  this.agent.steer({ role: "user", content: [{ type: "text", text }, ...(images ?? [])], timestamp: Date.now() });
}

两种策略:steer 中断当前 LLM 立即处理,followUp 等当前工具链完成。模式有 "all"(全排)和 "one-at-a-time"(一次一个),由 settings 控制。


段 6:模型管理(第 960-1130 行)

模型切换

1
2
3
4
5
6
7
8
9
async setModel(model: Model<any>): Promise<void> {
  if (!this._modelRegistry.hasConfiguredAuth(model)) throw new Error(...);
  const previousModel = this.model;       // 保存旧模型
  this.agent.state.model = model;          // 设置新模型
  this.sessionManager.appendModelChange(model.provider, model.id);  // 持久化
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);  // 保存为默认
  this.setThinkingLevel(thinkingLevel);    // 重新钳位 thinking level
  await this._emitModelSelect(model, previousModel, "set");  // 通知扩展
}

模型循环

1
2
3
4
async cycleModel(direction: "forward" | "backward"): Promise<ModelCycleResult | undefined> {
  if (this._scopedModels.length > 0) return this._cycleScopedModel(direction);     // --models 优先
  return this._cycleAvailableModel(direction);  // 回退到 ModelRegistry
}

两阶段:scopedModels(CLI --models)→ ModelRegistry(所有已认证模型)。

Thinking Level 钳位

1
2
3
4
5
6
7
setThinkingLevel(level: ThinkingLevel): void {
  const effectiveLevel = this.getAvailableThinkingLevels().includes(level)
    ? level
    : this._clampThinkingLevel(level, this.getAvailableThinkingLevels());
  // 只在实际改变时持久化
  if (effectiveLevel !== this.agent.state.thinkingLevel) { ... }
}

clampThinkingLevel() 来自 @earendil-works/pi-ai/compat,将请求的 level 适配到模型能力范围内。


段 7:Compaction 机制(第 1130-1600 行)

两种触发场景

场景 1: Context Overflow(溢出)
  LLM 返回 stopReason=error + isContextOverflow()
  → 移除错误消息(保留 session 历史)
  → 压缩
  → 自动重试(一次,_overflowRecoveryAttempted 防循环)

场景 2: Context Threshold(超阈值)
  assistant message usage > settings.threshold
  → 压缩
  → 不自动重试,用户手动继续

_checkCompaction() — 多层过滤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private async _checkCompaction(assistantMessage, skipAbortedCheck = true): Promise<boolean> {
  // 1. 跳过中止消息
  if (skipAbortedCheck && stopReason === "aborted") return false;
  // 2. 跳过来自不同模型的消息(用户已切换模型)
  if (!sameModel) return false;
  // 3. 跳过最近一次压缩之前的消息(防重复触发)
  if (assistantIsFromBeforeCompaction) return false;
  // 4. Overflow → 压缩 + 重试
  if (isContextOverflow(assistantMessage, contextWindow)) { ... }
  // 5. Threshold → 压缩
  if (shouldCompact(contextTokens, contextWindow, settings)) { ... }
}

_runAutoCompaction() — 自动压缩流程

1. compaction_start 事件(reason: manual/threshold/overflow)
2. 获取 API key + auth
3. prepareCompaction() → 选择要压缩的条目
4. session_before_compact 事件 → 扩展可 cancel / 提供自己的摘要
5. compact() — LLM 生成摘要 或 扩展提供
6. sessionManager.appendCompaction()
7. sessionManager.buildSessionContext() → 更新 agent.state.messages
8. session_compact 事件
9. compaction_end 事件
10. willRetry? → true → agent.continue()

扩展的 3 种干涉方式{ cancel: true } 阻止压缩;{ compaction: CompactionResult } 提供自己的摘要替代 LLM;{ customInstructions } 影响摘要方向。


段 8:自动重试机制(第 1600-1750 点)

_isRetryableError() — 错误模式匹配

1
2
3
4
5
6
7
8
private _isRetryableError(message: AssistantMessage): boolean {
  const err = message.errorMessage;
  if (this._isNonRetryableProviderLimitError(err)) return false;
  // 覆盖数百种错误:
  return /overloaded|provider.?returned.?error|rate.?limit|too many requests|
         429|500|502|503|504|service.?unavailable|connection.?error|connection.?refused|
         websocket.?closed|fetch failed|socket hang up|timed? out|terminated|retry delay/i.test(err);
}

正则覆盖:HTTP 429/5xx、网络错误、WebSocket 断开、超时、terminated。通过 _isNonRetryableProviderLimitError 排除"不可重试的 provider limit"(如配额用尽)。

_prepareRetry() — 指数退避

1
2
3
4
5
6
7
8
9
private async _prepareRetry(message: AssistantMessage): Promise<boolean> {
  this._retryAttempt++;
  if (this._retryAttempt > settings.maxRetries) { this._retryAttempt--; return false; }

  const delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
  // auto_retry_start 事件 → 移除错误消息 → sleep(delayMs, abortable)
  // 若 sleep 被中止 → auto_retry_end(success: false)
  return true;
}

公式baseDelayMs * 2^(attempt-1)_retryAbortController 使等待可中断。

计数器重置:收到非错误的 assistant 响应时 _retryAttempt = 0,避免跨多轮 LLM 调用累积。


段 9:Bash 执行(第 1750-1900 行)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
async executeBash(command, onChunk?, options?): Promise<BashResult> {
  this._bashAbortController = new AbortController();
  const result = await executeBashWithOperations(
    resolvedCommand,
    this.sessionManager.getCwd(),
    options?.operations ?? createLocalBashOperations({ shellPath }),
    { onChunk, signal: this._bashAbortController.signal },
  );
  this.recordBashResult(command, result, options);
  return result;
}

流式兼容:如果 agent 正在流式处理,bash 结果不立即加入 agent state(避免破坏 tool_use/tool_result 顺序),而是排队到 _pendingBashMessages,在 _flushPendingBashMessages()_runAgentPrompt 的 finally 块中)统一刷新。


段 10:会话树导航(第 1900-2200 行)

用户请求跳转到 entry X
    │
Step 1: 检查是否已在目标 → 无操作
    │
Step 2: collectEntriesForBranchSummary()
        (从当前叶节点回溯到共同祖先)
    │
Step 3: session_before_tree 事件
        (扩展可 cancel / 提供摘要 / 覆盖指令/标签)
    │
Step 4: 需要摘要?→ generateBranchSummary()(LLM)或扩展提供
    │
Step 5: 确定新叶节点位置
        ├── user 消息 → 叶=parent(编辑器恢复文本)
        ├── custom_msg → 叶=parent(恢复内容到编辑器)
        └── 其他 → 叶=选中节点
    │
Step 6: sessionManager.branchWithSummary() / branch() / resetLeaf()
        → 附加标签(label)
    │
Step 7: sessionManager.buildSessionContext() → agent.state.messages
    │
Step 8: session_tree 事件

fork / session 切换

1
2
3
4
// ExtensionCommandContextActions 中定义,由各运行模式实现
newSession(options?: { parentSession?, setup?, withSession? })
fork(entryId, options?: { position?, withSession? })
switchSession(sessionPath, options?: { withSession? })

段 11:扩展系统集成(第 2200-2650 行)

bindExtensions()_bindExtensionCore()

这是 AgentSession 和 ExtensionRunner 之间的核心桥接代码,将所有 ExtensionAPI 方法连接到 AgentSession 的内部实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
runner.bindCore(
  {
    // ExtensionActions — 扩展的 pi.sendMessage / pi.sendUserMessage 等
    sendMessage: (message, options) => this.sendCustomMessage(message, options),
    sendUserMessage: (content, options) => this.sendUserMessage(content, options),
    appendEntry: (customType, data) => this.sessionManager.appendCustomEntry(customType, data),
    setSessionName: (name) => this.setSessionName(name),
    getSessionName: () => this.sessionManager.getSessionName(),
    getActiveTools: () => this.getActiveToolNames(),
    getAllTools: () => this.getAllTools(),
    setActiveTools: (names) => this.setActiveToolsByName(names),
    refreshTools: () => this._refreshToolRegistry(),
    getCommands: () => [...extensionCommands, ...templates, ...skills],
    setModel: async (model) => { ... await this.setModel(model); return true; },
    getThinkingLevel: () => this.thinkingLevel,
    setThinkingLevel: (level) => this.setThinkingLevel(level),
  },
  { /* ExtensionContextActions — ctx.* 方法 */ },
  { /* Provider registration */ },
);

getCommands() 的三源合并:扩展命令(runner.getRegisteredCommands())+ 提示模板(this.promptTemplates)+ 技能(this._resourceLoader.getSkills().skills),统一返回用户可调用的斜杠命令列表。


段 12:工具注册与管理(第 2650-2900 行)

_buildRuntime() — 初始化工具

内置工具定义 (createAllToolDefinitions)
  + 基础工具覆盖 (baseToolsOverride)
  + SDK 自定义工具 (customTools)
  + 内置工具 AgentTool → ToolDefinition 转换
  + allowlist / denylist 过滤
  + 扩展注册的工具
        │
        ▼
_toolDefinitions Map<string, ToolDefinitionEntry>
_toolRegistry Map<string, AgentTool>  (给 agent-core 使用)
_toolPromptSnippets / _toolPromptGuidelines
        │
        ▼
_rebuildSystemPrompt() → 构建系统提示
_bindExtensionCore() → 注册到 ExtensionRunner

setActiveToolsByName() — 切换激活工具

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
setActiveToolsByName(toolNames: string[]): void {
  const tools: AgentTool[] = [];
  for (const name of toolNames) {
    const tool = this._toolRegistry.get(name);
    if (tool) { tools.push(tool); }
  }
  this.agent.state.tools = tools;                   // 更新 agent
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);  // 重建提示
  this.agent.state.systemPrompt = this._baseSystemPrompt;
}

段 13:上下文使用统计(第 2900-3050 行)

getContextUsage() — 复杂计算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
getContextUsage(): ContextUsage | undefined {
  // 压缩后:只信任压缩边界之后的 assistant usage
  // (压缩前的 usage 反映的是老的大上下文)
  const latestCompaction = getLatestCompactionEntry(branchEntries);
  if (latestCompaction && !hasPostCompactionUsage) {
    return { tokens: null, contextWindow, percent: null };  // 未知
  }
  // 正常情况:估算 token 数
  const estimate = estimateContextTokens(this.messages);
  return { tokens: estimate.tokens, contextWindow, percent: (estimate.tokens / contextWindow) * 100 };
}

段 14:导出与其他工具(第 3050-3159 行)

exportToHtml / exportToJsonl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
async exportToHtml(outputPath?: string): Promise<string> {
  // 创建工具渲染器(支持自定义工具 HTML 渲染)
  const toolRenderer = createToolHtmlRenderer({ getToolDefinition: (name) => this.getToolDefinition(name), ... });
  return await exportSessionToHtml(this.sessionManager, this.state, { outputPath, themeName, toolRenderer });
}

exportToJsonl(outputPath?: string): string {
  // 线性化分支:重新建立 parentId 链,写入 JSONL 文件
  let prevId: string | null = null;
  for (const entry of branchEntries) {
    const linear = { ...entry, parentId: prevId };
    writeFileSync(filePath, `${lines.join("\n")}\n`);
    prevId = entry.id;
  }
}

createReplacedSessionContext()

1
2
3
4
5
6
7
createReplacedSessionContext(): ReplacedSessionContext {
  // 使用 Object.defineProperties 复制惰性 getter
  const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext())) as ReplacedSessionContext;
  context.sendMessage = (message, options) => this.sendCustomMessage(message, options);
  context.sendUserMessage = (content, options) => this.sendUserMessage(content, options);
  return context;
}

语法要点Object.getOwnPropertyDescriptors():正确复制惰性 getter 的唯一方式。普通展开 { ...obj } 会立即求值所有 getter,丢失惰性特性。


关系总结

引用与被引用关系

agent-session.ts 被谁引用?
├── modes/interactive/InteractiveMode.ts        (构造 AgentSession)
├── modes/print/PrintMode.ts                    (构造 AgentSession)
├── modes/rpc/RPCMode.ts                        (构造 AgentSession)
└── ... (其他使用 AgentSession 的模式文件)

agent-session.ts 引用了什么?
├── ./extensions/*               → ExtensionRunner 和事件类型
├── ./session-manager.ts         → 会话持久化
├── ./model-registry.ts          → 模型注册中心
├── ./settings-manager.ts        → 设置管理
├── ./resource-loader.ts         → 技能/提示/主题加载
├── ./compaction/index.ts        → 压缩逻辑
├── ./bash-executor.ts           → Bash 执行
└── ... (共 ~25 个内部模块)

核心数据流

用户输入
    │
    ▼
AgentSession.prompt()
    │
    ├── input event → ExtensionRunner (扩展可拦截/转换)
    ├── skill/template 展开
    ├── auth 检查 → ModelRegistry
    │
    ▼
agent.prompt()  /  agent.continue()
    │
    ▼  (agent-core 内部循环)
├── LLM 调用 (Model + ApiKey)
├── 工具执行 → beforeToolCall/afterToolCall → ExtensionRunner
│
▼
_handleAgentEvent()
    │
    ├── _emitExtensionEvent() → ExtensionRunner → 扩展
    ├── _emit() → AgentSessionEventListener (UI/日志)
    └── sessionManager.appendMessage() → 文件持久化
        │
        ▼
    _handlePostAgentRun()
        ├── 重试? → _prepareRetry() → 指数退避
        ├── 压缩? → _runAutoCompaction() → LLM 摘要
        └── 队列? → agent.continue()

关键设计模式总结

模式位置说明
桥接模式_emitExtensionEvent()AgentEvent → ExtensionEvent 的类型转换与转发
责任链模式_handlePostAgentRun()重试 → 压缩 → 队列消费的链式检查
策略模式prompt() 中 streaming 分支steer/followUp 两种排队策略
观察者模式subscribe() / _emit()自定义 listener 订阅 AgentSession 事件
AbortController多处可中断的 sleep、compaction、bash、retry
两阶段模型解析cycleModel()–models 限定 → ModelRegistry 全局
惰性求值createReplacedSessionContext()Object.getOwnPropertyDescriptors 复制 getter
两阶段初始化_buildRuntime()bindExtensions()