@jpssff/vanor 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.
Files changed (45) hide show
  1. package/README-cn.md +166 -0
  2. package/README.md +120 -0
  3. package/base/config.js +162 -0
  4. package/base/core/compaction.js +58 -0
  5. package/base/core/harness.js +246 -0
  6. package/base/core/loop.js +72 -0
  7. package/base/core/prompt.js +126 -0
  8. package/base/core/session.js +255 -0
  9. package/base/events.js +54 -0
  10. package/base/i18n/index.js +80 -0
  11. package/base/i18n/locales/en.js +254 -0
  12. package/base/i18n/locales/zh-CN.js +252 -0
  13. package/base/llm/index.js +119 -0
  14. package/base/llm/providers/anthropic.js +147 -0
  15. package/base/llm/providers/openai.js +155 -0
  16. package/base/llm/sse.js +27 -0
  17. package/base/llm/trace.js +64 -0
  18. package/base/logger.js +57 -0
  19. package/base/memory/index.js +139 -0
  20. package/base/security/index.js +77 -0
  21. package/base/skills/loader.js +297 -0
  22. package/base/test/cli.test.js +91 -0
  23. package/base/test/config.test.js +63 -0
  24. package/base/test/core.test.js +154 -0
  25. package/base/test/i18n.test.js +32 -0
  26. package/base/test/loop.test.js +97 -0
  27. package/base/test/memory.test.js +47 -0
  28. package/base/test/message.test.js +38 -0
  29. package/base/test/session.test.js +324 -0
  30. package/base/test/skills.test.js +236 -0
  31. package/base/test/statusbar.test.js +143 -0
  32. package/base/test/tools.test.js +127 -0
  33. package/base/test/trace.test.js +62 -0
  34. package/base/test/tui.test.js +242 -0
  35. package/base/test/utils.test.js +35 -0
  36. package/base/tools/builtin.js +221 -0
  37. package/base/tools/index.js +157 -0
  38. package/base/transport/cli.js +417 -0
  39. package/base/transport/message.js +81 -0
  40. package/base/transport/statusbar.js +117 -0
  41. package/base/transport/tui.js +397 -0
  42. package/base/utils.js +150 -0
  43. package/docs/TECH_DESIGN.md +544 -0
  44. package/index.js +175 -0
  45. package/package.json +33 -0
