使い方
- インストールコマンドをコピーし、自分の環境へ導入する前にソースを確認します。
- 必要なツール、認証情報、対応環境を原文で確認します。
- 入力と成果物を指定し、出力を資料と照合します。
npx skills add https://github.com/oahmoza/agentmsg --skill tern開始前の確認
- 導入にはモデル契約、API枠、ファイルへのアクセス許可は含まれません。
- スター数や導入数は活動の指標であり、独立した品質・安全性試験ではありません。
見出し、例、コード、表、リンク、参照画像を含む原文を表示しています。
Tern 编排(tern CLI)
Tern 是多路复用终端:session → tab → block 三层,block 即 pane。tern CLI 通过 socket 与 daemon 通信,不需要在 Tern pane 里运行。
开始前
tern ls --json— 列出 sessions/tabs/blocks。失败 = 没有窗口在跑,停下告知用户。- block id 只从 JSON 输出里读(大整数,如
25769803867)。标题、程序名也能当 selector 但会撞名,脚本一律用 id。 tern whoami在 pane 里返回身份链,在 pane 外返回本机身份(如OahMoa@OAHMOA (local))——它不报错,别拿它当"是否在 Tern 里"的判据,判据是tern ls --json。在 pane 外运行时,命令作用于「第一个窗口」——控制前必须用tern ls --json确认那确实是用户想让你操作的窗口,有多个窗口时先问。
开一个干活 pane
tern split --json <block-id> right --keep-open -- claude # 返回 {"session":…,"tab":…,"block":…},取 .block
tern focus <原block-id> # split 会把焦点给新 pane,开完立刻还给用户--keep-open:程序退出后 block 不自动关。agent 块必加。--cwd DIR指定目录;默认沿用调用方的。不建新 session/tab,除非用户要求。- 方向:宽 pane 用
right,窄/高 pane 用down,避免连切出窄列。 - 普通命令不需要 agent 壳:
tern run <id> "just test"(原子发送命令+回车)。
驱动一个 coding agent
Tern 没有 agent 生命周期状态机,你就是状态机,固定三步节奏:
tern send <id> paste "Review the diff, report only actionable findings." # paste 只粘贴不回车,多行安全
tern send <id> keys enter # 第二步:回车提交
tern wait <id> --until idle=2000 --timeout 600 || true # 静默 2s 判稳定;退出码 124=超时
tern capture <id> # 必查!据此判定状态capture 后判定:
- 出现审批/提问框("Do you want"、
❯选项行、"allow"、"approve"、行首?等):不要发任何按键,把界面内容报给用户决定。 - 出现完整回答且回到空闲提示符:完成,读结果交付。
- 仍在转圈/输出半截:回到
wait继续等,直到--timeout。
规矩:
- 每次 send 之前都先做安全判定——claude 类有审批框,用 capture 查词表;omp 无审批框,用
tern wait --until idle静默门替代(见 omp 投递限制)。把文本打进审批框就是事故,Tern 无自动拦截,这条纪律就是唯一的拦截。 wait --until idle=MS是输出静默判定,不是 agent 回合语义;MS 不低于 1500,宁可多等。- 超时 ≠ 没送达。先 capture 再决定,别盲发第二遍。
- 需要按键(esc、ctrl+c)用
tern send <id> keys esc,逻辑键名。 - 给 agent 起名没有专门机制,在对话里自己记录
名字 → block id的映射并沿用。
读输出
tern capture <id> # 当前视口
tern capture <id> --scrollback # 带回滚——长回答用这个
tern capture <id> --ansi # 颜色/样式本身是证据时
tern process <id> --json # 查这个块里跑的进程注意:capture 空 ≠ pane 死了。根因已定案(2026-10-16 交叉实验):tern 0.8.3 的 capture 丢 omp 的 TUI 帧,0.8.4 已修——shown/hidden、交互/--resume 四种组合在 0.8.4 下全部可捕获;不可见的只有旧 daemon 升级前遗留 session 里的实例。遇到空 capture:判活用 tern process <id> --json 的 foreground,等回合用 tern wait --until idle(走输出流,与渲染无关),结果回收走信箱;先 tern --version 排除旧版本。备用屏本身(1049/同步更新)capture 是能看到的。
omp 渲染形态是第二个可见性开关(2026-10-21,0.9.5 实测):原生交互模式下帧不进文本模型(复现 0 字节、信箱收发正常),TUI 形态全可见——「0.8.4 已修」必要非充分。空屏排查:tern --version → 确认 omp 渲染形态 → tern process 判活 → tern wait --until idle 判完成 → 信箱回收。
capture 使用纪律:读屏 = 排障工具,不是监控——整帧文本(含 omp 推理全程)灌进编排方上下文。内容验证走信箱、完成判定走 idle 门、判活走 tern process;capture 只在超时/疑卡/要看现场时用一眼;监控用 tern events。机制与制度(tern/herdr 通用)见 docs/observation-discipline.md。
监控
tern events 持续输出 JSON 行直到中断。后台任务多时用它订阅,别轮询 capture。
agentmsg 接入(把新 pane 注册进信箱)
split 拿到 block id 后,编排方顺手一行注册(别让 agent 自探测——Windows pid 链不可靠):
tern split --json <宿主id> right --keep-open --cwd <项目目录> -- claude --permission-mode bypassPermissions
tern run <新block-id> "python agentmsg.py register <名字> --kind claude --block <新block-id>" # 未提交就补一次 keys enter- claude 建议 `--permission-mode bypassPermissions`(实测全协议闭环:read→reply→done;默认 auto mode 的分类器不可用会卡权限门)。
- 安检词表已适配:
bypass permissions on是模式指示不是审批框,不会误跳过。 - 信箱按项目隔离(
<cwd>/.agent-mail/mail.db):对别的项目里的 agent 操作 agentmsg 必须--cwd到那个项目(或--db指过去)。期待回信前先在该项目信箱注册自己的发件人名:agentmsg register <名字> --kind agent——reply的收件人必须已注册,否则对方回信被拒(agent 得自己补 register 发件人才投得出去)。 - 已存在的活 pane 也能接入,不必 split 新开:
agentmsg register <名字> --kind omp --block <现存id>(注册仍由编排方做,别让 agent 自探测)。 reply正文是位置参数:agentmsg reply <id> "正文",没有--body;长产物用--file。
任务正文里永远不要写死 msg id(发送时 id 还没定);agent 按通知里的 id 自己取信(python agentmsg.py read <通知里的id>)。通知只包含消息 ID 和固定取信指引,不注入原消息正文。
结果回收走信箱,不走 capture(即使 TUI 形态下 capture 可见——信箱是几行结构化记录,capture 是整帧推理文本,进的是编排方上下文):任务里要求它 agentmsg reply <id> --file 产物路径,完成后 agentmsg wait/inbox 收取;capture 降级为按需排障工具(见「读输出」的 capture 使用纪律)。
omp 投递限制(实测,勿踩)
- 安检对 omp 靠静默门,不靠词表:omp 无审批框,没有标准审批文案可依赖;原生交互模式下帧干脆不可见、词表彻底空转,TUI 形态下帧里是推理文本同样无从判断——防注入一律以
tern wait --until idle静默门为准,词表只作 claude 类的辅助。 - watch 对 omp 可能反复 skip(idle 门未过就保持 pending):这是设计行为,不是故障。要么等它空闲后重跑
watch --once,要么确认空闲后由编排方直接tern send <id> paste …+keys enter手投(omp 无审批框,直投风险低;仍须先wait --until idle确认不在干活)。 - 别用 agentmsg 的 watch 投递给 omp 后看到 skip 就反复重跑——先
tern wait --until idle再投,或改 cron/常驻 watch 让它自己等。
收尾
- 只
tern close <你开的id>;从不动用户的 block(close 关不掉或需要强杀才tern kill,且仅限自己开的)。 - agent 确认完成后关掉自己开的块;用户可能想看现场,就留着并说明。
- 不跑
tern remote/tern register/账号相关命令,除非用户明确要求。
已知边界
无 blocked/done 状态、无 kind 识别、无 agent 命名注册、无 --skill 自取入口——这些场景靠上面的「capture 判定约定」与人的判断兜底;凡涉及审批框等不确定界面,一律上报用户,不代按。
黄金集(实测可用序列,直接抄)
2026-10-16 在 tern 0.8.4 + omp/claude 上逐条跑通。信箱命令一律先 --cwd 到目标项目(信箱按项目隔离)。G1 给另一个 omp 发消息并收回执(全链路)
tern ls --json # 找目标 block id(只认 JSON 里的 id)
tern process <id> --json # foreground=omp.exe → 活着
tern capture <id> # 安检:idle ❯ 可投;审批框 → 上报用户
tern wait <id> --until idle=3000 --timeout 120 || true
python agentmsg.py agents # 确认对方已注册;顺带看自己的发件人名在不在
python agentmsg.py register <我的名字> --kind agent # 不在就注册,否则对方 reply 被拒
python agentmsg.py send <我的名字> <对方agent名> "任务正文,勿写死 msg id"
tern send <id> paste "有新消息:agentmsg read <通知里的id>,完成后 agentmsg reply <id> \"回复正文\""
tern send <id> keys enter
tern wait <id> --until idle=3000 --timeout 600 || true
python agentmsg.py read <回信id> # 主路径:信箱收回执(结构化、省 token)
tern capture <id> --scrollback # 仅怀疑卡住/要肉眼看现场时才看G2 接入一个现存的活 omp(不 split)
tern process <id> --json # foreground 必须是目标 agent 程序
python agentmsg.py register <名字> --kind omp --block <id> # 注册由编排方做,别让 agent 自探测G3 split 新 claude pane + 注册
tern split --json <宿主id> right --keep-open --cwd <项目> -- claude --permission-mode bypassPermissions
tern run <新id> "python agentmsg.py register <名字> --kind claude --block <新id>" # 未提交就补 keys enter
tern focus <宿主id> # 焦点还给用户G4 capture 为空的排查序列
tern --version # 0.8.3 丢 omp 帧(0.8.4 已修);旧 daemon 遗留 session 仍会空
# 2026-10-21:omp 渲染形态是第二开关——原生交互模式(非 TUI)在 0.9.5 同样 0 字节,先确认 omp 是 TUI 形态
tern process <id> --json # 1) 活没活:foreground
tern wait <id> --until idle=3000 # 2) 干完没:输出流判定,与渲染无关
tern capture <id> --scrollback --ansi # 3) 换变体再试
# 仍空 → 走 G1 的信箱回收路径,别盲发第二遍G5 验证信箱链路是否通(最小回环)
python agentmsg.py register <我的名字> --kind agent
python agentmsg.py send <我的名字> <对方名> "请回复一条确认消息(agentmsg reply)证明信箱链路可用。"
tern send <对方block> paste "有新消息:agentmsg read <通知里的id>,读完后用同脚本 agentmsg reply <id> \"已收到,确认。\""
tern send <对方block> keys enter
tern wait <对方block> --until idle=3000 --timeout 120 || true
python agentmsg.py inbox <我的名字> # 看到 reply → 链路通G6 新项目冷启动
python agentmsg.py init # 建 <cwd>/.agent-mail/mail.db;.agent-mail/ 记得加 .gitignore
python agentmsg.py register <我的名字> --kind agent
# 接下来:G2/G3 接入 agent,G1 派活G7 收尾清理
# 只关自己开的块(对话里记 名字→block id 映射)
tern close <id> # 关不掉才 tern kill <id>,且仅限自己开的
tern ls --json # 确认块消失;焦点被抢就 tern focus <原block>G8 长任务等待与防串信
tern wait <id> --until idle=3000 --timeout 600 || true # 超时≠没干完,先 capture 再决定
python agentmsg.py wait <我的名字> --after-id <我发的msg id> --thread <thread-id> --timeout 600
# ⚠ 实测:不带 --after-id 会立刻返回线程里的旧信;--thread 只等指定线程,防跨任务串信G9 多 agent 并行 fan-out(双发双收)
# 先按 G2/G3 接入两个 agent(实测 omp + bypassPermissions 的 claude);就绪确认:claude 用 capture 查审批框,omp 用 idle 门
python agentmsg.py send <我> <agentA> "任务A…" # 不带 --thread → 各自新线程,天然隔离
python agentmsg.py send <我> <agentB> "任务B…"
tern send <blockA> paste "有新消息:<脚本全路径> read <idA>,完成后 agentmsg reply <idA> \"…\""
tern send <blockA> keys enter
tern send <blockB> paste "有新消息:<脚本全路径> read <idB>,完成后 agentmsg reply <idB> \"…\""
tern send <blockB> keys enter
python agentmsg.py wait <我> --expect 2 --after-id <最后发出的msg id> --timeout 300 # 双回信齐了才返回G10 task 派单三件套(task new → 通知 → done)
# ⚠ 实测:task new --assign 只插行不通知,必须配一条信箱消息把 agent 叫醒
python agentmsg.py task new "任务描述" --assign <agent>
python agentmsg.py send <我> <agent> "分派 task <id>:task list 查看;完成后 task set <id> done,并 reply 本消息结果"
tern send <block> paste "有新消息:<脚本全路径> read <msgid>,按里面说的做。"
tern send <block> keys enter
python agentmsg.py wait <我> --after-id <msgid> --timeout 300
python agentmsg.py task list # state=done + 产物文件在,才算闭环
