@epoch-agent/protocol

中文文档
6.2.0发布于 2026/11/13Apache-2.0TypeScriptES Module零依赖近 30 天下载 6694

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

中文使用文档
文档目录3 节
  1. 文件
  2. 设置那一份( wire-settings.ts )
  3. 改这里要注意

@epoch-agent/protocol

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

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

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

文件

文件内容
tool.tsEpochTool / ToolContext / ToolResult / ToolExposure。输出预算那两格(ToolContext.outputBudget 读额度、EpochTool.governsOwnOutput 免截)的判据在各自的 JSDoc 上,嵌入宿主那侧的说明在 EMBEDDING.md
plugin.tsEpochPlugin —— 第三方加工具的唯一入口
events.tsAgentEvent 判别联合 —— 引擎与所有宿主之间的事件契约、TokenUsage
usage.tsTokenUsage 的口径算术 + 压缩那条线(compressionThresholdTokens)
message.tsEpochMessage 及其内容块(文本 / 图片 / @ 提到的文件 / @: 引用的会话)。加第五种部件时 assertNeverPart 会让漏掉的分支在编译期红
permission.ts4 级权限(default / acceptEdits / plan / auto;bypass 是 auto 的老别名)、操作类型、审批请求 / 应答、PermissionManager 接口
plan.tsplan 模式(临时只读区间,出口要人批一次)—— 与 plan 那个权限级别的口径
question.ts结构化提问的请求 / 答复(不过权限层 —— 提问不是审批)
hook.tsHook 类型与 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.tsTelemetry / SpanLike / GenAI 语义约定常量 / NOOP_TELEMETRY
artifact.ts大块工具输出的落盘引用
wire.ts线上的信封(WireEnvelope / WireHubEvent / WireAuthGuard)
wire-rest.tsWeb 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。

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

设置那一份(wire-settings.ts)

它发的不是值,是值加上它赢在哪一层(epoch 的设置有六层来源,见 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 和 web 共用的同一份形状: 假服务端(web/dev/fake-server.ts)的每个响应体都挂 satisfies Wire*Response, 所以字段名漂了两边一起编译不过。这是故意的——之前假服务端自己编了一套形状, 于是「先对假服务端验前端」这条验收顺序失去了意义。

events.ts 的 AgentEvent 加成员时,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 共发布 57 个版本,首次发布于 2026/08/19;过去 12 个月发布了 57 个版本。

版本发布日期距今
6.2.0latest2026/11/13今天
6.1.02026/11/112 天前
6.0.02026/11/112 天前
5.0.02026/11/103 天前
4.1.12026/11/067 天前
4.1.02026/11/067 天前

关于 @epoch-agent/protocol 的常见问题

@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 和提交记录判断。