minimal-agent 0.6.1 → 0.6.2
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/README.md +144 -486
- package/package.json +3 -1
- package/src/config/configFile.js +36 -6
- package/src/main.js +12 -1
- package/src/plugins/pluginLoader.js +41 -1
- package/src/tools/grep/rgPath.js +10 -0
- package/src/utils/greenRoot.js +33 -0
- package/src/utils/resourcePaths.js +9 -1
package/README.md
CHANGED
|
@@ -1,573 +1,231 @@
|
|
|
1
1
|
# minimal-agent
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> 10 个内置工具
|
|
3
|
+
> 一个**最小化但功能完整**的 AI 编程助手 —— Bun + React + TypeScript + Ink TUI 构建,
|
|
4
|
+
> OpenAI 兼容、多 provider,10 个内置工具 + Skills + 插件 + Workflow DSL + 自动上下文压缩。
|
|
5
|
+
> 为**学习与教学**而生:代码量克制、注释充分,把一个 agent「从输入到工具循环到落盘」讲透。
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
> 项目由**两个独立的 npm 包**组成:`minimal-agent`(主体 ReAct CLI)+ `minimal-workflw`(可视化 workflow 编辑器)。两边只通过 `workflows/*.yaml` 文件耦合,零运行时依赖。
|
|
7
|
+
`v0.5.1` · Runtime: **Bun**(主)/ Node ≥ 20 · License: **PolyForm Noncommercial 1.0.0**
|
|
9
8
|
|
|
10
9
|
---
|
|
11
10
|
|
|
12
|
-
##
|
|
11
|
+
## 这是什么
|
|
12
|
+
|
|
13
|
+
一个能在终端里跟你对话、并真正动手改代码的 AI agent。核心是一个 **AsyncGenerator 主循环**
|
|
14
|
+
(`src/loop.ts`):
|
|
13
15
|
|
|
14
16
|
```
|
|
15
|
-
|
|
16
|
-
│ minimal-agent (主包) │
|
|
17
|
-
│ │
|
|
18
|
-
│ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
|
|
19
|
-
│ │ ReAct │ ─→ │ Tools │ │ Plugins/ │ │
|
|
20
|
-
│ │ runQuery│ │ 10 个 │ │ ralph-loop │ │
|
|
21
|
-
│ └─────────┘ └─────────┘ │ workflow-* │ │
|
|
22
|
-
│ ↑ └─────────────┘ │
|
|
23
|
-
│ │ 调用 ↑ │
|
|
24
|
-
│ ↓ │ 读 │
|
|
25
|
-
│ ┌─────────┐ │ │
|
|
26
|
-
│ │ Skills/ │ ─── 23 个 SKILL.md ──────┘ │
|
|
27
|
-
│ └─────────┘ │
|
|
28
|
-
└──────────────────────────────────┬───────────────────────────────┘
|
|
29
|
-
│
|
|
30
|
-
↓ 读写 workflows/*.yaml
|
|
31
|
-
│
|
|
32
|
-
┌──────────────────────────────────┴───────────────────────────────┐
|
|
33
|
-
│ minimal-workflw (独立编辑器包, editor/) │
|
|
34
|
-
│ │
|
|
35
|
-
│ Bun + Vite + React + React Flow │
|
|
36
|
-
│ ┌────────┐ ┌────────┐ ┌────────┐ │
|
|
37
|
-
│ │ Palette│ │ Canvas │ │Inspector│ │
|
|
38
|
-
│ └────────┘ └────────┘ └────────┘ │
|
|
39
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
17
|
+
用户输入 → 调 LLM → LLM 要调工具就执行 → 结果喂回去 → 继续调 LLM → … → 不再调工具 → 输出最终回答
|
|
40
18
|
```
|
|
41
19
|
|
|
42
|
-
|
|
20
|
+
每轮 `yield` 一个事件给 UI 实时刷新。上下文快撑爆时自动压缩成结构化摘要。麻雀虽小,
|
|
21
|
+
工具系统、Skills、插件、Workflow、多 session、自动压缩、可编程子进程契约一应俱全。
|
|
43
22
|
|
|
44
23
|
## 特性
|
|
45
24
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| 🧰 Skills 系统 (23) | 扫描 `skills/<name>/SKILL.md` frontmatter,按需懒加载完整 prompt |
|
|
55
|
-
| 🔌 插件系统 | `plugins/<id>/` drop-in 目录,声明式命令 / 富插件双契约;自带 ralph-loop + workflow-runner |
|
|
56
|
-
| 🎬 Workflow 引擎 | YAML DAG,7 种 step 类型(tool / llm / skill / assert / pause / branch / loop) |
|
|
57
|
-
| 🖼️ 可视化编辑器 | 独立 `minimal-workflw` 包,拖拽组装 workflow,节点面板从 `src/tools/` + `skills/` 自动派生 |
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
> ⚠️ **0.2.0 已发布到 npm 但插件系统不可用**(dynamic import .ts 在 node_modules 下被 Node 拒绝)。请使用 **0.3.0+**:源码 import 统一 `.js` 后缀 + tsc 原地编译,Bun(dev 模式)和 Node(install 模式)共用一份目录布局。
|
|
62
|
-
|
|
63
|
-
## 系统要求
|
|
64
|
-
|
|
65
|
-
- **Node.js ≥ 20**(npm install 后跑 bin 的运行时)或 **Bun ≥ 1.1**(推荐,dev 模式必需)
|
|
66
|
-
- 一个 OpenAI 兼容的 LLM API(MiniMax / OpenAI / DeepSeek / Kimi / 自建 vLLM 均可)
|
|
67
|
-
- *可选*:[Tavily](https://tavily.com) API key(开启 WebSearch)
|
|
68
|
-
- *可选*:[OpenRouter](https://openrouter.ai) API key(用 `image-gen-openrouter` skill 生成图片)
|
|
69
|
-
- *可选*:[MiniMax T2A](https://www.minimaxi.com/) 或 `pip install edge-tts`(用 `tts-zh` skill 做中文配音)
|
|
70
|
-
- *可选*:`ffmpeg` + `ffprobe`(跑视频合成类 workflow)
|
|
71
|
-
- *可选*:`playwright-core` + chromium(开启 WebBrowser;不装会自动降级到 WebFetch)
|
|
72
|
-
- *无需安装 ripgrep* —— 项目自带 `vendor/ripgrep/` 6 个平台二进制(arm64/x64 × win32/darwin/linux)
|
|
25
|
+
- **10 个内置工具**:Read / Write / Edit / MultiEdit / Glob / Grep / WebSearch / WebFetch / WebBrowser / Bash
|
|
26
|
+
- **OpenAI 兼容**:Message 形状对齐 Chat Completions,一套代码接 MiniMax / 火山 ARK / DeepSeek / OpenAI / Moonshot…
|
|
27
|
+
- **Skills 系统**:放一个 `SKILL.md` 就能扩展能力,运行时实时扫描注入 prompt,改文件即生效
|
|
28
|
+
- **插件系统**:`plugins/<id>/` drop-in,自带 `/ralph-loop`(复制任务循环)与 `/workflow`(YAML DAG 执行器)
|
|
29
|
+
- **自动上下文压缩**:接近上限时把旧对话压成摘要,保留系统提示 + 摘要 + 最近几条
|
|
30
|
+
- **多 session + 归档**:同一工作目录多个并行会话,`/new` 归档、`/restore` 恢复
|
|
31
|
+
- **可编程子进程**:`-p --output-format json` 输出一行结构化结局契约,方便被宿主程序 spawn 调度
|
|
32
|
+
- **三种运行形态**:源码跑(dev)/ npm 全局安装 / **bun --compile 绿色单文件夹**(免安装,可装 U 盘)
|
|
73
33
|
|
|
74
34
|
---
|
|
75
35
|
|
|
76
|
-
##
|
|
77
|
-
|
|
78
|
-
### A. 从 npm 安装(终端用户)
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
# 1. 装主包(ReAct CLI + 内置工具/插件/skills)
|
|
82
|
-
npm install -g minimal-agent
|
|
83
|
-
|
|
84
|
-
# 2. (可选)装可视化 workflow 编辑器
|
|
85
|
-
# ⚠ 当前 0.1 版编辑器 bin 直接吃 .ts,运行需要 Bun ≥1.1 在 PATH 上
|
|
86
|
-
# (主包 minimal-agent 在纯 Node 下完全可用,不依赖 Bun)
|
|
87
|
-
npm install -g minimal-workflw # 或:bun install -g minimal-workflw
|
|
88
|
-
|
|
89
|
-
# 3. 跑起来
|
|
90
|
-
minimal-agent # 启 TUI;首次会弹配置向导
|
|
91
|
-
minimal-workflw ~/my-agent-project # 在指定项目目录里启编辑器
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
两个包**独立发版、独立安装**。只想要 ReAct CLI 就装 `minimal-agent`;想拖拽编排 workflow 再加 `minimal-workflw`。两者通过 `<project>/workflows/*.yaml` 文件耦合,没有进程间通信。
|
|
36
|
+
## 快速开始
|
|
95
37
|
|
|
96
|
-
###
|
|
38
|
+
### 方式一:源码运行(推荐学习用)
|
|
97
39
|
|
|
98
40
|
```bash
|
|
99
41
|
git clone https://github.com/billsoft/kakadeai.git
|
|
100
42
|
cd kakadeai/minimal-agent
|
|
101
43
|
bun install
|
|
102
|
-
|
|
103
|
-
#
|
|
104
|
-
bun run start # TUI
|
|
105
|
-
bun run src/main.tsx -p "hi" # 非交互
|
|
106
|
-
|
|
107
|
-
# Workflow 编辑器(另开终端)
|
|
108
|
-
cd editor && bun install
|
|
109
|
-
bun scripts/cli.ts --project .. # 启编辑器并加载父项目的 workflows/
|
|
44
|
+
cp .env.example .env # 填入你的 API key(见下)
|
|
45
|
+
bun run start # 启动 TUI
|
|
110
46
|
```
|
|
111
47
|
|
|
112
|
-
|
|
48
|
+
> 没装 Bun?见 https://bun.sh 。也可在缺 Bun 时用 Node 跑发布产物(见「三种运行形态」)。
|
|
113
49
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
支持两种配置方式,**任选其一**即可:
|
|
117
|
-
|
|
118
|
-
### 方式 A:手写 .env(适合开发者 / `-p` 非交互模式)
|
|
50
|
+
### 方式二:全局安装后随处可用
|
|
119
51
|
|
|
120
52
|
```bash
|
|
121
|
-
|
|
122
|
-
#
|
|
123
|
-
bun run start
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 方式 B:首次启动配置向导(适合零配置上手)
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
bun run start # 没检测到任何配置 → 自动弹 Ink 向导
|
|
53
|
+
npm install -g minimal-agent
|
|
54
|
+
minimal-agent # 首次运行进入配置向导
|
|
130
55
|
```
|
|
131
56
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
> 测试不通过或中途 ESC 退出 → **不写文件** → 下次启动继续走向导,直到成功为止。
|
|
57
|
+
## 配置
|
|
135
58
|
|
|
136
|
-
|
|
59
|
+
两种方式任选其一(**环境变量优先级高于配置文件**):
|
|
137
60
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
```
|
|
61
|
+
1. **`.env` 文件**(源码模式,Bun 自动加载)—— 复制 `.env.example` 改值。
|
|
62
|
+
2. **配置向导** —— 首次运行 `minimal-agent`(TUI)会引导你填,写到 `~/.minimal-agent/config.json`。
|
|
141
63
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
### .env 字段
|
|
64
|
+
必需三项 + 可选项:
|
|
145
65
|
|
|
146
66
|
```env
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
#
|
|
153
|
-
MINIMAL_AGENT_PROVIDER=minimax # 仅显示用,默认 "env"
|
|
154
|
-
MINIMAL_AGENT_CONTEXT_WINDOW=204800 # 上下文窗口,默认 128000
|
|
155
|
-
TAVILY_API_KEY= # 启用 WebSearch
|
|
156
|
-
OPENROUTER_API_KEY= # 启用 image-gen-openrouter skill
|
|
157
|
-
MINIMAX_API_KEY= # 启用 tts-zh skill(minimax 通道)
|
|
158
|
-
MINIMAX_GROUP_ID= # 同上
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## 命令行参数
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
minimal-agent [选项] [提示...]
|
|
167
|
-
echo "提示" | minimal-agent [选项]
|
|
168
|
-
|
|
169
|
-
选项:
|
|
170
|
-
-p, --print 非交互模式,问答一次后退出
|
|
171
|
-
-v, --verbose 显示工具调用 / 压缩详情
|
|
172
|
-
-d, --cwd <dir> 指定工作目录(不存在自动创建)
|
|
173
|
-
-h, --help 帮助
|
|
174
|
-
-V, --version 版本号
|
|
67
|
+
MINIMAL_AGENT_BASE_URL=https://api.minimax.chat/v1 # 必需:API 地址
|
|
68
|
+
MINIMAL_AGENT_API_KEY=your-api-key # 必需:API key
|
|
69
|
+
MINIMAL_AGENT_MODEL=MiniMax-M2.7 # 必需:模型名
|
|
70
|
+
MINIMAL_AGENT_PROVIDER=minimax # 可选:友好名,默认 "env"
|
|
71
|
+
MINIMAL_AGENT_CONTEXT_WINDOW=204800 # 可选:上下文窗口,默认 128000
|
|
72
|
+
TAVILY_API_KEY= # 可选:WebSearch 工具用(https://tavily.com)
|
|
175
73
|
```
|
|
176
74
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
| 输入 | 效果 |
|
|
180
|
-
|---|---|
|
|
181
|
-
| `/new` | 清空历史,重建 system prompt;同时清掉当前工作目录下所有 `.minimal-agent*/` 状态目录 |
|
|
182
|
-
| `/compact` | 手动压缩当前对话 |
|
|
183
|
-
| `/config` | 重新配置 LLM provider(LLM 驱动 skill,必须单独输入) |
|
|
184
|
-
| `/init` | 让 LLM 扫一遍项目目录,生成或更新 `minimal-agent.md` |
|
|
185
|
-
| `/ralph-loop "<目标>" [...]` | 复制任务循环(plugin: ralph-wiggum) |
|
|
186
|
-
| `/workflow <name> [--input k=v]` | 执行 workflow yaml(plugin: workflow-runner) |
|
|
187
|
-
| `/workflows` | 列出可用 workflow |
|
|
188
|
-
| `Alt+Enter` | 输入框换行 |
|
|
189
|
-
| `ESC` / `Ctrl+C` | 中断当前任务 |
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## 内置工具(10 个)
|
|
194
|
-
|
|
195
|
-
| 名称 | 用途 | 实现 |
|
|
196
|
-
|---|---|---|
|
|
197
|
-
| `Read` | 读文件(支持 offset / limit / 二进制嗅探) | `node:fs/promises` |
|
|
198
|
-
| `Write` | 整体写文件(覆盖前要先 Read) | `node:fs/promises` |
|
|
199
|
-
| `Edit` | 字符串精确替换(old_string 必须唯一) | `node:fs/promises` |
|
|
200
|
-
| `MultiEdit` | 一次性多处替换(原子操作) | `node:fs/promises` |
|
|
201
|
-
| `Glob` | 文件名模式匹配 | `fast-glob` |
|
|
202
|
-
| `Grep` | 文件内容搜索 | spawn vendor 内置 ripgrep |
|
|
203
|
-
| `Bash` | 终端命令执行(黑名单 + 超时双保险) | `node:child_process` |
|
|
204
|
-
| `WebSearch` | Web 搜索(自动注入当前日期) | Tavily API |
|
|
205
|
-
| `WebFetch` | 抓网页 → HTML→Markdown(LRU 缓存) | `fetch` + `turndown` |
|
|
206
|
-
| `WebBrowser` | 浏览器交互(点击 / 填表单 / 截图 / JS 渲染) | playwright-core(**可选依赖**) |
|
|
207
|
-
|
|
208
|
-
工具入口:`src/tools/index.ts` 的 `ALL_TOOLS`。
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## Skills 系统(23 个)
|
|
213
|
-
|
|
214
|
-
每个 `skills/<name>/SKILL.md` 都是一段 LLM 子系统提示词 + frontmatter 元数据。框架在每轮组装 system prompt 时**实时扫描**目录注入 `name + description`,完整 body 按需懒加载(用户输入命中 description 触发词时才载入)。**新增 skill 改文件即可,不用改代码。**
|
|
215
|
-
|
|
216
|
-
当前内置 skills:
|
|
217
|
-
|
|
218
|
-
| 类别 | Skill |
|
|
219
|
-
|---|---|
|
|
220
|
-
| **配置 / 元** | `config` `init` `compact` `simplify` `batch` `skill-creator` `mcp-builder` |
|
|
221
|
-
| **创作** | `algorithmic-art` `canvas-design` `frontend-design` `theme-factory` `web-artifacts-builder` `webapp-testing` |
|
|
222
|
-
| **文档** | `docx` `pdf` `pptx` `xlsx` `diff` `commit` |
|
|
223
|
-
| **多媒体流水线** | `image-gen-openrouter`(OpenRouter Nano Banana 文生图) `tts-zh`(MiniMax + edge-tts 中文配音) `subtitle-srt`(通用 SRT 字幕) `video-compose`(ffmpeg 视频合成) |
|
|
224
|
-
|
|
225
|
-
**双源加载**:cwd 优先,packageRoot fallback。同名(frontmatter::name)时 cwd 覆盖。用户在自己项目里放 `skills/<name>/SKILL.md` 即可扩展或覆盖 npm 包自带的 skill。
|
|
75
|
+
`.env.example` 里附了 **MiniMax / 火山 ARK / DeepSeek / OpenAI / Moonshot** 五套现成模板,注释切换即可。
|
|
226
76
|
|
|
227
77
|
---
|
|
228
78
|
|
|
229
|
-
##
|
|
230
|
-
|
|
231
|
-
**硬约束**:`src/plugins/` 只放**框架**(discovery / 命令解析 / plugin.ts 加载 / hook 引擎 / transcript);任何具体插件功能都住在各自的 `plugins/<id>/` 目录内。**新增插件 = 把目录 drop 进 `plugins/`,`src/` 一行不动。**
|
|
79
|
+
## 使用
|
|
232
80
|
|
|
233
|
-
###
|
|
234
|
-
|
|
235
|
-
#### `plugins/ralph-wiggum/` — `/ralph-loop`
|
|
236
|
-
|
|
237
|
-
Karpathy 风格的复制任务循环:PLAN → BUILD → VERIFY → HEAL → DONE 五阶段 FSM,配合 verification gate 和 stop-hook 反馈。
|
|
81
|
+
### 交互式 TUI
|
|
238
82
|
|
|
239
83
|
```bash
|
|
240
|
-
|
|
84
|
+
bun run start # 或全局安装后直接 minimal-agent
|
|
241
85
|
```
|
|
242
86
|
|
|
243
|
-
|
|
87
|
+
布局自上而下:消息列表(scrollback)→ 本轮过程区(流式思维链/工具进度)→ 输入框 → 状态栏。
|
|
88
|
+
`ESC` / `Ctrl+C` 中断当前任务,`Alt+Enter` 输入框换行。
|
|
244
89
|
|
|
245
|
-
|
|
90
|
+
**内置 slash 命令**(不走 LLM,UI 层直接拦截):
|
|
246
91
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
| step 类型 | 用途 |
|
|
92
|
+
| 命令 | 作用 |
|
|
250
93
|
|---|---|
|
|
251
|
-
|
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
|
|
|
256
|
-
|
|
|
257
|
-
| `pause` | 等待用户输入 |
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
/workflow youtube-shorts --input topic="猫咪赛博朋克城市探险"
|
|
261
|
-
/workflow youtube-shorts "猫咪赛博朋克城市探险" # 位置参数:按 inputs 声明顺序映射,.yaml 后缀可省
|
|
262
|
-
/workflows # 列出所有 workflow
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
> ⚠ **位置参数依赖 yaml 文件 `inputs:` 数组的声明顺序**。重排 inputs 顺序会破坏所有现有 CLI 位置参数调用(位置参数会映射到错误的 input)。
|
|
266
|
-
> 多 input workflow 推荐使用 `--input key=value` 显式 KV 形式,更稳。
|
|
267
|
-
|
|
268
|
-
### 写自己的插件
|
|
269
|
-
|
|
270
|
-
参考 `plugins/HOW-TO-WRITE-A-PLUGIN.md`。两种契约任选其一即可生效:
|
|
271
|
-
|
|
272
|
-
- **声明式**(纯 markdown):`plugins/<id>/commands/<cmd>.md` 用 frontmatter + prompt 模板,框架自动注册命令,零 TS 代码。
|
|
273
|
-
- **富插件**:根目录写 `plugin.ts` 导出 `default { runCommand }`,由插件自己 yield `LoopEvent` 流(可调 `runQuery` / 跑循环 / 起子进程)。
|
|
274
|
-
|
|
275
|
-
插件作者唯一稳定 import 路径:`from '../../src/plugin-sdk.ts'`。
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## minimal-workflw 可视化编辑器(editor/ 子项目)
|
|
94
|
+
| `/new`(`/clear`) | 归档当前对话 → 清空 → 用最新 skills 重建 system prompt |
|
|
95
|
+
| `/compact` | 手动压缩当前对话 |
|
|
96
|
+
| `/session <name>` · `/sessions` | 切换 / 列出同目录下的并行会话 |
|
|
97
|
+
| `/list-archive` · `/restore <id>` | 列出 / 恢复归档对话 |
|
|
98
|
+
| `/show <id>` | 展开某次工具调用的完整输出(列表默认折叠到 3 行) |
|
|
99
|
+
| `/exit`(`/quit`) | 退出 |
|
|
280
100
|
|
|
281
|
-
|
|
101
|
+
`/init`、`/config` 等是 **LLM 驱动的 skill**;`/workflow`、`/ralph-loop` 是**插件命令**(见下)。
|
|
282
102
|
|
|
283
|
-
###
|
|
103
|
+
### 非交互模式(`-p`)—— 脚本 / CI / 被宿主程序调用
|
|
284
104
|
|
|
285
105
|
```bash
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
minimal-
|
|
289
|
-
minimal-
|
|
290
|
-
|
|
291
|
-
# 从源码跑(在 editor/ 目录下)
|
|
292
|
-
bun scripts/cli.ts --project .. # 一键起后端 + Vite
|
|
293
|
-
bun run dev # 只起 Vite 前端(不带后端文件 IO)
|
|
294
|
-
bun run server # 只起 Bun.serve() 后端
|
|
106
|
+
minimal-agent -p "帮我写一个 hello world"
|
|
107
|
+
echo "解释这段代码" | minimal-agent -p
|
|
108
|
+
minimal-agent -p "处理资料" -d /tmp/job-123 # -d 隔离工作目录
|
|
109
|
+
minimal-agent -p --output-format json "做点事" # 一行结构化结局契约
|
|
295
110
|
```
|
|
296
111
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
2. 检查 `<project>/.editor-cache/manifest.json` 新鲜度,过期则 spawn `<project>/scripts/export-manifest.ts` 重新生成(脚本缺失时 fallback 到 `editor/public/manifest.fallback.json`)
|
|
306
|
-
3. 启动 `Bun.serve()` 在 127.0.0.1:5174(文件 IO + manifest 端点)
|
|
307
|
-
4. 启动 `bunx --bun vite` 在 127.0.0.1:5173
|
|
308
|
-
|
|
309
|
-
### 解耦红线(D2 红线)
|
|
310
|
-
|
|
311
|
-
- ❌ 编辑器**绝不** import `minimal-agent/src/*` 任何模块
|
|
312
|
-
- ❌ minimal-agent 主程序**绝不**在启动路径里 spawn 编辑器
|
|
313
|
-
- ✅ 接触面只有:`workflows/*.yaml`(双向)+ `.editor-cache/manifest.json`(单向,主项目→编辑器)
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
## 示例:YouTube Shorts 生产流水线
|
|
318
|
-
|
|
319
|
-
`workflows/youtube-shorts.yaml` 是一条端到端示例,演示如何在编辑器里拖出非编程用户场景:
|
|
320
|
-
|
|
321
|
-
```
|
|
322
|
-
输入主题
|
|
323
|
-
↓
|
|
324
|
-
LLM 写 5 幕剧本 (JSON)
|
|
325
|
-
↓
|
|
326
|
-
parse_script (jq 拆出 prompts.txt / narrations.txt)
|
|
327
|
-
↓
|
|
328
|
-
loop 5×: image-gen-openrouter ──→ assets/img_0..4.png
|
|
329
|
-
↓
|
|
330
|
-
loop 5×: tts-zh ──→ assets/audio_0..4.mp3
|
|
331
|
-
↓
|
|
332
|
-
probe_durations + merge_audio ──→ assets/audio.mp3
|
|
333
|
-
↓
|
|
334
|
-
subtitle-srt ──→ assets/subs.srt
|
|
335
|
-
↓
|
|
336
|
-
video-compose (ffmpeg) ──→ out/shorts.mp4
|
|
337
|
-
```
|
|
112
|
+
| 选项 | 说明 |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `-p` / `--print` | 非交互单次问答,输出后退出 |
|
|
115
|
+
| `-v` / `--verbose` | stderr 显示工具调用、压缩等过程 |
|
|
116
|
+
| `-d <dir>` / `--cwd <dir>` | 指定工作目录(不存在自动创建),隔离文件视图与会话 |
|
|
117
|
+
| `--output-format <text\|json>` | `text`(默认,纯文本答案)/ `json`(一行结局契约) |
|
|
118
|
+
| `--max-turns <n>` | 最大工具循环轮数(防失控,默认 50) |
|
|
119
|
+
| `-V` / `--version` · `-h` / `--help` | 版本 / 帮助 |
|
|
338
120
|
|
|
339
|
-
|
|
121
|
+
**`--output-format json` 结局契约**(供宿主程序可靠 spawn):
|
|
340
122
|
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
|
|
123
|
+
```json
|
|
124
|
+
{ "ok": true, "result": "...", "is_error": false,
|
|
125
|
+
"stop_reason": "end_turn", "num_turns": 3, "error": null }
|
|
344
126
|
```
|
|
345
127
|
|
|
346
|
-
|
|
128
|
+
`ok = (stop_reason === 'end_turn')`;`stop_reason ∈ end_turn | max_turns | error | interrupted | config_error`。
|
|
129
|
+
> ⚠️ `ok=true` 只代表「正常跑完未报错」,**不代表任务目标达成**——宿主应额外校验 `result` / 产物文件。
|
|
347
130
|
|
|
348
131
|
---
|
|
349
132
|
|
|
350
|
-
##
|
|
133
|
+
## 工具
|
|
351
134
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
│ ├── tools/ # 10 个内置工具
|
|
365
|
-
│ └── ui/ # Ink TUI 组件
|
|
366
|
-
├── plugins/ # 实际插件(drop-in)
|
|
367
|
-
│ ├── HOW-TO-WRITE-A-PLUGIN.md
|
|
368
|
-
│ ├── ralph-wiggum/ # /ralph-loop
|
|
369
|
-
│ └── workflow-runner/ # /workflow + /workflows
|
|
370
|
-
├── skills/ # 23 个 SKILL.md
|
|
371
|
-
├── workflows/ # YAML DAG 工作流定义
|
|
372
|
-
│ ├── book-review-short.yaml
|
|
373
|
-
│ ├── e2e-write-greet.yaml
|
|
374
|
-
│ └── youtube-shorts.yaml
|
|
375
|
-
├── editor/ # minimal-workflw 独立子包
|
|
376
|
-
│ ├── package.json # name: minimal-workflw, bin: minimal-workflw
|
|
377
|
-
│ ├── scripts/
|
|
378
|
-
│ │ ├── cli.ts # bin 入口(一键起后端+前端)
|
|
379
|
-
│ │ └── server.ts # Bun.serve() 文件 IO + manifest 端点
|
|
380
|
-
│ ├── public/manifest.fallback.json
|
|
381
|
-
│ └── src/ # React + React Flow + Monaco UI
|
|
382
|
-
├── scripts/
|
|
383
|
-
│ └── export-manifest.ts # 主项目 → editor 的工具/skill 元数据导出
|
|
384
|
-
├── vendor/ripgrep/ # 自带 ripgrep 二进制(6 个平台)
|
|
385
|
-
├── test/ # bun test 用例
|
|
386
|
-
└── minimal-agent.md # 项目指令(类似 CLAUDE.md,自动注入 system prompt)
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## 发布与安装(npm 工作流)
|
|
392
|
-
|
|
393
|
-
项目作为**两个独立 npm 包**发布:
|
|
394
|
-
|
|
395
|
-
| 包 | 路径 | 命令 | 职责 |
|
|
396
|
-
|---|---|---|---|
|
|
397
|
-
| `minimal-agent` | 仓库根 | `minimal-agent` | ReAct CLI + 工具 + 插件 + skills |
|
|
398
|
-
| `minimal-workflw` | `editor/` | `minimal-workflw` | 可视化 workflow 编辑器 |
|
|
399
|
-
|
|
400
|
-
> **为什么不打成一个包**?90% 的 ReAct CLI 用户不需要编辑器,编辑器的依赖(React / Vite / @xyflow/react / Monaco 等)有几十 MB,不应该污染主包。两边解耦后用户按需取用,通过 `<project>/workflows/*.yaml` 文件交流,无进程间通信。
|
|
401
|
-
|
|
402
|
-
### 模块解析模式(dev vs install 通用范式)
|
|
403
|
-
|
|
404
|
-
主包采用**标准 NodeNext + tsc 原地编译**模式,解决了 npm 包"dev 跑 .ts,install 跑 .js"的通用目录差异问题:
|
|
135
|
+
| 工具 | 底层 | 说明 |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| **Read** | `node:fs` | 支持 offset/limit、二进制嗅探 |
|
|
138
|
+
| **Write** | `node:fs` | 整体写入;覆盖前需先 Read |
|
|
139
|
+
| **Edit** | `node:fs` | `old_string` 必须唯一 |
|
|
140
|
+
| **MultiEdit** | `node:fs` | 单文件一次多处替换 |
|
|
141
|
+
| **Glob** | `fast-glob` | 文件名 pattern |
|
|
142
|
+
| **Grep** | 自带 `ripgrep` | 内容搜索,**自带 6 平台二进制**,开箱即用 |
|
|
143
|
+
| **WebSearch** | Tavily API | 需 `TAVILY_API_KEY` |
|
|
144
|
+
| **WebFetch** | `fetch` + `turndown` | HTML → Markdown,LRU 缓存 |
|
|
145
|
+
| **WebBrowser** | `playwright-core`(可选) | 缺依赖时自动降级 WebFetch |
|
|
146
|
+
| **Bash** | `child_process` | 黑名单 + 超时双保险 |
|
|
405
147
|
|
|
406
|
-
|
|
407
|
-
- dev 模式跑 `bun run dev`:Bun 把 `'./foo.js'` 透明解析到 `./foo.ts` 源文件,**无需任何构建**
|
|
408
|
-
- publish 前 `tsc --build`:原地输出 `./foo.js`(与 .ts 兄弟位置,路径布局完全一致)
|
|
409
|
-
- `.npmignore` 排除 `*.ts/*.tsx`,tarball 里**只发 .js**
|
|
410
|
-
- install 后 Node 直接吃 .js,Bun 也吃 .js(行为统一)
|
|
411
|
-
- 源码 `plugin.ts` 里 `import '../../src/plugin-sdk.js'` 在 dev / install 两种模式下都指向同一份模块(dev=plugin-sdk.ts,install=plugin-sdk.js),**模块身份天然 singleton**
|
|
148
|
+
工具失败**不抛异常**——把 error 写进结果喂回 LLM 让它自己重试。
|
|
412
149
|
|
|
413
|
-
|
|
150
|
+
## Skills · 插件 · Workflow
|
|
414
151
|
|
|
415
|
-
|
|
152
|
+
- **Skills**:每个 `skills/<name>/SKILL.md`(带 frontmatter)。运行时实时扫描注入 prompt,prompt 体按需懒加载。
|
|
153
|
+
cwd 优先、包内 fallback,用户可在自己项目里覆盖或新增。内置 20+ 个(`/init`、`/config`、文档生成、设计等)。
|
|
154
|
+
- **插件**(`plugins/<id>/`,drop-in,`src/` 一行不改):
|
|
155
|
+
- **`/ralph-loop`**(ralph-wiggum)—— Karpathy 式复制任务循环,自带 PLAN/BUILD/VERIFY/HEAL 状态机 + 验证门
|
|
156
|
+
- **`/workflow` · `/workflows`**(workflow-runner)—— YAML DAG 执行器,9 种 step(tool/llm/skill/assert/branch/loop/pause/parallel/vote)
|
|
157
|
+
- **可视化编辑器**:`editor/` 子项目(Bun + Vite + React Flow)拖拽编排 workflow YAML(不属于 npm 发布物)。
|
|
416
158
|
|
|
417
|
-
|
|
418
|
-
# 只装主包
|
|
419
|
-
npm install -g minimal-agent
|
|
420
|
-
minimal-agent # 启 ReAct TUI
|
|
159
|
+
---
|
|
421
160
|
|
|
422
|
-
|
|
423
|
-
npm install -g minimal-workflw
|
|
424
|
-
minimal-workflw /path/to/your-project # 启编辑器
|
|
425
|
-
```
|
|
161
|
+
## 三种运行形态
|
|
426
162
|
|
|
427
|
-
|
|
163
|
+
| 形态 | 怎么跑 | 适合 |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| **源码 dev** | `bun run dev`(Bun 直接吃 `.ts`,watch 热重载) | 学习、改源码 |
|
|
166
|
+
| **npm 安装** | `npm i -g minimal-agent` → `minimal-agent`(Node 吃 tsc 编译的 `.js`) | 日常使用 |
|
|
167
|
+
| **绿色版** | `bun run compile:green` → 单文件夹(内嵌运行时,**免装 Node/Bun**) | 免安装分发 / 装 U 盘 |
|
|
428
168
|
|
|
429
|
-
|
|
169
|
+
**绿色版**把运行时 + 全部依赖 + 工具执行器编译进**单个可执行文件**,连同 `vendor/ripgrep`、`skills`、
|
|
170
|
+
`workflows`、插件一起组装成「拷到任意机器即插即用」的文件夹;支持把 `config.json` 预置在目录里做到
|
|
171
|
+
**零配置开箱即用**。三平台(Windows / macOS / Linux)各一份。详见 **[GREEN-BUILD.md](./GREEN-BUILD.md)**。
|
|
430
172
|
|
|
431
173
|
```bash
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
npm publish # prepublishOnly 自动跑 clean + tsc --build
|
|
174
|
+
bun run compile:green # 三主平台:win-x64 / mac-arm64 / linux-x64
|
|
175
|
+
bun run compile:green:host # 仅当前平台
|
|
176
|
+
bun run compile:green -- --config 你的config.json # 编译时烧录预配置
|
|
436
177
|
```
|
|
437
178
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
```json
|
|
441
|
-
{
|
|
442
|
-
"name": "minimal-agent",
|
|
443
|
-
"bin": { "minimal-agent": "src/main.js" },
|
|
444
|
-
"files": ["src", "plugins", "skills", "workflows", "vendor/ripgrep", "README.md", "LICENSE"],
|
|
445
|
-
"engines": { "node": ">=20" },
|
|
446
|
-
"scripts": {
|
|
447
|
-
"build": "tsc --build",
|
|
448
|
-
"clean": "bun scripts/clean-build.ts",
|
|
449
|
-
"prepublishOnly": "bun run clean && bun run build"
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
`prepublishOnly` 先清旧产物再跑 tsc 原地编译,`.npmignore` 排除源码 .ts/.tsx 让 tarball 里**只有 .js**。`src/main.tsx` 首行 `#!/usr/bin/env node` 由 tsc 编译产物保留,直接作为 bin。
|
|
179
|
+
---
|
|
455
180
|
|
|
456
|
-
|
|
181
|
+
## 开发
|
|
457
182
|
|
|
458
183
|
```bash
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
bun run
|
|
462
|
-
|
|
463
|
-
|
|
184
|
+
bun install # 安装依赖
|
|
185
|
+
bun run start # 运行(= bun run src/main.tsx)
|
|
186
|
+
bun run dev # watch 热重载
|
|
187
|
+
bun test # 跑所有测试
|
|
188
|
+
bun run typecheck # tsc --noEmit
|
|
189
|
+
bun run build # tsc --build → src/*.js + plugins/*.js(npm 发布用)
|
|
190
|
+
bun run clean # 删除原地编译产物
|
|
464
191
|
```
|
|
465
192
|
|
|
466
|
-
|
|
193
|
+
格式/检查用 **Biome**(2 空格缩进、单引号、80 列)。模块解析 NodeNext:源码 import 统一写 `.js` 后缀,
|
|
194
|
+
dev 下 Bun 透明回退到 `.ts` 兄弟,发布前 `tsc --build` 原地产出 `.js`。
|
|
467
195
|
|
|
468
|
-
|
|
196
|
+
## 架构速览
|
|
469
197
|
|
|
470
|
-
|
|
198
|
+
| 模块 | 职责 |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `src/loop.ts` | `runQuery()` 主循环(AsyncGenerator)—— 系统的「心脏」 |
|
|
201
|
+
| `src/llm/client.ts` | OpenAI 兼容流式客户端(text / tool_call / reasoning 三路) |
|
|
202
|
+
| `src/types.ts` | 对齐 Chat Completions 的 Message / ToolCall / 事件类型 |
|
|
203
|
+
| `src/tools/` | 10 个工具 + `executeTool()` 参数防线(JSON.parse → Zod → call) |
|
|
204
|
+
| `src/context/compact.ts` | 自动上下文压缩 |
|
|
205
|
+
| `src/prompts/` | system prompt 组装 + Skills 实时扫描注入 |
|
|
206
|
+
| `src/plugins/` | 插件框架(discovery / 命令路由 / hook 引擎 / transcript) |
|
|
207
|
+
| `src/ui/` | Ink TUI(MessageList / LiveArea / InputBox / StatusLine) |
|
|
208
|
+
| `src/config.ts` · `src/config/` | 分层 provider 配置(env → 配置文件) |
|
|
471
209
|
|
|
472
|
-
|
|
210
|
+
> 给 Claude Code / 维护者的深入说明见 **[CLAUDE.md](./CLAUDE.md)**。
|
|
473
211
|
|
|
474
|
-
##
|
|
212
|
+
## 目录结构
|
|
475
213
|
|
|
476
214
|
```
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
├── for each tool_call:
|
|
489
|
-
│ executeTool # zod 校验 → tool.call(args)
|
|
490
|
-
│ append tool result # 写回 history
|
|
491
|
-
│
|
|
492
|
-
└── continue # 下一轮
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
`/workflow` / `/ralph-loop` 这类插件命令在主循环**之前**被路由器拦截,由 `pluginRunner` 分发到对应插件的 `runCommand` 接管。详见 `src/loop.ts` + `src/plugins/pluginRunner.ts`。
|
|
496
|
-
|
|
497
|
-
---
|
|
498
|
-
|
|
499
|
-
## 开发命令
|
|
500
|
-
|
|
501
|
-
```bash
|
|
502
|
-
# 主包
|
|
503
|
-
bun run start # TUI
|
|
504
|
-
bun run dev # 热重载
|
|
505
|
-
bun test # 跑全部测试
|
|
506
|
-
bun test test/llm.live.test.ts # 单个测试文件
|
|
507
|
-
bun run typecheck # tsc --noEmit
|
|
508
|
-
bun run build # tsup 打包
|
|
509
|
-
|
|
510
|
-
# 编辑器
|
|
511
|
-
cd editor
|
|
512
|
-
bun scripts/cli.ts --project .. # 一键起后端 + 前端
|
|
513
|
-
bun run dev # 只起前端
|
|
514
|
-
bun run server # 只起后端
|
|
515
|
-
bun test
|
|
215
|
+
minimal-agent/
|
|
216
|
+
├─ src/ # 主程序(loop / tools / llm / ui / plugins / config …)
|
|
217
|
+
├─ skills/ # 内置 skill(每个一个 SKILL.md)
|
|
218
|
+
├─ plugins/ # 内置插件(ralph-wiggum / workflow-runner)
|
|
219
|
+
├─ workflows/ # 内置 workflow(YAML)
|
|
220
|
+
├─ vendor/ripgrep/ # Grep 自带的 6 平台 ripgrep 二进制
|
|
221
|
+
├─ scripts/ # 构建脚本(含绿色版 build-green.ts)
|
|
222
|
+
├─ editor/ # workflow 可视化编辑器(独立子项目)
|
|
223
|
+
├─ test/ # 测试
|
|
224
|
+
├─ GREEN-BUILD.md # 绿色版/U 盘分发指南
|
|
225
|
+
└─ CLAUDE.md # 架构与开发指南
|
|
516
226
|
```
|
|
517
227
|
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
## 与 [kakadeai](../) 主项目的关系
|
|
521
|
-
|
|
522
|
-
minimal-agent 是 Claude Code 源码的**教学版**:
|
|
523
|
-
|
|
524
|
-
| 维度 | kakadeai/Claude Code | minimal-agent |
|
|
525
|
-
|---|---|---|
|
|
526
|
-
| 代码量 | 数十万行 | 数千行 |
|
|
527
|
-
| 主循环 | QueryEngine + 状态机 | 单层 while + AsyncGenerator |
|
|
528
|
-
| Tool 数 | 60+(含 MCP / Agent / Notebook 等) | 10 个内置 |
|
|
529
|
-
| Skill 加载 | `registerBundledSkill()` 注册 | 扫描目录 + frontmatter 解析 |
|
|
530
|
-
| Plugin | builtin / bundled plugin registry | drop-in `plugins/<id>/` 目录 |
|
|
531
|
-
| Workflow | / | YAML DAG + 可视化编辑器 |
|
|
532
|
-
| Provider | Anthropic SDK | OpenAI Chat Completions(多家兼容) |
|
|
533
|
-
| UI | 完整 Ink 框架 + 主题系统 | 基础 Ink 组件 |
|
|
534
|
-
|
|
535
|
-
设计目标是**砍掉一切非必要的抽象**,把"AI agent 是什么 / 怎么扩展"暴露在你眼前。
|
|
536
|
-
|
|
537
|
-
---
|
|
538
|
-
|
|
539
|
-
## 故障排查
|
|
540
|
-
|
|
541
|
-
| 现象 | 原因 / 处理 |
|
|
542
|
-
|---|---|
|
|
543
|
-
| `-p` 模式报 `缺少必需的环境变量` | `-p` 不弹向导。先用 `bun run start` 走向导,或手动写 `.env` |
|
|
544
|
-
| TUI 启动就弹向导,但我已经写了 .env | 检查 .env 是否在项目根(`bun run` 当前目录),三项变量名拼写无误 |
|
|
545
|
-
| 向导测试卡 `HTTP 401` / `HTTP 404` | 401 = API key 错;404 = baseURL 没拼 `/v1` 或 model id 不对 |
|
|
546
|
-
| 改完 `~/.minimal-agent/config.json` 没生效 | 当前进程仍持有旧配置;Ctrl+C 退出后重新 `bun run start` |
|
|
547
|
-
| `rg: command not found` | 不该出现——项目自带 ripgrep;若出现说明 vendor 目录未随 npm tarball 复制 |
|
|
548
|
-
| `WebBrowser 报"无法启动浏览器"` | 需 `npm install playwright-core && npx playwright install chromium`;或改用 WebFetch |
|
|
549
|
-
| `minimal-workflw` 命令找不到 | 没装编辑器包;`npm install -g minimal-workflw` 即可 |
|
|
550
|
-
| 编辑器空白 / 节点面板没工具 | `.editor-cache/manifest.json` 缺失;`minimal-workflw --refresh-manifest` 强制刷新 |
|
|
551
|
-
| 编辑器看不到自定义 skill | 检查 `<project>/skills/<name>/SKILL.md` frontmatter 是否有 `name` 和 `description` 两个必填字段 |
|
|
552
|
-
| ffmpeg / ffprobe 未找到 | `winget install Gyan.FFmpeg` / `brew install ffmpeg` / `apt install ffmpeg` |
|
|
553
|
-
| 历史"被吃掉"了 | 上次进程崩溃前未保存;`~/.minimal-agent/last-context.json` 是最后写入的一次 |
|
|
554
|
-
|
|
555
|
-
---
|
|
556
|
-
|
|
557
228
|
## License
|
|
558
229
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
**允许**:
|
|
562
|
-
- 个人学习、教学、研究、非商业实验用途
|
|
563
|
-
- 修改源代码、本地构建、自托管运行
|
|
564
|
-
- 在非商业项目中引用 / fork
|
|
565
|
-
- 慈善机构、教育机构、公共研究组织等非营利组织使用
|
|
566
|
-
|
|
567
|
-
**禁止**:
|
|
568
|
-
- 任何形式的商业使用(包括但不限于:销售本软件、用于公司内部生产环境、作为商业产品的组件、提供付费服务)
|
|
569
|
-
- 未经授权的再分发(必须保留 LICENSE 与版权声明)
|
|
570
|
-
|
|
571
|
-
如需商业授权,请联系作者另行洽谈。
|
|
572
|
-
|
|
573
|
-
`vendor/ripgrep/` 内的二进制为上游 ripgrep 项目的 MIT / Unlicense 双重许可,**不受**本项目 PolyForm Noncommercial License 约束。
|
|
230
|
+
[PolyForm Noncommercial License 1.0.0](./LICENSE) © 2026 billsoft。
|
|
231
|
+
允许非商业用途的使用、修改与分发;商业用途请联系作者。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minimal-agent",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "最小化 Agent 系统 —— 10 工具 + 插件系统 + workflow DSL + 自动压缩 + OpenAI 兼容 + Ink TUI;NodeNext + tsc 原地编译,dev 用 Bun .ts、install 用 Node .js(学习/教学用)",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Bill Wang <leiwang0359@gmail.com>",
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"clean": "bun scripts/clean-build.ts",
|
|
55
55
|
"test": "bun test",
|
|
56
56
|
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
57
|
+
"compile:green": "bun scripts/build-green.ts",
|
|
58
|
+
"compile:green:host": "bun scripts/build-green.ts --host",
|
|
57
59
|
"prepublishOnly": "bun run clean && bun run build"
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
package/src/config/configFile.js
CHANGED
|
@@ -20,18 +20,24 @@
|
|
|
20
20
|
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
21
21
|
import { homedir } from 'node:os';
|
|
22
22
|
import { dirname, join } from 'node:path';
|
|
23
|
-
|
|
23
|
+
import { execSiblingDir } from '../utils/greenRoot.js';
|
|
24
|
+
/**
|
|
25
|
+
* 配置文件的**写**路径(带环境变量覆盖,主要给测试用)。
|
|
26
|
+
* `saveConfig()` / 首次配置向导只写这里——env override 或 home,**绝不写 exe 同级**
|
|
27
|
+
* (U 盘可能只读,且不该污染产品目录)。读路径见 readSavedConfig 的分层。
|
|
28
|
+
*/
|
|
24
29
|
export function getConfigFilePath() {
|
|
25
30
|
return (process.env.MINIMAL_AGENT_CONFIG_FILE ??
|
|
26
31
|
join(homedir(), '.minimal-agent', 'config.json'));
|
|
27
32
|
}
|
|
33
|
+
/** home 默认配置路径(读分层的最后一档)。 */
|
|
34
|
+
function homeConfigPath() {
|
|
35
|
+
return join(homedir(), '.minimal-agent', 'config.json');
|
|
36
|
+
}
|
|
28
37
|
/**
|
|
29
|
-
*
|
|
30
|
-
* - 文件不存在 / JSON 损坏 / 必填字段缺失 → 返回 null
|
|
31
|
-
* - 成功 → 返回 SavedConfig
|
|
38
|
+
* 解析单个 config.json:不存在 / JSON 损坏 / 必填字段缺失 → null;成功 → SavedConfig。
|
|
32
39
|
*/
|
|
33
|
-
|
|
34
|
-
const file = getConfigFilePath();
|
|
40
|
+
async function parseConfigFile(file) {
|
|
35
41
|
try {
|
|
36
42
|
const raw = await readFile(file, 'utf8');
|
|
37
43
|
const data = JSON.parse(raw);
|
|
@@ -61,6 +67,30 @@ export async function readSavedConfig() {
|
|
|
61
67
|
return null;
|
|
62
68
|
}
|
|
63
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* 读取已保存的 provider 配置(**分层**,按优先级返回第一个有效的):
|
|
72
|
+
* 1. `MINIMAL_AGENT_CONFIG_FILE` 显式覆盖 —— **只读这一个,不 fallthrough**
|
|
73
|
+
* (保持既有语义 + 测试隔离)
|
|
74
|
+
* 2. exe 同级 `config.json` —— **绿色版 / U 盘的卖家预置配置**(compile 后
|
|
75
|
+
* `execSiblingDir()` = 真实 exe 目录;dev/npm 下是 node/bun 二进制目录、无此文件 → 跳过)
|
|
76
|
+
* 3. `~/.minimal-agent/config.json` —— 首次配置向导写出的(原行为)
|
|
77
|
+
*
|
|
78
|
+
* 这层让卖家把配好的 config.json 放进绿色目录即完成预配置,客户端零配置开箱即用
|
|
79
|
+
* (loadProviderLayered 拿到 provider → 不进向导、`-p` 不报 config_error)。
|
|
80
|
+
*
|
|
81
|
+
* 加性保证:dev / npm 模式无 override 时,exe 同级无 config.json → 只命中 home → 行为不变。
|
|
82
|
+
*/
|
|
83
|
+
export async function readSavedConfig() {
|
|
84
|
+
const override = process.env.MINIMAL_AGENT_CONFIG_FILE;
|
|
85
|
+
if (override)
|
|
86
|
+
return parseConfigFile(override);
|
|
87
|
+
for (const file of [join(execSiblingDir(), 'config.json'), homeConfigPath()]) {
|
|
88
|
+
const cfg = await parseConfigFile(file);
|
|
89
|
+
if (cfg)
|
|
90
|
+
return cfg;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
64
94
|
/**
|
|
65
95
|
* 写入向导收集到的配置。
|
|
66
96
|
* unix 上 chmod 600(仅本人可读写),Windows 上 chmod 是 no-op,靠 NTFS ACL。
|
package/src/main.js
CHANGED
|
@@ -5,7 +5,18 @@ import { existsSync, mkdirSync } from 'node:fs';
|
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
|
-
const pkg =
|
|
8
|
+
const pkg = (() => {
|
|
9
|
+
try {
|
|
10
|
+
return require('../package.json');
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return {
|
|
14
|
+
version: typeof __GREEN_BUILD_VERSION__ === 'string'
|
|
15
|
+
? __GREEN_BUILD_VERSION__
|
|
16
|
+
: '0.0.0',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
9
20
|
import { extractCwdArg } from './bootstrap/cwdArg.js';
|
|
10
21
|
import { initWorkingDir, getWorkingDir } from './bootstrap/workingDir.js';
|
|
11
22
|
import { applyToolKeysToEnv, loadProviderLayered } from './config.js';
|
|
@@ -24,9 +24,28 @@
|
|
|
24
24
|
* ============================================================
|
|
25
25
|
*/
|
|
26
26
|
import { existsSync, statSync } from 'node:fs';
|
|
27
|
-
import { join } from 'node:path';
|
|
27
|
+
import { basename, join } from 'node:path';
|
|
28
28
|
import { pathToFileURL } from 'node:url';
|
|
29
29
|
const loaderCache = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* 内置插件静态注册表(仅绿色版 compile 入口 entry.compiled.tsx 会填充)。
|
|
32
|
+
*
|
|
33
|
+
* 背景:`bun --compile` 后,rich 插件的 plugin.ts 顺链 import 的框架 SDK
|
|
34
|
+
* (`../../../src/plugin-sdk.js`)已被焊进 exe、磁盘上不存在 → 动态 import
|
|
35
|
+
* 外部 plugin.ts 必抛错。绿色版改为在 compile 入口**静态 import** 内置插件
|
|
36
|
+
* (`src/plugins/builtinRegistry.ts`),让 bun 把执行器整树打进 exe,再经
|
|
37
|
+
* setBuiltinPluginApi 注册到这里;loadPluginApi 在磁盘加载缺失/失败时回退到此表。
|
|
38
|
+
*
|
|
39
|
+
* dev / npm 模式:entry.compiled 永不被 import → 此表恒为空 → 回退分支不触发
|
|
40
|
+
* → loadPluginApi 行为与改动前逐字节一致。
|
|
41
|
+
*
|
|
42
|
+
* key = 插件目录名(`plugins/<id>/` 的 `<id>`,等于 `basename(pluginRoot)`)。
|
|
43
|
+
*/
|
|
44
|
+
const builtinApis = new Map();
|
|
45
|
+
/** 由绿色版 compile 入口调用,注册内置插件执行器。dev/npm 不会调用。 */
|
|
46
|
+
export function setBuiltinPluginApi(key, api) {
|
|
47
|
+
builtinApis.set(key, api);
|
|
48
|
+
}
|
|
30
49
|
function isPluginApi(value) {
|
|
31
50
|
if (!value || typeof value !== 'object')
|
|
32
51
|
return false;
|
|
@@ -62,8 +81,24 @@ export async function loadPluginApi(pluginRoot) {
|
|
|
62
81
|
// stat 失败静默,不影响加载
|
|
63
82
|
}
|
|
64
83
|
}
|
|
84
|
+
// 绿色版回退:磁盘加载缺失/失败时,按目录名查 exe 内静态注册表。
|
|
85
|
+
// dev/npm 注册表恒空 → 永远返回 null → 不改变下方任何分支的最终结果。
|
|
86
|
+
const builtinKey = basename(pluginRoot);
|
|
87
|
+
const builtinFallback = () => {
|
|
88
|
+
const api = builtinApis.get(builtinKey);
|
|
89
|
+
if (api) {
|
|
90
|
+
loaderCache.set(pluginRoot, api);
|
|
91
|
+
return api;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
65
95
|
const pluginEntry = jsExists ? jsPath : tsExists ? tsPath : undefined;
|
|
66
96
|
if (!pluginEntry) {
|
|
97
|
+
// 磁盘无 plugin.js/.ts:绿色目录只铺声明式部分(plugin.json + commands/),
|
|
98
|
+
// 执行器在 exe 内注册表 → 这里命中。dev/npm 注册表空 → 维持原 null。
|
|
99
|
+
const fb = builtinFallback();
|
|
100
|
+
if (fb)
|
|
101
|
+
return fb;
|
|
67
102
|
loaderCache.set(pluginRoot, null);
|
|
68
103
|
return null;
|
|
69
104
|
}
|
|
@@ -79,6 +114,11 @@ export async function loadPluginApi(pluginRoot) {
|
|
|
79
114
|
return candidate;
|
|
80
115
|
}
|
|
81
116
|
catch (err) {
|
|
117
|
+
// 绿色版:磁盘铺了 plugin.js 但它顺链 import 的框架 SDK 在 exe 内、磁盘没有
|
|
118
|
+
// → import 抛错 → 回退到 exe 内注册表。dev/npm 注册表空 → 维持原 warn + null。
|
|
119
|
+
const fb = builtinFallback();
|
|
120
|
+
if (fb)
|
|
121
|
+
return fb;
|
|
82
122
|
console.warn(`[minimal-agent] failed to load plugin entry at ${pluginRoot}: ${err instanceof Error ? err.message : String(err)}`);
|
|
83
123
|
loaderCache.set(pluginRoot, null);
|
|
84
124
|
return null;
|
package/src/tools/grep/rgPath.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { spawn } from 'node:child_process';
|
|
27
27
|
import { chmodSync, existsSync } from 'node:fs';
|
|
28
28
|
import { resolve } from 'node:path';
|
|
29
|
+
import { execSiblingDir } from '../../utils/greenRoot.js';
|
|
29
30
|
import { findPackageRoot } from '../../utils/packageRoot.js';
|
|
30
31
|
let cached;
|
|
31
32
|
/**
|
|
@@ -53,6 +54,15 @@ async function detect() {
|
|
|
53
54
|
ensureExecutable(vendored);
|
|
54
55
|
return vendored;
|
|
55
56
|
}
|
|
57
|
+
// ②.5 绿色版(bun --compile 单文件):exe 同级 vendor/ripgrep/<arch>-<platform>/rg
|
|
58
|
+
// compile 后 import.meta.url 是虚拟路径,findPackageRoot 失效(② 命不中),
|
|
59
|
+
// 绿色目录把 vendor/ 摆在可执行文件旁边,从这里捞回来。dev/npm 下 exe 同级
|
|
60
|
+
// 没有 vendor/ → existsSync 过滤掉 → 不影响 ②/③/④ 原有优先级。
|
|
61
|
+
const sibling = resolve(execSiblingDir(), 'vendor', 'ripgrep', subdir(), exeName());
|
|
62
|
+
if (existsSync(sibling)) {
|
|
63
|
+
ensureExecutable(sibling);
|
|
64
|
+
return sibling;
|
|
65
|
+
}
|
|
56
66
|
// ③ PATH 上的 rg —— 直接试 spawn rg --version
|
|
57
67
|
if (await trySpawn('rg'))
|
|
58
68
|
return 'rg';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/utils/greenRoot.ts —— 绿色版(bun --compile 单文件)资源探测锚点
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 问题:`bun build --compile` 把运行时 + 全部 JS 焊进单个可执行文件,
|
|
6
|
+
* 此时 `import.meta.url` 指向嵌入式虚拟路径(`/$bunfs/...`),磁盘上
|
|
7
|
+
* 既没有 package.json 也没有源码树 —— `findPackageRoot` 会抛错,靠它
|
|
8
|
+
* 定位的 `vendor/ripgrep/`、`skills/`、`plugins/`、`workflows/` 全失效。
|
|
9
|
+
*
|
|
10
|
+
* 方案:绿色版把这些外部资产与可执行文件**放在同一目录**,运行时用
|
|
11
|
+
* `dirname(process.execPath)` 找回来(exe 同级)。
|
|
12
|
+
*
|
|
13
|
+
* 为什么 dev / npm 模式零影响:
|
|
14
|
+
* - dev(`bun run src/main.tsx`):execPath = bun 二进制,其同级目录
|
|
15
|
+
* (如 `~/.bun/bin/`)没有 vendor/skills/plugins。
|
|
16
|
+
* - npm 全局安装(`node dist/main.js`):execPath = node 二进制,其
|
|
17
|
+
* 同级目录同样没有这些资源目录。
|
|
18
|
+
* - 故调用方一律对返回值做 `existsSync` 过滤:dev/npm 下命不中 → 被
|
|
19
|
+
* 丢弃 → 行为与现状逐字节一致。只有真·绿色版(资产摆在 exe 旁边)
|
|
20
|
+
* 才会命中。因此这里**无条件返回** execPath 目录即可,无需判别
|
|
21
|
+
* "是否 compiled"。
|
|
22
|
+
* ============================================================
|
|
23
|
+
*/
|
|
24
|
+
import { dirname } from 'node:path';
|
|
25
|
+
/**
|
|
26
|
+
* 返回可执行文件所在目录(绿色版资产的同级锚点)。
|
|
27
|
+
*
|
|
28
|
+
* 调用方必须用 existsSync 过滤返回路径下的目标子目录 —— dev/npm 模式
|
|
29
|
+
* execPath 指向 node/bun 二进制,其同级没有资源目录,过滤后无副作用。
|
|
30
|
+
*/
|
|
31
|
+
export function execSiblingDir() {
|
|
32
|
+
return dirname(process.execPath);
|
|
33
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { existsSync } from 'node:fs';
|
|
22
22
|
import { join, resolve } from 'node:path';
|
|
23
23
|
import { getWorkingDir } from '../bootstrap/workingDir.js';
|
|
24
|
+
import { execSiblingDir } from './greenRoot.js';
|
|
24
25
|
import { findPackageRoot } from './packageRoot.js';
|
|
25
26
|
/**
|
|
26
27
|
* 返回资源目录的搜索顺序数组(cwd 优先 + packageRoot fallback)。
|
|
@@ -35,6 +36,13 @@ export function getResourceSearchPaths(name, metaUrl) {
|
|
|
35
36
|
if (existsSync(cwdPath)) {
|
|
36
37
|
paths.push(cwdPath);
|
|
37
38
|
}
|
|
39
|
+
// 绿色版(bun --compile 单文件):exe 同级 <name>/ —— compile 后 findPackageRoot
|
|
40
|
+
// 失效,资源目录摆在可执行文件旁边从这里命中。dev/npm 下 exe 同级(node/bun
|
|
41
|
+
// 二进制所在目录)没有资源目录 → existsSync 过滤 → 数组与现状一致。
|
|
42
|
+
const sibPath = resolve(join(execSiblingDir(), name));
|
|
43
|
+
if (!paths.includes(sibPath) && existsSync(sibPath)) {
|
|
44
|
+
paths.push(sibPath);
|
|
45
|
+
}
|
|
38
46
|
let pkgPath;
|
|
39
47
|
try {
|
|
40
48
|
pkgPath = resolve(join(findPackageRoot(metaUrl), name));
|
|
@@ -42,7 +50,7 @@ export function getResourceSearchPaths(name, metaUrl) {
|
|
|
42
50
|
catch {
|
|
43
51
|
return paths;
|
|
44
52
|
}
|
|
45
|
-
if (pkgPath
|
|
53
|
+
if (!paths.includes(pkgPath) && existsSync(pkgPath)) {
|
|
46
54
|
paths.push(pkgPath);
|
|
47
55
|
}
|
|
48
56
|
return paths;
|