# @epoch-agent/protocol 中文文档

规范地址：https://npmcn.com/package/%40epoch-agent/protocol/

版本：6\.2\.0
npm 元数据更新时间：2026-11-13T08:32:37.632Z
中文译文更新时间：2026-11-13T19:50:34.905Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40epoch-agent/protocol
原文对照：https://npmcn.com/package/%40epoch-agent/protocol/?lang=original
项目主页：https://github.com/Ddbor/epoch-agent#readme
源码仓库：https://github.com/Ddbor/epoch-agent

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：1.2 MB，5 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.0\.0
- 运行依赖：0 个
- 首次发布：2026-08-19T10:31:59.486Z
- 最新版本发布：2026-11-13T08:32:37.396Z
- 近 30 天下载量：6694（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：57（近 12 个月 57 个）

## 中文简介

epoch-agent 契约层：工具 / 事件 / 权限 / 插件的类型定义，零运行时依赖

## 安装

```sh
npm install @epoch-agent/protocol
```

也可以使用：`pnpm add @epoch-agent/protocol`、`yarn add @epoch-agent/protocol`、`bun add @epoch-agent/protocol`

## README

# @epoch-agent/protocol

契约层。跨包边界上的类型与常量，没有任何实现。

-   ✅ **做**：定义跨包共用的类型 / 常量（工具、事件、权限、插件、Hook、配置、线协议）， 以及**纯粹作用在这些类型上的口径函数**（`contentToText` / `promptTokens` 这种）—— 它们跟着类型走，不跟着某个包走，而且 [tui](https://npmcn.com/package/%40epoch-agent/tui) 只依赖 protocol， 放别处它就够不着，只能自己抄一份
-   ❌ **不做**：领域逻辑、任何 I/O、任何状态
-   **依赖**：无。零 runtime 依赖，谁都可以依赖它

判断一个类型该不该放这里：**是否被两个以上的包共用**。只有 core 和它的直接下游用到的 （比如 `SessionManager` 的输入输出类型）不放这里，留在实现包。

## 文件

| 文件 | 内容 |
| --- | --- |
| `tool.ts` | `EpochTool` / `ToolContext` / `ToolResult` / `ToolExposure`。输出预算那两格（`ToolContext.outputBudget` 读额度、`EpochTool.governsOwnOutput` 免截）的判据在各自的 JSDoc 上，嵌入宿主那侧的说明在 [EMBEDDING.md](https://npmcn.com/package/docs/EMBEDDING.md#%E5%B7%A5%E5%85%B7%E8%BE%93%E5%87%BA%E9%A2%84%E7%AE%97outputbudget) |
| `plugin.ts` | `EpochPlugin` —— 第三方加工具的唯一入口 |
| `events.ts` | `AgentEvent` 判别联合 —— 引擎与所有宿主之间的事件契约、`TokenUsage` |
| `usage.ts` | `TokenUsage` 的口径算术 + **压缩那条线**（`compressionThresholdTokens`） |
| `message.ts` | `EpochMessage` 及其内容块（文本 / 图片 / `@` 提到的文件 / `@:` 引用的会话）。加第五种部件时 `assertNeverPart` 会让漏掉的分支在编译期红 |
| `permission.ts` | 4 级权限（`default` / `acceptEdits` / `plan` / `auto`；`bypass` 是 `auto` 的老别名）、操作类型、审批请求 / 应答、`PermissionManager` 接口 |
| `plan.ts` | plan **模式**（临时只读区间，出口要人批一次）—— 与 `plan` 那个权限级别的口径 |
| `question.ts` | 结构化提问的请求 / 答复（**不过权限层** —— 提问不是审批） |
| `hook.ts` | Hook 类型与 trigger |
| `config.ts` | 配置形状 |
| `sidebar-menu.ts` | 宿主自定义的那几格侧栏菜单：**只有一项长什么样**（2026-11-06 起消息契约搬去 `wire-host-events.ts`）。**只有 Web UI 读它**，但契约得住这儿（`EpochConfig` 和 `WireConfigResponse` 都引它） |
| `wire-host-events.ts` | 宿主通信那条正规通道：\`POST |
| `context.ts` | 上下文预算的构成（`/context` 那张账目：每一段花了多少；每段带 `kind`、建议带结构化的 `notes`，消费方按自己的语言翻） |
| `task.ts` | 后台任务的形状（宿主要展示它们：状态栏计数、`/tasks`、Web 那一栏） |
| `keybindings.ts` | 键位的**值域**（动作全集 + `KeyChord` + 与 `"ctrl+l"` 写法互转）。默认表在 tui |
| `i18n.ts` | 界面语言的值域（`Lang` / `LANGS` / `DEFAULT_LANG`）—— **封闭的两个值**；外加 API URL 上那个 `WIRE_LANG_PARAM`（方案 58）。**2026-08-22 起还有纯查表**（`makeTranslate` / `flattenCatalog` / `placeholdersOf`，方案 40 PR-2）—— 消费方是 tui 和 web，它们俩连 infra 都不许依赖。分界是「有没有 I/O 和状态」：读盘和进程语言仍在 infra |
| `model-ref.ts` | `<provider>/<model>` 的解析 |
| `agent-role.ts` | 子 agent 角色（`delegate_task` 的 role 取值） |
| `commands.ts` | 自定义斜杠命令的定义 / 展开结果 / 内置命令保留名单 |
| `mentions.ts` | `@` 提及的抽取规则：`@路径`（文件）和 `@:xxx`（会话，方案 53）**走同一条正则**。TUI 和引擎共用一份，两边写岔了会静默错位 |
| `system-note.ts` | 「这条 `user` 消息其实是系统在说话」那个前缀 + 拼 / 剥两个函数。**四处共用，且必须逐字节相同**，理由在它的文件头 |
| `secret.ts` | 凭据存储后端的接口 |
| `trust.ts` | 工作区信任的判定结果 |
| `schedule.ts` | 定时任务：三档触发器（判别联合）、一条任务 / 一次运行的形状、欠条、宿主注入点 |
| `schedule-pending.ts` | 「还欠着的那几张欠条」那份投影 + `unfixedRules()`。**那个集合运算落地前在仓库里有三份**（server / cli / 假服务端），而它是提权路径上的判定 |
| `diagnostics.ts` | 启动诊断（`DiagnosticSink`，`epoch doctor` 打的就是这些）。`detail` 收 `string | LocalizedDetail` —— 后者能按语言重放，`toList(lang)` 是那条路 |
| `telemetry.ts` | `Telemetry` / `SpanLike` / GenAI 语义约定常量 / `NOOP_TELEMETRY` |
| `artifact.ts` | 大块工具输出的落盘引用 |
| `wire.ts` | 线上的信封（`WireEnvelope` / `WireHubEvent` / `WireAuthGuard`） |
| `wire-rest.ts` | Web UI 的 REST 载荷（`Wire*Response`）与鉴权常量 |
| `wire-capability.ts` | 能力页那一个端点的载荷。**单开一个文件**是因为 `wire-rest.ts` 当时已经 788 行（⚠️ 那是**当时的理由**；该文件今天 4232 行，判据没变 —— 新端点别再往里堆） |
| `wire-host-badges.ts` | 宿主未读点（`POST/GET /api/host/badges` + `host-badges` 帧）：运行期状态不是配置，补齐为什么走流里那一帧而不是一发 GET，判据在它的文件头 |
| `wire-host-topbar.ts` | 顶栏左侧那排宿主按钮的运行期清单（`PUT/GET/DELETE /api/host/topbar` + `host-topbar` 帧）。**两件事它单独说的**：为什么这一族是「整份替换」而托盘那族是「逐项覆盖」（两条判据不打架的账），以及 `items: []`（宿主清空了）和 `items` 缺省（撤掉运行期那份、回落配置）是两句话 |
| `wire-security.ts` | 安全中心那五块。**全是读** —— 一个写字段都没有，判据在它的文件头 |
| `wire-permission.ts` | 改**这个会话此刻**的权限档那一对（决定 20 ③）。和上一行不是一回事，见下 |
| `wire-settings.ts` | 设置页：每一项赢在哪一层 + 能写进哪几层。见下面「设置那一份」 |
| `wire-goal.ts` | 目标那一格（方案 52 PR-4）：四态 + `blocked` 两字段 + 界面要而库里没有的那四把尺子。⚠️ `WireGoalPhase` / `WireGoalRefusal` 是 **core 那两个联合的镜像**（web 够不着 core），逐项相等由 `core/__tests__/goal-wire.test.ts` 钉着；写那条路上**表达不出 `blocked`**，判据在它的文件头 |
| `headless-wire.ts` | 程序驱动 `epoch` 时 stdin / stdout 的形状（`init` / `result` + 五个入站事件） |

> **第三方要接这套 wire 自己做前端**：这几个文件就是契约，直接 `import type`， 别照着文档抄（文档会过期，类型不会）。接法见 [docs/EMBEDDING.md §10](https://npmcn.com/package/docs/EMBEDDING.md)。

> ⚠️ **`wire-security.ts` 和 `wire-permission.ts` 别按「都带 permission」并起来。** 前者是**安全中心那一屏**的载荷，全是读，答的是「规矩是什么」（规则表、遮蔽 发现、托管锁、信任、策略、审计流水）；后者是**输入框框外那一行**上那一格， 有写，答的是「这个会话现在是哪一档、改不改得动」。 两者的耦合是真的（`WirePermissionState.level` 就是 `WirePermissionStatus.level` 那一格，`WireBlockedLevel` 是 `WireManagedLock.bypassDisabled` 的投影）， 所以后者 `import type` 了前者那两个 —— 判据写在它的文件头， 拆文件不等于解耦。

### 设置那一份（`wire-settings.ts`）

它发的不是值，是**值加上它赢在哪一层**（epoch 的设置有六层来源，见 [docs/SETTINGS.md](https://npmcn.com/package/docs/SETTINGS.md)）—— 只显示「当前值」的设置界面会在 「用户改了一个开关、被上面某一层压回去」时撒谎。

⚠️ 这份契约里最要紧的一个字段是 `WireSettingWrite.futile`，而它在**读**响应上： 写进的层压不过当前赢的那一层时，那个改动写了等于没写，**而这一句必须在用户按下 保存之前说出来**。做成写响应的一个字段就迟了 —— 那时它只是一句事后通知。

`WireSettingApply` 是另一条容易接错的：写完这个进程**不会重读配置**，所以落盘 之后 `GET` 回来的值一个字都不会变。别乐观更新那一行 —— 那会显示一个这台机器上 哪儿都不存在的状态。

## 改这里要注意

`wire.ts` 的信封有**两个出口**：`epoch web` 的 SSE 和 `epoch --output-format stream-json` 的 stdout NDJSON。共用是刻意的 —— `fixtures/wire/*.json` 那几份录像因此能同时当两边的夹具，`seq` 的语义也只有一份。 `headless-wire.ts` 只加了两个生命周期事件，信封本身一个字段没动。

`wire.ts` / `wire-rest.ts` 是 [server](https://npmcn.com/package/%40epoch-agent/server) 和 [web](https://npmcn.com/package/%40epoch-agent/web) 共用的**同一份**形状： 假服务端（`web/dev/fake-server.ts`）的每个响应体都挂 `satisfies Wire*Response`， 所以字段名漂了两边一起编译不过。这是故意的——之前假服务端自己编了一套形状， 于是「先对假服务端验前端」这条验收顺序失去了意义。

`events.ts` 的 `AgentEvent` 加成员时，[view](https://npmcn.com/package/%40epoch-agent/view) 的 reducer 要跟着处理， 否则 TUI 和 Web 会静默丢事件。**先想清楚谁发** —— `diff` 那一条在这个联合里 声明了、消费了、渲染了整整一轮，中间一个发射方都没有（判据写在它的 JSDoc 上）。

`run-start`（2026-08-15）是这个联合最新的一条，由 core 在**第一次模型请求之前、 预算闸门之后**发，一次 `run()` 最多一条。它带的 `estimatedTokens` 是 **运行前估算**，界面上必须挂「估算」字样 —— 2026-08-15 换了真分词器 （`o200k_base`）之后偏差小了，但它仍然是发之前算的，真值只有 provider 回的 `usage` 说了算。判据全文在 `provider/tokenizer.ts` 的文件头末尾。

`agent-role.ts` 的 `AgentRoleScope` 分「子 agent」和「会话」两档，引擎拿它**只** 挑 system prompt 的身份行；工具收窄、轮次预算、prompt 追加段两档完全一样。 **逐条挑专家（方案 57，2026-08-17）没有第三档**：`session` 那句身份行是 「这一次以「x」的身份工作」，「这一次」在逐条语境下正好是对的 —— 两者在模型读到的 字节上没有区别。网线上那一格是 `WireSendMessageRequest.role`（缺席 = 用这段会话的 角色，认不出来是 400 `unknown-role`），完整判据在 `AgentRoleScope` 的文件注释里。

`WireSessionSummary.workspace` 是**三档判别联合**（`bound` / `none` / `unknown`， 2026-08-19），不是一个可空引用 —— 这是一次**破坏性 wire 变更**（同 `{binding}` 那次：同一个 monorepo 里 `packages/web` 是唯一消费方，同批改）。换掉它的理由和 `{workspace: … | null}` → `{binding}` 那次逐字同源：那个 `null` 一个值扛了 「明确不使用工作区」和「这个进程说不出它在哪儿干过活」两件事，而**侧栏把这一行 归到哪一组，正好取决于这两者的差别**。三档的取值优先级（内存绑定 → `live` → 库里落着的那次决定 → `unknown`）在 server 的 `sessions/summary.ts`， **这边不抄第二份**。⚠️ 同一个类型上另外三格（`pendingSince` / `turnStartedAt` / `lastFinish`）**照旧是可空的、也照旧只有 Hub 手里那些会话才有** —— 它们是运行时活性，进程一退就真的不存在；`workspace` 能落盘是因为它是 **用户的一次决定**。

`TokenUsage` 的五个 token 字段**有两种不同的包含关系**（缓存两项与 `inputTokens` 不重叠、`reasoningTokens` 含在 `outputTokens` 里），口径表就写在 `events.ts` 上。 要「一共多少 token」或「提示词多大」，用 `usage.ts` 的两个函数， **别自己把字段加一遍** —— 漏掉缓存那两项的症状是上下文占用显示成个位数百分比， 而且只在开了 prompt caching 的 provider 上才出现。

同一个文件里还有\*\*「快满了」那条线\*\*（2026-08-16）： `compressionThresholdTokens(contextLength, threshold)` 和 `isContextNearlyFull(prompt, contextLength, threshold)`。它们住在这儿的理由和上面 那两个逐字相同 —— **两个必然的消费方，必须给出同一个数**：引擎那边是 `ContextCompressor.shouldCompress()`（真的触发压缩的那一处），界面那边是设置页 「记忆与上下文」里那句「压缩在 64,000 token 时开始」。各写一遍的症状极难查： 界面说「还早」，下一轮却压了。

⚠️ 它只是**那条线**，不是「会不会压」的完整答案 —— 压缩器上还有冷却期和熔断， 两者都只会让压缩更晚发生。所以界面拿它画的是「到线了」，不是「下一轮一定压」。

## 常见问题

### @epoch-agent/protocol 是什么？

epoch-agent 契约层：工具 / 事件 / 权限 / 插件的类型定义，零运行时依赖

### 如何安装 @epoch-agent/protocol？

在项目目录运行 npm install @epoch-agent/protocol，或使用 pnpm add / yarn add / bun add。

### @epoch-agent/protocol 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @epoch-agent/protocol 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @epoch-agent/protocol 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @epoch-agent/protocol 可以商用吗？

@epoch-agent/protocol 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @epoch-agent/protocol 的下载量有多少？

根据 npm 官方下载统计，@epoch-agent/protocol 在 2026/10/14 – 2026/11/12 这 30 天内被下载 6,694 次，日均约 223 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @epoch-agent/protocol 还在维护吗？

最新版本 6\.2\.0 发布于 2026/11/13（今天）；至今共发布 57 个版本，其中过去 12 个月发布了 57 个；首次发布于 2026/08/19。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

