给 AI 用的持久终端:tmux 常驻会话 + DSH 面板 + 审计链 + 引用库
层级:用户指南 适用:0.1.x(具体变化见 CHANGELOG) 权威性:使用流程的唯一来源;参数细节见《工具与参数》
从安装到日常操作:面板、工具、引用库、宏、工作区、审计、键位与已知限制。
参数的权威定义见 工具与参数。 本文只讲「怎么用、要注意什么」,不复述实现。
一个 DSH 插件:私有 tmux 服务端持有若干持久 shell,关页面 / 换对话 / 热重载 / 重启 DSH 都不丢; 另有 7 个模型工具、一个真终端面板、一条可检测篡改的审计链、一个只能由人写入的引用库(vault)。
npx -y terminalkeeper install [--profile web] # 登记进 profile、装依赖、检查 tmux
npx -y terminalkeeper doctor # 体检
npx -y terminalkeeper uninstall # 反向移除(数据保留)
dsh web;之后如果只是插件页面/客户端更新,刷新页面或等热重载即可。node purge-all.mjs(默认 dry-run)→ 确认后加 --yes;或 npx -y terminalkeeper purge-data --dry-run/--yes。terminalkeeper backup / restore(默认落在 $HOME,不会写进插件安装目录)。两种入口:设置页(面板 ⚙,7 个分区)与 cordis.patch.yml 的 config。设置页写入的覆盖值优先。
分区:功能开关 / 终端与会话 / 界面与外观 / 面板与显示 / 审计与留痕 / 安全 / 服务端与集成。
常用项与默认值:
| 键 | 默认 | 说明 |
|---|---|---|
shell / shellArgs |
bash / [] |
新会话用的 shell 与参数(写进 tmux 服务端配置,改了要重启) |
cols / rows |
120 / 32 | 新会话尺寸(范围 20–1000 × 5–500) |
historyLimit |
100000 | 每会话保留的历史行数(改了要重启) |
maxSessions |
8 | 全局存活上限(1–64);超出后新建整批拒绝 |
idleClose / idleCloseMinutes |
false / 60 |
空闲自动回收总开关与兜底分钟数 |
guardDangerousCommands |
true |
危险命令守卫(启发式,不是沙箱) |
redactSecrets |
true |
输出打码 |
audit / auditRetentionDays |
true / 30 |
审计链与保留期 |
captureOutput |
true |
终端输出留痕(默认上限 64 MiB/会话) |
replayCapture |
true |
屏幕帧回放录制(占磁盘,可关) |
panelPollMs |
800 | 面板轮询节奏(有 SSE 推送时只作兜底) |
allowedHosts |
[] |
面板 HTTP 允许的额外 Host(默认只认回环) |
清理执行二次确认:设置页的「立即执行」第一次点击只进入确认态,第二次才真正按策略删除/轮转文件;API 上对应 action:'apply', confirm:true。
安全项二次确认:guardDangerousCommands / audit / redactSecrets / auditDir / allowedHosts / shell / shellArgs 等影响安全边界的项,值变化时第一次保存会被拒绝(409 needConfirm),再保存一次即确认;auditDir 必须是绝对路径且不能含 ..。
立即生效 vs 重启:改尺寸、空闲、审计、开关、面板外观等下次调用/渲染即生效;
shell / shellArgs / defaultTerminal / historyLimit / extendedKeys 写在 tmux 服务端配置里,必须重启 dsh 才重建服务端。
右下角胶囊 >_ N 🔒:N 是会话数,🔒 表示当前锁着(点一下解锁即可直接打字)。
sudo、vim、REPL、补全、历史全通);抓屏 SSE 推送,断线自动退回轮询;
面板内搜索 Ctrl+F(Aa 区分大小写、.* 正则、◐ 按文字/背景颜色 + 目标色与容差);Ctrl+Shift+↑/↓ 切换会话;F2 改名;Esc 关浮层(没有浮层时按键照常送给终端)。
完整键位见 键位。REFUSED(不会等待);只读查询不受影响。插件不提供对话授权页面或按对话规则。是否能用写工具由 DSH 的沙箱模式决定:完全控制(danger-full-access)下直接可用;工作区/只读模式下改动类工具会被模式闸门拒绝。只读工具(shell_state / shell_read / shell_audit)始终可用。
所有工具共用 { tasks: [{ target, action, wait, output, safety }] } 结构:
target.sessions:mine(本对话创建的全部 shell)/ *(全部存活)/ 具体 id(逗号分隔)。action.kind:一个任务只做一件事;同一个 shell 在一次调用里只能出现一次(重复直接整次拒绝)。| 工具 | 用途 |
|---|---|
shell_open |
开持久 shell(label/cwd/cols/rows/idleMinutes/watch/from) |
shell_run |
跑命令:run(配 command)/ steps(短交互序列)/ retry |
shell_send |
像人一样输入:preKeys → text → keys(text 不自动回车) |
shell_read |
读屏幕/历史/增量;subscribe / unsubscribe 订阅新输出 |
shell_manage |
生命周期与资源:rename/resize/respawn/close/reap/idle/snapshot(-list)/doctor/宏/工作区/vault-list |
shell_state |
看会话状态与归属(只读,扁平过滤参数) |
shell_audit |
读审计链(按 actor/source/days 过滤,可 export) |
长任务的推荐流程(异步启动 → 期间读增量 → 事后取退出码):
// 1) 开
{ "tasks": [{ "action": { "kind": "open", "label": "build", "cwd": "/repo" } }] }
// 2) 异步启动(立即返回,返回里带 run=<id>)
{ "tasks": [{ "target": { "sessions": "dsh-ab12cd" },
"action": { "kind": "run", "command": "make -j8" },
"wait": { "kind": "none" } }] }
// 3) 期间读增量(不要读整屏)
{ "tasks": [{ "target": { "sessions": "dsh-ab12cd" },
"action": { "kind": "read", "mode": "since" } }] }
// 4) 结束后取退出码(runId 绑定到第 2 步那次运行)
{ "tasks": [{ "target": { "sessions": "dsh-ab12cd" },
"action": { "kind": "read" },
"wait": { "kind": "exit", "runId": "ab12cd34", "timeoutMs": 180000 } }] }
// 5) 关
{ "tasks": [{ "target": { "sessions": "dsh-ab12cd" }, "action": { "kind": "close" } }] }
三个必须知道的坑:
exit —— 它结束的是 shell 本身,退出码标记就永远不会出现;需要提前返回用子 shell ( … )。/ 的命令不会被包装(拿不到退出码标记):要退出码用 wait.kind="idle",或包一层 sh -c "…"。只有人能写:面板「🔑 引用库」或 CLI;AI 没有写入入口,默认只看到键名。
先记住边界:引用库只防止敏感值直接进入模型上下文,不防拿到 shell 的 AI 主动读取。它适合降低“顺手把密码贴进对话”的风险,不是秘密隔离。
scrypt 派生密钥,再加设备 pepper(32 字节随机值,存在 vault.pepper):只拿到 vault.json、没有 pepper 时无法直接用密文离线猜口令。
pepper 和 vault.json 在同一数据目录,同权限 AI 仍可读;备份时必须把 vault.pepper 和 vault.json 一起保存,丢失 pepper 会让口令组永久无法解密。
口令模式的上限:未解锁时磁盘上只有密文,插件不会自动解密;用户解锁后,宿主内存里会有 KEK,本机同源接口和同权限进程不再被这层保护挡住。
锁定组:不列键名、不能引用、不能迁移或删除;先在组那一行点「解密」。secret 等)/ 剩余可用次数 / 有效期至 / 备注。
剩余可用次数是输入框(不再是复选框):填 N = 还能再用 N 次;1 = 一次性(用掉即焚);留空 = 不限。
reuseName:true)。(典型是 `shell_run` 的 `steps[].send`,
把值直接喂给程序的标准输入提示,如 `read -s pw` / `sudo -S`)。
`shell_manage action=vault-list` 只回**键名 + 额度/已用次数**,永远不含值。
面板的 `GET /vault-list` 也不返回值;要取某个值必须点该行「复制值」(单键 `POST /vault-get`,每次读取都记一条 `read-value` 审计)。
**AI 的 shell 若访问本机接口,同样可以按单键读取**;同源脚本也能逐个取。这是“防直接进上下文”,不是“防主动读取”。
**把 嵌进命令**(curl -H "…"、echo、管道)会被直接拒绝:值一旦进 AI 可加工的管线,
任何变换输出(base64/rev/od)都能绕过打码(转换外泄,实测)。需要的话由人在面板「引用库」旁亲自输入。
宏由人写时可嵌 ``(宏体全量入链);AI 写的宏展开时同样按裸引用规则校验。base64/rev/od 等变形成非字面值),
output/ 留痕是原始字节流,值进过屏幕就会进留痕;同权限进程能直接读 vault.json / vault.pepper(0600 只挡别的用户)。
要真正保密,唯一办法是别把重要凭据交给 AI 可达的 shell。terminalkeeper vault list|set|rm(写入的是同一份 vault.json)。shell_manage action=macro-set(name / text / scope=global|conversation|shell / submit)。shell_run 的命令里写 `` —— 直接把宏名当命令敲不会执行。submit:true 表示展开后自动回车;宏里可以嵌 ``(但含引用的宏展开后同样拿不到退出码标记)。cat)仍会让值出现在屏幕上;打码只覆盖“引用注入产生的行”,程序自打的明文按已泄露对待。workspace-save name 存一组会话布局;workspace-list 查看;workspace-restore name 会新开 shell(不是原地恢复);workspace-delete name 删除。snapshot 手动存(按会话 id);snapshot-list 列出已有快照;shell_open { from: "<旧会话 id>" } 复活。
close / 空闲回收 / resize / rename 也会自动留快照(reason 字段注明),但只恢复屏面与历史,不恢复进程。<数据目录>/audit/audit-YYYY-MM-DD.jsonl,逐条封链(SHA-256 串接)。output/*.log(原始字节流,容量可配),close 时把 sha256+bytes 写进审计。shell_audit 工具 / terminalkeeper verify-audit <目录> / purge-audit(删除会入链见证)。Z);shell_audit 的 output.lines 是返回记录条数(默认 50、最大 500,取最后 N 条),更早记录仍在链上,可调大 lines 或用 export:true 导出。路由挂在 /plugins/shell/* 下,没有独立鉴权,防线是 DSH 的浏览器会话 + 本地围栏(Host 必须回环、拒绝跨站、写请求必须 Content-Type: application/json)。
被拒的请求会记进面板「闸门拒绝」。
路由一览(按用途):
/list /new /kill /rename /resize /respawn /screen /stream /keys /busy /lock/settings /status /diagnose /probe /notice /debugctl/audit /audit-files /audit-size /chain /supervisor /panel-retention/replay /replays/macro-list /macro-set /macro-rm/vault-list /vault-set /vault-rm /vault-groups /vault-group-set /vault-mode /vault-move /vault-lock /vault-unlock /vault-devkey /vault-history| 情况 | 要做的事 |
|---|---|
| 面板页面、按钮之类的更新 | 刷新页面,或等面板自动热重载 |
| 插件后台逻辑更新 | 通常会自动热重载;没生效再重启 dsh web |
| 升级插件版本 | 装完后重启一次 dsh web 最稳 |
| 改了启动方式、systemd scope 等服务配置 | 重启 dsh web |
改了 shell/shellArgs/historyLimit/extendedKeys |
重启(要重建 tmux 服务端) |
| 面板提示「DSH 的代码比面板旧 / 面板比 DSH 旧」 | 重启 dsh web |
vault.json;口令组未解锁时见到的是密文,解锁后不再受这层保护;值进过屏幕就会进留痕。mkfs、dd 写块设备、curl|sh、tmux kill-server、npm publish、git push --force 等,rm -rf /tmp/x 这类普通清理不拦。top/vim 这类全屏 TUI 订阅只会得到重绘噪音(用 read 读当前屏)。0.0.0.0 时本地围栏无法判断是否本机(DNS rebinding 面),面板会明确标出降级。当前为固定绑定;自定义键位在规划中。焦点不在面板时,按键照常交给终端。
面板快捷键:
| 动作 | 默认键 |
|---|---|
| 新建 shell | Ctrl+Shift+T |
| 关闭当前 shell | Ctrl+Shift+W |
| 上一个 / 下一个 shell | Ctrl+Shift+↑ / ↓ |
| 跳到第 N 个 shell | Ctrl+Shift+1…9 |
| 会话选择器 | Ctrl+Shift+P |
| 详情 | Ctrl+Shift+D |
| 引用库 | Ctrl+Shift+K |
| 日志管理 | Ctrl+Shift+L |
| 锁定 / 解锁 | Ctrl+Shift+M |
| 重命名当前 shell | F2 |
| 面板内搜索 | Ctrl+F |
| 关闭浮层 | Esc |
终端键位与 tmux 直通(^b 前缀等)不改动;完整三组键位表在面板「详情 → 键位帮助」。