agent-from-zero 0.35.0 → 0.35.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 +93 -195
- package/dist/agent.js +39 -24
- package/dist/completion.js +1 -1
- package/dist/config.js +36 -1
- package/dist/context.js +46 -6
- package/dist/ink/app.js +103 -25
- package/dist/ink/chat.js +3 -1
- package/dist/ink/commands.js +5 -2
- package/dist/ink/setup.js +15 -15
- package/dist/loop.js +34 -8
- package/dist/mcp.js +332 -16
- package/dist/skills.js +189 -41
- package/dist/ui.js +8 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,244 +1,142 @@
|
|
|
1
1
|
# mini-agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个照着 Claude Code 做出来的命令行 coding agent,TypeScript 实现,默认接 DeepSeek(任何支持 function calling 的 OpenAI 兼容模型都能换上)。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 这是什么
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
mini-agent 是我模仿 Claude Code 自己搓的。从终端 UI 的样子,到背后的各种机制,基本都是照着 Claude Code 的做法来实现的:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
- **交互界面**:Ink 终端 UI、方向键审批菜单、多问题表单、`@file` 引用、diff 预览、推理过程流式展示
|
|
10
|
+
- **Skill**:`SKILL.md` 按需加载,项目级放 `.mini-agent/skills/`,全局放 `~/.config/mini-agent/skills/`。和 Claude Code 一样,skill 列表通过带 `<system-reminder>` 的 user message 告诉模型,新增或修改 skill 不用重启;可以用 `/skill <名字> [参数]` 或 `/<名字> [参数]` 手动运行
|
|
11
|
+
- **MCP**:stdio 和 HTTP 两种传输都支持,远程服务器走 OAuth
|
|
12
|
+
- **权限**:allow / ask / deny 三档,deny 永远优先;另外有 plan 模式、auto 模式(交给模型审核)、`--dangerously-skip-permissions`
|
|
13
|
+
- **其他**:上下文压缩、长期记忆、hooks、子 agent、后台任务、todo、撤销、会话续接、成本统计、非交互的 `-p` 模式
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
这个版本是我自己一直在 dogfooding、边用边改出来的,到现在已经挺顺手了。我把自己工作用的 **Slack** 和 **Linear** MCP 都接了进来,底层模型用的是 DeepSeek。平时主要让它干这些活:
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- 查 bug、看日志、定位问题
|
|
18
|
+
- 回 Slack 消息
|
|
19
|
+
- 跑数据 backfill 之类的脏活
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
## 用下来的感受
|
|
22
|
+
|
|
23
|
+
老实说,跟 Claude Code 比体验还是差一截。但跟我直接用 DeepSeek 自带的 harness 相比,mini-agent 好用太多了。
|
|
24
|
+
|
|
25
|
+
说句题外话:我对 A 社这家公司的人品不太认可,但产品确实做得好。至少从 Claude Code 的体验来看,我觉得它远远好过 Codex 和 DeepSeek 的 harness。所以与其从头去想一个 agent 该长什么样,不如直接照着最好的那个来做。
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
## 安装与配置
|
|
28
|
+
|
|
29
|
+
### 安装
|
|
30
|
+
|
|
31
|
+
需要 Node >= 20.6。不用 clone 仓库,直接用 npx 运行 npm 包(包名是 `agent-from-zero`):
|
|
25
32
|
|
|
26
33
|
```bash
|
|
27
|
-
|
|
28
|
-
npm install
|
|
29
|
-
cp .env.example .env # 填入你的 DeepSeek API Key
|
|
30
|
-
npm start # 跑最新版
|
|
31
|
-
npm run eval # 10 道题的回归考试
|
|
34
|
+
npx agent-from-zero
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
### 配置 API key
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
推荐把 API key 写进 **shell 的启动配置文件**(zsh 是 `~/.zshrc`,bash 是 `~/.bashrc`),这样在任何目录启动 mini-agent 都能用。默认用 DeepSeek,只需要一个 key(在 [platform.deepseek.com](https://platform.deepseek.com) 获取),变量名是 `DEEPSEEK_API_KEY`:
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
MINI_AGENT_MODEL=gpt-4.1-mini
|
|
42
|
+
# ~/.zshrc 或 ~/.bashrc
|
|
43
|
+
export DEEPSEEK_API_KEY=sk-你的key
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
改完运行 `source ~/.zshrc`(或 `source ~/.bashrc`),或者新开一个终端。
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
如果某个项目想单独用一个 key,也可以在那个目录下放一个 `.env` 文件,写 `DEEPSEEK_API_KEY=sk-...`,不带 `export`。mini-agent 启动时会读当前目录的 `.env`,但**环境变量优先**:shell 配置文件里已经导出的变量,`.env` 里的同名值会被忽略。
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
### 配置审核模型 judge.model
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
mini-agent -p "解释这个项目" --model deepseek-flash --effort high
|
|
52
|
-
mini-agent exec --model deepseek-flash --effort max --prompt "检查当前改动"
|
|
53
|
-
cat error.log | mini-agent -p "分析这份日志"
|
|
54
|
-
mini-agent -p "总结当前改动" --output-format json > result.json
|
|
52
|
+
开启 auto 模式(会话里输入 `/auto`,或启动时加 `--auto`)后,每个需要审批的工具调用会先交给一个审核模型判断:安全的直接执行,有风险或拿不准的仍然问你。审核模型只有两种选择:
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```
|
|
54
|
+
1. **主 agent 用的模型**(默认):和主 agent 共用同一个 API key 和 base URL。没配 `judge.model` 就直接用主 agent 当前的模型。
|
|
55
|
+
2. **Jev**:配好 Jev 的 key 后自动优先用它(见下一节)。
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
`--prompt`,二者不能同时使用。以 `-` 开头的任务文字放在 `--` 后,例如
|
|
62
|
-
`mini-agent -p -- "--help 参数是什么意思"`。没有位置 prompt 时也可以只从 stdin 读取任务;
|
|
63
|
-
有 prompt 时,管道内容追加在其后。管道会读到 EOF,脚本调用时应关闭 stdin,或用 `< /dev/null`。
|
|
57
|
+
`judge.model` 写在 `~/.config/mini-agent/settings.json`(全局)或 `.mini-agent/settings.json`(项目级):
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"judge": { "model": "deepseek-flash" }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
注意:`judge.model` 走的是主 agent 的 API 端点,所以只能填主 agent 那个服务商下的模型,不能填别家的模型,也不能在这里填 Jev。不确定的话就不要配,默认用主 agent 的模型最稳妥。
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
`--output-format json` 输出单个结果对象,含 `result`、`is_error`、`reason`、`session_id`、
|
|
72
|
-
`model`、`effort`、`usage` 和 `estimated_cost_usd`。usage 与费用是本地会话计量,
|
|
73
|
-
不含 Jev 等辅助审核调用。当前支持 text / json,不兼容 Claude 的 stream-json SDK 协议。
|
|
67
|
+
### 配置 Jev(可选)
|
|
74
68
|
|
|
75
|
-
|
|
76
|
-
`-r` / `--resume` 仍可续接最近会话;`--auto` 仍经过原有审核,无人确认的操作直接拒绝。
|
|
77
|
-
单独使用 `-p` 不会放宽权限;它不能弹出交互审批,未获批准的操作会被拒绝。
|
|
69
|
+
**Jev 不是必须的**,不配也能正常使用 auto 模式,审核会用上面说的主 agent 模型。
|
|
78
70
|
|
|
79
|
-
|
|
71
|
+
Jev 是 TypeSafe 提供的审核模型。想用的话,和 DeepSeek 的 key 放在同一个地方,加上 Jev 的 key(两个变量名都可以,二选一):
|
|
80
72
|
|
|
81
73
|
```bash
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
# ~/.zshrc 或 ~/.bashrc
|
|
75
|
+
export JEV_API_KEY=你的key
|
|
76
|
+
# 或者
|
|
77
|
+
export TYPESAFE_API_KEY=你的key
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
配了 key 之后,下次启动会自动优先用 Jev 审核;Jev 额度用完或出错时,会自动退回主 agent 的模型。Jev 的模型版本默认是 `jev-1.13.0`,要换的话在 settings.json 里设置 `autoMode.model`:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"autoMode": { "model": "jev-1.13.0" }
|
|
85
|
+
}
|
|
85
86
|
```
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
它只对本次进程生效,不写入 settings;显式 bypass 优先于 `--auto` 和配置里的 auto mode。
|
|
89
|
-
`--permission-mode auto` 启用自动审核,`--permission-mode default` 关闭 auto 和 bypass。
|
|
90
|
-
`--dangerously-skip-permissions` 等价于 bypass 模式,并优先于同一命令中的 mode 参数。
|
|
91
|
-
交互界面会在底部显示 `bypass permissions on`;`/auto` 不会改变这个启动选项,需重新启动才能取消。
|
|
88
|
+
### 换别的模型
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
业务澄清问题不会自动代答。源码对照为 `utils/permissions/permissionSetup.ts` 的
|
|
95
|
-
`initialPermissionModeFromCLI` 与 `utils/permissions/permissions.ts` 的 bypass 分支:
|
|
96
|
-
先检查规则,再放行普通审批。mini-agent 保留自己的硬性规则,不宣称完全复制 Claude 的全部例外。
|
|
90
|
+
和 API key 放在同一个地方,设置这三个变量就行,各家的 base URL 写在 [.env.example](.env.example) 里:
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
```bash
|
|
93
|
+
export MINI_AGENT_API_KEY=sk-...
|
|
94
|
+
export MINI_AGENT_BASE_URL=https://api.openai.com/v1
|
|
95
|
+
export MINI_AGENT_MODEL=gpt-4.1-mini
|
|
96
|
+
```
|
|
100
97
|
|
|
101
|
-
|
|
98
|
+
模型必须支持 function calling。如果它的上下文窗口比 DeepSeek 小(默认按约 1M 算),在 settings.json 里按模型配置窗口大小,`/model` 切换模型时会跟着变:
|
|
102
99
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"contextWindows": { "gpt-4.1-mini": 1000000, "deepseek-v4-pro": 1000000 }
|
|
103
|
+
}
|
|
104
|
+
```
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
`/status openai`、`/status anthropic` 查询对应的官方账户;不会把代理 key 当成官方账户 key。
|
|
109
|
-
组织报表按 UTC 本月至今统计,涵盖管理凭证所属组织,区别于当前会话。
|
|
110
|
-
接口、凭证和统计范围见 [Status 说明](docs/status.md)。
|
|
106
|
+
也可以用环境变量 `MINI_AGENT_CONTEXT_WINDOW` 统一覆盖所有模型。
|
|
111
107
|
|
|
112
|
-
|
|
108
|
+
### 上下文和自动压缩
|
|
113
109
|
|
|
114
|
-
|
|
110
|
+
上下文大小用的是 API 每次返回的真实 `prompt_tokens`,再加上之后新增消息的估算值,所以基本是准的。快满时自动压缩:触发点是窗口减去给回复预留的 20k 和 13k 安全余量(约 1M 的窗口大概在 97% 左右)。状态栏的 `ctx N%` 表示离自动压缩还有多远,到 100% 时下一次调用模型前就会先压缩。也可以随时手动 `/compact`。
|
|
115
111
|
|
|
116
|
-
|
|
112
|
+
### 配置 MCP
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
MINI_AGENT_AUTO_POLICY=rules npm start -- --auto
|
|
120
|
-
```
|
|
114
|
+
在 `~/.config/mini-agent/settings.json`(全局)或 `.mini-agent/settings.json`(项目级)的 `mcpServers` 里添加服务器,写法和 Claude Code 一样。保存后会话里自动生效,不用重启:新加的服务器会连上,删掉的会断开,改过的会重连。也可以手动运行 `/mcp reload`。服务器自己的工具有增减时(MCP 的 `tools/list_changed` 通知),工具列表也会自动更新。启动时 MCP 在后台并行连接,不会卡住输入框,哪个连上了它的工具就马上可用;有连接失败的,状态栏右下角会用红色标出是哪个(需要登录的是黄色)。
|
|
121
115
|
|
|
122
|
-
|
|
123
|
-
`TYPESAFE_API_KEY` 后先用 Jev 初筛;有疑点再用同一 vendor 的 `judge.model` 复核,
|
|
124
|
-
未指定则使用当前模型。没有 key 或 Jev 故障时直接走模型。普通任务内检查、测试、修改及明确要求的常规
|
|
125
|
-
commit/push 按实际效果审核;删除、丢弃工作、改写共享历史、基础设施写入和权限修改
|
|
126
|
-
仍要求人工确认。违反明确限制的操作直接拒绝,让 agent 改用合规方案;连续 3 次或
|
|
127
|
-
累计 20 次自动拒绝会停止,等待用户给出方向。子 agent 和中断恢复共用预算。
|
|
116
|
+
输入 `/mcp` 选一个服务器,会看到和 Claude Code 一样的详情(状态、登录、协议版本、URL、配置文件位置、能力、工具数)和操作:View tools、Re-authenticate、Clear authentication、Reconnect、Disable。也可以直接敲 `/mcp tools|auth|clear-auth|reconnect|disable|enable <名字>`。
|
|
128
117
|
|
|
129
|
-
|
|
130
|
-
不会直接因初筛分数弹人工审批。历史动作有缺失时也转模型。这个预览阈值尚未经过
|
|
131
|
-
真实数据校准,不代表整体误放率;新策略仍需显式选择,默认保留旧 `scores` 策略。
|
|
132
|
-
Jev 额度耗尽或出错会提示并在本会话回退到模型。使用 `/auto debug on` 可记录两阶段
|
|
133
|
-
实际上下文、分流原因与最终决定,详见 [策略设计及评测](docs/auto-mode-redesign.md)。
|
|
118
|
+
### 非交互模式
|
|
134
119
|
|
|
135
|
-
|
|
120
|
+
```bash
|
|
121
|
+
npx agent-from-zero -p "总结当前改动"
|
|
122
|
+
cat error.log | npx agent-from-zero -p "分析这份日志"
|
|
123
|
+
npx agent-from-zero -p "总结当前改动" --output-format json
|
|
124
|
+
```
|
|
136
125
|
|
|
137
|
-
|
|
126
|
+
## 常用命令
|
|
138
127
|
|
|
139
|
-
|
|
128
|
+
在会话里输入 `/help` 可以看到全部命令,比较常用的有:`/model` `/effort` `/plan` `/auto` `/skills` `/mcp` `/memory` `/compact` `/diff` `/undo` `/resume` `/status` `/cost`。
|
|
140
129
|
|
|
141
|
-
|
|
142
|
-
- 没有 Jev key:复用主 agent 的 API endpoint 和 key,默认使用当前模型;启动时显示一条 Jev 配置提示,不影响使用。
|
|
143
|
-
- 在 `.mini-agent/settings.json` 或全局 settings 中设置 `judge.model`,可以选择同一 vendor 支持的其他审核模型。例如,你的 endpoint 支持 `deepseek-flash` 时可以这样配置:
|
|
130
|
+
## 开发
|
|
144
131
|
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/ftfw0621/mini-agent.git && cd mini-agent
|
|
134
|
+
npm install
|
|
135
|
+
cp .env.example .env # 填入 DEEPSEEK_API_KEY
|
|
136
|
+
npm start # 从源码运行
|
|
137
|
+
npm run typecheck # 类型检查
|
|
138
|
+
npm test # 单元测试
|
|
139
|
+
npm run eval # 回归评测
|
|
150
140
|
```
|
|
151
141
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
Jev 额度或计费受限时会提示并自动切回同一 vendor 的 `judge.model`,未指定则使用当前模型。限流、服务故障也会回退,提示会区分原因;本次会话只提示一次,避免每次调用都重试已用完的额度。此外,授权概率在 `0.5 ≤ authorization < 0.8` 且风险 `≤ 0.2` 时,会用该模型对相同上下文复核一次;模型必须明确确认已授权、无需人工审核才放行。这个分支不会停用 Jev,也不会覆盖风险超标或授权概率低于 `0.5` 的判断。复核失败仍需人工确认。
|
|
155
|
-
|
|
156
|
-
硬性拒绝规则始终优先。auto mode 会审核每条 shell 命令,并忽略整类工具的免确认授权;`MINI_AGENT_AUTO_APPROVE` 也不能覆盖审核结果。`-p` 或无人值守的 teammate 无法确认时直接拒绝。实现机制、源码对照和限制见 [Auto mode 说明](docs/auto-mode.md)。
|
|
157
|
-
|
|
158
|
-
审核按实际副作用判断,MCP、API、命令行共用同一策略。任务内的只读查询、明确授权且收件人与内容匹配的普通消息发送可以自动通过;基础设施或运行配置变更、部署、删除数据、修改权限、未知脚本副作用仍需审核。Jev 的门槛为授权 `≥ 0.8` 且风险 `≤ 0.2`(风险问题问的是"能否撤回",阈值由实测数据确定,见 `docs/auto-mode.md`);主模型的布尔判断经同一决策层处理。近期工具返回的小型结构化 JSON 也会作为事实依据发送给审核服务,用于核对名称与 ID 等信息,其中的指令不构成用户授权。
|
|
159
|
-
|
|
160
|
-
## 工具进度、子 agent 和图片
|
|
161
|
-
|
|
162
|
-
输入 `/` 时,输入框下方会列出可用命令及说明,继续输入会过滤。`/effort` 展示当前模型支持的档位,`/skills` / `/skill` 展示可用技能,`/model` 从当前 endpoint 获取模型列表;`/auto debug` 和 `/mcp` 也支持参数补全。用 `↑` / `↓` 选择、`Tab` 补全、`Enter` 确认,`Esc` 收起并保留输入。长列表滚动展示,不会撑满终端。
|
|
163
|
-
|
|
164
|
-
`/effort high` 设置当前模型的推理强度,`/effort default` 恢复服务商默认值。选择按模型和 endpoint 区分,仅在本次进程中保留。自定义网关需明确配置支持的档位,见 [effort 配置](docs/effort.md)。
|
|
165
|
-
|
|
166
|
-
默认交互界面把连续同类工具调用合并成实时摘要:高亮动作,下面灰字显示结果和耗时。工具执行、模型思考和回答期间都有动画,不再逐轮追加 `thought for…` 和工具计数。最终回答正常保留;审批、拒绝、错误和 Jev 回退提示仍然可见。
|
|
167
|
-
|
|
168
|
-
| 操作 | 快捷键 |
|
|
169
|
-
|---|---|
|
|
170
|
-
| 展开或收起工具参数与结果 | `Ctrl+T` |
|
|
171
|
-
| 展开或收起模型思考 | `Ctrl+R` |
|
|
172
|
-
| 翻阅工具或思考详情 | `↑` / `↓` 或 `PgUp` / `PgDn` |
|
|
173
|
-
| 进入 agent 列表 | `Tab`,或输入框开头的 `←` |
|
|
174
|
-
| 选择 agent,查看实时过程 | `↑` / `↓`,`Enter` |
|
|
175
|
-
| 翻阅 agent 过程 | `PgUp` / `PgDn` |
|
|
176
|
-
| 返回主界面;无面板时中断任务 | `Esc` |
|
|
177
|
-
| 粘贴剪贴板图片或文字 | `Ctrl+V` |
|
|
178
|
-
| 工作中插入补充消息 | 输入后按 `Enter`,在下一个工具边界送入 |
|
|
179
|
-
| 中断当前步骤并优先处理补充消息 | `Ctrl+Enter`;已有排队消息时也可按 `Esc` |
|
|
180
|
-
|
|
181
|
-
工作中的补充消息先以灰字显示在待处理区,送入模型时转为正常用户消息。支持多条消息和图片,保持发送顺序;当前已执行的操作不会因此撤销,尚未启动的旧工具调用会跳过并按新消息重新规划。`Esc` 优先关闭详情面板。部分终端不能区分 `Ctrl+Enter` 和 `Enter`,此时可先按 `Enter` 排队,再按 `Esc`。
|
|
182
|
-
|
|
183
|
-
审批或问题选项出现时,输入框仍可输入文字;有文字时按 `Enter` 会取消当前选择并发送 follow-up,空输入时按 `Enter` 才确认选项。只有一个问题时,选中答案后按一次 `Enter` 即提交,不再显示额外的 Submit answers;多个问题仍统一提交。
|
|
184
|
-
|
|
185
|
-
粘贴至少 4 行或 1000 字符的文本时,会折成 `[Pasted text #1 +13 lines]` 之类的占位符;长单行文本显示字符数。左右方向键跨过整个占位符,Backspace 整块删除。可以混合输入文字、多个文本块和图片。发送及权限审核收到完整原文,输入历史仍可恢复文本块;补充消息和已发送消息保留简洁展示。
|
|
186
|
-
|
|
187
|
-
agent 列表竖排显示 `main`、subagent 和 teammate,右侧显示当前活动、`idle` / `done` / `failed`、耗时及输出 token。带 `~` 的 token 是流式估算;API 返回用量后使用实报数据。切换视图不会停止其他 agent,也不会把它们的详细过程塞进主对话。
|
|
188
|
-
|
|
189
|
-
粘贴图片后,输入框显示 `[Image #1]`;可以继续输入说明或粘贴多张图片。光标紧跟占位符时按 Backspace 可以删除附件。每条消息最多 4 张、每张最多 5 MB。发送时使用真实图像数据,当前模型须同时支持图片和工具调用;模型拒绝图片时会提示检查限制或用 `/model` 切换。图片随会话保存,恢复会话和后续追问可以继续引用。
|
|
190
|
-
|
|
191
|
-
剪贴板通过 `ClipboardSource` 接口接入:macOS 使用 AppKit,Windows 使用 PowerShell/Windows Forms,Linux Wayland 使用 `wl-paste`,X11 使用 `xclip`(Linux 需安装相应工具)。界面和图像消息构造不依赖操作系统。以上交互适用于默认 Ink 界面;`MINI_AGENT_NO_INK=1` 保留文字版 readline 界面。实现对照和验证范围见 [交互界面说明](docs/interactive-ui.md)。
|
|
192
|
-
|
|
193
|
-
## 进度
|
|
194
|
-
|
|
195
|
-
| Tag | What it adds |
|
|
196
|
-
|---|---|
|
|
197
|
-
| `day1` | ~50-line raw tool-calling loop: one tool, one loop, two safety nets (errors fed back, a round cap) |
|
|
198
|
-
| `day2` | The five core tools (read/write/edit/search/bash): read-before-edit, `old_string` uniqueness, a `dispatch` that never throws, the four-part tool manual |
|
|
199
|
-
| `day3` | Main loop as a state machine: error classification, exponential backoff (with jitter), dual retry budgets, a circuit breaker, 8 terminal states each with a plain-English note, graceful Ctrl+C exit |
|
|
200
|
-
| `day4` | Permission system (allow/ask/deny): hard-blocked no-fly zones (.git/.ssh/.env), deny always wins (even bypass can't override), fail-closed when non-interactive, bash risk graded by the command, secret files kept out of context |
|
|
201
|
-
| `day5` | Context management: token estimation (byte-based, err high), an 80% trigger line, a 6-section structured summary (tools double-disabled), file recovery (last 5, re-read from disk), proactive + reactive compaction, a compaction circuit breaker |
|
|
202
|
-
| `day6` | Multi-turn REPL (line-queue input, `/clear`, three-state Ctrl+C), a system-prompt constitution (number anchors / negative prohibitions / no false claims), AGENT.md project memory, streaming output + a dual watchdog (90s stall / 30s warn-only) |
|
|
203
|
-
| `day7` | Sub-agents (the `task` tool: isolated context, one level deep with no nesting, the report is material not the verdict, file-state snapshot isolation) + a minimal eval (10 fixed tasks, evidence-based scoring, 8/10 pass bar, `npm run eval`) |
|
|
204
|
-
| `day8` | Packaging & release: a `bin` entry + tsc build, one-line `npx` launch (package `agent-from-zero`); built-in .env loading; a plain-English error when the key is missing |
|
|
205
|
-
| `day9` | The tutorial itself: 11 Chinese chapters open-sourced into [docs/](docs/README.md) |
|
|
206
|
-
| `day10` | Bring your own model: three env vars swap in any OpenAI-compatible endpoint; `npm run eval` doubles as a model bake-off |
|
|
207
|
-
| `day11` | Industrial-grade CLI surface: two-layer settings.json (user permission rules, deny always wins), slash commands (/help /model /compact), `-p` one-shot mode, `--version`, a test suite in the repo (`npm test`, 60 cases) |
|
|
208
|
-
| `day12` | Session persistence (`--resume`, snapshot-style + atomic writes) + local telemetry (event JSONL + `/stats`, never leaves the machine, off via `MINI_AGENT_NO_TELEMETRY=1`) |
|
|
209
|
-
| `day13` | Async execution: run_bash switched to spawn (no UI freeze, instant Ctrl+C, 30s timeout then SIGKILL), oversized output spilled to disk, read-only tools run in parallel; `npm test` 65 cases |
|
|
210
|
-
| `day14` | Hook lifecycle system: PreToolUse (intercept a tool) / PostToolUse (append checks) / SessionStart (inject context) / Stop (test-driven AI), exit-code protocol, stderr fed back to the model; `npm test` 76 cases |
|
|
211
|
-
| `day15` | MCP integration: a stdio JSON-RPC client (handshake / line buffering / id pairing), external-server tools registered just like built-ins (same dispatch + permission gate, ask by default); `npm test` 84 cases |
|
|
212
|
-
| `day16` | Optional LLM permission judge: only for the ask gray-zone the rules left undecided, can only ask→allow, a rigid prompt format, fail-closed parsing, its own circuit breaker, off by default; `npm test` 98 cases |
|
|
213
|
-
| `day17` | Long-term memory: a cross-session MEMORY.md (human-readable) + a remember tool, injected as "background" (vs AGENT.md's "commands"), bounded at 30 entries / 8KB and self-healing; `npm test` 114 cases |
|
|
214
|
-
| `day18` | Cost accounting: capture usage from the stream (include_usage), smooth over DeepSeek/OpenAI format differences, pure-function cost + cache-hit-rate math, `/cost` to see spend (the hit rate doubles as a cache-bust warning); `npm test` 130 cases |
|
|
215
|
-
| `day19` | Tool-call validation: just-enough schema checks (required / type / shape), pinned at dispatch's single entry, batched precise fix-it guidance, auto-covering built-in + MCP tools (bad args caught before they're sent); `npm test` 146 cases |
|
|
216
|
-
| `day20` | Plan mode: read-only research, think it through, hand you a plan to approve; one outer filter in the permission layer (only tightens, never loosens, overrides pre-approvals, keeps no-fly reasons) + an `exit_plan_mode` tool that borrows the ask flow to close approval + a `/plan` toggle; `npm test` 167 cases |
|
|
217
|
-
| `day21` | Diff preview: before a write, show the real change (green +/red -) for you to approve; a ~dozen-line LCS line-diff, collapsed unchanged lines, side-effect-free replay of tool semantics, never throws (fails to null); printed after the gate, before execution; `npm test` 189 cases |
|
|
218
|
-
| `day22` | `/undo` reverts the last write: stash a "before" snapshot ahead of each write (a bounded stack), `/undo` restores the content or deletes a newly created file, reusing the Day 21 diff to draw the undo; `/clear` also clears the stack; `npm test` 204 cases |
|
|
219
|
-
| `day23` | Sub-agent model tiering: a `subAgentModel` config runs delegation on a different tier (cheap model for chores / strong model as a reviewer), a pure `subAgentModelFor` defines the fallback, first end-to-end proof via a fake client that "orchestrate→delegate→regress" really switched models; `npm test` 212 cases |
|
|
220
|
-
| `day24` | Remote MCP (HTTP/SSE): peel Day 15's transport out of the protocol into a Transport interface, add an HTTP transport (POST → parse JSON/SSE by Content-Type, carry Mcp-Session-Id, AbortSignal timeout); set `url` to go remote; zero changes to the protocol / discovery / registration; `npm test` 218 cases |
|
|
221
|
-
| `day25` | `/diff` session change overview: alongside the undo ledger, a "session-start baseline" table (each file as first touched, recorded once), `/diff` compares each against disk, reuses Day 21 to render net changes, skips net-zero, three states created/modified/deleted; `npm test` 227 cases |
|
|
222
|
-
| `day26` | `@file` mentions: an `@path` in your question attaches the file's content to the message, saving a read_file round-trip; recognizes `@` at line start / after whitespace, strips trailing punctuation; each mention passes the read_file gate — secret files (.env/*.pem) are refused, refusals voiced, misses silent; `npm test` 244 cases |
|
|
223
|
-
| `day27` | `/resume` session picker: `session.ts` gains listSessions (summaries) / loadSession (by id) / sessionTitle (first line as title, strips @file attachments), a REPL list + pick-by-number + wholesale switch (rebuild the constitution, swap the id, clear read-state and the undo stack), sessions dir made lazy for testability; `npm test` 256 cases |
|
|
224
|
-
| `day28` | Terminal UI: a new `ui.ts` owns the look — a rounded welcome box, the `❯` prompt, `⏺`/`⎿` activity markers (replacing emoji), a ticking spinner (riding the watchdog); explains why "color codes pollute width math" so you align on plain text first, then color; `npm test` 277 cases |
|
|
225
|
-
| `day29` | Arrow-key approval menu: approval moves from "type y/N" to ↑↓ select (`menu.ts` borrows stdin into raw mode, redraws in place, hands it back), numbered + a footer hint; the middle "don't ask again this session" reuses the allow rule (run_bash only escalates ask, never touches hard deny); non-TTY falls back to typing; `npm test` 289 cases |
|
|
226
|
-
| `day30` | Multi-question form: give the model an `ask_user` tool that pops a form when it needs your decision (`form.ts` pure state machine: flat cursor + reduce + submit validation + jump-to-gap, `promptForm` a thin keyboard layer), collected into question→answer fed back; the input area becomes Claude-Code-style "rule + ❯"; `npm test` 309 cases |
|
|
227
|
-
| `day31` | Switch models at runtime: `/model` upgrades from read-only to switchable — `/model <name>` sets it, bare `/model` pulls the endpoint's `/models` list into the Day 29 menu; the switch only changes `CONFIG.model` and takes effect immediately via the loop re-reading it each round; `npm test` 314 cases |
|
|
228
|
-
| `day32` | Make a model switch visible: the streaming loop also reads `reasoning_content`, streaming a reasoning model's (R1) thinking dimmed (render-only, never into history, never sent back); the spinner shows the current model as proof the switch took; `npm test` 324 cases |
|
|
229
|
-
| `day33` | Skill system (Markdown as a plugin): a `SKILL.md` (frontmatter + body) = one plugin, executed by the model; progressive disclosure (the listing in context / the body loaded on demand), the `skill` tool manual is the listing, `disableModelInvocation` splits model-callable vs user-only, `/skills` `/skill <name>`; `npm test` 360 cases |
|
|
230
|
-
| `day34` | Deeper memory: four types (user/feedback/project/reference, feedback the most valuable), auto-extraction each turn (a cheap model reads the conversation and stores "what the user corrected", off by default, never throws), grouped injection by type, backward-compatible with the old format; `npm test` 375 cases |
|
|
231
|
-
| `day35` | Deeper hooks: 4→10 events (adding UserPromptSubmit/SessionEnd/Pre·PostCompact/Subagent*), config becomes an open record, exit-2 meaning interpreted per-event by the caller, an extra-short SessionEnd timeout; **`PreToolUse` can rewrite a tool's arguments** (the "side road" the three-state permission can't do, strictly after the safety check); `npm test` 381 cases |
|
|
232
|
-
| `day36` | TodoWrite: a `todo_write` tool that lets the agent keep its own checklist (whole-list replace, at most one in_progress, ✓/▶/○ render, `/todos` to view) — planning, not new powers; plus a nag that injects a reminder when an unfinished plan goes stale for N rounds, so long tasks don't drift off the original goal; `npm test` 474 cases |
|
|
233
|
-
| `day37` | Background tasks: a `run_bash_background` tool spawns a slow command (install/build/test/dev server) and returns a `bg_*` id immediately instead of blocking on run_bash's 30s watchdog — the agent keeps reasoning while it runs; when it finishes the loop injects a `<task_notification>` as the next turn (once per task), a `bash_output` tool polls a job's new output (essential for servers that never "finish"), `/bg` lists them and they're SIGKILLed on exit; same permission gate as run_bash, so the danger rules are identical; `npm test` 502 cases |
|
|
234
|
-
| `day38` | Agent teams: past the one-shot sub-agent — a `spawn_teammate` tool starts PERSISTENT teammates that run concurrently (fire-and-forget async loops interleaving on the one event loop) and coordinate through `send_message`; mailboxes are real `.jsonl` files on disk (`.mini-agent/team/`, consumptive read = observable, no lock needed on a single event loop), the loop injects each agent's inbox as a turn and the lead WAITS on the team instead of ending early, teammates are bounded (round cap) with a focused toolset and a non-interactive permission policy (writes auto-proceed, risky bash declined, deny still wins), `/team` lists them; `npm test` 523 cases |
|
|
235
|
-
| `day39` | Team protocols: explicit message contracts, not vibes — every protocol is the same request→response correlated by a `request_id`, tracked as `ProtocolState` (pending→approved/rejected) with a `match_response` that rejects a mismatched reply (a shutdown_response can't approve a plan). Two contracts: **graceful shutdown** (lead `request_shutdown` → teammate finishes, confirms, exits — no orphans/half-writes) and **plan approval** (teammate `submit_plan` → lead `review_plan` before risky work runs). Teammates go from bounded auto-exit to an IDLE LOOP (wait for work, leave on the shutdown handshake; bounded backstop so an abandoned one self-exits), and the lead disbands the team gracefully when it finishes; the inbox-consume step routes protocol messages then hands the rest to the model; `npm test` 545 cases |
|
|
236
|
-
| `day40` | Autonomous agents: a shared TASK BOARD teammates pull from instead of being hand-fed work. `.mini-agent/tasks/<id>.json` files (on disk = observable), `Task {status, owner, blockedBy}` with `blockedBy` resolved by id OR subject; `create_task`/`list_tasks`/`claim_task`/`complete_task` tools, and `claimTask` is a synchronous read-check-write so two scanners can't both win the same task — no lock, same single-event-loop argument as the mailbox. The teammate IDLE loop now polls inbox-first (shutdown priority) then auto-claims the next ready task (`claimNextAvailable`) and resumes WORK; the lead lays out a dependency graph and teammates respect it (schema → routes → tests), `/tasks` shows the board; `npm test` 574 cases |
|
|
237
|
-
|
|
238
|
-
## 📖 配套教程(免费开源)
|
|
239
|
-
|
|
240
|
-
**[完整教程在 docs/ 目录](docs/README.md)**——主线 + 进阶共三十多章,每章对应一个 tag:为什么这么写、Claude Code 原版怎么做、你会踩的坑。每章五段结构:做完长什么样 → 跟着写 → 为什么 → 坑 → 小结。
|
|
241
|
-
|
|
242
|
-
想看更深的「为什么」:公众号的 **Harness Engineering 拆书系列**(30+ 篇)逐章拆了《Harness Engineering:从 Claude Code 看 AI 编码工程》——本仓库每个设计决策背后的原理、数据和事故案例都在那里。公众号 @老李是谁。
|
|
243
|
-
|
|
244
|
-
如果这个项目帮你搞懂了 agent,点个 ⭐ 就是最大的支持。
|
|
142
|
+
各个功能的实现思路见 [docs/](docs/)。
|
package/dist/agent.js
CHANGED
|
@@ -8,12 +8,12 @@ import { CONFIG, requireApiKey, saveGlobalSetting, PROJECT_SETTINGS_PATH, GLOBAL
|
|
|
8
8
|
import { runLoop, TerminateReason, MAX_RETRIES, killAllSubAgents } from "./loop.js"; // the state machine
|
|
9
9
|
import { buildSystemMessage, readProjectInstructions } from "./prompt.js"; // the constitution + optional AGENT.md project memory
|
|
10
10
|
import { forgetFilesExcept, registerExternalTool } from "./tools.js"; // file-state reset + tool registration
|
|
11
|
-
import { compactHistory,
|
|
11
|
+
import { compactHistory, compactThreshold, contextPercent, contextWindowFor } from "./context.js"; // /compact, /model info, the ctx % in the status line
|
|
12
12
|
import { newSessionId, saveSession, latestSession, listSessions, loadSession, setSessionTitle } from "./session.js"; // conversation persistence (project-local) + the /resume picker
|
|
13
13
|
import { generateSessionTitle, setTerminalTitle } from "./title.js"; // concise session name, generated after the first message + the terminal tab that shows it
|
|
14
14
|
import { initTelemetry, emit, statsReport } from "./telemetry.js"; // local-only event log + /stats
|
|
15
15
|
import { runHooks } from "./hooks.js"; // SessionStart lifecycle hook
|
|
16
|
-
import { connectMcpServers, listMcpServers, mcpActionsFor, runMcpAction } from "./mcp.js"; // external tool servers (MCP) + /mcp
|
|
16
|
+
import { connectMcpServers, listMcpServers, MCP_SUBCOMMANDS, mcpActionsFor, mcpServerDetails, reloadMcpServers, runMcpAction, watchMcpConfig } from "./mcp.js"; // external tool servers (MCP) + /mcp
|
|
17
17
|
import { Judge } from "./judge.js"; // optional LLM permission classifier
|
|
18
18
|
import { AutoMode } from "./auto.js";
|
|
19
19
|
import { effortMenu, setEffort, selectedEffort } from "./effort.js";
|
|
@@ -33,7 +33,7 @@ import { promptSelect, promptForm } from "./menu.js"; // arrow-key approval menu
|
|
|
33
33
|
import { editLine } from "./editor.js"; // our own line editor (keeps the status footer pinned even when input wraps)
|
|
34
34
|
import { normalizeDroppedPaths } from "./drop.js"; // drag-and-drop: a dropped file's path → a clean absolute path in the input
|
|
35
35
|
import { rememberTool, readMemory, readMemoryTyped, extractMemories, MEMORY_PATH } from "./memory.js"; // long-term project memory + auto-extract
|
|
36
|
-
import {
|
|
36
|
+
import { currentSkills, findSkill, userSkillMessages } from "./skills.js"; // Markdown-as-plugin skills
|
|
37
37
|
import { initCostMeter, DEFAULT_PRICING } from "./cost.js"; // token & cost accounting for /cost
|
|
38
38
|
import { launchInk } from "./ink/launch.js"; // the Ink REPL — the default front-end for interactive sessions
|
|
39
39
|
import { listBackground, hasRunningBackground, killAllBackground } from "./background.js"; // background tasks: /bg view + kill-on-exit (Day 37)
|
|
@@ -96,7 +96,8 @@ const SESSION_HELP = `commands:
|
|
|
96
96
|
/memory show the durable facts the agent remembers about this project
|
|
97
97
|
/status session usage and vendor account balance/cost
|
|
98
98
|
/cost tokens, cache hit rate and estimated spend this session (local)
|
|
99
|
-
/mcp list configured MCP servers + status; select one to authenticate / reconnect / disable
|
|
99
|
+
/mcp list configured MCP servers + status; select one to view tools / authenticate / clear auth / reconnect / disable
|
|
100
|
+
/mcp reload re-read mcpServers from settings.json (saves are also picked up automatically)
|
|
100
101
|
/plan toggle plan mode — research-only; the agent presents a plan you approve before any change
|
|
101
102
|
/effort [level] list/select the current model’s supported reasoning effort
|
|
102
103
|
/auto toggle auto mode — risky or uncertain actions still require approval
|
|
@@ -110,7 +111,7 @@ const SESSION_HELP = `commands:
|
|
|
110
111
|
/diff show every file changed this session, as a diff from where it started
|
|
111
112
|
/resume list recent sessions in this project and continue one of them
|
|
112
113
|
/skills list the reusable skills available in this project
|
|
113
|
-
/skill <name> run a skill yourself (works even for user-only skills)
|
|
114
|
+
/skill <name> [args] run a skill yourself (works even for user-only skills); /<name> [args] works too
|
|
114
115
|
exit leave (Ctrl+C at the prompt does the same)
|
|
115
116
|
|
|
116
117
|
keys (at the prompt):
|
|
@@ -197,6 +198,8 @@ async function main() {
|
|
|
197
198
|
disconnectMcp();
|
|
198
199
|
};
|
|
199
200
|
process.on("exit", disconnectMcp); // best-effort cleanup of server subprocesses
|
|
201
|
+
if (!cli.print)
|
|
202
|
+
process.on("exit", watchMcpConfig()); // hot reload: a settings.json save adds/removes/reconnects servers, no restart (a one-shot -p run doesn't need it)
|
|
200
203
|
process.on("exit", killAllBackground); // Day 37: SIGKILL any background job (dev server, slow install) so it never outlives the agent as an orphan
|
|
201
204
|
process.on("exit", killAllSubAgents); // mark any still-running sub-agents as killed so they don't leave pending promises
|
|
202
205
|
process.on("exit", stopCronScheduler); // Day s14: stop the setInterval on exit
|
|
@@ -219,14 +222,12 @@ async function main() {
|
|
|
219
222
|
const memCount = readMemory().length;
|
|
220
223
|
if (memCount)
|
|
221
224
|
console.log(chalk.dim(`(long-term memory: ${memCount} facts loaded)`));
|
|
222
|
-
// Skills: reusable Markdown procedures
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
if (skills.length)
|
|
229
|
-
console.log(chalk.dim(`(skills: ${skills.length} loaded — ${skills.map((s) => s.name).join(", ")})`));
|
|
225
|
+
// Skills: reusable Markdown procedures, re-read from disk before every model
|
|
226
|
+
// call (so a new SKILL.md needs no restart). The loop registers the `skill`
|
|
227
|
+
// tool and sends the listing as a <system-reminder>; here we just announce them.
|
|
228
|
+
const startupSkills = currentSkills();
|
|
229
|
+
if (startupSkills.length)
|
|
230
|
+
console.log(chalk.dim(`(skills: ${startupSkills.length} loaded — ${startupSkills.map((s) => s.name).join(", ")})`));
|
|
230
231
|
// Token/cost meter for this session. Prices come from settings, falling back
|
|
231
232
|
// to the defaults; the loop records usage into it from every stream.
|
|
232
233
|
const costMeter = initCostMeter({
|
|
@@ -476,7 +477,7 @@ async function main() {
|
|
|
476
477
|
// No name: show the current model and a picker.
|
|
477
478
|
console.log(chalk.dim(`model: ${CONFIG.model}\n` +
|
|
478
479
|
(CONFIG.subAgentModel ? `sub-agent model: ${CONFIG.subAgentModel} (delegated task work)\n` : "") +
|
|
479
|
-
`endpoint: ${CONFIG.baseURL}\ncontext window: ${
|
|
480
|
+
`endpoint: ${CONFIG.baseURL}\ncontext window: ${contextWindowFor()} tokens (auto-compact at ~${compactThreshold()})`));
|
|
480
481
|
if (!process.stdin.isTTY)
|
|
481
482
|
return; // no picker without a TTY — switch with: /model <name>
|
|
482
483
|
const models = await listModels();
|
|
@@ -493,12 +494,18 @@ async function main() {
|
|
|
493
494
|
apply(models[choice]); // picker is session-only too; "/model save <name>" to persist
|
|
494
495
|
};
|
|
495
496
|
// /mcp — list configured MCP servers + status, then manage one (like Claude Code).
|
|
496
|
-
const mcpStatusText = (status) => status === "connected" ? chalk.green("connected") : status === "needs-auth" ? chalk.yellow("needs auth") : status === "failed" ? chalk.red("failed") : chalk.dim("disabled");
|
|
497
|
+
const mcpStatusText = (status) => status === "connected" ? chalk.green("connected") : status === "needs-auth" ? chalk.yellow("needs auth") : status === "failed" ? chalk.red("failed") : status === "pending" ? chalk.dim("connecting…") : chalk.dim("disabled");
|
|
497
498
|
const handleMcpCommand = async (arg) => {
|
|
498
499
|
const parts = arg.trim().split(/\s+/).filter(Boolean);
|
|
499
|
-
|
|
500
|
+
// /mcp reload — re-read settings.json now (the file watcher normally does this on save).
|
|
501
|
+
if (parts[0] === "reload") {
|
|
502
|
+
const changes = await reloadMcpServers();
|
|
503
|
+
console.log(chalk.dim(changes.length ? `(mcp: ${changes.join(", ")})` : "(mcp: no changes in settings.json)"));
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (parts.length >= 2 && MCP_SUBCOMMANDS[parts[0]]) {
|
|
500
507
|
const name = parts.slice(1).join(" ");
|
|
501
|
-
const action =
|
|
508
|
+
const action = MCP_SUBCOMMANDS[parts[0]];
|
|
502
509
|
try {
|
|
503
510
|
console.log(chalk.dim(await runMcpAction(name, action, (url) => console.log(chalk.dim(`authenticate in your browser: ${url}`)))));
|
|
504
511
|
}
|
|
@@ -524,6 +531,7 @@ async function main() {
|
|
|
524
531
|
return;
|
|
525
532
|
}
|
|
526
533
|
const server = servers[choice];
|
|
534
|
+
console.log(`\n${chalk.bold(`${server.name} MCP Server`)}\n\n${mcpServerDetails(server)}\n`); // Claude Code's server screen
|
|
527
535
|
const actions = mcpActionsFor(server);
|
|
528
536
|
const actionChoice = await select(actions.map((a) => a.label));
|
|
529
537
|
if (actionChoice < 0) {
|
|
@@ -580,10 +588,13 @@ async function main() {
|
|
|
580
588
|
await handleModelCommand(line.slice("/model".length).trim());
|
|
581
589
|
return true;
|
|
582
590
|
}
|
|
583
|
-
// /skill <name> runs a skill on the user's behalf
|
|
584
|
-
//
|
|
591
|
+
// /skill <name> [args] runs a skill on the user's behalf. Like Claude Code it
|
|
592
|
+
// sends two user messages: a <command-name> marker (the model then knows the
|
|
593
|
+
// skill is already loaded) and the body. Works for user-only skills too.
|
|
585
594
|
if (line === "/skill" || line.startsWith("/skill ")) {
|
|
586
|
-
const
|
|
595
|
+
const skills = currentSkills(); // live — a SKILL.md added mid-session is found
|
|
596
|
+
const [name = "", ...rest] = line.slice("/skill".length).trim().split(/\s+/);
|
|
597
|
+
const args = rest.join(" ");
|
|
587
598
|
if (!name) {
|
|
588
599
|
console.log(chalk.dim(skills.length ? `usage: /skill <name> — available: ${skills.map((s) => s.name).join(", ")}` : "(no skills in this project — add one at .mini-agent/skills/<name>/SKILL.md)"));
|
|
589
600
|
return true;
|
|
@@ -593,8 +604,9 @@ async function main() {
|
|
|
593
604
|
console.log(chalk.yellow(`(no skill named "${name}")`));
|
|
594
605
|
return true;
|
|
595
606
|
}
|
|
596
|
-
|
|
597
|
-
|
|
607
|
+
for (const content of userSkillMessages(s, args))
|
|
608
|
+
messages.push({ role: "user", content });
|
|
609
|
+
autoMode.recordRequest(`/${s.name}${args ? ` ${args}` : ""}`);
|
|
598
610
|
console.log(chalk.dim(`(running skill: ${s.name})`));
|
|
599
611
|
running = true;
|
|
600
612
|
interrupted = false;
|
|
@@ -606,13 +618,14 @@ async function main() {
|
|
|
606
618
|
console.log(chalk.yellow(`\n⚠️ ${EXIT_NOTES[r.reason]}`));
|
|
607
619
|
return true;
|
|
608
620
|
}
|
|
609
|
-
// /mcp [reconnect|auth|enable|disable <name>]
|
|
621
|
+
// /mcp [reload | reconnect|auth|enable|disable <name>]
|
|
610
622
|
if (line === "/mcp" || line.startsWith("/mcp ")) {
|
|
611
623
|
await handleMcpCommand(line.slice("/mcp".length).trim());
|
|
612
624
|
return true;
|
|
613
625
|
}
|
|
614
626
|
switch (line) {
|
|
615
627
|
case "/skills": {
|
|
628
|
+
const skills = currentSkills();
|
|
616
629
|
if (!skills.length) {
|
|
617
630
|
console.log(chalk.dim("(no skills — add one at .mini-agent/skills/<name>/SKILL.md or ~/.config/mini-agent/skills/)"));
|
|
618
631
|
return true;
|
|
@@ -819,6 +832,8 @@ async function main() {
|
|
|
819
832
|
return true;
|
|
820
833
|
}
|
|
821
834
|
default:
|
|
835
|
+
if (line.startsWith("/") && findSkill(currentSkills(), line.slice(1).split(/\s+/)[0]))
|
|
836
|
+
return handleCommand(`/skill ${line.slice(1)}`); // /<skill-name> [args], like Claude Code
|
|
822
837
|
if (line.startsWith("/")) {
|
|
823
838
|
console.log(chalk.dim(`unknown command: ${line} — try /help`)); // typo guard
|
|
824
839
|
return true; // still consumed — don't send typos to the model
|
|
@@ -857,7 +872,7 @@ async function main() {
|
|
|
857
872
|
// input stays clean.
|
|
858
873
|
let footer;
|
|
859
874
|
if (process.stdin.isTTY) {
|
|
860
|
-
const ctxPct =
|
|
875
|
+
const ctxPct = contextPercent(messages); // % of the way to auto-compaction
|
|
861
876
|
console.log(""); // one blank line separates the previous answer from the prompt
|
|
862
877
|
footer = statusLine(CONFIG.model, path.basename(process.cwd()), gitBranch(), ctxPct, costMeter.cost(), Date.now() - sessionStartedAt); // status pinned below the prompt
|
|
863
878
|
}
|
package/dist/completion.js
CHANGED
|
@@ -58,7 +58,7 @@ export function slashCompletions(input, ctx) {
|
|
|
58
58
|
], input.trimEnd()) };
|
|
59
59
|
}
|
|
60
60
|
if (command === "/mcp") {
|
|
61
|
-
const items = [{ value: "/mcp", description: "Open MCP server menu" }, ...["
|
|
61
|
+
const items = [{ value: "/mcp", description: "Open MCP server menu" }, { value: "/mcp reload", description: "Re-read MCP servers from settings.json" }, ...["tools", "auth", "clear-auth", "reconnect", "enable", "disable"].flatMap((action) => ctx.servers.map((name) => ({ value: `/mcp ${action} ${name}`, description: `${action} ${name}` })))];
|
|
62
62
|
return { items: filter(items, input.trimEnd()) };
|
|
63
63
|
}
|
|
64
64
|
return { items: filter(COMMANDS), hint: "Commands" };
|
package/dist/config.js
CHANGED
|
@@ -40,6 +40,39 @@ function readSettings(p) {
|
|
|
40
40
|
}
|
|
41
41
|
const globalSettings = readSettings(GLOBAL_SETTINGS_PATH); // the user's defaults
|
|
42
42
|
const projectSettings = readSettings(PROJECT_SETTINGS_PATH); // this project's rules
|
|
43
|
+
// Re-read ONLY the mcpServers map from both layers, mid-session (the MCP hot
|
|
44
|
+
// reload in mcp.ts). Unlike startup, a file that won't parse is NOT fatal here:
|
|
45
|
+
// an editor saving in two steps can leave half-written JSON for a moment, and
|
|
46
|
+
// killing a live session over that would be absurd. Return null instead — the
|
|
47
|
+
// caller keeps the servers it already has and waits for the next save.
|
|
48
|
+
export function readMcpServers() {
|
|
49
|
+
const layers = [];
|
|
50
|
+
for (const p of [GLOBAL_SETTINGS_PATH, PROJECT_SETTINGS_PATH]) {
|
|
51
|
+
if (!fs.existsSync(p)) {
|
|
52
|
+
layers.push({});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
layers.push(JSON.parse(fs.readFileSync(p, "utf8")));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null; // unparseable right now — don't guess, keep what is running
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { ...(layers[0].mcpServers ?? {}), ...(layers[1].mcpServers ?? {}) }; // same merge as startup: project wins
|
|
63
|
+
}
|
|
64
|
+
// Which settings file defines this MCP server (the /mcp panel's "Config
|
|
65
|
+
// location"). Project entries override global ones, so check the project first.
|
|
66
|
+
export function mcpConfigPath(name) {
|
|
67
|
+
try {
|
|
68
|
+
if (JSON.parse(fs.readFileSync(PROJECT_SETTINGS_PATH, "utf8")).mcpServers?.[name])
|
|
69
|
+
return PROJECT_SETTINGS_PATH;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* no project settings (or unreadable) — it's global */
|
|
73
|
+
}
|
|
74
|
+
return GLOBAL_SETTINGS_PATH;
|
|
75
|
+
}
|
|
43
76
|
// Provider-agnostic configuration. Resolution order, most specific wins:
|
|
44
77
|
// env MINI_AGENT_* > project settings > global settings > legacy DEEPSEEK_* env > defaults
|
|
45
78
|
export const CONFIG = {
|
|
@@ -51,12 +84,14 @@ export const CONFIG = {
|
|
|
51
84
|
apiKey: process.env.MINI_AGENT_API_KEY || // generic key wins...
|
|
52
85
|
process.env.DEEPSEEK_API_KEY || // ...over the back-compat one
|
|
53
86
|
"", // empty = missing; requireApiKey() turns that into a helpful error
|
|
54
|
-
model: process.env.MINI_AGENT_MODEL || projectSettings.model || globalSettings.model || "deepseek-
|
|
87
|
+
model: process.env.MINI_AGENT_MODEL || projectSettings.model || globalSettings.model || "deepseek-flash", // must support function calling
|
|
55
88
|
effortProfiles: { ...(globalSettings.effortProfiles ?? {}), ...(projectSettings.effortProfiles ?? {}) },
|
|
56
89
|
// Sub-agents (the task tool) can run on a DIFFERENT model than the orchestrator:
|
|
57
90
|
// a cheap/fast one for grunt work (reading many files, broad search), or a
|
|
58
91
|
// strong one as an "advisor" to double-check. undefined → use the main model.
|
|
59
92
|
subAgentModel: process.env.MINI_AGENT_SUBAGENT_MODEL || projectSettings.subAgentModel || globalSettings.subAgentModel || undefined,
|
|
93
|
+
// Per-model windows (context.ts contextWindowFor): project entries override global ones.
|
|
94
|
+
contextWindows: { ...(globalSettings.contextWindows ?? {}), ...(projectSettings.contextWindows ?? {}) },
|
|
60
95
|
contextWindow: Number(process.env.MINI_AGENT_CONTEXT_WINDOW) || // env override first
|
|
61
96
|
projectSettings.contextWindow || // then files
|
|
62
97
|
globalSettings.contextWindow ||
|