🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

终端智能体界面 openma-ai/Martty

DeepSeek Harnessv0.3.3
openma-ai3 天前有提交已提供中文文档扫描:含敏感能力

统一 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 martty
npm 源 · martty 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · ui
装得上吗
静态安装检查通过
未做真实安装
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成

本站实装验证(真实安装 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
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件
查看扫描证据(共 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插件
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 无关联。
预览分享卡片长什么样 ↓openma-ai/Martty
分享 · DeepSeek Harness Hub
openma-ai/Martty

统一 Harness TUI(ACP 客户端)。DeepSeek Harness 的自改进 TUI 插件。这里的一切也都是插件。

这是你的仓库?认领条目

回到顶部