# @epoch-agent/infra 中文文档

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

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

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

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

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

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

## 中文简介

epoch-agent 基础设施：路径真源、SQLite 连接与迁移、日志、平台兼容、命令安全表

## 安装

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

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

## README

# @epoch-agent/infra

基础设施层。跨模块共用的**叶子**能力。

-   ✅ **做**：路径真源、SQLite 连接与迁移、日志、凭据存储、平台兼容、命令安全启发式表、 工作区归属判定（`workspace.ts`）、外部可执行文件的探测与降级（`ripgrep.ts`）、异步小工具
-   ❌ **不做**：任何领域逻辑（ReAct / 记忆 / 权限判定都属于 [core](https://npmcn.com/package/%40epoch-agent/core)）、任何 UI
-   **依赖**：只有 node 内置模块 + `better-sqlite3` + `zod`。不依赖任何 `@epoch-agent/*` 包

判断一个东西该不该放这里：**无状态或只管进程级资源，且被两层以上共用**。

`workspace.ts` 和 `command-safety.ts` 看着像「权限判定」，其实是同一类东西： 一张**纯判据**（这个路径在不在地盘里 / 这条命令像不像手滑），谁都能问、问了不改变 任何状态。「问到之后要不要放行、要不要弹审批」才是权限判定，那部分在 core。 判据落在 infra 是因为按分层 `plugin-*` 只能依赖 protocol + infra，够不着 core —— 放 core 就意味着两个 plugin 各抄一份，而这次 `--add-dir` 就是被那三份抄本坑到才收归一处的。

## 文件

| 文件 | 内容 |
| --- | --- |
| `paths.ts` | `~/.epoch/` 下每个文件的**唯一**真源，外加机器级的 `managedSettingsPath()`。别处再 `join(homedir(), '.epoch')` 就是埋雷 |
| `artifact-store.ts` | 往 artifact 目录写文件的**唯一**写法：`0700` 目录 / `0600` 文件 / `open(path,'wx')` 独占写。core 的多模态闸门和 plugin-terminal 的大输出落盘共用 |
| `text-slice.ts` | 按 **Unicode 码点**取头尾，截断不许切出半个字符（emoji 和 CJK 扩展 B 是代理对）。命令输出预览和工具结果裁剪共用 |
| `workspace.ts` | `isInWorkspace(target, workDir, extraRoots)`——「这个路径算不算在地盘里」的**唯一**判定，权限层 / plugin-file / plugin-terminal 共用 |
| `sqlite.ts` | 连接池（引用计数）+ 迁移。`openDatabase` / `releaseDatabase` 配对使用 |
| `logger.ts` | 结构化日志 |
| `jobs.ts` | **长期子进程作业表的真源**（`t1` 后台命令 / `s1` 持久会话 / `a1` 后台子 agent 共用一张表，kind 无关）。`task_*` 与 `shell_*` 两组工具都读它，判据在文件头 —— 2026-11-03 补进这张表（上一版漏了它，而 core / plugin-terminal 两份 README 都在指它） |
| `config-yaml.ts` | `config.yaml` 的**按行手术**（改一行不重写整份，用户的注释和顺序一个字节不动）+ 手写解析与 schema 校验的接合点。`epoch config set` 与设置页都走它 |
| `mask.ts` | 脱敏（`maskApiKey` / `isSensitiveKey`），`epoch config show` 靠它 |
| `platform.ts` | Windows / POSIX 差异：shell 选择、spawn 与 pty 参数、控制台编码探测与解码 |
| `ripgrep.ts` | `rg` 从哪来的分档解析（几档、每档说什么，真源是文件头那张表）：系统 PATH → 随包带的 vendor 二进制 → 都没有。嵌入宿主里 vendor 那一档会绕开 `app.asar`、改取 `app.asar.unpacked` 里那份（`unpackAsarPath`）。`file_search` 和将来的 `@` 补全共用 |
| `git-probe.ts` | `canProbeGit()`——后台探测 git 之前先问的那一句。mac 上 `/usr/bin/git` 是命令行开发者工具的**存根**（和 `clang` / `python3` / `swift` 同一个 inode），没装过工具的机器上一执行就弹系统安装框，而那个框 `stdio: 'ignore'` 挡不住、`catch` 也接不住。三处调用点共用：`getGitStatus()`、`@` 补全的 `listWorkspaceFiles()`、工作区 diff 的 `runGit()`。拦下来一律退化不报错 |
| `process-tree.ts` | 进程树清理——杀干净孙进程，且只杀该杀的 |
| `spawn-guard.ts` | `guardSpawn(start, onSpawnError)`——起子进程那一下的**同步**失败通道。node 只把 `EACCES / EAGAIN / EMFILE / ENFILE / ENOENT` 转成异步 `error` 事件，名单外的（最常见 `ENOTDIR`：`cwd` 不是目录、`PATH` 里某一项是文件）**同步抛**，`execFile` 连回调都不调。全仓每一处 `spawn` / `execFile` 都要经过它，由根 `__tests__/spawn-guard-callsites.test.ts` 扫 AST 守着（六处登记在案的例外自己用 try/catch 接住了） |
| `child-process.ts` | 长期子进程（后台任务 / language server / PTY）的启动、登记、回收。**`start()` 起不来时返回 `null`**（2026-09-20，同步抛那一族）。**表按所有者分**：每个 runtime 一张（`createProcessTable()`，按 `sessionId` 路由）+ 一张进程级兜底表（引用计数，归零才收）—— 判据和那条真 bug 写在文件头。`detached: false` 的理由也在那儿。还有 `EXEC_PATH_AS_NODE_ENV`——「拿 `process.execPath` 当 node 使」的地方必须带的那一格（嵌入 Electron 的宿主里它不是 node），两个消费者分在 core 和 plugin-lsp |
| `shell-parse.ts` | 命令分词与分段（引号感知，带 POSIX / Windows 两种口味）。危险判定、权限规则匹配、斜杠命令的位置参数都建在它上面 |
| `command-safety.ts` | 危险命令与混淆的启发式表，建在 `shell-parse` 之上。**是护栏不是沙箱** |
| `sandbox/` | **这个才是沙箱**：Seatbelt（macOS）/ bubblewrap（Linux，运行时暂未验证）/ Windows 的受限令牌 + 完整性标签（2026-11-11 接上，只覆盖写与删，`enforcement` 恒为 `partial`）/ 探测不到就降级。`confine()` 按调用带 policy 并如实报强制完整度；两组 stderr 分类器分开「被挡住」和「沙箱没起来」。2026-08-16 从 core 搬来，理由同 `command-safety.ts`：`code_exec` 和 `plugin-terminal` 共用，而 plugin 够不着 core |
| `schema.ts` | 配置校验（zod + 宽松解析，坏字段降级而不是整份拒绝） |
| `i18n.ts` | `t()` / 语言解析 / catalog 加载与三层回落。文案真源是仓库根的 `locales/*.yaml` |
| `async.ts` | 异步小工具 |
| `secret/` | 凭据存储：DPAPI（Windows）/ Keychain（macOS）/ libsecret / 明文回落 + 迁移 |

## 界面文案：`t()` 与三层回落

`i18n.ts` 也是一个进程级状态（`setLang('zh' | 'en')`，形状同 `setShell`）。 `Lang` 的值域在这里和 [protocol](https://npmcn.com/package/%40epoch-agent/protocol) 各写了一份 —— 同 `ShellKind`， 本包零内部依赖，import 不了 protocol；根 `__tests__/i18n-catalog.test.ts` 里有一条用例锁着两边**逐项相等**。

文案的真源是**仓库根的 `locales/{zh,en}.yaml`**，不在本包里。发布时由 `scripts/copy-locales.mjs` 在 `tsup` 之后拷进 `dist/locales` （`files: ["dist"]` 只发 dist，不拷就等于装了包的人满屏 key 路径）。

`t()` **永远返回字符串、永远不抛**，三层回落：

```
目标语言的 key → zh（DEFAULT_LANG）的 key → 返回 key 路径本身
```

第三层是关键：catalog 写坏了不能让 agent 起不来。同一条原则下，坏 YAML / 缺文件 只产出 `i18nDiagnostics()` 里的一条 warn，宿主拿去塞进启动诊断。 语言怎么选出来（`EPOCH_LANGUAGE` → `display.language` → 系统 locale → 中文） 见 [docs/CONFIGURATION.md](https://npmcn.com/package/docs/CONFIGURATION.md#%E7%95%8C%E9%9D%A2%E8%AF%AD%E8%A8%80)。

## Windows 上的 shell 选择

`platform.ts` 里有一个进程级状态：`setShell('cmd' | 'powershell' | 'pwsh')`， 装配层（[runtime](https://npmcn.com/package/%40epoch-agent/runtime)）读完配置调一次，`shellSpawnArgs` / `shellPtyArgs` 据此拼参数。形状跟 `setLogLevel` / `setSecretStore` 一致。POSIX 上是**空操作**， 永远 `/bin/sh` —— macOS 走的就是这一支。

只收这三个具名值，**不收任意可执行文件路径**：每加一种都要重新验「引号怎么转义」 和「退出码怎么传」两件事，放开成任意路径等于承诺一件没验过的事。

PowerShell 那条路上有两个**不报错**的坑，都写进了 `platform.ts` 的注释：

-   PowerShell 5.1 不传播被调命令的退出码（实测 `-Command "cmd /c exit 3"` → `1`）， 所以命令后面必须接一段同时读 `$?` 和 `$LASTEXITCODE` 的收尾语句。只读后者更糟： 命令不存在时它是 `$null`，`exit $null` → **0**，一次彻底失败被报成成功
-   命令和收尾之间必须是**真换行**，不能用 `;`。用 `;` 的话命令末尾带一句 `# 注释` 就把整段收尾注释掉了，实测 `cmd /c exit 5 # note` 退出码变成 `1`

`ShellKind` 的值域在这里和 [protocol](https://npmcn.com/package/%40epoch-agent/protocol) 各写了一份——本包零内部依赖， import 不了 protocol。[core](https://npmcn.com/package/%40epoch-agent/core) 里有一条用例锁着两边相等。

## 凭据存储的后端顺序

`secret/catalog.ts` 按平台探测可用后端，探不到就降级，最后回落到 `~/.epoch/.env` 明文。 `epoch config secret` 打的就是这个判定结果。

> ⚠️ **POSIX 分支不等于 Linux 分支，别顺手删。** macOS 走的就是 POSIX 那一支 （`pgrep` / `kill` 进程树、`/bin/sh`、危险命令表的 POSIX 那张）——删这些等于砍掉 macOS。真正只有 Linux 会走到的是显式判 `platform() === 'linux'` 的分支 （`secret/libsecret.ts`）：Linux 暂不支持，所以那条路径**尚未实测**，但代码留着， 将来补 Linux 支持时从它接着往下走。

## 开发

```bash
pnpm --filter @epoch-agent/infra test
```

## 常见问题

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

epoch-agent 基础设施：路径真源、SQLite 连接与迁移、日志、平台兼容、命令安全表

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

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

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

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

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

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

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

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

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

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

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

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

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

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

