@epoch-agent/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,219 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
203
+
204
+ --------------------------------------------------------------------------------
205
+
206
+ epoch-agent
207
+ Copyright 2024-2026 bowen
208
+
209
+ Portions of this software are derived from the Gemini CLI
210
+ (https://github.com/google-gemini/gemini-cli), Copyright 2025 Google LLC,
211
+ licensed under the Apache License, Version 2.0. Those files have been modified;
212
+ each one retains its original copyright notice and SPDX-License-Identifier
213
+ header. See the NOTICE file in the project source repository for the complete
214
+ list of derived files.
215
+
216
+ This software also quotes, with attribution, two lines of prose from OpenAI
217
+ Codex (https://github.com/openai/codex), Copyright 2025 OpenAI, licensed under
218
+ the Apache License, Version 2.0. No source file is a derived work of Codex.
219
+ See the NOTICE file for details.
package/README.md ADDED
@@ -0,0 +1,297 @@
1
+ # @epoch-agent/core
2
+
3
+ Agent 引擎。ReAct 循环、Provider 路由、权限、记忆、技能、Hook、Policy、插件、上下文压缩。
4
+
5
+ - ✅ **做**:领域逻辑
6
+ - ❌ **不做**:装配(在 [runtime](../runtime))、命令解析(在 [cli](../cli))、
7
+ 渲染(在 [tui](../tui) / [web](../web))、任何 UI
8
+ - **依赖**:[protocol](../protocol) + [infra](../infra) + `ai`(Vercel AI SDK)+
9
+ `better-sqlite3` + `gpt-tokenizer` + `js-yaml` + `zod`
10
+
11
+ > 嵌入方一般**不直接装这个包**,装 [runtime](../runtime)——它把 core 的十几个服务
12
+ > 装配好了。直接用 core 意味着自己接线。MCP 客户端也不在这里,在
13
+ > [plugin-mcp](../plugins/plugin-mcp)。
14
+
15
+ ## 模块
16
+
17
+ | 目录 | 职责 |
18
+ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `agent/` | ReAct 循环(`loop.ts`)、工具执行、实时输出泵、prompt 组装、Nudge / Stall 检测、消息编解码、plan 模式状态、**运行前 token 估算**(`run-estimate.ts`) |
20
+ | `provider/` | 多 provider 路由 + 降级、模型元数据探测、AI SDK 适配、**token 估算**(`tokenizer.ts`,全仓唯一口径) |
21
+ | `config/` | 10 层配置解析链 + 迁移 + 设置来源分层(`sources.ts`)+ **「为什么是这个值」**(`provenance.ts`,每个键赢在哪一层)+ **「我现在写、写完谁说了算」**(`write.ts`)+ JSON Schema 导出。`schema.ts` 是配置形状的**唯一**真源 |
22
+ | `session/` | SQLite 会话持久化 + FTS5 搜索,落盘完整 `EpochMessage`(工具调用 / artifact 引用)+ 逐轮真实用量(`recordUsage`)+ 已批准的计划 + **那一次工作区决定**(v7 两列,见下) |
23
+ | `memory/` | 文件 + SQLite 双存储 + 后台审查(`reviewer.ts`) |
24
+ | `skill/` | `SKILL.md` 文件系统 + 自动学习(`learner.ts`)。**读正文有两条路,别混**:`view()` 是模型那条(会 `matchCount++`,而那个数喂着有效性评分、也就是模型下次看得见哪些技能),`body()` 是「人点开看一眼」那条(**一个字都不动统计**)—— 一次浏览不是一次命中,判据写在 `SkillSystem.body` 上。**写口只有两个导出**(`previewSkillImport` / `importSkills`,方案 42 §六):从本机目录导入技能,收路径不收字节,staging 那一层刻意不导出 —— 判据在 `skill/import.ts` 文件头 |
25
+ | `hook/` | Hook 匹配与执行、命令执行;`sources.ts` 定「读哪几份 hooks.json」,`config-loader.ts` 只管解析 |
26
+ | `permission/` | 5 级权限(`default` / `acceptEdits` / `plan` / `auto` / `bypass`)+ 审批缓存 + 无头策略 + 紧凑规则 `Tool(content)` + **审计流水**(`audit.ts`:权限层每一次裁决的定长流水,`getAudit()` 取,**只在进程内、永不进遥测**) |
27
+ | `workspace/` | 这次会话的地盘:主根 + `--add-dir` 的额外根、目录体检、「哪些指令文件没被加载」,外加「已知工作区」最近使用清单(`~/.epoch/workspaces.json`,**本机文件不是服务**)。判据本身在 infra 的 `isInWorkspace`,这里是壳 |
28
+ | `policy/` | 策略文件加载与校验;`sources.ts` 定「扫哪几个 policies 目录」 |
29
+ | `context/` | 上下文压缩(工具结果裁剪是**摘要之前的独立一步**,头中尾保留,裁完够了就不调模型;2026-08-16 起认 `compression.*` 两个配置项,见下)、项目发现、指令文件与它的 `@import` 展开 + 项目外放行闸门、artifact 闸门与清理、图片 token 估算、`@` 提及的候选清单与解析 |
30
+ | `sandbox/` | `CodeSandbox`(`code_exec` 的执行器)+ 隔离能力的**对外说法**:`describeIsolation()` 那句中文、以及「哪些工具走沙箱」两份清单。**机制那一半 2026-08-16 搬去了 [infra](../infra) 的 `sandbox/`**(消费者不止 core 一个了,`plugin-terminal` 够不着 core);既有 import 路径靠再导出保住 |
31
+ | `delegate/` | 子 agent 任务委托(`delegate_task` 的后端) |
32
+ | `agent-role/` | agent 角色定义、注册与工具作用域收窄(子 agent 和**顶层会话**两条来路走同一个 `roleScopedProvider`);`createRoleScope` 把它包成**可变槽** —— 身份行和工具表从同一个变量读,好让「这一条消息换个专家」有地方落 |
33
+ | `extensions/` | 项目级扩展:自定义斜杠命令的发现 / 加载 / 插值 / 按轮次收窄工具表;三种扩展物共用的 frontmatter 解析 |
34
+ | `plugin/` | 插件:清单校验、四种来源的安装、安装记录(带跨进程锁)、市场、加载成「六类扩展物的来源」(`PLUGIN_LAYOUT` 那七个约定子路径)。见 [docs/PLUGINS.md](../../docs/PLUGINS.md) |
35
+ | `tools/` | `ToolRegistry` + 9 个内置工具(`todo` / `memory` / `code_exec` / `delegate_task` / `ask_user_question` / plan 模式那两个 / 会话检索那两个) |
36
+ | `checkpoint/` | 写类工具动手前的文件快照 + 回退(两阶段原子、不覆盖手工改动)。见 [docs/CHECKPOINTS.md](../../docs/CHECKPOINTS.md) |
37
+ | `tracker/` | 待办追踪的 SQLite 表 |
38
+ | `schedule/` | 定时任务(方案 45):store / 单实例锁 / 触发器算术 / 保存期校验 / 录像与留存 / 注册器 / `doctor`,外加 `os/` 下的两个 OS 后端(`schtasks` / `launchd`)。**不含执行器** —— 跑一次要 `buildRuntime()`,那在 [runtime](../runtime) 的 `fireSchedule()` |
39
+ | `cost/` | 计价表、预算守卫、花费状态。缓存命中单独计价,口径见 `pricing.ts` 文件头 |
40
+ | `trust/` | 工作区信任判定与闸门 |
41
+ | `telemetry/` | span 属性脱敏 |
42
+ | 目录 | 职责 |
43
+ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | `agent/` | ReAct 循环(`loop.ts`)、工具执行、实时输出泵、prompt 组装、Nudge / Stall 检测、消息编解码、plan 模式状态、**运行前 token 估算**(`run-estimate.ts`) |
45
+ | `provider/` | 多 provider 路由 + 降级、模型元数据探测、AI SDK 适配、**token 估算**(`tokenizer.ts`,全仓唯一口径) |
46
+ | `config/` | 10 层配置解析链 + 迁移 + 设置来源分层(`sources.ts`)+ **「为什么是这个值」**(`provenance.ts`,每个键赢在哪一层)+ **「我现在写、写完谁说了算」**(`write.ts`)+ JSON Schema 导出。`schema.ts` 是配置形状的**唯一**真源 |
47
+ | `session/` | SQLite 会话持久化 + FTS5 搜索,落盘完整 `EpochMessage`(工具调用 / artifact 引用)+ 逐轮真实用量(`recordUsage`)+ 已批准的计划 + **那一次工作区决定**(v7 两列,见下) |
48
+ | `memory/` | 文件 + SQLite 双存储 + 后台审查(`reviewer.ts`) |
49
+ | `skill/` | `SKILL.md` 文件系统 + 自动学习(`learner.ts`)。**读正文有两条路,别混**:`view()` 是模型那条(会 `matchCount++`,而那个数喂着有效性评分、也就是模型下次看得见哪些技能),`body()` 是「人点开看一眼」那条(**一个字都不动统计**)—— 一次浏览不是一次命中,判据写在 `SkillSystem.body` 上。**写口只有两个导出**(`previewSkillImport` / `importSkills`,方案 42 §六):从本机目录导入技能,收路径不收字节,staging 那一层刻意不导出 —— 判据在 `skill/import.ts` 文件头 |
50
+ | `hook/` | Hook 匹配与执行、命令执行;`sources.ts` 定「读哪几份 hooks.json」,`config-loader.ts` 只管解析 |
51
+ | `permission/` | 5 级权限(`default` / `acceptEdits` / `plan` / `auto` / `bypass`)+ 审批缓存 + 无头策略 + 紧凑规则 `Tool(content)` + **审计流水**(`audit.ts`:权限层每一次裁决的定长流水,`getAudit()` 取,**只在进程内、永不进遥测**) |
52
+ | `workspace/` | 这次会话的地盘:主根 + `--add-dir` 的额外根、目录体检、「哪些指令文件没被加载」,外加「已知工作区」最近使用清单(`~/.epoch/workspaces.json`,**本机文件不是服务**)。判据本身在 infra 的 `isInWorkspace`,这里是壳 |
53
+ | `policy/` | 策略文件加载与校验;`sources.ts` 定「扫哪几个 policies 目录」 |
54
+ | `context/` | 上下文压缩(含**头中尾保留**的工具结果裁剪,2026-08-16 起认 `compression.*` 两个配置项,见下)、项目发现、指令文件与它的 `@import` 展开 + 项目外放行闸门、artifact 闸门与清理、图片 token 估算、`@` 提及的候选清单与解析 |
55
+ | `sandbox/` | `CodeSandbox`(`code_exec` 的执行器)+ 隔离能力的**对外说法**:`describeIsolation()` 那句中文、以及「哪些工具走沙箱」两份清单。**机制那一半 2026-08-16 搬去了 [infra](../infra) 的 `sandbox/`**(消费者不止 core 一个了,`plugin-terminal` 够不着 core);既有 import 路径靠再导出保住 |
56
+ | `delegate/` | 子 agent 任务委托(`delegate_task` 的后端) |
57
+ | `agent-role/` | agent 角色定义、注册与工具作用域收窄(子 agent 和**顶层会话**两条来路走同一个 `roleScopedProvider`);`createRoleScope` 把它包成**可变槽** —— 身份行和工具表从同一个变量读,好让「这一条消息换个专家」有地方落。⚠️ 2026-08-18 起这里多了一条**写**的路(`create.ts`,全仓第一条建角色的路):它和读口共用同一份 frontmatter schema、同一个解析器,写完**先读回来验一遍**再落盘。那道「什么样的客户端才准写」的闸门**不在这儿**,在 `@epoch-agent/server`(判据见那个文件头第三节) |
58
+ | `extensions/` | 项目级扩展:自定义斜杠命令的发现 / 加载 / 插值 / 按轮次收窄工具表;三种扩展物共用的 frontmatter 解析 |
59
+ | `plugin/` | 插件:清单校验、四种来源的安装、安装记录(带跨进程锁)、市场、加载成「六类扩展物的来源」(`PLUGIN_LAYOUT` 那七个约定子路径)。见 [docs/PLUGINS.md](../../docs/PLUGINS.md) |
60
+ | `tools/` | `ToolRegistry` + 9 个内置工具(`todo` / `memory` / `code_exec` / `delegate_task` / `ask_user_question` / plan 模式那两个 / 会话检索那两个) |
61
+ | `checkpoint/` | 写类工具动手前的文件快照 + 回退(两阶段原子、不覆盖手工改动)。见 [docs/CHECKPOINTS.md](../../docs/CHECKPOINTS.md) |
62
+ | `tracker/` | 待办追踪的 SQLite 表 |
63
+ | `schedule/` | 定时任务(方案 45):store / 单实例锁 / 触发器算术 / 保存期校验 / 录像与留存 / 注册器 / `doctor`,外加 `os/` 下的两个 OS 后端(`schtasks` / `launchd`)。**不含执行器** —— 跑一次要 `buildRuntime()`,那在 [runtime](../runtime) 的 `fireSchedule()` |
64
+ | `cost/` | 计价表、预算守卫、花费状态。缓存命中单独计价,口径见 `pricing.ts` 文件头 |
65
+ | `trust/` | 工作区信任判定与闸门 |
66
+ | `telemetry/` | span 属性脱敏 |
67
+
68
+ 另外 10 个工具在四个 plugin 里,不在这里:见
69
+ [plugin-file](../plugins/plugin-file) / [plugin-terminal](../plugins/plugin-terminal) /
70
+ [plugin-web](../plugins/plugin-web) / [plugin-mcp](../plugins/plugin-mcp)。
71
+
72
+ ## 几个刻意的取舍
73
+
74
+ **三个「问人」的回调都带一个 `sessionId`,而它取自 `ToolContext`。**
75
+ `QuestionAskFn`(`ask_user_question`)、`PlanApprovalFn`(`exit_plan_mode`)、
76
+ `SubAgentRunner`(`delegate_task` 的 `parentSessionId`)——三处的第二个参数都是
77
+ 「**是谁在调这个工具**」。原因是这些工具注册在一个**共享的** `ToolRegistry` 上,
78
+ 而装配层从 2026-08-15 起会在一个进程里开多个会话(方案 30 §6.3 的多会话工厂):
79
+ 没有这个参数的话,会话 B 的提问会弹在会话 A 的界面上、B 派出去的子任务会把钱记在
80
+ A 的预算上、把文件快照落进 A 的检查点目录。
81
+
82
+ **这不是破坏性改动**:参数少的函数可以赋给参数多的函数类型,所以单会话调用方
83
+ (CLI / TUI / 用例)写 `(req) => …` 照旧编译得过,一行不用改。
84
+
85
+ **`AgentConfig.role` 的 `scope` 缺省是 `'delegate'`,而顶层会话必须显式给
86
+ `'session'`。** 引擎拿 `scope` **只**挑 system prompt 的身份行 —— 工具收窄、
87
+ 轮次预算、`## 你的职责` 那一段两档完全一样。缺省值挑 `'delegate'` 是因为它是这个
88
+ 字段出现(2026-08-15)之前唯一的来路,缺省必须让老调用方的 prompt **逐字节不变**
89
+ (eval 的 prompt 指纹盯着 `systemPromptSegments()`,改一个字五份录像全废)。
90
+
91
+ 不给 `'session'` 的后果很具体:`--agent explore` 跑的是顶层会话,却收到一句
92
+ 「你是 epoch-agent 的『explore』**子智能体**,**由主智能体派来**完成一个独立子任务」
93
+ —— 而那一次没有任何主智能体派过它。**这个仓库里没有任何东西会因此变红**,
94
+ 所以那句假话从 `--agent` 落地那天起活到了 2026-08-15。
95
+
96
+ `role.source` 同理是**纯展示**(`run-start` 事件上那枚来源徽标的唯一来路),
97
+ 引擎一处都不许据此分叉。
98
+
99
+ **配置校验失败不抛异常。** 单个配置文件写错不该让 agent 起不来(safeInit 语义)。
100
+ 代价是必须把 issues 交出去——`loadConfig(diags)` 的可选参数,或事后
101
+ `getConfigIssues()`。静默吞掉等于没校验。
102
+
103
+ **模板展开在校验之前。** `maxTurns: ${MAX_TURNS}` 在展开前是字符串,先校验会把合法
104
+ 配置判成类型错误。
105
+
106
+ **设置的来源分层不在 `loadConfig()` 里,在 `resolveSettings()` 里。** 项目级
107
+ `.epoch/settings.json` 要先判完工作区信任才敢读,而信任判定要 `config.homeDir`——
108
+ 放进 loader 就是个环。所以 loader 只解析到用户级,分层合并由装配层
109
+ ([runtime](../runtime) 的 `buildServices`)在信任判定之后调 `resolveSettings()` 完成。
110
+
111
+ **未信任目录的项目级设置只有 `deny` 生效**,`allow` / `ask` 和标量键一律丢弃。
112
+ 这条不对称是整件事的意义所在:一份提交进仓库的 `settings.json` 能替你收紧,
113
+ 不能替你放开。判定画在 `sources.ts` 一处,而不是在每个消费方各判一次。
114
+
115
+ **只有 `--settings` 那一层(第 ⑤ 层)坏了会抛,其余层一律「整份忽略 + 一条 issue」。**
116
+ `.epoch/settings.json` 是躺在仓库里的,别人写坏了不该让人连 agent 都起不来;而
117
+ `--settings` 是用户**这一次敲进来**的,静默忽略它意味着「规则一条都没生效」——
118
+ 在 CI 里恰好等于「审批全部走默认」。抛的是 `SettingsFileError`,
119
+ 消息里**带行号**(`describeJsonError`:按 `JSON.parse` 的 position 自己数一遍换行,
120
+ 数不出来就原样转述 Node 那句,不编假行号)。这也是和 `loader.ts` 的
121
+ 「损坏就回退默认」唯一的分歧点,判定画在 `sources.ts` 的 `finishLayer` 一处。
122
+
123
+ **改一个设置项之前,先说得出「写了会不会等于没写」(`config/write.ts`)。**
124
+ `provenance.ts` 答的是「这个值现在是谁写的」,`write.ts` 答的是反过来那个问题。
125
+ 宿主拿它做三件事:算出一个键能写进哪几层(只有 ② 用户级和 ④ 项目本地 ——
126
+ ③ 项目级要进 git,一次保存改的是整个团队的配置)、**写进那一层会不会被更高的层
127
+ 压回去或被信任闸门整份丢掉**、以及真的落盘。那句「会不会」必须在用户按下去
128
+ **之前**说出来:写完再说在时序上对、在用处上是零。
129
+
130
+ 落盘那一步对 `config.yaml` 是**按行动手术加回读校验**,不是 `load` 完 `dump` 回去
131
+ ——后者会抹掉用户手写的全部注释。回读发现除目标键之外还有东西变了就整个不写,
132
+ 回一个「这份文件这一层认不出它的写法」。**「改不动」比「改坏了」好说话得多。**
133
+ 坏 JSON 同理不覆盖:那是用户正在修的东西。
134
+
135
+ **hook 和策略规则的项目级闸门没有那条不对称。** 上面那条「只有 `deny` 生效」
136
+ 是给**设置**留的口子,`hook/sources.ts` 和 `policy/sources.ts` 一律**整份不加载**:
137
+ hook 会 spawn 子进程,那不是注入风险、是执行漏洞;策略规则的一个 `[[rule]]` 块里
138
+ `tool_name` 和 `decision` 是耦合的,物理上拆不出「只收紧」的那一半。
139
+ 代价小是因为 `matchPolicyRule` 全表扫完按 `deny > ask > allow` 合并,
140
+ 项目级 `allow` 本来就压不过用户级 `deny`。
141
+
142
+ **闸门画在 `sources.ts` 里,不在 `HookManager` / `loadPolicyFiles` 里。**
143
+ 装配层调 `HookManager` 那一步裹在 `safeInit` 里,闸门判定要是写在里面,
144
+ 一次异常就被降级成一条诊断 —— 而「闸门抛异常了」和「闸门放行了」
145
+ 在外部看起来一模一样。同理,`config-loader.ts` 拿到的永远是已经读进内存的
146
+ 原始对象,它不知道也不需要知道那份东西来自哪里。
147
+
148
+ **企业托管设置(`config/managed.ts`)压过一切,且不过信任闸门。** 它来自这台机器
149
+ 的管理员而不是仓库,拿仓库信任去闸它方向是反的(托管策略的典型内容是 `deny`,
150
+ 越是不信任的目录越需要它生效)。三个元开关(`allowManagedPermissionRulesOnly` /
151
+ `allowManagedHooksOnly` / `disableBypassPermissionsMode`)的缺省一律 `false`,
152
+ **读不到、读坏了也是 `false`** —— 反过来会让一次磁盘故障把所有人锁死。
153
+ 路径由 [infra](../infra) 的 `managedSettingsPath()` 给,**没有环境变量能覆盖**,
154
+ 覆盖点只有 `resolveSettings(…, { managedPath })` 这个参数。用户能读的清单见
155
+ [docs/SETTINGS.md 第五节](../../docs/SETTINGS.md#五企业托管设置)。
156
+
157
+ **JSON Schema 的生成器住在源码里(`config/json-schema.ts`),不在 `scripts/` 里。**
158
+ 漂移检查要跑在 `pnpm check` 里,而 `pnpm ci` 是 `check && build` —— check 跑的时候
159
+ `dist/` 还是上一次的产物,脚本读 dist 就等于拿旧代码比产物。所以 core 导出
160
+ `buildSchemaExports()`,真正的门禁是 `__tests__/settings-schema.test.ts`(import 源码
161
+ 里的 zod 定义比对 `schemas/*.json`),`scripts/gen-settings-schema.mjs` 只是把它写到盘上。
162
+ 字段说明表是**必填**的:往 zod 里加字段却没登记,`buildSchemaExports()` 直接抛。
163
+
164
+ **沙箱是隔离,不是安全边界。** 危险命令的判定表在 [infra](../infra) 的
165
+ `command-safety.ts`,那是**护栏不是沙箱**。真沙箱在 infra 的 `sandbox/`。
166
+
167
+ **`code_exec` 和 `terminal` 走同一批后端,边界不一样,别合成一句话说。**
168
+ 前者写入限于沙箱临时目录、默认禁网、读取白名单;后者(2026-08-16 接上,方案 46)
169
+ **只管写入**,读取和网络都不设限 —— 给终端上读白名单会让 `git push`(读 `~/.ssh`)
170
+ 和 `pnpm install`(读 `~/.npmrc`)一起废掉。`epoch doctor` 的沙箱一节把两者分两行印,
171
+ 安全中心那一屏也是;**「沙箱:已启用」单独摆着是这块最贵的一句假话**。
172
+
173
+ **工具的实时输出由引擎定节奏,不由工具自己定。** 工具只管把 chunk 交给
174
+ `ctx.onOutput`,合并窗口、只发完整行、剥 ANSI、`\r` 折叠、总量封顶五件事都在
175
+ `agent/tool-output-pump.ts` 里做完,再变成 `tool-output-delta` 事件。放在这里是因为
176
+ **节奏本身就是宿主要看到的东西**:每个工具各做一份,三个宿主看到的就是三种节奏。
177
+ 这条通道是纯展示——模型看到的始终只有 `tool-result` 里那份完整输出。
178
+
179
+ 那里面的 `sanitizeToolOutput()`(剥控制序列 + 折叠 `\r`)是**转出去的**:自定义状态栏
180
+ ([cli/src/statusline.ts](../cli/src/statusline.ts))跑的也是用户的命令,要解决的问题
181
+ 一模一样,转出来省掉第二份 ANSI 正则。名字里的 `ToolOutput` 是出身,不是限制。
182
+
183
+ **降级是「这一次请求」的事,不改你的选择。** `ProviderRouter` 里可变的
184
+ `selection` 和不可变的 `configSelection` 是两个字段,而每次请求的起点由
185
+ `startPoint()` **当场**读 `selection` 算出来 —— 降级过程只动一份调用局部的
186
+ `AttemptState`。所以一次 `/model` 换过去的模型哪怕上一轮降级过,下一轮还是从它起步:
187
+ 自动降级是替你救场,不是替你改主意。
188
+
189
+ 阶梯是 `--fallback-model`(同 provider)→ 内置默认模型 → 换 provider。
190
+ `fallback.model` **只对主 provider 有效** —— 模型名是 provider 私有的命名空间,
191
+ `--fallback-model gpt-4.1-mini` 说的是「我的 OpenAI 挂了就用它」,一旦降到 deepseek
192
+ 那个名字就只是一次必然的 404。判据是**当前落点**而不是配置里那家(用户 `/model`
193
+ 换到别家之后同理)。
194
+
195
+ **节流的单位是请求,不是重试。** 同一个模型在一次 `generate()` 里重试三次只算一次
196
+ 失败;连续三次把请求拖垮才停掉自动使用它,成功一次或用户显式 `/model` 选回它就清账
197
+ (后者是明确的「我知道,再试一次」)。
198
+
199
+ **token 估算只有一份,而且它是真分词。** `provider/tokenizer.ts` 的
200
+ `estimateTokens(text, model?)` 是全仓唯一口径 —— `/context` 的占用表、路由的
201
+ 「这一轮塞不塞得下」、压缩器的尾部预算都走它。底下是 `gpt-tokenizer` 的
202
+ `o200k_base`,**懒加载**(进程启动一毫秒都不多花,第一次真要算时才把词表读进来)
203
+ 且带结果缓存;加载失败时回落到旧的 `chars/4` 并打**一条** debug 日志,**不抛**。
204
+
205
+ 改造前它是 `Math.ceil(len / 4)`:英文和纯代码场景一直是准的,**中文低估 2–2.7 倍**
206
+ (纯中文样本 2.69×)。选型判据(冷启动 > 无 wasm > 体积 > 许可证)和那张实测偏差表
207
+ 都在该文件的头注释里。**今天所有模型走同一套编码**,这是有意的近似;`model` 形参
208
+ 是留给将来按模型分套的形状,现在被忽略。
209
+
210
+ **这件事要说出来,但不走 `AgentEvent`。** 路由器在 `loop.ts` **下面**,而
211
+ `AgentEvent` 是穷举联合([view 的 reducer](../view/src/reducer.ts) 里有
212
+ `const _never: never = event`),加一个 notice 变体的波及面远大于这条通知本身。
213
+ 所以是 `notices: string[]` + `drainNotices()` —— **拉**而不是推,宿主在一轮收尾时
214
+ 取一次。代价是「延迟到轮末」,而这条消息本来就是给人看的事后说明。
215
+
216
+ ## 自动压缩:`compression.enabled` / `compression.threshold`(2026-08-16)
217
+
218
+ 在这之前压缩是**一段没有旋钮的行为** —— 关不掉,阈值硬编码 `0.5`。设计稿上那个
219
+ 「快满时自动压缩」开关因此连着两轮被记成「只记不改」(判据:「没有配置项就别画
220
+ 开关」)。现在两个键都在 `EpochConfig` 上。
221
+
222
+ - **缺省 = 和改造前逐字相同**(开着、0.5)。这一段走 `.prefault({})` +
223
+ 子字段 `.default()`,**是 `retry` 那一类而不是 `trust` 那一类**:它不是一道
224
+ 默认关着的闸,是一个一直在跑的行为多了旋钮。判据在 `CompressionSchema` 上
225
+ - **默认阈值只有一份**:`config/schema.ts` 的 `DEFAULT_COMPRESSION_THRESHOLD`。
226
+ 压缩器 import 它当回落,不在那边再写一个 `0.5`
227
+ - **关掉的只有自动那一条路**。手动 `/compact` 照常能压 —— 关掉它表达的是
228
+ 「别自作主张改写我的早期消息」,不是「这个能力不存在」
229
+ - **装配层的唯一入口是 `compressionOptions(config)`**(全仓唯一一处
230
+ 「没配 = 开着」的三态判断)。别自己拼那两行,两行各有一个踩得到的坑
231
+
232
+ **那条线(「快满了」)的定义在 protocol**:`compressionThresholdTokens()` /
233
+ `isContextNearlyFull()`。`shouldCompress()` 用的就是它们,设置页画的也是它们 ——
234
+ 两处各写一遍乘法的症状是「界面说还早、引擎已经压了」。
235
+
236
+ ⚠️ **`compression.*` 判不进 `SettingsFileSchema` 白名单**,三条判据逐条在
237
+ `config/sources.ts` 文件头(2026-08-16 那一节)。最要紧的一条:仓库作者拿
238
+ `enabled: false` 能让别人在这个仓库里开的长会话悄悄撞满窗口,而症状出现在
239
+ provider 那一侧。
240
+
241
+ ## 一次压缩里,摘要那一发**不一定会打**(2026-08-18,方案 47 PR-3)
242
+
243
+ 裁剪工具结果(头中尾保留)现在是 `compress()` **最前面的独立一步**
244
+ (`context/prune.ts`),裁完**重新量一次**:
245
+
246
+ ```
247
+ 量 token → 超阈值? ─是→ 裁剪工具结果 → 重新量 → 还超阈值? ─是→ 走摘要
248
+ └─否→ 到此为止,不调模型 ✅
249
+ ```
250
+
251
+ 那个「✅」就是全部收益:**一次 LLM 摘要调用的钱和时间省下来,而且没有信息损失**
252
+ (摘要是有损的,头尾裁剪不是)。改造前 Phase 3 是无条件跑的 —— 裁剪常常已经把
253
+ 上下文压回阈值以下了,而那一发请求照打不误。
254
+
255
+ 三条约束,改这一段之前先读:
256
+
257
+ - **「重新量」量的是 `promptTokens - 省下的`**,不是把裁完的消息重新加一遍。
258
+ 后者不含 system prompt 和工具表(上万 token),而触发压缩的判定比的是 provider
259
+ 报的整个提示词 —— 两把尺子,混用的话「一条都没裁」也会被判成「够了」。
260
+ 判据全文在 `prunedEnough()` 上
261
+ - **拿不到基线就不走这条捷径。** `CompressConfig.promptTokens` 只有自动那一路给;
262
+ 手动 `/compact` 不给(用户要的就是一份摘要)
263
+ - **裁剪仍然只在压力触发时跑**,不是每一步都裁一遍 —— 模型可能下一步就要读某条
264
+ 结果的中间部分。`protectFirstN` / `protectLastN` 之内的一律不裁
265
+
266
+ 省下这一发时 `CompressResult.summarySkipped` 为 `true`。**别拿 `summary === ''`
267
+ 反推**:摘要请求打了但失败也是空串,而那一种是花了钱的。
268
+
269
+ ## 会话行上那一次「工作区决定」(会话库 v7,2026-08-19)
270
+
271
+ `sessions` 表加了 `workspace_state` / `workspace_root` 两列(`SessionMeta` 上是
272
+ `workspaceState` / `workspaceRoot`),三态:`NULL`(没记过,v7 之前的老行)/
273
+ `'none'`(明确选过不使用工作区)/ `'bound'` + 一个根。判据全文在
274
+ [session/db.ts](src/session/db.ts) 那条迁移的注释里(`id: 'sessions-workspace-decision'`
275
+ —— 迁移**认账认 id 不认版本号**),这边只留三条改之前必须知道的:
276
+
277
+ - ⚠️ **不许改写成蹭现成的 `cwd` 列。** 它有三处记不准而且都可达,最硬的一条是
278
+ 「明确不使用工作区」的会话那儿记着服务进程的启动目录 —— 拿它去绑,等于替用户
279
+ 宣布一句他没说过的话。另有一条独立理由:`cwd` 是**鉴权边界**
280
+ ([session/authorization.ts](src/session/authorization.ts) 拿它决定哪些历史会话
281
+ 检索得到,方案 48 / 53 同 TRUST.md)
282
+ - **可空、不回填。** 老库升上来两列全 `NULL`,读侧按 v7 之前的行为处理
283
+ (「这个进程说不出它在哪儿干过活」)—— 那些决定从来没被记下来过,编不出来
284
+ - **写在「决定发生的那一刻」,不在建行那一刻。** 这一条是上一点的镜像:`cwd` 记错
285
+ 的根因正是 `ensure()` 只在新建行时写它。写入侧在
286
+ [runtime](../runtime) 的 `SessionWorkspaces`,core 这边只提供列和 `SessionMeta`
287
+ 那两格
288
+
289
+ ## 开发
290
+
291
+ ```bash
292
+ pnpm --filter @epoch-agent/core test
293
+ ```
294
+
295
+ `~/.epoch/` 下每个文件的路径都从 [infra](../infra) 的 `paths.ts` 拿,别在这里
296
+ `join(homedir(), '.epoch')`。平台差异(默认 shell、spawn / pty 参数、控制台编码)
297
+ 同样在 infra 的 `platform.ts`。