统一 Harness TUI(ACP 客户端)。DeepSeek Harness 的自改进 TUI 插件。这里的一切也都是插件。
56.5/100
综合分
81
GitHub 星标
396
npm 周下载
—/5
用户评分
数据截至 2026/11/1 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add marttynpm 源 · martty 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过安装清单取自 npm/package.json
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包martty @ 0.3.3
✓Node 引擎要求 >=22.19.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 04:32:06
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件
查看扫描证据(共 104 处,展示前 92 条)
- 读取本地文件lib/ember.js:10
readFileSync(new URL('./palettes/ember.json', import.meta.url), 'utf8'), - 读取本地文件lib/tui-theme.js:18
readFileSync(new URL('./palettes/default.json', import.meta.url), 'utf8'), - 读取本地文件lib/tui-theme.js:83
const value = JSON.parse(readFileSync(settingsPath, 'utf8')) - 写入本地文件lib/tui-theme.js:94
mkdirSync(dirname(settingsPath), { recursive: true }) - 写入本地文件lib/tui-theme.js:99
writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}\n`) - 写入本地文件lib/tui-theme.js:100
renameSync(temporary, settingsPath) - 读取本地文件lib/tui-presets.js:63
const value = JSON.parse(readFileSync(settingsPath, 'utf8')) - 写入本地文件lib/tui-presets.js:74
mkdirSync(path.dirname(settingsPath), { recursive: true }) - 写入本地文件lib/tui-presets.js:80
writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}\n`) - 写入本地文件lib/tui-presets.js:81
renameSync(temporary, settingsPath) - 读取本地文件lib/acp-host.js:9
const OWN_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version - 读取本地文件lib/acp-host.js:215
return { file, manifest: JSON.parse(readFileSync(file, 'utf8')) } - 读取本地文件lib/creator-overlay.js:214
const skillDocument = readFileSync( - 执行外部命令来自配置/变量lib/runner.js:40
const child = spawn( - 执行外部命令来自配置/变量bin/martty.js:148
exitFromSpawn(spawnSync(binaryPath, argv, { stdio: 'inherit' })) - 执行外部命令来自配置/变量bin/martty.js:155
exitFromSpawn(spawnSync(process.execPath, [demoSkinPath, ...args], { - 执行外部命令来自配置/变量lib/spawn-tui.js:77
const child = spawn(bin, ['--attach-fds', ...extra], { - 执行外部命令来自配置/变量lib/spawn-tui.js:104
const child = spawn(bin, ['--attach-tcp', `127.0.0.1:${address.port}`, ...extra], { - 读取本地文件lib/agent.js:20
const manifest = JSON.parse(readFileSync(acpPackageJson, 'utf8')) - 读取本地文件lib/agent.js:81
].find((dir) => existsSync(join(dir, 'package.json'))) - 读取本地文件lib/agent.js:82
if (existsSync(bin) && creator) { - 读取本地文件lib/dsh-compat.js:23
const ownManifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) - 读取本地文件lib/tui-plugin-store.js:68
parsed = JSON.parse(readFileSync(file, 'utf8')) - 读取本地文件lib/tui-plugin-store.js:102
if (existsSync(home) || !existsSync(legacy)) return home - 删除本地文件递归lib/tui-plugin-store.js:107
rmSync(staged, { recursive: true, force: true }) - 写入本地文件lib/tui-plugin-store.js:109
renameSync(staged, home) - 删除本地文件递归lib/tui-plugin-store.js:112
rmSync(staged, { recursive: true, force: true }) - 读取本地文件lib/tui-plugin-store.js:137
if (legacyRoot !== undefined && legacyRoot !== root && existsSync(legacyRoot)) { - 写入本地文件lib/tui-plugin-store.js:138
mkdirSync(root, { recursive: true }) - 读取本地文件lib/tui-plugin-store.js:139
for (const entry of readdirSync(legacyRoot, { withFileTypes: true })) { - 读取本地文件lib/tui-plugin-store.js:143
if (!existsSync(target)) cpSync(source, target, { recursive: true, errorOnExist: true }) - 读取本地文件lib/tui-plugin-store.js:154
if (!existsSync(root)) return [] - 写入本地文件lib/tui-plugin-store.js:210
mkdirSync(dir, { recursive: true }) - 写入本地文件lib/tui-plugin-store.js:213
writeFileSync(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' }) - 写入本地文件lib/tui-plugin-store.js:214
renameSync(temporary, file) - 删除本地文件lib/tui-plugin-store.js:216
rmSync(temporary, { force: true }) - 删除本地文件递归lib/tui-plugin-store.js:224
rmSync(dir, { recursive: true, force: false }) - 读取本地文件lib/boot.js:301
if (existsSync(settingsPath)) return false - 读取本地文件lib/boot.js:303
if (!existsSync(legacyPath)) continue - 读取本地文件lib/boot.js:305
const value = JSON.parse(readFileSync(legacyPath, 'utf8')) - 写入本地文件lib/boot.js:307
mkdirSync(path.dirname(settingsPath), { recursive: true }) - 写入本地文件lib/boot.js:308
copyFileSync(legacyPath, settingsPath, constants.COPYFILE_EXCL) - 读取本地文件lib/harnesses.js:319
if (!existsSync(settingsPath)) return {} - 读取本地文件lib/harnesses.js:322
value = JSON.parse(readFileSync(settingsPath, 'utf8')) - 写入本地文件lib/harnesses.js:340
renameSync(settingsPath, `${settingsPath}.corrupt-${Date.now()}`) - 写入本地文件lib/harnesses.js:347
mkdirSync(path.dirname(settingsPath), { recursive: true }) - 写入本地文件lib/harnesses.js:349
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`) - 写入本地文件lib/harnesses.js:350
renameSync(temporary, settingsPath) - 读取本地文件lib/harnesses.js:487
const stat = statSync(command) - 读取本地文件lib/harnesses.js:628
const metadata = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8')) - 读取本地文件lib/harnesses.js:641
for (const entry of readdirSync(directory, { withFileTypes: true })) { - 读取本地文件lib/harnesses.js:658
try { return readdirSync(directory, { withFileTypes: true }) } catch { return [] } - 读取本地文件lib/dsh-launch.js:31
const OWN_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version - 读取本地文件lib/dsh-launch.js:97
return JSON.parse(readFileSync(file, 'utf8')) - 读取本地文件lib/dsh-launch.js:110
if (!existsSync(manifestPath)) { - 读取本地文件lib/dsh-launch.js:118
manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 读取本地文件lib/ayu.js:12
readFileSync(new URL('./palettes/ayu.json', import.meta.url), 'utf8'), - 读取本地文件lib/catppuccin.js:13
readFileSync(new URL('./palettes/catppuccin.json', import.meta.url), 'utf8'), - 读取本地文件lib/kanagawa.js:12
readFileSync(new URL('./palettes/kanagawa.json', import.meta.url), 'utf8'), - 读取本地文件lib/everforest.js:11
readFileSync(new URL('./palettes/everforest.json', import.meta.url), 'utf8'), - 读取本地文件lib/iceberg.js:11
readFileSync(new URL('./palettes/iceberg.json', import.meta.url), 'utf8'), - 读取本地文件lib/solarized.js:11
readFileSync(new URL('./palettes/solarized.json', import.meta.url), 'utf8'), - 读取本地文件lib/one.js:11
readFileSync(new URL('./palettes/one.json', import.meta.url), 'utf8'), - 读取本地文件lib/tomorrow.js:12
readFileSync(new URL('./palettes/tomorrow.json', import.meta.url), 'utf8'), - 读取本地文件lib/harness-registry.js:50
if (statSync(cachePath).size > 16 * 1024 * 1024) throw new Error('oversized cache') - 读取本地文件lib/harness-registry.js:51
const cached = JSON.parse(readFileSync(cachePath, 'utf8')) - 读取本地文件lib/harness-registry.js:57
return preferMarttyPiAcp(normalizeAcpRegistry(JSON.parse(readFileSync(new URL('./acp-registry.snapshot.json', import.meta.url), 'utf8')), options)) - 写入本地文件lib/harness-registry.js:65
mkdirSync(path.dirname(cachePath), { recursive: true }) - 写入本地文件lib/harness-registry.js:66
writeFileSync(temporary, JSON.stringify({ source: options.registryUrl ?? ACP_REGISTRY_URL, catalog: value })) - 写入本地文件lib/harness-registry.js:67
renameSync(temporary, cachePath) - 删除本地文件lib/harness-registry.js:69
finally { try { rmSync(temporary, { force: true }) } catch { } } - 执行外部命令来自配置/变量lib/harness-registry.js:327
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true }) - 读取本地文件lib/harness-registry.js:378
return statSync(command).isFile() - 读取本地文件lib/harness-registry.js:416
if (existsSync(installDir)) { - 写入本地文件lib/harness-registry.js:422
mkdirSync(parent, { recursive: true }) - 读取本地文件lib/harness-registry.js:435
if (statSync(archivePath).size > MAX_ARCHIVE_BYTES) throw new Error('binary archive is larger than 512 MiB') - 写入本地文件lib/harness-registry.js:446
mkdirSync(extracted) - 写入本地文件lib/harness-registry.js:468
if (process.platform !== 'win32') chmodSync(extractedCommand, 0o755) - 删除本地文件递归lib/harness-registry.js:478
rmSync(temporary, { recursive: true, force: true }) - 读取本地文件lib/harness-removal.js:15
if (lstatSync(directory).isSymbolicLink()) return true - 读取本地文件lib/harness-removal.js:16
if (!lstatSync(directory).isDirectory()) return false - 读取本地文件lib/harness-removal.js:17
return readdirSync(directory).some(name => hasLink(path.join(directory, name))) - 读取本地文件lib/harness-removal.js:22
const canonicalRoot = existsSync(root) ? realpathSync(root) : root - 读取本地文件lib/harness-removal.js:34
if (lstatSync(cursor).isSymbolicLink()) return { resources: [], cleanupReason: 'A symbolic link is present; resource cleanup is disabled.' } - 读取本地文件lib/harness-removal.js:40
if (!existsSync(directory)) return { resources: [], cleanupReason: 'Private installation is already absent.' } - 写入本地文件lib/harness-removal.js:75
renameSync(resource, quarantine) - 写入本地文件lib/harness-removal.js:80
for (const { resource, quarantine } of moved.reverse()) renameSync(quarantine, resource) - 删除本地文件递归lib/harness-removal.js:84
try { rmSync(quarantine, { recursive: true }) } - 读取本地文件lib/download.js:53
lstatSync(filePath) - 删除本地文件lib/download.js:96
await rm(filePath, { force: true }) - 读取本地文件lib/download.js:145
const size = statSync(filePath).size - 读取本地文件lib/demo-skin.js:17
const { version } = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'))
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
界面预览
3 张 · 点击查看大图功能亮点
- 统一HarnessTUI
- 连接兼容ACP
- 自改进TUI插件
最近更新
在 GitHub 查看全部 →v0.3.32026-10-17
· 兼容性
· 支持的 @deepseek-ai/dsh 现在改为下限 >=0.1.7-rc.2,声明在 engines.dsh 里(dsh 的插件兼容性检查会读取它);精确版本的 dsh.supported 列表已移除。更新的 dsh 版本也会尝试;比最新测试过的版本(0.2.1-rc.2)还新的 dsh 只会给出警告。
· 内置 @openma/deepseek-harness-acp 0.4.39,它采用同样的下限,并对 dsh 0.2.1 的子代理清理 API 做特性检测。
· 新增
· martty --dsh(也可用 npx martty --dsh)在版本检查后挂到全局 dsh 的配置档;低于下限时回退到内置 harness,并给出可关闭的提示。
· 修复
· 发布的包不再声明 postinstall 脚本,所以 dsh plugin add martty 在 pnpm 严格的构建脚本策略下也能安装(ERR_PNPM_IGNORED_BUILDS)。全局安装迁移改到首次启动时执行;在那之前,全新安装只暴露 martty-cli。
· 在 dsh 0.2.1 上运行 dsh --profile martty 不再让 tui-runner 和 tui-host-creator 一直处于 pending。ACP 插件无法在 Host 上运行时,TUI 会启动并说明原因(已安装的 dsh、下限或缺少能力,以及可选方案),而不是静默留下 pending 条目。
· 变更内容
· fix(dsh):去掉安装脚本、dsh 版本下限 + TUI 内提示、martty --dsh、dsh 插件 CI(latest/next)
· docs(npm):包 README——--dsh、无安装脚本、harness 解析、dsh 下限
· chore(deps):为 0.3.3 内置 @openma/deepseek-harness-acp 0.4.39
完整变更日志:
v0.3.3-beta.12026-10-17预发布
· 变更内容
· fix(dsh):去掉安装脚本,提高 dsh 版本下限并在 TUI 内提示,martty --dsh,dsh 插件 CI(latest/next)
完整变更日志:
v0.3.22026-10-17
· 兼容性
· 打包 @openma/deepseek-harness-acp 0.4.38(兼容范围 >=0.4.38 <0.5.0)。
· 支持的 @deepseek-ai/dsh:0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2。
· Pi harness 启动 @openma/pi-acp@latest(发版时为 0.1.8)。
· 已知问题
· #168 ACP 认证:智能体认证还在等待时选另一种方式没有反应;手动输入的密钥会掉进提示词队列。
· 变更内容
· 将 @openma/deepseek-harness-acp 升到 0.4.36
· docs:采用组织贡献指南和 PR 证据报告
· acp:从智能体的 initialize _meta 协商 Send Now steer 路由
· feat(acp):按智能体能力控制 additionalDirectories
· feat(slash):原生 /fork,同名 Harness 命令改带前缀
· chore(deps):内置 Pi harness 升到 @openma/pi-acp@0.1.6
· ci:发版检查,release notes 和版本必须对齐
· 将 @openma/deepseek-harness-acp 升到 0.4.37
· fix(ui):通过 OSC 11 把终端默认背景同步到画布
· fix(ui):去掉输入框底部标签里的内联按键提示
· fix(sessions):按会话隔离模型、@openma/pi-acp@latest、共享 ~/.oma、显示上游错误和重试
· feat(sessions):关闭单个会话(×、ctrl+shift+w、/close)
· feat(tui):拖动输入框上边框调整大小,去掉最大化按钮
· fix(npm):全局安装不再与旧的 @openma/deepseek-harness-tui 可执行文件冲突
· fix(tui):创建者浮层租约 + /status 显示解析后的 dsh 运行时
· 修复 #163 引入的 Windows 配置档冒烟测试回归,由 @devin-ai-integration[bot] 提交
· fix(tui):把 pi harness 显示为 pi-acp,显示注册表仓库
· feat(auth):基于 initialize.authMethods 的 ACP 认证选择器 + ACP 登出
· build:把开发/测试调试信息压缩到行表,由 @devin-ai-integration[bot] 提交
· 集成:Martty TUI 打磨(#144–#148)
· acp:采用共享 config.toml 自定义智能体存储,由 @devin-ai-integration[bot] 提交
· chore(deps):为 0.3.2 打包 @openma/deepseek-harness-acp 0.4.38
· 新贡献者
· @devin-ai-integration[bot] 首次贡献于
完整变更日志:
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成Martty
DSH-first Agent TUI,使用与 DSH 同款的 Cordis 插件能力,也可连接其他兼容 ACP agent。
中文 · English
快速开始
用 npm install 安装时,本机需已装好 Node.js,可能还需要 pnpm。
AI 智能体 / 自动化安装步骤见 Agent 安装说明。
全局安装后启动:
npm install --global martty
martty-cli # 首次启动会创建 martty 命令,之后直接运行 martty
不想装到全局,可以直接运行:
npx --yes martty
Martty 内置 ACP 连接层,默认启动并连接 DSH。
展开完整文档(README 剩余 1 节)
Martty
DSH-first Agent TUI,使用与 DSH 同款的 Cordis 插件能力,也可连接其他兼容 ACP agent。
中文 · English
快速开始
用 npm install 安装时,本机需已装好 Node.js,可能还需要 pnpm。
AI 智能体 / 自动化安装步骤见 Agent 安装说明。
全局安装后启动:
npm install --global martty
martty-cli # 首次启动会创建 martty 命令,之后直接运行 martty
不想装到全局,可以直接运行:
npx --yes martty
Martty 内置 ACP 连接层,默认启动并连接 DSH。已有 DSH 环境时,也可以把 Martty 装进独立配置档,交给 DSH 管理插件和升级:
npm install --global @deepseek-ai/dsh
dsh plugin --profile martty add martty@latest
dsh --profile martty
已经装好这样的配置档时,也可以从 npm 入口直接连到全局 dsh:
npx martty --dsh martty # 等价于 dsh --profile martty,先做版本检查
dsh plugin add 由 dsh 通过 pnpm 安装插件。Martty 不声明任何安装脚本(preinstall/install/postinstall),因此在 pnpm 严格的 build-scripts 策略下(ERR_PNPM_IGNORED_BUILDS)也能直接安装,无需 --ignore-scripts 或批准构建。
之前用 0.3.2 及更早版本安装失败过的配置档(martty 已是依赖却不在 dsh.profile.bundles 中,重复 add 无效),先执行 dsh plugin --profile martty remove martty 再重新 add。
只想看界面,可以运行:
martty --demo
martty --demo-skin
Agent UI
Martty 在终端中呈现完整的智能体工作过程,包括流式回复、推理、工具调用、子代理、Plan、token 用量和持久化会话。图片可以从文件或剪贴板加入提示词;工具输出、Markdown、代码块和图片预览都使用终端原生交互。
回合运行时可以继续排队消息,也可以立即 steer 当前智能体。会话通过 /new、 /resume 和 --session-id 管理,workspace、模型、权限和界面选择会随会话恢复。
常用操作:
| 按键 / 命令 | 行为 |
| enter | 发送消息;composer 为空且 Queue 非空时立即发送队首 |
| ctrl+enter | 立即 steer 当前智能体(macOS 也可用 ⌘⏎) |
| alt+↑ | 选择任意 Queue 条目;↑/↓ 移动,enter 编辑,ctrl+d 删除 |
| ↓ · ←/→ · enter | 空输入时展开 Agent 导航、移动并打开;esc 折叠 |
| esc | 中断当前回合并保留草稿 |
| / | 打开命令与参数候选 |
| /model · /agent | 选择模型和 Agent Preset |
| /harness [id] | 在 TUI 内切换 Harness 并启动新的空会话 |
| /permission · shift+tab | 选择或轮换权限模式 |
| /image <path> · /clip | 添加本地图片或剪贴板图片 |
| !cmd | 在 workspace 的会话级本地 shell 中执行命令 |
| /help · /keys | 查看命令与快捷键 |
| /vim | 切换 vim 模态编辑(默认关闭;normal 模式 h/j/k/l 移动、dd 删行、i 插入) |
文本编辑(composer 输入框)
| 按键 | 行为 |
| ← / → | 移动光标(ctrl+b / ctrl+f) |
| ↑ / ↓ | 按屏幕行移动(多行草稿) |
| home / end | 行首 / 行尾(ctrl+a / ctrl+e;macOS ⌘←/→) |
| alt+← / alt+→ | 按词移动(Linux/Win ctrl+←/→) |
| ⌫ / delete | 删除前 / 后一个字符 |
| ctrl+w | 删除光标前一个词 |
| ctrl+k / ctrl+u | 删到行尾 / 行首(macOS ⌘⌫ 删到行首) |
| ctrl+shift+k | 删除整行 |
| ctrl+z / ctrl+shift+z | 撤销 / 重做(macOS ⌘z / ⌘⇧z) |
| ctrl+y | 粘贴最近删除的文本(yank) |
| shift+←/→/↑/↓ · shift+home/end | 扩展选区 |
| ctrl+shift+c / ctrl+x | 复制 / 剪切选区(键盘与鼠标拖选均适用) |
| shift+enter / ctrl+j | 草稿内换行 |
完整的编辑快捷键、鼠标操作(点击定位、拖选复制)与开发者集成说明,见 Composer 输入控件文档。
插件系统
Martty 的界面不是一组写死的开关。UI、主题、Slot、命令、Overlay 和 Session 视图都由 Cordis Plugin 组合,并随 Plugin 生命周期一起加载和卸载。这套插件系统也是 Martty 的自进化路径:Agent 可以读取当前能力,创建新的界面 Plugin,并在运行中继续观察和修改。
一个 Cordis Plugin Package 可以包含两个部分:
Cordis Plugin Package
├── code.host # 可选,运行在 Host Cordis tree └── code.client # 可选,运行在 Martty Client Cordis tree
只包含 code.host 是 Host-only,只包含 code.client 是 Client-only,两者都有就是双向 Plugin。双向 Plugin 仍是一个 Plugin run;Client half 可以通过
host.call(method, args) 调用同一 run 的 Host half。
四个 Plugin 视图
| 视图 | 显示什么 |
| /ui | UI Plugin 列表,例如 Martty 与 DeepSeek;选择后切换整套 UI 组合 |
| /theme | Theme Plugin 列表;选择后切换配色及该 Plugin 的其他能力 |
| /plugins | 当前已经加载的静态 Plugin,只读 |
| /cordis-plugins | Cordis 模式刚创建的临时 Plugin;可随 run 停止、替换或回收 |
/ui 和 /theme 是视图,不是 Plugin 类型。列表中的 Plugin 可以是 Client-only,也可以是同时带有 Host half 与 Client half 的双向 Plugin。
Theme Plugin 与明暗模式彼此独立。使用 /theme 选择 Theme Plugin,使用 /theme toggle 或 ctrl+t 切换当前 Theme Plugin 的 dark/light 变体。输入 /theme 时,上拉候选会把 toggle 与 Theme Plugin 分区显示。在 /theme 对话框与 /theme 上拉候选里移动高亮(↑/↓、翻页键、滚轮)会即时预览 高亮所在的主题包,方便逐套对比——此时只是预览,并未确定;按 Enter 才真正切换 Theme Plugin 并持久化;按 Esc 或把高亮移开则会回到已经确定的主题。
六个 Slot
带有 Client half 的 Plugin 可以通过 tuiSlots.register 向六个位置提供界面内容:
| Slot | 类型 | 位置与用途 |
| welcome.hero | single · root | 欢迎页品牌区 |
| welcome.info | single · root | 欢迎页版本、模型、workspace 与 session 信息 |
| chrome.right | list · root | 主界面右栏,适合监控面板和持续状态 |
| conversation.input.dock | list · session | 输入框上方,适合 Plan、任务和 Goal 摘要 |
| conversation.navigation.dock | list · session | composer 内部、输入区与模式行之间,适合 Agent、branch 与 session 导航 |
| conversation.composer.dock | list · session | composer 外层底部,适合 token、耗时等紧凑统计 |
节点类型、更新和卸载生命周期见 Plugin API:tuiSlots,字段定义见 tui-node.v0.schema.json。
创建 Plugin
Creator 可以检查当前 Host 与 Client 暴露的 Service、Slot 和 Schema,再生成 code.host、code.client 或双向 Package。Client-only Artifact 可以保存到 $MARTTY_HOME/plugins/<artifact-id>/plugin.json;包含 code.host 的 Package 由当前 Harness 管理。
自进化过程是一个可观察的闭环:
inspect → generate → run → observe → update / rollback → save
Creator 先读取真实 API,再运行生成的 Plugin。装载错误、Schema 错误和绘制结果都能反馈到下一次修改;满意后再保存,不满意可以更新、停止或回滚。Plugin 只能使用公开的 Service 和语义节点,不能直接操作 TTY、raw mode 或终端坐标。
从临时 Plugin 升级为常驻 Plugin
Cordis 模式里的 Plugin 默认只属于当前进程。验证完成后,可以把它升级为重启后仍会加载的常驻 Plugin:
临时 Plugin → 验证当前 Package → 保存或打包 → 启动时加载
可用 /liang on、/liang off 显式控制。缺省关闭,/liang on 召唤。
Client-only Plugin 可以直接走短路径:
cordis_define → cordis_run → tui_plugin_save → $MARTTY_HOME/plugins
Martty 会在下次启动时重新发现这个 Artifact。包含 code.host 的 Host-only 或双向 Plugin 不能写进 .oma;Creator 先用 cordis_inspect_self 读取已经验证的 Package 源码,再把它整理成普通 Cordis npm Package 或本地 Package,安装进 profile:
dsh plugin --profile martty add <package-or-path>
安装后的 Package 随 profile 启动,由 Harness 管理 Host half,并把 Client entry 交给 Martty。当前没有把任意动态 Package 一键转换为静态 Package 的 promote 命令;升级会显式经过保存或打包,避免把一次临时 run 自动写入长期配置。
安装第三方 Package:
dsh plugin --profile martty add <package-or-path>
完整的 API、生命周期与示例见 插件开发文档。
连接其他 ACP agent
Martty 的 ACP client 是内置的。默认配置连接 DSH;连接其他 ACP server 时,只需修改 agent 或 stream 配置,不需要替换 Martty 的 ACP 层。
Standalone 模式可以指定启动命令:
DSH_TUI_AGENT="<acp-command> [args...]" martty
Harness CLI 快速上手
在系统终端先浏览目录,再复制目标条目的 ID。下方 <id> 必须替换为 find 输出的 ID,不是显示名称,也不要原样输入尖括号。
martty harness find # 浏览 Registry 与本地候选 martty harness add <id> # 安装/保存配置 martty harness use <id> # 设置下次启动的默认 Harness martty # 在当前工作目录启动 TUI
add 和 use 不会切换已经运行的 TUI;需要切换当前会话时,在 TUI 中使用 /harness。
查看、刷新和移除配置:
martty harness list
martty harness find --refresh
martty harness remove <id> --cleanup --dry-run # 只预览 martty harness remove <id> # 确认后仅移除配置 martty harness remove <id> --cleanup # 同时清理独占私有安装目录
如果提示 martty: command not found,在仓库根目录执行 node npm/bin/martty.js harness --help;其他命令同样将 martty 替换成 node npm/bin/martty.js。完整步骤、临时 shell 入口、手动配置与清理边界见 Harness CLI 使用指南。
Registry 与 TUI 配置流程
三个入口共享同一份 ACP Registry:可以直接编辑 settings.json,使用上述 martty harness CLI,或在运行中的 TUI 输入 /harness 打开原生单选表单。
harness find 默认读取缓存或随包的官方目录快照,harness find --refresh 才联网刷新(https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json),并把本地 PATH 扫描结果作为补充;它不是 npm 包搜索,也不会在后台切换。Pi 条目由 Martty 使用 @openma/pi-acp@latest 配置;官方目录原始快照仍保持不变。Registry 的 npx / uvx 分发会显示可直接配置的命令,不会添加 --yes 或 --prefer-offline。Registry 的 binary 分发在选择后明确确认,并下载到 $MARTTY_HOME/bin/<id>/<version>/<platform>,校验 SHA-256 后再配置;不会写系统 PATH。TUI 中的 /harness find 提供同样的发现、配置、安装流程,/harness <id> 可直接切换。未收录但命名为 -acp / _acp 的本地程序也会被发现。
Windows 使用 %MARTTY_HOME%\bin\<id>\<version>\<platform>(windows-x86_64 或 windows-aarch64),并通过 PATHEXT 识别 npx.cmd、uvx.exe 等入口。若某条目同时提供 package 与当前平台 binary,而本机缺少 npx/uvx,Martty 会回落到受管 binary 安装;只有 package 分发时则说明应安装 Node.js/npm 或 uv,不会保存一个无法启动的 Harness。
无参 harness find 会展示完整目录,用户不需要预先知道 Codex 或任何其他 Harness 的名字;只有在已经知道目标时才把后续文字作为可选过滤条件。CLI 会在每个未配置候选下直接给出可复制的 martty harness add <id> 命令。
CLI add 只安装、保存配置,已配置项会直接复用;use <id> 只设置下次启动的默认项,不启动或认证智能体。martty harness remove <id> 在终端列出清理范围并确认,默认只移除配置;--cleanup 同时清理独占的私有 binary 目录,--dry-run 只预览,非交互调用需显式 --yes。全局程序、共享 npx/uvx 缓存、历史和凭据均保留。清理资源前请退出使用它的其他 Martty 实例。下载进度输出到 stderr,Ctrl-C 会取消并清理临时目录,不写入半完成配置。
TUI 选择会立即替换 standalone ACP 子进程;若当前会话已经发送过 prompt 或由 /session 恢复,则先确认,且原会话仍可从 /session 返回。
Harness TUI 操作
在 Martty 输入 /harness 打开切换菜单:当前项置顶并标记 (current),选择其他已配置项后按 Enter 走正常切换流程。需要新增时选择 + Add Harness…。
Harness 切换菜单,当前项置顶
Add 面板支持输入搜索,已安装或配置的项目与未下载项目分组展示,无需预先知道 ID。
Add Harness 的搜索与安装分组
在切换列表选中已保存项按 Delete 可移除配置,并可选择清理私有安装资源;当前项不能删除。删除确认页按 Esc 返回删除方式,再按 Esc 返回列表并保留选择。
登录使用 /auth,状态查看使用 /status;失败面板直接展示错误,Enter 重试。
完整操作见 Harness TUI 使用指南。
在 /harness 选择 + Add Harness,或直接输入 /harness add,即可打开可搜索的目录,不必先知道命令或 Registry ID。目录优先复用经包元数据核对的本地 npm/uv 工具;运行器存在只表示 Available via npx/uvx,不代表 ACP 已连接。缺依赖时有安装指引与重新检测,Registry 离线仍保留本地项。npx/uvx 包准备与 binary 的下载、解压、验证都在面板中进行;下载中 Enter 不关闭,Esc 隐藏面板后仍在后台继续(须保持 Martty 运行)。完成或失败会在 composer 提示,可从 /harness 重新打开进度。安装完成自动保存配置,完成面板 Enter 走 /harness 的正常切换流程,Esc 只关闭;失败后 Enter 重试。Add / Install / Connect 本身不自动切换或认证;用户在完成面板按 Enter,或从 /harness 明确选择后才切换。退出 Martty 会停止未完成的任务。
安装输出不会直接写入终端;下载和解压有超时。切换只有在实际 initialize + session/new 成功后才更新 defaultHarness;失败保留原默认值,并提供重试入口。
本地探测在后台 worker 中与 Registry 请求并行进行,面板先打开再逐步补齐结果,保留搜索文字与当前选择。Installed / configured 与 Not downloaded 之间有不可选中的分组线;“已配置”不等于包已下载。binary 下载只限制连接等待与无进展时间,不限制总时长。
源码目录可运行 node scripts/harness-discovery-scenario.mjs --check 构造六种隔离探测状态,或用 --tui 打开后输入 /harness add 手测;不使用真实模型、不下载真实包、不改用户配置。
harness list 会列出已保存项、包内置 DSH runtime,以及已配置命令和 PATH 中名称以 -acp / _acp 结尾的可执行文件;当前项以 * 标记。add / use 写入 $MARTTY_HOME/settings.json 的 harnesses 与 defaultHarness,并保留同文件中的主题、语言和 UI Plugin 设置。选择在下一次 standalone 启动生效,启动时依次通过 ACP initialize 与 session/new 连接选中的 Harness,因此欢迎页在首条消息前就能显示 Agent 上报的模型与 effort。这只绑定空会话,不会启动模型 turn。不会把旧 Harness 的会话带到新 Harness;dsh --profile martty 的 Host runtime 仍由该 profile 所有。
dsh runtime 选择与兼容范围
Harness 按以下顺序解析:
1. 默认(martty / npx martty):使用内置的 @openma/deepseek-harness-acp 及其 bundled dsh runtime。隐式发现(PATH、当前目录、全局 npm)永远不会覆盖它。
2. --dsh <profile>:连接 PATH 上的全局 dsh 及其 ~/.dsh/profiles/<profile>。profile 中装有 martty 时,终端交给 dsh --profile <profile>;只装有 @openma/deepseek-harness-acp 时,dsh --profile <profile> 作为本 Client 的 ACP agent。启动前会检查全局 dsh 的版本;低于下限时自动回退到第 1 种,并在界面中显示一条可用 /dismiss 关闭的简短提示说明原因。找不到 dsh 或 profile 不存在时直接报错并给出命令。
3. 作为 dsh 插件启动(dsh --profile martty):使用启动它的 host dsh。
4. Harness 配置显式指定 dsh 路径(--dsh-path / DSH_PATH)时使用该路径。
支持的 dsh 版本:>=0.1.7-rc.2——这是下限,不是固定版本列表,声明在 npm/package.json 的 engines.dsh(dsh 自身的插件兼容性检查读取这个字段),与 @openma/deepseek-harness-acp 的下限一致。更新的 dsh 一律尝试运行:两者之间的 API 差异按能力检测处理;新于已测试的最高版本(当前 0.2.1-rc.2)时只打印警告并继续。
只有低于下限、或 host 缺少必需能力时才不会挂载 ACP 插件——此时 dsh --profile martty 仍会启动界面,并在会话中说明已安装的 dsh 版本、要求的下限或缺失的能力,以及可选做法(改用 npx martty 内置模式,或安装受支持的 dsh),不会留下静默挂起的条目。
CI 在 dsh 的 latest 与 next dist-tag 上以全局安装验证插件安装路径与 --dsh。
/status 会显示 ACP agent 实际启动的 dsh runtime(- dsh · <版本> via <来源>)。
本版本内置 @openma/deepseek-harness-acp 0.4.39。
运行中的 standalone TUI 也可以直接添加并切换(Registry binary 会先确认安装):
/harness add local --label "Local ACP" --command local-acp --arg --stdio
这会保存 Harness、设为 defaultHarness,并立即切到新的空会话;已有会话时先确认。
Standalone 启动优先级是显式 --agent、DSH_TUI_AGENT、产品内部初始化配置 forcedHarness(默认为空)、持久化的 defaultHarness、包内置回退值。
forcedHarness 不是 CLI/用户启动参数;它由产品宿主在需要时注入,存在时每次启动优先于用户默认。--agent 仍是不改保存选择的一次性覆盖。
Cordis 嵌入场景可以使用 config.agent: { command, args } 启动 ACP server,或使用 config.stream 接入调用方已有的标准管道。单次运行也可以使用 --agent 与重复的 --agent-arg 覆盖启动命令。
只实现标准 ACP 的 agent 可以正常使用会话、认证、prompt、permission、配置与 session/update。支持 DSH Cordis 扩展的 agent 还可以提供动态 Plugin、Client 能力发现和 Package RPC。
配置示例见 Agent 接入。
运行架构
dsh --profile martty 启动两个进程。DSH Host 进程运行 Base Cordis tree、ACP Plugin 和 Agent;独立的 Martty Client 进程运行 UI、Theme、Slot、Overlay 与 Session Service。
两边通过 ACP 通信,不共享 Plugin Fiber 或 Service 对象。
Rust/ratatui painter 由 Martty Client 驱动并独占用户 TTY。ACP 使用 Client 进程的 stdin/stdout;用户 TTY 使用 fd 3/4,两条通道互不混用。
架构细节与 Mermaid 源文件:
| 文档 | 内容 |
| 运行架构 | Host、Client、ACP 与 painter 的边界 |
| Composer 输入控件 | 文本输入使用说明:快捷键、鼠标、撤销/重做与开发者集成 |
| Agent 安装说明 | 面向 AI agent / 自动化:可验证的安装步骤与排查 |
| Plugin API | Service、Slot、Overlay、生命周期与 Package RPC |
| 运行时 Mermaid | DSH-first 双进程数据流 |
| Plugin Mermaid | Host half、Client half 与 Plugin 视图 |
| ACP 连接 Mermaid | 默认 DSH 与其他 ACP server 的连接方式 |
| 迁移计划 | 已完成能力与后续阶段 |
English documentation
See the English README for installation, Harness setup,
session modes and Shift+Tab, and
building and testing from source.
从源码构建
需要 Rust stable 和 Node.js 22.19+:
make rust-test
node --test scripts/package-native.test.mjs
bash scripts/build-npm.sh
开发环境使用:
make tui-test
GitHub Actions 会构建 macOS arm64/x64、Linux arm64/x64 和 Windows x64 原生二进制,分别发布为 @openma/martty-<platform>-<arch> 包。安装 martty 时 npm 仅安装当前平台的可选依赖;若使用了 --omit=optional,需重新安装并启用可选依赖。
项目结构
| 路径 | 内容 |
| src/ | Rust TUI、输入、绘制、ACP 状态与会话生命周期 |
| npm/ | Cordis Client、内置 ACP、Plugin Runner、CLI 与原生二进制入口 |
| scripts/ | 构建、打包、协议校验与资源生成 |
| assets/ | Logo、截图与界面资产 |
| docs/ | 架构、Plugin API、Schema、迁移与 Agent 接入文档 |
从旧包名迁移
旧的 DeepSeek Harness TUI 包名为 @openma/deepseek-harness-tui,旧 profile 通常叫 tui。
如果是全局 npm 安装,直接安装 martty 即可——无论旧包处于什么状态(0.2.18 时代的 dsh-tui/dsb bin、0.2.14/0.2.20 占用的 martty bin,还是 npm link 的检出目录),都不需要先删除:
npm install --global martty
发布出去的 martty 包只声明 martty-cli——这个名字不会被任何其他包占用,npm 因此永远不可能因 bin 冲突拒绝安装。包不带任何安装脚本:迁移在 首次启动时完成——运行一次 martty-cli(或 npx martty)会清理确实属于旧包的 martty/dsh-tui/dsb 链接(第三方 dsh-tui 保持原样),自行创建 martty 命令,并摘除旧包清单里的 bin 声明,使之后执行 npm rm -g @openma/deepseek-harness-tui 不会误删新的 martty。删除旧包只是可选的收尾;旧包还在时 martty 每次启动都会提示——交互终端中提示会延迟到进程退出时打印,避免被界面覆盖。本仓库的源码包名本身就是 martty,因此 npm link 的检出目录与正式包对 npm 来说是同一个包,两个方向都能干净替换、不会冲突。
注意事项:
- 全新安装后、首次启动前只有 martty-cli;从 0.3.1 升级的用户(当时 martty 由 npm 声明)同样需要运行一次 martty-cli。从 0.3.2 升级时已有的 martty 保持可用。
当某个不相干工具占用了 martty 时,martty-cli 也是本包自己的命令名。
- npm rm -g martty 只会移除 npm 声明的 martty-cli 链接;留下的 martty 是一个自清理的 shim 而不是死链接——下次运行它时会提示重装命令并自行删除,不留残留。
在 dsh 配置档内迁移:
dsh plugin --profile tui remove @openma/deepseek-harness-tui
dsh plugin --profile martty add martty@latest
旧配置和会话数据会在首次启动时复制到 Martty 路径,不会删除原数据。
martty 包不再提供 dsh-tui 命令(该名字同时与旧包和第三方 dsh-tui 包冲突);需要的话可用 alias dsh-tui=martty 恢复。
License
MIT。Martty 与 DeepSeek、xAI 无关联。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
openma-ai/Martty
统一 Harness TUI(ACP 客户端)。DeepSeek Harness 的自改进 TUI 插件。这里的一切也都是插件。
相关插件 · UI / 主题
破甲增强版JeffKeeping/dsh-purge-plus
dsh-purge 的非官方分支fork:适配 DeepSeek Harness 0.2.1-rc.2、破甲内核…
★ 387已汉化DeepSeek 客户端
71.9
无代码业务系统平台nocobase/nocobase
NocoBase 是什么
★ 24,804已汉化DSH
71.8
1Panel 服务器面板1Panel-dev/1Panel
全球 250 万+ 自托管用户共同喜爱。
★ 37,344已汉化DSH
71.5
终端编码代理esengine/DeepSeek-Reasonix
· 指南 · ACP · 扩展开发 · 规格 · 官方网站 · Discord · QQ
★ 36,045已汉化DSH
71.2
工作状态行动态ccch1mneyyy/working-activity
作者:chimney@ccch1mneyyy。社区项目,非 DeepSeek 官方项目。
★ 664已汉化DSH
71.2
多模型配置管家farion1231/cc-switch
跨平台桌面一体化助手,支持 Claude Code、Codex、OpenCode、DeepSeek Harness 等工具,唯一官网 ccswitch.io
★ 151,202已汉化DSH
71.1
这是你的仓库?认领条目