context-mode stdin JSON 协议详解:上下文优化 Hooks 与 17 个平台间的线协议完全指南
【免费下载链接】context-modeContext window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms via MCP + hooks.项目地址: https://gitcode.com/GitHub_Trending/cl/context-mode
context-mode 是一款面向 AI 编程助手的上下文优化(context window optimization)MCP 插件:它通过沙箱化工具输出(最高 98% 上下文缩减)、持久化会话记忆,以及基于 MCP + Hooks 的强制路由,让 Claude Code、Cursor、Gemini CLI 等 17 个平台的 Agent 不再把原始字节塞进上下文窗口。本文带你读懂它最容易被忽视、却撑起整个架构的底层机制——Hooks 与宿主平台之间的 stdin JSON 线协议:数据怎么进、决策怎么出、跨平台差异如何抹平。
什么是 stdin JSON 线协议:先搞清楚这条"线"
很多 Hook 系统的本质是"事件订阅 + 进程管道"。当宿主平台(Claude Code、Cursor、Codex CLI……)即将执行某个工具调用时,它会fork 一个node X.mjs子进程,把事件载荷以 JSON 写入该进程的 stdin;Hook 进程读完后,再把一行 JSON 决策写回stdout,宿主据此决定放行、拦截、改写或注入上下文。
这条"stdin 进 / stdout 出"的通道就是 context-mode 的线协议(wire protocol)。全部 17 个平台适配层最终都收敛到同一套核心模块:
- 读入侧:hooks/core/stdin.mjs(跨平台 stdin 读取器)
- 解析侧:hooks/session-helpers.mjs(
parseStdin/getSessionId/getInputProjectDir) - 决策侧:hooks/core/routing.mjs(归一化路由决策)
- 输出侧:hooks/core/formatters.mjs(平台特化的 JSON 渲染)
事件类型在 hooks/hooks.json 中注册,共 6 类:PreToolUse、PostToolUse、UserPromptSubmit、PreCompact、SessionStart、Stop。
入站载荷:宿主推送了哪些字段
事件载荷的常见字段
各平台推送的 JSON 字段名并不统一,context-mode 用一套"多候选"解析策略兼容它们:
| 字段 | 用途 | 备注 |
|---|---|---|
tool_name/tool_input/tool_response | 工具名、入参、输出 | PostToolUse的核心素材 |
cwd/workspace_roots | 项目目录 | 优先级最高的定位来源 |
session_id/sessionId/conversation_id/transcript_path | 会话标识 | 多候选,按优先级回退 |
agent_id/agent_type | 子代理上下文标记 | 用于关闭 MCP 重定向 |
mcp_servers/model/permission_mode | 会话设置快照 | SessionStart信封字段 |
会话 ID 的五级回退链
不同宿主对"当前会话是谁"的表达方式五花八门。hooks/session-helpers.mjs 中的getSessionId定义了明确的回退优先级:
- 从
transcript_path文件名里正则提取 UUID(Claude Code 的 transcript 文件名即会话 ID) conversation_id(Antigravity CLI)sessionId(camelCase 写法)session_id(snake_case 写法)- 平台专属环境变量(如
CURSOR_SESSION_ID) - 最终兜底:
pid-<父进程PID>
项目目录同理——getInputProjectDir按input.cwd→input.workspace_roots[0]→ 平台环境变量(CLAUDE_PROJECT_DIR/CURSOR_CWD/VSCODE_CWD等)→process.cwd()的顺序回退(见 hooks/session-helpers.mjs)。每个平台对应一组"平台选项"(CLAUDE_OPTS、GEMINI_OPTS、CODEX_OPTS……),集中定义在 hooks/session-helpers.mjs。
出站决策:一个路由决策,四种动作
PreToolUse是最核心的拦截点。hooks/core/routing.mjs 定义了一套与平台无关的归一化决策对象:
{ action: "deny", reason: "..." } // 拦截并给出理由 { action: "ask" } // 请求用户确认 { action: "modify", updatedInput: {...} } // 改写工具入参 { action: "context", additionalContext: "..." } // 向模型注入引导 null // 直通,不做任何事典型流程见 hooks/pretooluse.mjs:读 stdin →routePreToolUse()产出决策 →formatDecision()翻译成平台 JSON →stdout 写入是最后一步,进程随即退出,保证 Hook 延迟最小化。
平台差异如何被"格式化器"抹平
同一个决策,不同宿主只认不同的 JSON 形状。hooks/core/formatters.mjs 为 9 个 Hook 平台各写了一套渲染器,几个代表性差异:
| 平台 | deny 的形状 | 特殊行为 |
|---|---|---|
| Claude Code | hookSpecificOutput.permissionDecision | Bash 改写被宿主忽略,降级为带引导文案的 deny |
| Gemini CLI | 顶层{decision, reason} | 没有 "ask" 概念,直接返回 null |
| Cursor | {permission, user_message} | 用agent_message注入上下文 |
| Codex | hookSpecificOutput | 旧版本不支持改写命令时fail closed:把重定向转成可执行的 deny |
| Kimi | 仅识别 deny | ask / modify / context 一律返回 null(宿主解析器只认 deny) |
这种"能力探测 + 降级"策略是线协议的关键设计:宁可收窄表达,也不发出宿主会拒绝或忽略的形状,避免字节洪水守卫被静默绕过(见 hooks/core/formatters.mjs 中 codex 与 kimi 的实现注释)。
健壮性细节:超时、BOM 与崩溃兜底
stdin 空闲超时:四种结局
读 stdin 看似简单,实则踩过一堆跨平台坑。hooks/core/stdin.mjs 采用事件驱动的 flowing 模式(规避for await在 macOS 管道下的挂起、readFileSync(0)在 Windows 上的 EOF/EISDIR),并定义了带空闲超时(默认 1500ms,可用CONTEXT_MODE_HOOK_STDIN_IDLE_MS调整)的四种结局:
| 场景 | 结局 |
|---|---|
| EOF 且无数据 | 返回空串,Hook 正常 no-op |
| EOF 且有数据 | 返回缓冲区,并剥离 BOM(Cursor on Windows 会发 U+FEFF 前缀) |
| 空闲且 0 字节 | 返回空串——覆盖"宿主一直开着管道却不关"的场景 |
| 空闲但有部分字节 | 抛错退出——宁可显式失败,也不让截断的 JSON 污染下游JSON.parse |
崩溃兜底:Hook 永不返回非零
hooks/run-hook.mjs 是统一的"防弹外壳":所有 Hook 用await runHook(async () => {...})包裹,动态 import 业务模块,任何异常都被写入<configDir>/context-mode/hook-errors.log并以exit 0结束——因为宿主会把非零退出当作"每次工具调用都报错"展示给用户,形成告警轰炸。
跨 Hook 协作:tmpdir 标记文件
同一次工具调用前后是两个独立进程,它们通过临时目录里的标记文件"接力":PreToolUse 写入context-mode-rejected-<sessionId>.txt(被拒原因)和context-mode-redirect-<sessionId>.txt(被拦截的字节数,格式tool:type:bytesAvoided:summary),PostToolUse 读取后落库并删除(见 hooks/posttooluse.mjs)。这样ctx stats才能准确统计"每次重定向帮你省了多少字节"。
一图看懂:协议支撑起 17 个平台
核心文件速查表
- 线协议总入口(事件注册):hooks/hooks.json
- stdin 读取器(超时/BOM 语义):hooks/core/stdin.mjs
- 载荷解析与会话/项目定位:hooks/session-helpers.mjs
- 归一化路由决策:hooks/core/routing.mjs
- 平台特化输出格式化:hooks/core/formatters.mjs
- 崩溃兜底外壳:hooks/run-hook.mjs
- 三个典型 Hook 实现:hooks/pretooluse.mjs、hooks/posttooluse.mjs、hooks/sessionstart.mjs
- 平台适配层源码:src/adapters/(17 个平台各自的路径、Hook 注册与用量解析)
小结:为什么这条"线"值得你关注
- 入站:宿主 fork 子进程并把事件 JSON 写入 stdin;context-mode 用多级回退从
tool_name、session_id、cwd等字段中稳定提取会话与项目身份。 - 出站:路由层只产出 4 种归一化决策(deny/ask/modify/context),格式化器负责把决策"翻译"成每个宿主只认的 JSON 形状,并对不支持的能力优雅降级。
- 健壮性:空闲超时防挂起、BOM 剥离防解析崩溃、exit-0 外壳防告警轰炸、标记文件实现跨 Hook 协作——每一个设计背后都是一个真实世界的平台怪癖。
理解了这条 stdin JSON 线协议,你就理解了 context-mode 为什么能在 17 个行为各异的平台上保持同一套上下文优化语义——协议是统一的,差异都被收敛在了格式化器里。
【免费下载链接】context-modeContext window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms via MCP + hooks.项目地址: https://gitcode.com/GitHub_Trending/cl/context-mode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考