@@ -0,0 +1,544 @@
1
+ # Vanor 技术设计
2
+
3
+ > 本文是 [README](../README.md) 的实现级展开:README 讲「是什么」,本文讲「怎么实现」。
4
+ > 标 `[v2]` 者为后续版本,MVP 不实现但预留接口。
5
+
6
+ ## 目录
7
+
8
+ 1. [设计目标与约束](#1-设计目标与约束)
9
+ 2. [架构总览](#2-架构总览)
10
+ 3. [统一消息协议 Message](#3-统一消息协议-message)
11
+ 4. [AgentLoop](#4-agentloop)
12
+ 5. [LLM 层](#5-llm-层)
13
+ 6. [工具系统](#6-工具系统)
14
+ 7. [技能 Skills](#7-技能-skills)
15
+ 8. [记忆系统](#8-记忆系统)
16
+ 9. [会话 Session](#9-会话-session)
17
+ 10. [配置 config.json](#10-配置-configjson)
18
+ 11. [多语言 i18n](#11-多语言-i18n)
19
+ 12. [日志与事件](#12-日志与事件)
20
+ 13. [安全模型](#13-安全模型)
21
+ 14. [进程模型](#14-进程模型)
22
+ 15. [自我更新 self-evolve `[v2]`](#15-自我更新-self-evolve-v2)
23
+ 16. [插件机制 `[v2]`](#16-插件机制-v2)
24
+ 17. [模块与文件职责](#17-模块与文件职责)
25
+ 18. [里程碑](#18-里程碑)
26
+ 19. [开放问题](#19-开放问题)
27
+
28
+ ---
29
+
30
+ ## 1. 设计目标与约束
31
+
32
+ - **通用智能体**:大模型驱动,自主推理、规划、执行多步任务。
33
+ - **零依赖**:仅用 Node 原生模块。运行环境 **Node ≥ 18**(依赖原生 `fetch`、`node:test`、`AbortController`、`readline/promises` 等)。
34
+ - **隐私优先**:默认本地文件存储,不外发数据(LLM 调用除外)。
35
+ - **可观测**:关键动作全部落 JSONL,事件名全局可枚举。
36
+ - **可自我改写** `[v2]`:见 §15。
37
+
38
+ **非目标(MVP)**:分布式 / 多租户、向量数据库、GUI、内置鉴权服务。
39
+
40
+ ---
41
+
42
+ ## 2. 架构总览
43
+
44
+ | 层 | 模块 | 职责 |
45
+ |----|------|------|
46
+ | Transport | `transport/` | 外部 I/O ↔ 统一 `Message`;CLI 内置,其他通道由插件实现 |
47
+ | Core (Harness) | `core/harness.js` | 编排:会话、phase、配置快照、compaction 触发、事件分发 |
48
+ | Loop | `core/loop.js` | 单轮 LLM ↔ 工具迭代 |
49
+ | LLM | `llm/` | provider 适配、流式、failover |
50
+ | Tools | `tools/` | 工具注册、参数校验、执行 |
51
+ | Skills | `skills/` | 技能加载与 prompt 注入 |
52
+ | Memory | `memory/` | 短期 / 长期 / 工作记忆 |
53
+ | 横切 | `security/` `logger.js` `events.js` `config.js` `i18n/` | 审批、日志、事件枚举、配置、多语言 |
54
+
55
+ **依赖方向**(单向,无循环):
56
+
57
+ ```text
58
+ Transport → Core → Loop → { LLM, Tools, Memory }
59
+ Skills / Security 被 Core、Loop 调用
60
+ Config / Logger / Events / i18n 为横切,被各层只读引用
61
+ ```
62
+
63
+ ### 2.1 Phase 状态机
64
+
65
+ ```text
66
+ ┌─────────────────────────────────────────┐
67
+ ▼ │
68
+ [idle] ──prompt──▶ [turn] ──tool_calls──▶ [tool_exec]
69
+ ▲ │ ▲ │
70
+ │ │ └──────results─────────┘
71
+ │ over_threshold
72
+ │ ▼
73
+ └──── done ── [compaction]
74
+ (self_update 仅 [v2],见 §15)
75
+ ```
76
+
77
+ - **结构性操作**(切换 base-user、reset 会话)仅允许在 `idle`。
78
+ - 运行中修改 `config.json` 后,下一轮前自动检查 mtime 并热重载;也可用 `/reload` 手动重载。重载只更新 **core config**、i18n 与运行时对象,下一轮构造 system prompt / LLM 请求时生效,不影响进行中的 turn。
79
+
80
+ ### 2.2 turn snapshot vs core config
81
+
82
+ 借鉴 pi 的分离:`core config` 是最新运行时配置(可随时改);`turn snapshot` 是某一轮开始时冻结的具体配置(model、tools、system prompt、记忆快照)。一轮内所有逻辑用同一 snapshot,保证一致性与 prefix cache 命中。
83
+
84
+ ---
85
+
86
+ ## 3. 统一消息协议 Message
87
+
88
+ 所有通道、Core、Loop 全程使用 `Message`,仅在 LLM 边界转换为各 provider 格式。
89
+
90
+ ```ts
91
+ type Role = "system" | "user" | "assistant" | "tool";
92
+
93
+ interface Message {
94
+ id: string; // msg_<ulid>
95
+ role: Role;
96
+ content: string | ContentPart[]; // 文本或多模态(图片等)
97
+ toolCalls?: ToolCall[]; // 仅 assistant:本轮要调用的工具
98
+ toolCallId?: string; // 仅 tool:对应的调用 id
99
+ name?: string; // 仅 tool:工具名
100
+ channel?: string; // cli | telegram | web ...
101
+ meta?: Record<string, unknown>; // timestamp、model、usage 等
102
+ }
103
+
104
+ interface ToolCall {
105
+ id: string; // call_<id>
106
+ name: string;
107
+ arguments: unknown; // 已解析的 JSON 参数
108
+ }
109
+
110
+ type ContentPart =
111
+ | { type: "text"; text: string }
112
+ | { type: "image"; url: string }; // [v2] 多模态
113
+ ```
114
+
115
+ **角色交替规则**(provider 强约束,Core 在持久化前校验):
116
+
117
+ - 系统消息后:`user → assistant → user → ...`
118
+ - 工具调用中:`assistant(toolCalls) → tool → tool → ... → assistant`
119
+ - 不得连续两条 `assistant` 或两条 `user`;仅 `tool` 可连续(并行结果)。
120
+
121
+ ---
122
+
123
+ ## 4. AgentLoop
124
+
125
+ ```ts
126
+ async function runTurn(ctx: Context, snap: TurnSnapshot, signal: AbortSignal) {
127
+ for (let i = 0; i < snap.maxIterations; i++) {
128
+ if (signal.aborted) return emit("agent.turn.aborted");
129
+
130
+ const llmMessages = buildMessages(ctx, snap); // 系统prompt + 记忆快照 + 历史
131
+ const assistant = await streamLLM(llmMessages, snap, signal); // 内含 retry/failover
132
+ ctx.push(assistant);
133
+
134
+ if (!assistant.toolCalls?.length) { // 纯文本 → 结束
135
+ await maybeFlushMemory(ctx, snap);
136
+ return emit("agent.turn.end", assistant);
137
+ }
138
+
139
+ // 执行工具:默认并行;标记 exclusive 的工具串行
140
+ const results = await runTools(assistant.toolCalls, snap, signal);
141
+ ctx.push(...results); // role: "tool"
142
+
143
+ if (overThreshold(ctx, snap)) await compact(ctx, snap); // → compaction phase
144
+ }
145
+ return emit("agent.turn.max_iterations");
146
+ }
147
+ ```
148
+
149
+ - **终止条件**:assistant 无 toolCalls / 达 `maxIterations` / `abort` / 致命错误。
150
+ - **中断**:CLI `Ctrl+C` → `AbortController.abort()`;进行中的 LLM 流与工具收到 signal 后尽快收敛。
151
+ - **错误处理**:
152
+ - LLM 失败 → 按错误分类重试(指数退避),耗尽后走 failover 模型(§5)。
153
+ - 工具失败 → 包装为 `ToolResult{ isError:true }` 回传给模型,由模型决定后续,不中断 loop。
154
+
155
+ ---
156
+
157
+ ## 5. LLM 层
158
+
159
+ ### 统一接口
160
+
161
+ ```ts
162
+ interface LLMRequest {
163
+ model: string; // "<provider>/<model-id>"
164
+ messages: Message[];
165
+ tools?: ToolSchema[];
166
+ thinking?: "off" | "low" | "high";
167
+ stream: true;
168
+ }
169
+
170
+ interface LLMProvider {
171
+ name: string; // openai | anthropic
172
+ complete(req: LLMRequest, signal: AbortSignal): AsyncIterable<LLMChunk>;
173
+ }
174
+
175
+ type LLMChunk =
176
+ | { type: "text"; delta: string }
177
+ | { type: "tool_call"; call: ToolCall }
178
+ | { type: "usage"; usage: Usage }
179
+ | { type: "done"; finishReason: string };
180
+ ```
181
+
182
+ ### Provider 适配
183
+
184
+ | Provider | 协议 | 适用 |
185
+ |----------|------|------|
186
+ | `openai` | Chat Completions | OpenAI、兼容端点、多数国产/开源厂商 |
187
+ | `anthropic` | Messages | Claude 原生 |
188
+ | `openai-responses` `[v2]` | Responses | Codex / Responses API |
189
+
190
+ - **流式**:原生 `fetch` + `response.body`(`ReadableStream`)逐块解析 SSE,零依赖。
191
+ - **工具调用格式**:内部统一为 `ToolCall`,各 provider 在适配层双向转换。
192
+ - **failover**:`llm.fallback` 列出备用模型;错误分为「可重试(限流/超时/5xx)」与「致命(鉴权/参数)」,可重试耗尽后切下一模型。
193
+ - **事件**:`llm.request` `llm.response` `llm.retry` `llm.error`。
194
+
195
+ ---
196
+
197
+ ## 6. 工具系统
198
+
199
+ ### 定义
200
+
201
+ ```ts
202
+ interface ToolSchema {
203
+ name: string;
204
+ description: string; // 供模型理解何时调用
205
+ parameters: JSONSchema; // 供模型生成参数 + 运行时校验
206
+ }
207
+
208
+ interface Tool extends ToolSchema {
209
+ danger: "safe" | "confirm" | "deny-default"; // 审批级别,见 §13
210
+ exclusive?: boolean; // 是否禁止与其他工具并行
211
+ handler(args: unknown, ctx: ToolContext): Promise<ToolResult>;
212
+ }
213
+
214
+ interface ToolResult { content: string; isError?: boolean; }
215
+ ```
216
+
217
+ ### 内置工具(MVP)
218
+
219
+ | 工具 | 参数 | danger | 说明 |
220
+ |------|------|--------|------|
221
+ | `read_file` | `path, offset?, limit?` | safe | 读文件,限 workspace |
222
+ | `write_file` | `path, content` | confirm | 写 / 覆盖文件 |
223
+ | `edit_file` | `path, old_string, new_string` | confirm | 唯一匹配后替换 |
224
+ | `list_dir` | `path` | safe | 列目录 |
225
+ | `exec` | `command, cwd?, timeout?` | confirm | 执行命令,受 allowlist/denylist |
226
+
227
+ `[v2]`:`spawn`(常驻子进程)、`search`(grep)、`fetch_url`、`delegate`(子 Agent)。
228
+
229
+ ### 执行约束
230
+
231
+ - **参数校验**:用 `parameters` 的 JSON Schema 校验,非法直接回 `isError`。
232
+ - **工作区边界**:所有文件工具限制在 `security.workspaceRoot`(默认启动目录)内;越界路径需审批或被拒(`allowOutsideWorkspace`)。
233
+ - **命令执行**:在 workspace cwd 运行,套用审批与 allow/deny,超时杀进程,输出超限截断;清理敏感环境变量。
234
+ - **并行**:同一 assistant 的多个 toolCalls 默认 `Promise.all` 并行,`exclusive` 工具串行。
235
+ - **事件**:`tool.call` `tool.result` `tool.error` `tool.denied`。
236
+
237
+ ---
238
+
239
+ ## 7. 技能 Skills
240
+
241
+ 技能是**过程性记忆**:把「做某类任务的步骤」沉淀为可复用单元。
242
+
243
+ ### 格式
244
+
245
+ ```text
246
+ <skill-dir>/
247
+ ├── SKILL.md # frontmatter: name, description;正文为操作说明
248
+ └── ... # 可选脚本 / 模板,由工具(exec/read_file)调用
249
+ ```
250
+
251
+ ### 加载
252
+
253
+ | 优先级 | 路径 | 范围 |
254
+ |--------|------|------|
255
+ | 最高 | `~/.vanor/skills/` | Vanor 专属 |
256
+ | 高 | `<workspace>/.skills/`、`<workspace>/skills/` | 当前项目技能 |
257
+ | 中 | `~/.cursor/skills-cursor/`、`~/.claude/skills/`、`~/.agents/skills/`、`~/.codex/skills/` | 本机已有 Agent 技能 |
258
+ | 低 | `~/.agent/skills/`、`~/.skills/` | 兼容旧路径 / 全局 |
259
+
260
+ - 兼容 [agentskills.io](https://agentskills.io) 的 `SKILL.md`;同名按优先级去重。
261
+ - **调用方式**:每次请求前刷新技能目录,并将各技能的 `name + description` 注入 system prompt(低成本);模型判断需要时,再用 `skills.read` / `read_file` 读取 `SKILL.md` 全文与脚本,按说明执行。
262
+ - **管理工具**:`skills.list/read/write/edit/remove`;读取可作用于所有已加载技能,写入/修改/删除仅作用于 `~/.vanor/skills/`,避免误改共享技能。
263
+ - **事件**:`skill.load` `skill.invoke` `skill.save`。
264
+
265
+ ---
266
+
267
+ ## 8. 记忆系统
268
+
269
+ ### 短期(会话)
270
+
271
+ 即 `sessions/<id>.jsonl` 本身,不另存。超过 `compactThreshold`(默认上下文 75%)触发 compaction:
272
+
273
+ ```text
274
+ 保留:最近 N 轮完整消息 + 被读/写文件清单 + pinned 内容
275
+ 压缩:更早的历史 → 一条 summary(compaction 条目,见 §9)
276
+ ```
277
+
278
+ ### 长期(有界 Markdown)
279
+
280
+ | 文件 | 用途 | 上限 |
281
+ |------|------|------|
282
+ | `MEMORY.md` | 环境事实、约定、经验 | ~2200 字符 |
283
+ | `USER.md` | 用户偏好、沟通风格 | ~1375 字符 |
284
+
285
+ - 条目以 `§` 分隔;超限时模型自行整合/替换。
286
+ - **冻结快照**:会话开始时注入系统 prompt,会话中途变更立即落盘但下一会话才进 prompt(保 prefix cache)。
287
+ - `memory` 工具:`add` / `replace` / `remove`(子串唯一匹配),无 `read`(已在 prompt 中)。
288
+ - **检索**:MVP 用关键词倒排(Node 实现)补充召回;`[v2]` 可选向量(sqlite-vec / 本地 embedding)。
289
+
290
+ ### 工作记忆
291
+
292
+ `memory/working/`:任务内的 plan、中间结果。任务结束清理,或选择性 `promote` 到长期。
293
+
294
+ **事件**:`memory.store` `memory.retrieve` `memory.compress` `memory.promote`。
295
+
296
+ ---
297
+
298
+ ## 9. 会话 Session
299
+
300
+ - **sessionId**:`<yyyymmddHHMMSS>-<rand>`,每会话一文件 `sessions/<id>.jsonl`。
301
+ - **快速恢复索引**:退出 / 会话 start / resume 时写入 `~/.vanor/state.json`,维护 `workspaces[workspaceRoot].latestSessionId`;启动恢复优先按当前工作区读取对应单个 JSONL 文件,失效时才回退扫描 `sessions/` 全目录。保留顶层 `latestSessionId` 兼容旧状态。
302
+ - **存储条目**:
303
+
304
+ ```ts
305
+ type SessionEntry =
306
+ | { type: "message"; ts: string; message: Message }
307
+ | { type: "compaction"; ts: string; summary: string;
308
+ readFiles: string[]; modifiedFiles: string[] }
309
+ | { type: "meta"; ts: string; key: string; value: unknown }; // 模型切换、leaf 等
310
+ ```
311
+
312
+ - **路由**:MVP 单一 `main` 会话;`[v2]` IM 多用户按 `per-channel-peer` 隔离。
313
+ - **生命周期**:MVP 手动 `/new`、`/reset`;`[v2]` daily / idle 自动重置。
314
+ - **slash 命令**:`/new` `/reset` `/messages` `/compact` `/retry` `/model` `/language` `/usage` `/skills` `/reload` `/auto-run`。
315
+ - **事件**:`session.start` `session.reset` `session.compact`。
316
+
317
+ ---
318
+
319
+ ## 10. 配置 config.json
320
+
321
+ ```jsonc
322
+ {
323
+ "llm": {
324
+ "defaultModel": "wanyou/deepseek/deepseek-v4-pro",
325
+ "providers": {
326
+ "wanyou": { "type": "openai", "apiKey": "env:VANOR_API_KEY", "baseURL": "https://wanyouzhisuan.com/api/v1", "models": ["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash"] },
327
+ "anthropic": { "apiKey": "env:ANTHROPIC_API_KEY" }
328
+ },
329
+ "fallback": ["wanyou/deepseek/deepseek-v4-flash"] // 主模型失败后依次尝试
330
+ },
331
+ "agent": { "maxIterations": 25, "thinking": "off", "contextWindow": 256000 },
332
+ "memory": { "memoryMaxChars": 2200, "userMaxChars": 1375, "compactThreshold": 0.75 },
333
+ "security": {
334
+ "approval": "ask", // ask | auto | deny
335
+ "allowlist": ["git *", "ls *", "cat *"],
336
+ "denylist": ["rm -rf *", "sudo *"],
337
+ "workspaceRoot": ".",
338
+ "allowOutsideWorkspace": false
339
+ },
340
+ "session": { "restore": "last", "reset": { "idleMinutes": 0, "daily": false }, "dmScope": "main" },
341
+ "logging": { "level": "info", "llmTrace": false, "llmTraceRedact": true },
342
+ "ui": { "color": true, "stream": true, "language": "auto" }
343
+ }
344
+ ```
345
+
346
+ - **加载顺序**:内置默认 → `~/.vanor/config.json` → 环境变量覆盖。`config.js` 负责校验与归一。
347
+ - **密钥**:值以 `env:` 前缀表示从环境变量读取;apiKey 永不写入日志。
348
+ - **模型列表**:provider 可选 `models` 数组,供 `/model <关键词>` 搜索与选择;未配置时使用当前模型、默认模型和 fallback 作为候选。
349
+ - **会话恢复**:`session.restore = "last" | "none"`;默认按当前工作区恢复最近会话。
350
+ - **LLM Trace**:`logging.llmTrace` 默认关闭;开启后记录 LLM 请求 / 响应详情,`llmTraceRedact` 默认脱敏敏感 header。
351
+ - **语言**:`ui.language` 支持 `auto | en | zh-CN`。`auto` 按 `VANOR_LANG`、`LC_ALL`、`LC_MESSAGES`、`LANGUAGE`、`LANG`、`Intl` 顺序检测,无法确认则英文;CLI 内 `/language [auto|en|zh-CN]` 可即时切换并只写回 `ui.language`。
352
+
353
+ ---
354
+
355
+ ## 11. 多语言 i18n
356
+
357
+ ```text
358
+ base/i18n/
359
+ ├── index.js # detectLanguage / createI18n / t(key, vars)
360
+ └── locales/
361
+ ├── en.js
362
+ └── zh-CN.js
363
+ ```
364
+
365
+ - 文案使用嵌套 key 管理,如 `cli.help.text`、`prompt.runtimeTitle`。
366
+ - `t(key, vars)` 支持 `{name}` 插值;缺失翻译回退英文,英文也缺失时返回 key。
367
+ - 语言检测优先级:显式 `config.ui.language`(非 `auto`)→ 环境变量 → `Intl.DateTimeFormat().resolvedOptions().locale` → `en`。
368
+ - Harness 持有当前 i18n 状态;配置热重载或 `/language` 后重建 i18n,后续 CLI 输出与 system prompt 立即使用新语言。
369
+ - `/language` 写回配置时使用 raw config,只更新 `ui.language`,避免把 `env:` 密钥解析值写回明文。
370
+ - system prompt 会注入当前 UI 语言,并要求模型默认使用该语言回复;用户显式要求其它语言时,以用户要求为准。
371
+ - CLI 的 `helpText(t)`、确认提示、模型选择、`/messages` 表头、`/skills` 汇总、`/reload`、`/auto-run` 等用户文案均通过 i18n key 生成。
372
+ - 工具链的模型可见 / 用户可见文案也接入 i18n:内置工具 schema 描述、工具执行结果、审批确认描述、参数校验错误、workspace 路径错误、skills 工具返回值。
373
+
374
+ ---
375
+
376
+ ## 12. 日志与事件
377
+
378
+ - **行格式**:`{ "ts": ISO8601, "level": "debug|info|warn|error", "event": "<domain>.<action>", "detail": {...} }`,每行一条,写入 `logs/`。
379
+ - **命名规范**:`<domain>.<action>`。`events.js` 为唯一定义源(枚举对象),杜绝散落字符串。
380
+
381
+ | domain | 关键事件 | 级别 |
382
+ |--------|----------|------|
383
+ | `agent` | `turn.start` `turn.end` `turn.aborted` `turn.max_iterations` | info |
384
+ | `llm` | `request` `response` `retry` `error` | info/warn |
385
+ | `tool` | `call` `result` `error` `denied` | info/warn |
386
+ | `memory` | `store` `retrieve` `compress` `promote` | info |
387
+ | `session` | `start` `resume` `reset` `compact` | info |
388
+ | `skill` | `load` `invoke` `save` | info |
389
+ | `security` | `approve` `deny` | info/warn |
390
+ | `config` | `load` `change` | info |
391
+ | `process` `[v2]` | `spawn` `exit` `daemon.start` `daemon.stop` | info |
392
+ | `self_update` `[v2]` | `diff` `test` `rollback` | info/warn |
393
+ | `error` | `*` | error |
394
+
395
+ ---
396
+
397
+ ## 13. 安全模型
398
+
399
+ **威胁模型**:MVP 单用户本地,主要防误操作;`[v2]` IM 暴露后,入站消息视为不可信。
400
+
401
+ - **命令审批**:`approval = ask`(默认逐次确认)| `auto`(仅 allowlist 放行)| `deny`。`danger: confirm` 的工具受此约束。
402
+ - **allowlist / denylist**:glob/前缀匹配;denylist 优先级最高。
403
+ - **文件边界**:`workspaceRoot` 内自由读写,越界需审批(`allowOutsideWorkspace` 控制)。
404
+ - `[v2]` **DM pairing**:未知发送者先发配对码,批准前不处理其消息。
405
+ - `[v2]` **隔离**:建议对非 main 会话启用子进程 / 容器沙箱。
406
+ - **`vanor doctor`**:检查弱审批配置、公开暴露、明文密钥、越权 workspace 等。
407
+ - **事件**:`security.approve` `security.deny`。
408
+
409
+ ---
410
+
411
+ ## 14. 进程模型
412
+
413
+ ### MVP(前台 CLI)
414
+
415
+ - 单个前台进程承载 TUI + Core + Loop。
416
+ - **重活子进程化**:`exec` 等命令、自测等用 `child_process.fork/spawn`,崩溃隔离、便于中断。
417
+ - **IPC**:子进程经 stdio 传 JSON 消息(请求 / 结果 / 进度)。
418
+
419
+ ### `[v2]` 常驻 daemon
420
+
421
+ - 常驻进程负责:定时任务(`tasks/`)、IM 网关常驻监听。
422
+ - `run/daemon.pid`、`run/daemon.sock`(daemon ↔ 子进程 IPC)、`run/state.json`。
423
+ - 管理:`vanor daemon start|stop|status`。
424
+ - **热重启**:daemon 收到「切换 base-user」请求 → 先以 `--canary` 子进程验证(§15)→ 通过后平滑切换处理进程;MVP 无 daemon,直接重启进程即可。
425
+
426
+ ---
427
+
428
+ ## 15. 自我更新 self-evolve `[v2]`
429
+
430
+ `evolve.js` 负责 Agent 对自身代码的改写、校验、切换、回退。
431
+
432
+ - **隔离**:禁改包内 `base/`;首次改写时 `base/` → `~/.vanor/base-user/`,记派生版本于 `base-user/.base-version`。
433
+ - **改写边界**:默认仅 `base-user/` 内非核心模块、`~/.vanor/plugins/`、`~/.vanor/skills/`;`core/`、`tools/` 等核心需显式解锁。
434
+ - **四级自测**(任一失败即判无效,保留 `base/`,失败副本归 `backups/`,记 `self_update.test`):
435
+ 1. 静态:`node --check`、模块可加载、无循环依赖
436
+ 2. 单元:逐模块跑契约测试(`base/test/`,mock LLM、零 token)
437
+ 3. 集成冒烟:mock LLM 跑最小 loop,验证 Transport→Core→Loop→Tools 全链路
438
+ 4. 金丝雀:子进程 `--canary` 健康自检,通过才切主进程
439
+ - **契约测试**:随 `base/` 分发,定义各模块对外接口;base-user 复用同一套,Agent 新增能力须同步补测。
440
+ - **版本兼容**:`vanor update` 后比对 `.base-version`,不一致则提示——pin 旧 base 继续运行,或基于新 base 重新 fork。
441
+ - **回退**:`vanor rollback` 从 `backups/` 恢复;用户可随时切回 `base/`。
442
+ - **审计**:`self_update.diff` `self_update.test` `self_update.rollback`。
443
+
444
+ ---
445
+
446
+ ## 16. 插件机制 `[v2]`
447
+
448
+ 插件主要用于扩展**通道(Transport)**,可选注册工具。
449
+
450
+ ```ts
451
+ interface TransportPlugin {
452
+ name: string; // telegram | web | ...
453
+ start(ctx: PluginContext): Promise<void>; // 建立连接,入站调 ctx.dispatch(msg)
454
+ stop(): Promise<void>;
455
+ send(sessionId: string, msg: Message): Promise<void>; // 出站
456
+ tools?: Tool[]; // 可选:注册额外工具
457
+ }
458
+ ```
459
+
460
+ - **加载**:包内 `plugins/`(官方)+ `~/.vanor/plugins/`(用户)。
461
+ - **plugin vs skill**:
462
+
463
+ | | plugin | skill |
464
+ |--|--------|-------|
465
+ | 形态 | 代码(JS 模块) | 数据 + 说明(`SKILL.md`) |
466
+ | 用途 | 通道 / 集成 | 任务步骤(过程性记忆) |
467
+ | 加载 | 进程启动时 | prompt 注入 + 按需读取 |
468
+ | 信任 | 高(执行代码) | 中(被模型引用) |
469
+
470
+ - **安全**:DM pairing 由插件层与 Core 协作(§13)。
471
+
472
+ ---
473
+
474
+ ## 17. 模块与文件职责
475
+
476
+ | 路径 | 职责 |
477
+ |------|------|
478
+ | `index.js` | CLI 入口;解析子命令、配置向导、诊断、会话恢复启动 |
479
+ | `base/core/harness.js` | 编排、phase、配置热重载、i18n 状态、事件分发 |
480
+ | `base/core/loop.js` | LLM ↔ 工具迭代 |
481
+ | `base/core/session.js` | 会话读写、路由、生命周期 |
482
+ | `base/core/compaction.js` | 上下文压缩算法 |
483
+ | `base/core/prompt.js` | 系统 prompt 组装(记忆快照、技能、工具说明) |
484
+ | `base/i18n/index.js` | 语言检测、翻译器创建、key 插值与英文 fallback |
485
+ | `base/i18n/locales/` | 各语言翻译字典(当前 `en`、`zh-CN`) |
486
+ | `base/llm/index.js` | 统一 LLM 接口、failover |
487
+ | `base/llm/providers/` | 各 provider 适配 |
488
+ | `base/tools/` | 工具注册表与内置工具 |
489
+ | `base/skills/loader.js` | 技能发现、加载、去重 |
490
+ | `base/memory/` | 短 / 长 / 工作记忆 |
491
+ | `base/transport/message.js` | Message 协议与校验 |
492
+ | `base/transport/cli.js` | CLI TUI 与编解码 |
493
+ | `base/security/` | 审批、allow/deny、workspace 边界 |
494
+ | `base/evolve.js` `[v2]` | 自我更新 |
495
+ | `base/events.js` | 全局事件枚举 |
496
+ | `base/logger.js` | JSONL 日志 |
497
+ | `base/config.js` | 配置加载、校验、`env:` 解析、`ui.language` 安全写回 |
498
+ | `base/test/` | 契约测试(mock LLM) |
499
+
500
+ ---
501
+
502
+ ## 18. 里程碑
503
+
504
+ - **MVP**:CLI 交互;`read_file`/`write_file`/`edit_file`/`list_dir`/`exec`;有界记忆 + 关键词检索;工作区会话恢复与 compaction;技能加载与管理;多语言 i18n;命令审批 + workspace 边界;JSONL 日志;OpenAI/Anthropic 两 provider + failover。
505
+ - **v1.5**:子 Agent(`delegate`)、`search`/`fetch_url`、向量检索(可选 SQLite)。
506
+ - **v2**:自我改写(base-user + 四级自测)、IM 多通道插件、常驻 daemon(定时任务、网关、热重启)。
507
+
508
+ ---
509
+
510
+ ## 19. 开放问题
511
+
512
+ 1. failover 默认策略:自动切换 vs 仅提示?跨 provider 的 thinking 等参数如何降级。
513
+ 2. 关键词检索的召回上限,何时必须引入向量。
514
+ 3. daemon 是否提前到 v1.5(定时任务需求强烈时)。
515
+ 4. 技能调用是否需要独立 `skill` 工具,而非复用 `read_file`。
516
+ 5. 配置热加载范围:哪些键可运行时改、哪些需重启。
517
+ 6. Windows 原生支持(命令执行、路径、子进程差异)。
518
+ 7. base-user 改写的人类闸门:是否所有核心改动都需 `vanor diff` + 用户确认。
519
+
520
+
521
+ ## 测试大模型
522
+
523
+ 密钥通过环境变量注入,避免明文入库(原先贴在此处的明文 key 请尽快轮换作废):
524
+
525
+ ```bash
526
+ export VANOR_API_KEY="<your-key>"
527
+ ```
528
+
529
+ `~/.vanor/config.json`:
530
+
531
+ ```jsonc
532
+ {
533
+ "llm": {
534
+ "defaultModel": "wanyou/deepseek/deepseek-v4-pro",
535
+ "providers": {
536
+ "wanyou": {
537
+ "type": "openai",
538
+ "baseURL": "https://wanyouzhisuan.com/api/v1",
539
+ "apiKey": "env:VANOR_API_KEY"
540
+ }
541
+ }
542
+ }
543
+ }
544
+ ```