`dsh` shipped two config trees that were 43 rows the same: apps/cli/cordis.yml composed web as 74 flat rows, while the TUI booted examples/tui-agent/cordis.yml whose single `@deepseek-ai/dsh-tui-demo` row mounted twelve plugins behind a twenty-key pass-through Config. Neither file was what its location claimed — apps/cli hardcoded the "example" as the product default and the "demo" bundle was the application — and every capability change had to be made twice. - apps/cli/base.cordis.yml holds the 43 shared rows; tui.cordis.yml and web.cordis.yml are patch lists stating only what differs per surface - overlays apply as SIBLING patch lists at one include level, because include patches never cross an include boundary. Precedence: base < surface < (--config | personal ~/.dsh/config.yaml) < launcher flag/profile patches - `--config` now applies an overlay INSTEAD OF the personal one, so a demo or test tree never inherits the user's route; new `--config-replace` boots a file as the entire tree (the old `--config` behaviour). Both survive /resume - vendor/include: index each `insert`ed row as it is added so a later patch can configure or disable it. Upstream built the id index once before the patch loop, leaving every surface-only row — the whole TUI front door — silently unpatchable from user config. Logged as local modification 8 - session identity moves to dsh-agent-loop's CONFIGURED_AGENT_IDENTITIES_KEY; dsh-tui's MAIN_SESSION_ID_KEY is deleted (only the bundle read it) - delete examples/tui-agent, examples/cordis-agent, packages/examples/tui-demo; TUI tests → apps/cli/tests, cordis e2e → packages/cordis/tool-cordis/tests, examples/code-mode survives as an overlay leaf - `dsh web` gains --config, threaded into AppCLIEntry as an extra overlay Three latent defects surfaced and are fixed here: the TUI captured the optional sessionQuery service once at construction and could permanently disable /resume when it won the mount race; the session-store root silently reverted to a project-local ./.sessions; --config-replace was dropped by the resume handoff. Verified by booting each tree through the real Loader (TUI 55 entries, web 75, zero unsettled) rather than reading YAML. All eight terminal snapshots replay byte-identically; 14/14 PTY smoke, 112/112 snapshots, 25/25 doc-sync, hygiene and lint clean.
tui-agent
English | 中文
全屏交互式编码 agent(智能体):DeepSeek V4、本地 bash 与文件系统工具、压缩(compaction)、subagent、工作流与全新 agent Ralph 迭代、plan mode(/plan 进入,exit_plan_mode 评审退出)、超时/溢出策略,以及通过 @deepseek-ai/dsh-tui-demo 提供的 JSONL 持久化;该应用从 cordis.yml 加载。同级 headless-agent 以适合管道调用的单次任务形式运行同一能力类,acp-agent 则通过 JSON-RPC 提供该能力。
运行
# repo root .env (gitignored) or exported env:
# DEEPSEEK_API_KEY=sk-…
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
pnpm run demo:tui
演示脚本和可安装的 dsh CLI(命令行界面,见 apps/cli)都会以此示例的 cordis.yml 作为已交付的默认配置启动;dsh 还会应用 ~/.dsh 中的个人覆盖,并将调用目录作为工作区。
输入一项编码任务。agent 使用 read/write/edit 文件系统工具处理常规文件操作,使用 bash(加上面向后台任务的通用 task_output/task_list/task_kill)执行 shell 命令、搜索和测试。每次 bash 调用都在新的 bash -c 中运行(系统提示词要求模型传递 workdir,而不是使用 cd)。文件系统工具和 bash 都会相对于会话工作区解析相对路径。agent 还可以通过 subagent/subagent_fork 委托。
todo_write 任务跟踪器是选用的,不在已交付配置中:请将 @deepseek-ai/dsh-tool-todo 添加到 cordis.yml(或在 ~/.dsh 下使用个人配置覆盖)以公开该工具。加载后,模型会把整表计划记录到会话日志,TUI 则渲染它。
TUI 渲染 Markdown 历史、推理(reasoning)、工具自有的终端/diff/通用卡片、token 总量,以及加载 todo_write 时的最新计划。较长的工具正文保留首尾预览;Ctrl+O 展开或折叠所有卡片。Enter 用于提交,或在 agent 运行时进行 steering(中途引导);Ctrl+R 切换推理,Escape 取消,/help 列出命令。/plan 为下一步骤选择 plan mode;/plan <message> 还会将消息提交到该步骤,/plan off 则在没有模型输入的情况下选择默认 mode。/status 会展开当前会话的标识、活动计数、精确 token/缓存 bucket、上下文用量和时间戳,而不中断正在运行的轮次。/model 打开当前提供方目录的键盘选择器;使用 Up/Down 聚焦模型,使用 Shift+Tab 循环切换为该模型公布的推理强度,再用 Enter 选择;也可以使用 /model <model> 和 /model <provider>/<model> 直接选择。ask_user_question 会打开一个位于左下方的宽键盘面板,包含批次进度和编号选项。
恢复早先的会话
每次运行默认都会启动新会话(其事件日志落在 ./.sessions/ 下)。如需 继续 先前对话,请将其 id 传给已安装的 dsh CLI:此时 main agent 会重新水化持久日志,而不会从头开始,因此模型会将早先轮次视为历史:
dsh --resume <prior-session-id>
/resume 打开可搜索键盘选择器,显示标题、活动、上一轮结果、模型路由、持久化目标阶段和实时/已持久化状态。已安装的 dsh 宿主会等待刷写完成,对当前应用执行 dispose(资源释放),然后以 dsh --resume <id> 替换进程。TUI 仍会在退出时打印该命令,并在自定义宿主无法移交时显示它。dsh --resume <id> 在启动上下文中提供 id,cordis.yml 会读取它(resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined");没有标志时,agent 会开始新会话。缺失或无法读取的 id 不会启动 agent,而会发出 agent-loop/config-start-failed:TUI 打印失败并以非零状态退出。选择器没有跨进程会话锁,因此拥有并发宿主的部署必须自行协调会话所有权。
Code Mode
code-mode.cordis.yml 在同一树上覆盖 worker 线程运行时和 tools: { mode: code }。模型会收到一个 run_code 传输工具,加上一份为可见工具生成的 TypeScript SDK;只有程序输出会返回模型上下文。使用 mode: both 可在 run_code 旁同时公开原生调用。执行契约详见 Code Mode Agent Note(agent 决策记录)。
pnpm run demo:code-mode # this overlay under the TUI (default UI)
pnpm run demo:code-mode acp # the acp-agent example's same-shaped overlay
尝试一项横跨多个工具调用的任务,例如:
统计 docs/ 下每个
*.md文件的行数,并将最大的三个写入 summary.txt。
然后观察 transcript(文本记录):一次 run_code 调用、一个循环调用工具的程序,以及模型筛选后的结果,而不是五次原始工具输出往返。
每个叶节点配置项所演示的内容
此示例是轻量叶节点 cordis.yml:它选择可替换后端、加载一个应用包(package),并添加有意放在共享主干外的产品工具。主干(会话、系统提示词、工具、agent、不变式、agent-loop)和入口集群(JSONL 持久化、pi-tui 通道、预创建的 main agent)位于 @deepseek-ai/dsh-tui-demo 应用及其加载的 @deepseek-ai/dsh-agent-spine-demo 组合包中;叶节点负责接线后端与面向模型的可选工具:
| 配置项 | 演示内容 |
|---|---|
hmr (@cordisjs/plugin-hmr) |
开发/演示的编辑-重载循环:它是 叶节点 配置项(不内置到应用),因为它依赖 Loader 的内部模块访问 |
llm-deepseek |
默认原生适配器 |
bash (dsh-bash-local) |
执行器实现:bash seam 中可替换的实现侧。面向模型的 bash schema(tool-bash)和通用 task_* 控制(tool-tasks)由 dsh-agent-spine-demo 提供,因此叶节点只选择执行器 |
tui-agent (@deepseek-ai/dsh-tui-demo) |
应用组合包:agent-spine 演示 + JSONL 持久化 + pi-tui 通道 + 预创建的 main agent |
subagent, subagent-spawn, subagent-fork |
subagent 提供方注册表加两个进程内后端:新子 agent,以及用父 agent 已完成轮次前缀播种的子 agent |
tool-subagent, tool-subagent-fork |
两次面向模型的 dsh-tool-subagent 加载,每次绑定不同提供方,并以不同工具名(subagent、subagent_fork)公开 |
workflow-workerthread, tool-workflow |
worker 线程工作流引擎及其面向模型的 workflow 工具,子调用通过 spawn 后端路由 |
plan-mode |
插件拥有的 /plan [message] 进入命令和 /plan off 退出命令、plan-mode 提示词策略、工具限制,以及经评审的 exit_plan_mode 转换 |
fs-local, fs-policy, tool-fs |
文件系统栈:本地 ctx.fs 提供方、先读后写/编辑策略门禁(位于 fs/* 事件门禁),以及面向模型的 read/write/edit 工具。相对路径相对于会话工作区解析 |
端到端测试(pnpm run test:e2e)
与 UI 无关的带密钥套件通过 tests/harness.ts 以程序方式组装完整栈(无 PTY、无 Loader):
tests/full-loop.e2e.ts:canary 测试:真实模型通过真实 bash 工具运行echo e2e-ok;断言tool/call/tool/result会话事件和最终答案。tests/coding-task.e2e.ts:类 swebench 冒烟测试:临时目录包含add.js(其中a - b写在本应是a + b的位置)和失败的add.test.js;agent 必须修复错误并验证。测试会自行重新运行node add.test.js并检查文件,不信任 agent 的说法。tests/resume.e2e.ts:跨进程持久连续性:第一次运行告诉真实模型一个密码并将轮次持久化到临时 JSONL 根目录,然后 dispose 整个上下文;第二次运行在同一根目录上创建新上下文,恢复会话 id 并要求模型回忆密码。只有重新水化的日志能够提供该回忆。tests/compaction.e2e.ts:压缩冒烟测试:一项真实多步 bash 任务在故意设得很小的上下文窗口中运行,使自动压缩监听器在会话中途触发。测试验证外部状态:真实日志中出现compact/start…end对,模型可见内容缩减(一个替换节点遮蔽了较旧节点),且 agent 在压缩后仍给出正确最终答案。tests/todo-write.e2e.ts:加载选用的todo_write工具,由真实模型驱动,测试验证产生的todo/write会话事件。tests/code-mode.e2e.ts:带密钥 Code Mode 证明:使用真实模型和双工具任务,断言协议层工具列表精确为[run_code],tool/code-dispatch事件位于父调用下,且筛选后的答案已返回。
这些测试在没有 DEEPSEEK_API_KEY 时自行跳过。无密钥 tests/tui-keyless-smoke.e2e.ts 通过 PTY 启动真实 Loader 树(唯一获准的 PTY 界面):基础启动 + /plan + /exit,一次带问题对话框和工具往返的脚本 LLM(大语言模型)对话,Code Mode 覆盖配置的欢迎行,以及恢复失败退出路径。
快照测试
tests/snapshots/<scenario>/session.jsonl 提供已录制的用户提示词和模型分片;同级子日志驱动 subagent 和工作流。无密钥套件通过真实循环和工具实现执行这些脚本,然后比较可读的预期终端单元格/样式输出。对于仅涉及展示的变更,使用 pnpm run test:snapshot:refresh;已录制的模型流程改变时,使用 DeepSeek 密钥运行 pnpm run test:snapshot:record。已实现的 TUI 快照 Agent Note 规定了场景矩阵,以及已录制旅程、包级瞬态快照与 PTY 覆盖之间的分工。