feat(skills): expose installed skills to the model + zh-CN README

Add a model-visible skills block to the system prompt (progressive
disclosure: lists name/description/path, never inlines SKILL.md bodies)
with a 12k-char prompt budget and a 512-char per-description cap.
EngineConfig gains skills_dir, threaded through the three construction
sites (TUI app, exec agent, runtime thread manager).

README skills section is rewritten to document the discovery order,
the SKILL.md frontmatter contract, and the install/update/uninstall/
trust commands. Adds Simplified Chinese README cross-link and full
README.zh-CN.md translation (DeepSeek went viral in CN -- discoverability
matters).

Tests cover happy path, empty/missing dir → None, oversize description
truncation with U+2026 marker, internal-whitespace collapse, and the
overflow-budget omission notice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hunter Bown
2026-05-01 01:38:04 -05:00
parent ac7c11e751
commit 1b9cf072c2
8 changed files with 554 additions and 5 deletions
+36 -2
View File
@@ -2,6 +2,8 @@
> **A terminal-native coding agent for [DeepSeek V4](https://platform.deepseek.com) models — with 1M-token context, thinking-mode reasoning, and full tool-use.**
[简体中文 README](README.zh-CN.md)
```bash
npm i -g deepseek-tui
```
@@ -332,8 +334,37 @@ or via the `LC_ALL`/`LANG` environment variables. See [docs/CONFIGURATION.md](do
## Publishing your own skill
DeepSeek-TUI can install community skills directly from a GitHub repo, with no
backend service in the loop:
DeepSeek-TUI discovers skills from the active skills directory. Workspace-local
`.agents/skills` wins when present, then `./skills`, then the configured global
directory (`~/.deepseek/skills` by default). Each skill is a directory with a
`SKILL.md` file:
```text
~/.deepseek/skills/my-skill/
└── SKILL.md
```
`SKILL.md` must start with YAML frontmatter:
```markdown
---
name: my-skill
description: Use this when DeepSeek should follow my custom workflow.
---
# My Skill
Instructions for the agent go here.
```
Run `/skills` to list discovered skills, `/skill <name>` to activate one for
the next message, or `/skill new` to use the bundled skill-creator helper.
Installed skills are also listed in the model-visible session context so the
agent can choose relevant skills when the user names them or when the task
matches their descriptions.
DeepSeek-TUI can also install community skills directly from a GitHub repo,
with no backend service in the loop:
1. Create a public GitHub repo with a `SKILL.md` at the root containing the
usual `---` frontmatter (`name`, `description`).
@@ -346,6 +377,9 @@ backend service in the loop:
placed under `~/.deepseek/skills/<name>/`.
5. Submit a PR to the curated `index.json` (default registry) to make the skill
installable by name (`/skill install <name>`) instead of the GitHub spec.
6. Use `/skill update <name>`, `/skill uninstall <name>`, or
`/skill trust <name>` for installed community skills. Trust is only needed
when you want scripts bundled with a skill to be eligible for execution.
## Documentation
+289
View File
@@ -0,0 +1,289 @@
# DeepSeek TUI
> **面向 [DeepSeek V4](https://platform.deepseek.com) 模型的终端原生编程智能体,支持 100 万 token 上下文、思考模式推理流和完整工具调用。**
[English README](README.md)
```bash
npm i -g deepseek-tui
```
[![CI](https://github.com/Hmbown/DeepSeek-TUI/actions/workflows/ci.yml/badge.svg)](https://github.com/Hmbown/DeepSeek-TUI/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/deepseek-tui)](https://www.npmjs.com/package/deepseek-tui)
![DeepSeek TUI screenshot](assets/screenshot.png)
---
## 这是什么?
DeepSeek TUI 是一个完全运行在终端里的编程智能体。它可以让 DeepSeek 前沿模型直接访问你的工作区:读取和编辑文件、运行 shell 命令、搜索和浏览网页、管理 git、调度子智能体,并通过快速的键盘驱动 TUI 完成多步开发任务。
它面向 **DeepSeek V4**`deepseek-v4-pro` / `deepseek-v4-flash`)构建,默认支持 100 万 token 上下文窗口和原生思考模式流式输出。模型推理、工具调用和最终回答会在终端里实时呈现。
### 主要功能
- **原生 RLM**`rlm_query` 工具):用现有 DeepSeek 客户端并行调度 1 到 16 个低成本 `deepseek-v4-flash` 子任务,用于批量分析、任务拆解或并行推理。
- **思考模式流式输出**:实时显示 DeepSeek 的推理过程。
- **完整工具集**:文件操作、shell 执行、git、网页搜索/浏览、apply-patch、子智能体、MCP 服务器。
- **100 万 token 上下文**:上下文接近上限时自动进行智能压缩。
- **三种交互模式**:Plan(只读探索)、Agent(默认交互并带审批)、YOLO(可信工作区内自动批准工具)。
- **推理强度档位**:用 `Shift+Tab``off -> high -> max` 之间切换。
- **会话保存和恢复**:适合长任务的断点续作。
- **工作区回滚**:通过 side-git 记录每轮前后快照,支持 `/restore``revert_turn`,不修改项目自己的 `.git`
- **HTTP/SSE 运行时 API**`deepseek serve --http` 可用于无界面智能体流程。
- **MCP 协议支持**:连接 Model Context Protocol 服务器扩展工具,见 [docs/MCP.md](docs/MCP.md)。
- **实时成本跟踪**:按轮次和会话统计 token 用量与成本估算。
- **深色主题**:DeepSeek 蓝色系终端界面。
---
## 快速开始
```bash
npm install -g deepseek-tui
deepseek
```
首次启动时会提示输入 [DeepSeek API key](https://platform.deepseek.com/api_keys)。也可以提前配置:
```bash
# 通过 CLI 保存
deepseek login --api-key "YOUR_DEEPSEEK_API_KEY"
# 或通过环境变量
export DEEPSEEK_API_KEY="YOUR_DEEPSEEK_API_KEY"
deepseek
```
### 中国大陆 / 镜像友好安装
如果在中国大陆访问 GitHub 或 npm 下载较慢,可以通过 Cargo 注册表镜像安装 Rust crate
```toml
# ~/.cargo/config.toml
[source.crates-io]
replace-with = "tuna"
[source.tuna]
registry = "sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/"
```
然后从主包安装已发布的两个二进制文件:
```bash
cargo install deepseek-tui --locked
deepseek --version
deepseek doctor --json
```
`v0.8.1` 开始,`deepseek-tui` 这个主 Cargo 包会同时安装:
- `deepseek`:推荐使用的调度器入口。
- `deepseek-tui`:交互式 TUI 伴随二进制。
也可以直接从 [GitHub Releases](https://github.com/Hmbown/DeepSeek-TUI/releases) 下载预编译二进制。如果你有镜像后的 release 资产目录,也可以配合 `DEEPSEEK_TUI_RELEASE_BASE_URL` 使用 TUNA、rsproxy、腾讯云 COS 或阿里云 OSS 等镜像。
### 从源码安装
```bash
git clone https://github.com/Hmbown/DeepSeek-TUI.git
cd DeepSeek-TUI
cargo install --path crates/tui --locked # 需要 Rust 1.85+
deepseek --version
```
---
## 其他模型提供方
### NVIDIA NIM
```bash
deepseek auth set --provider nvidia-nim --api-key "YOUR_NVIDIA_API_KEY"
deepseek --provider nvidia-nim
# 或仅对当前进程生效:
DEEPSEEK_PROVIDER=nvidia-nim NVIDIA_API_KEY="..." deepseek
```
### Fireworks 和自托管 SGLang
```bash
deepseek auth set --provider fireworks --api-key "YOUR_FIREWORKS_API_KEY"
deepseek --provider fireworks --model deepseek-v4-pro
# SGLang 通常是自托管;localhost 部署可以不配置鉴权。
SGLANG_BASE_URL="http://localhost:30000/v1" deepseek --provider sglang --model deepseek-v4-flash
```
---
## 使用方式
```bash
deepseek # 交互式 TUI
deepseek "explain this function" # 一次性提示
deepseek --model deepseek-v4-flash "summarize" # 指定模型
deepseek --yolo # YOLO 模式,自动批准工具
deepseek login --api-key "..." # 保存 API key
deepseek doctor # 检查配置和连接
deepseek doctor --json # 机器可读诊断
deepseek setup --status # 只读安装状态检查
deepseek setup --tools --plugins # 创建本地工具和插件目录
deepseek models # 列出可用 API 模型
deepseek sessions # 列出已保存会话
deepseek resume --last # 恢复最近会话
deepseek serve --http # HTTP/SSE API 服务
deepseek mcp list # 列出已配置 MCP 服务器
deepseek mcp validate # 校验 MCP 配置和连接
deepseek mcp-server # 启动 dispatcher MCP stdio 服务器
```
### 常用快捷键
| 按键 | 功能 |
|---|---|
| `Tab` | 补全 `/``@`;运行中则把草稿排队为后续消息;否则切换模式 |
| `Shift+Tab` | 切换推理强度:off -> high -> max |
| `F1` | 帮助 |
| `Esc` | 返回 / 关闭 |
| `Ctrl+K` | 命令面板 |
| `Ctrl+R` | 恢复旧会话 |
| `Alt+R` | 搜索提示历史和恢复草稿 |
| `@path` | 在输入框中附加文件或目录上下文 |
| `Alt+↑` | 编辑最后一条排队消息 |
| `/attach <path>` | 附加图片或视频路径引用 |
---
## 模式
| 模式 | 行为 |
|---|---|
| **Plan** | 只读调查;模型先探索并提出拆解计划,再进行更改 |
| **Agent** | 默认交互模式;多步工具调用带审批门禁 |
| **YOLO** | 在可信工作区自动批准工具;仍会保留计划和清单以便追踪 |
---
## 配置
主配置文件是 `~/.deepseek/config.toml`。完整选项见 [config.example.toml](config.example.toml) 和 [docs/CONFIGURATION.md](docs/CONFIGURATION.md)。
常用环境变量:
| 变量 | 用途 |
|---|---|
| `DEEPSEEK_API_KEY` | DeepSeek API key |
| `DEEPSEEK_BASE_URL` | API base URL |
| `DEEPSEEK_MODEL` | 默认模型 |
| `DEEPSEEK_PROVIDER` | 提供方:`deepseek``nvidia-nim``fireworks``sglang` |
| `DEEPSEEK_PROFILE` | 配置 profile 名称 |
| `NVIDIA_API_KEY` | NVIDIA NIM API key |
| `FIREWORKS_API_KEY` | Fireworks AI API key |
| `SGLANG_BASE_URL` | 自托管 SGLang 端点 |
| `SGLANG_API_KEY` | 可选 SGLang bearer token |
快速诊断:
```bash
deepseek setup --status
deepseek doctor --json
```
UI 语言与模型输出语言相互独立。可以在 `settings.toml` 里设置 `locale`,也可以通过 `LC_ALL` / `LANG` 环境变量自动选择。支持 `en``zh-Hans``ja``pt-BR` 等界面语言。
DeepSeek 上下文缓存是自动的;当 API 返回 cache hit/miss token 字段时,TUI 会把它们纳入用量和成本统计。
---
## 模型和价格
DeepSeek TUI 默认面向带 100 万 token 上下文窗口的 **DeepSeek V4** 模型。
| 模型 | 上下文 | 输入(缓存命中) | 输入(缓存未命中) | 输出 |
|---|---|---|---|---|
| `deepseek-v4-pro` | 1M | $0.003625 / 1M* | $0.435 / 1M* | $0.87 / 1M* |
| `deepseek-v4-flash` | 1M | $0.0028 / 1M | $0.14 / 1M | $0.28 / 1M |
旧别名 `deepseek-chat``deepseek-reasoner` 会自动映射到 `deepseek-v4-flash`
**NVIDIA NIM** 托管变体(`deepseek-ai/deepseek-v4-pro``deepseek-ai/deepseek-v4-flash`)使用你的 NVIDIA 账号条款,不走 DeepSeek 平台计费。
*DeepSeek 标注的 Pro 价格是限时 75% 折扣,有效期到 2026-05-05 15:59 UTC;该时间之后 TUI 成本估算会回退到 Pro 基础价格。*
---
## 文档
| 文档 | 主题 |
|---|---|
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | 代码库内部结构 |
| [CONFIGURATION.md](docs/CONFIGURATION.md) | 完整配置参考 |
| [MODES.md](docs/MODES.md) | Plan / Agent / YOLO 模式 |
| [MCP.md](docs/MCP.md) | Model Context Protocol 集成 |
| [RUNTIME_API.md](docs/RUNTIME_API.md) | HTTP/SSE API 服务 |
| [RELEASE_RUNBOOK.md](docs/RELEASE_RUNBOOK.md) | 发布流程 |
| [OPERATIONS_RUNBOOK.md](docs/OPERATIONS_RUNBOOK.md) | 运维和恢复 |
完整更新历史见 [CHANGELOG.md](CHANGELOG.md)。
---
## 创建和安装技能
DeepSeek-TUI 会从当前技能目录发现技能。优先级是:工作区
`.agents/skills`、工作区 `./skills`、全局目录(默认
`~/.deepseek/skills`)。每个技能都是一个包含 `SKILL.md` 的目录:
```text
~/.deepseek/skills/my-skill/
└── SKILL.md
```
`SKILL.md` 需要以 YAML frontmatter 开头:
```markdown
---
name: my-skill
description: 当 DeepSeek 需要遵循我的自定义工作流时使用这个技能。
---
# My Skill
这里写给智能体的指令。
```
常用命令:
```bash
/skills
/skill my-skill
/skill new
/skill install github:<owner>/<repo>
/skill update my-skill
/skill uninstall my-skill
/skill trust my-skill
```
`/skills` 列出已发现技能,`/skill <name>` 会把技能应用到下一条消息,
`/skill new` 会调用内置的 skill-creator 辅助创建新技能。已安装技能也会
进入模型可见的会话上下文;当用户点名某个技能,或任务明显匹配技能描述时,
智能体可以主动读取对应的 `SKILL.md` 并使用它。
社区技能可以直接从 GitHub 安装。安装过程受 `[network]` 策略约束,并会校验
压缩包大小、路径穿越和符号链接。`/skill trust <name>` 只在你希望技能内置脚本
可被执行时才需要。
---
## 贡献
欢迎提交 pull request。请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。
*本项目与 DeepSeek Inc. 无隶属关系。*
## 许可证
[MIT](LICENSE)
+15 -3
View File
@@ -83,6 +83,8 @@ pub struct EngineConfig {
pub notes_path: PathBuf,
/// Path to the MCP configuration file.
pub mcp_config_path: PathBuf,
/// Directory containing discoverable skills.
pub skills_dir: PathBuf,
/// Maximum number of assistant steps before stopping.
pub max_steps: u32,
/// Maximum number of concurrently active subagents.
@@ -134,6 +136,7 @@ impl Default for EngineConfig {
trust_mode: false,
notes_path: PathBuf::from("notes.txt"),
mcp_config_path: PathBuf::from("mcp.json"),
skills_dir: crate::skills::default_skills_dir(),
max_steps: 100,
max_subagents: DEFAULT_MAX_SUBAGENTS,
features: Features::with_defaults(),
@@ -1270,8 +1273,12 @@ impl Engine {
// Set up system prompt with project context (default to agent mode)
let working_set_summary = session.working_set.summary_block(&config.workspace);
let system_prompt =
prompts::system_prompt_for_mode_with_context(AppMode::Agent, &config.workspace, None);
let system_prompt = prompts::system_prompt_for_mode_with_context_and_skills(
AppMode::Agent,
&config.workspace,
None,
Some(&config.skills_dir),
);
session.system_prompt =
append_working_set_summary(Some(system_prompt), working_set_summary.as_deref());
@@ -2759,7 +2766,12 @@ impl Engine {
.session
.working_set
.summary_block(&self.config.workspace);
let base = prompts::system_prompt_for_mode_with_context(mode, &self.config.workspace, None);
let base = prompts::system_prompt_for_mode_with_context_and_skills(
mode,
&self.config.workspace,
None,
Some(&self.config.skills_dir),
);
let stable_prompt =
merge_system_prompts(Some(&base), self.session.compaction_summary_prompt.clone());
self.session.system_prompt =
+1
View File
@@ -3008,6 +3008,7 @@ async fn run_exec_agent(
trust_mode,
notes_path: config.notes_path(),
mcp_config_path: config.mcp_config_path(),
skills_dir: config.skills_dir(),
max_steps: 100,
max_subagents,
features: config.features(),
+15
View File
@@ -172,6 +172,16 @@ pub fn system_prompt_for_mode_with_context(
mode: AppMode,
workspace: &Path,
working_set_summary: Option<&str>,
) -> SystemPrompt {
system_prompt_for_mode_with_context_and_skills(mode, workspace, working_set_summary, None)
}
/// Get the system prompt for a specific mode with project and skills context.
pub fn system_prompt_for_mode_with_context_and_skills(
mode: AppMode,
workspace: &Path,
working_set_summary: Option<&str>,
skills_dir: Option<&Path>,
) -> SystemPrompt {
let mode_prompt = compose_mode_prompt(mode);
@@ -197,6 +207,11 @@ pub fn system_prompt_for_mode_with_context(
full_prompt = format!("{full_prompt}\n\n{summary}");
}
if let Some(skills_block) = skills_dir.and_then(crate::skills::render_available_skills_context)
{
full_prompt = format!("{full_prompt}\n\n{skills_block}");
}
if let Some(handoff_block) = load_handoff_block(workspace) {
full_prompt = format!("{full_prompt}\n\n{handoff_block}");
}
+1
View File
@@ -1537,6 +1537,7 @@ impl RuntimeThreadManager {
trust_mode: thread.trust_mode,
notes_path: self.config.notes_path(),
mcp_config_path: self.config.mcp_config_path(),
skills_dir: self.config.skills_dir(),
max_steps: 100,
max_subagents: self.config.max_subagents().clamp(1, MAX_SUBAGENTS),
features: self.config.features(),
+196
View File
@@ -22,6 +22,9 @@ use std::collections::HashMap;
use crate::logging;
const MAX_SKILL_DESCRIPTION_CHARS: usize = 512;
const MAX_AVAILABLE_SKILLS_CHARS: usize = 12_000;
// === Defaults ===
#[allow(dead_code)]
@@ -165,6 +168,96 @@ impl SkillRegistry {
}
}
/// Render a compact model-visible skills block.
///
/// The full `SKILL.md` body is intentionally not included here. This mirrors
/// Codex's progressive-disclosure contract: the model sees skill names,
/// descriptions, and paths up front, then opens the specific `SKILL.md` only
/// when a skill is relevant.
#[must_use]
pub fn render_available_skills_context(skills_dir: &Path) -> Option<String> {
let registry = SkillRegistry::discover(skills_dir);
if registry.is_empty() {
return None;
}
let mut skills = registry.list().to_vec();
skills.sort_by(|a, b| a.name.cmp(&b.name));
let mut out = String::new();
out.push_str("## Skills\n");
out.push_str(
"A skill is a set of local instructions stored in a `SKILL.md` file. \
Below is the list of skills available in this session. Each entry includes a \
name, description, and file path so you can open the source for full \
instructions when using a specific skill.\n\n",
);
out.push_str("### Available skills\n");
let mut omitted = 0usize;
for skill in skills {
let path = skills_dir.join(&skill.name).join("SKILL.md");
let description = truncate_for_prompt(&skill.description, MAX_SKILL_DESCRIPTION_CHARS);
let line = if description.is_empty() {
format!("- {}: (file: {})\n", skill.name, path.display())
} else {
format!(
"- {}: {} (file: {})\n",
skill.name,
description,
path.display()
)
};
if out.chars().count() + line.chars().count() > MAX_AVAILABLE_SKILLS_CHARS {
omitted += 1;
} else {
out.push_str(&line);
}
}
if omitted > 0 {
out.push_str(&format!(
"- ... {omitted} additional skills omitted from this prompt budget.\n"
));
}
if !registry.warnings().is_empty() {
out.push_str("\n### Skill load warnings\n");
for warning in registry.warnings().iter().take(8) {
out.push_str("- ");
out.push_str(&truncate_for_prompt(warning, MAX_SKILL_DESCRIPTION_CHARS));
out.push('\n');
}
}
out.push_str(
"\n### How to use skills\n\
- Discovery: The list above is the skills available in this session. Skill bodies live on disk at the listed paths.\n\
- Trigger rules: If the user names a skill (with `$SkillName`, `/skill <name>`, or plain text) OR the task clearly matches a skill description above, use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n\
- Missing/blocked: If a named skill is missing or its `SKILL.md` cannot be read, say so briefly and continue with the best fallback.\n\
- Progressive disclosure: After deciding to use a skill, read only that skill's `SKILL.md`. When it references relative paths such as `scripts/foo.py`, resolve them relative to the skill directory.\n\
- Context hygiene: Load only the specific referenced files needed for the task. Avoid bulk-loading unrelated skill resources.\n\
- Safety: Do not execute scripts from a community skill unless the user explicitly asks or the skill has been trusted for script use.\n",
);
Some(out)
}
fn truncate_for_prompt(value: &str, max_chars: usize) -> String {
let single_line = value.split_whitespace().collect::<Vec<_>>().join(" ");
if single_line.chars().count() <= max_chars {
return single_line;
}
let mut truncated = single_line
.chars()
.take(max_chars.saturating_sub(1))
.collect::<String>();
truncated.push('…');
truncated
}
// === CLI Helpers ===
#[allow(dead_code)] // CLI utility for future use
@@ -202,3 +295,106 @@ pub fn show(skills_dir: &Path, name: &str) -> Result<()> {
println!("{contents}");
Ok(())
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
fn create_skill_dir(tmpdir: &TempDir, skill_name: &str, skill_content: &str) {
let skill_dir = tmpdir.path().join("skills").join(skill_name);
std::fs::create_dir_all(&skill_dir).unwrap();
std::fs::write(skill_dir.join("SKILL.md"), skill_content).unwrap();
}
#[test]
fn render_available_skills_context_lists_paths_and_usage() {
let tmpdir = TempDir::new().unwrap();
create_skill_dir(
&tmpdir,
"test-skill",
"---\nname: test-skill\ndescription: A test skill\n---\nDo something special",
);
let rendered =
crate::skills::render_available_skills_context(&tmpdir.path().join("skills"))
.expect("skill context");
assert!(rendered.contains("## Skills"));
assert!(rendered.contains("- test-skill: A test skill"));
assert!(rendered.contains("test-skill/SKILL.md"));
assert!(rendered.contains("### How to use skills"));
}
#[test]
fn render_available_skills_context_returns_none_when_empty() {
let tmpdir = TempDir::new().unwrap();
let empty = tmpdir.path().join("skills");
std::fs::create_dir_all(&empty).unwrap();
assert!(crate::skills::render_available_skills_context(&empty).is_none());
let missing = tmpdir.path().join("does-not-exist");
assert!(crate::skills::render_available_skills_context(&missing).is_none());
}
#[test]
fn render_available_skills_context_truncates_long_descriptions() {
let tmpdir = TempDir::new().unwrap();
let long_desc = "x".repeat(2_000);
let body = format!("---\nname: bigdesc\ndescription: {long_desc}\n---\nbody");
create_skill_dir(&tmpdir, "bigdesc", &body);
let rendered =
crate::skills::render_available_skills_context(&tmpdir.path().join("skills"))
.expect("skill context");
let max = super::MAX_SKILL_DESCRIPTION_CHARS;
assert!(rendered.contains('…'), "expected truncation marker");
assert!(
!rendered.contains(&"x".repeat(max + 1)),
"untruncated long run should not appear"
);
}
#[test]
fn render_available_skills_context_collapses_internal_whitespace() {
let tmpdir = TempDir::new().unwrap();
create_skill_dir(
&tmpdir,
"spaced-skill",
"---\nname: spaced-skill\ndescription: alpha \t beta gamma\n---\nbody",
);
let rendered =
crate::skills::render_available_skills_context(&tmpdir.path().join("skills"))
.expect("skill context");
let line = rendered
.lines()
.find(|l| l.starts_with("- spaced-skill:"))
.expect("skill line");
assert!(line.contains("alpha beta gamma"), "got: {line:?}");
}
#[test]
fn render_available_skills_context_omits_overflowing_skills() {
let tmpdir = TempDir::new().unwrap();
let big_desc = "y".repeat(super::MAX_SKILL_DESCRIPTION_CHARS - 20);
for i in 0..200 {
let body = format!("---\nname: skill-{i:03}\ndescription: {big_desc}\n---\nbody");
create_skill_dir(&tmpdir, &format!("skill-{i:03}"), &body);
}
let rendered =
crate::skills::render_available_skills_context(&tmpdir.path().join("skills"))
.expect("skill context");
assert!(
rendered.contains("additional skills omitted from this prompt budget"),
"expected overflow notice"
);
assert!(
rendered.chars().count() < super::MAX_AVAILABLE_SKILLS_CHARS + 4_000,
"rendered length should stay near the budget"
);
}
}
+1
View File
@@ -355,6 +355,7 @@ fn build_engine_config(app: &App, config: &Config) -> EngineConfig {
trust_mode: app.trust_mode,
notes_path: config.notes_path(),
mcp_config_path: config.mcp_config_path(),
skills_dir: app.skills_dir.clone(),
// Effectively unlimited. V4 has a 1M context window and the user
// wants the model running until it's actually done. The previous cap
// of 100 hit the ceiling on long multi-step plans (wide refactors,