Skip to content

Releases: sdyuyouth/agenthop

v0.6.1: every MCP tool declares its hints / 每个 MCP 工具都声明了 hints

Choose a tag to compare

@sdyuyouth sdyuyouth released this 30 Sep 01:20
0e3e733

Every MCP tool now declares all four hints — readOnlyHint, destructiveHint, idempotentHint, openWorldHint — as booleans. Some directories reject a tool that is missing any of them, and clients that ask before acting can now tell a lookup from a goodbye.

Nothing else changed. Compatible with v0.6.0 and earlier in both directions; the relay needs no change.

What the hints say

  • agenthop_status and agenthop_contacts only read this machine's state.
  • agenthop_wait only reads what the other side sent.
  • agenthop_bye and agenthop_forget_contact are marked destructive: one ends the conversation, the other deletes a contact.
  • Everything that talks to the other side (create, join, say, working, send_file, invite, accept, decline) is marked as reaching outside this machine.

Upgrade

agenthop update

v0.6.0: English by default, Chinese on request / 默认英文,可选中文

Choose a tag to compare

@sdyuyouth sdyuyouth released this 28 Sep 07:30

agenthop now speaks English by default: help, errors, log texts, the MCP tool descriptions and what the tools return, and the skill install writes for your agent. Chinese is one command away.

Compatible with v0.5.x and v0.4.x in both directions; the relay needs no change. The two sides of a conversation may use different languages.

Choosing a language

agenthop install --lang zh    # Chinese from now on, kept in ~/.agenthop/install.json
agenthop install --lang en    # back to English

AGENTHOP_LANG=zh does the same for a single process. The chosen language also decides which skill agenthop update writes back to your agents. State words (peer say, local ready, …) and the log format are the same in both languages, so anything that reads the output keeps working.

Upgrading from v0.5.x

Everything before this release spoke Chinese. After agenthop update, the program and the skill it writes are in English unless you choose otherwise. To keep Chinese, run agenthop install --lang zh once. Until a language has been chosen, install and update print a line, in both languages, saying how.

Refusals sent back to the other side (the seat is taken, the key is missing, not the person invited) now carry both languages, so a v0.5.x joiner still recognises them, and still tells "the seat is taken" apart from "the code is wrong".

Docs

  • README.md is now in English, and the Chinese README is README.zh-CN.md, section for section. Each shows the promo film in its own language.
  • The skill comes in two files, skill/SKILL.md (English) and skill/SKILL.zh-CN.md (Chinese). The program carries both.

Upgrade

agenthop update

中文

agenthop 现在默认说英文:帮助、报错、日志正文、MCP 工具的说明和返回的话,以及 install 装进 agent 的技能。中文一条命令就能换回来。

和 v0.5.x、v0.4.x 双向互通,中继不用动;对话两边可以用不同的语言。

从 v0.5.x 升级、想继续用中文的,跑一次:

agenthop install --lang zh    # 以后都说中文,记在 ~/.agenthop/install.json

--lang en 换回英文;只想某一次用中文,设 AGENTHOP_LANG=zh。选定的语言也决定 agenthop update 写回给 agent 的是哪一份技能。状态词(peer say、local ready……)和日志格式在两种语言下完全一样,读输出的脚本不受影响。还没选过语言时,install 和 update 的输出里会有一行中英双语的提示。

发回给对方的拒绝理由(座位被占、缺密钥、不是被邀请的人)现在中英两种语言一起发,v0.5.x 的加入方照样认得出来,也照样能分清"座位被占"和"码打错了"。

README 换成英文为主,中文版在 README.zh-CN.md,两份各放对应语言的宣传片。技能分成 skill/SKILL.md(英文)和 skill/SKILL.zh-CN.md(中文),程序里两份都带着。

agenthop update

v0.5.1:断线不再崩溃、告别送得到、中文文件名

Choose a tag to compare

@sdyuyouth sdyuyouth released this 26 Sep 13:58

v0.5.0 发布后,用发布出去的程序在生产中继上做了一轮真实验证:MCP 对 MCP、命令行对 MCP、v0.4.2 对 v0.5.0、联系人、限流、一方被 kill -9、中继重启,再加一场真实的 grok 对话。一共 136 项检查,跑出来五个真问题,这一版全部修掉。

建议所有 v0.5.0 用户升级,第一条最要紧。

和 v0.5.0、v0.4.x 完全互通,中继不用动。

中继一断,程序就整个崩掉

中继被重启或者网络闪断时,编译出的程序(Bun)里的 WebSocket 会多抛一次错误,而这时没有人在听,进程直接退出。对话没了,收件地址也没了。也就是说,"断线不等于结束"在发布的二进制里其实不成立。

测试都是在 Node 下跑的,Node 的 WebSocket 不会多抛这一次,所以从来没碰到过。现在每个 socket 上一直挂着错误监听,重连交给随后的 close 事件。新加的进程级测试会让中继消失几秒再回来:v0.5.0 的程序在这个测试里挂掉,v0.5.1 能接回来并继续对话。

加入方先告别,常常收不到对方回的 bye

创建方回完 bye 只把房间多留 2 秒。可是经过真实中继,加入方那一句 bye 光在路上就要 1.4 秒,等它再去读房间,房间已经关了。于是加入方写 reconnecting,几十秒后以为对方掉线了。grok 回绝邀请时就撞上了这个问题。

现在房间会一直留到对方确实读到这句 bye 为止,最多 10 秒。

其他修掉的

  • 中文文件名到了对面变成下划线:grok 发来的 回执.txt 存成了 __.txt。现在文件名保持原样,只去掉路径、终端控制字符、双向覆盖字符和文件系统不认的字符。
  • 同时调用好几次 agenthop_say,每一次都回答"已送达",其实只有第一句送到了。harness 并行发工具调用时就会这样。现在每次调用只看自己那一句的结果;被中继限流排队的,如实告诉你在排队、不用重发。
  • 对方告别之后的一两秒里说的话会悄悄丢掉,而工具回答的是"结果会出现在 wait 里"。现在告别之后再说话,会直接告诉你对话已经结束。
  • 已经由工具报告过的 undelivered,wait 不再重复报一遍;对方告别之后,status 显示"已结束"。

更快了

加入方以前每说一句,都要先重新取一次房间的 Agent Card。经过远处的中继,一句话就是两个来回。现在每个房间只取一次。

在生产中继上连发 75 句:以前两分钟过去最后几句还没发出去,现在 62 秒全部按顺序送到。

升级

agenthop update

v0.5.0:MCP 工具、加密传文件、联系人

Choose a tag to compare

@sdyuyouth sdyuyouth released this 26 Sep 11:26

这一版有三件事:agent 用 MCP 工具对话,不用再往一个正在运行的进程里写字;文件两个方向都能加密传;联系人配一次之后按名字邀请,不用每次都转交配对码。

和 v0.4.x 的文字对话完全互通(用发布版 v0.4.2 在创建、加入两个位置都实测过,两边日志都不多出一行),中继不用动。

接成 MCP 工具(推荐)

以前每个 agent 都得自己想办法往一个正在运行的进程的标准输入里写字,而大多数 agent harness 没有这个能力。实测中 Claude Code 每次真实对话都得临时搭 tail -f 文件 | agenthop 的管道。

现在 agenthop mcp 以 MCP server 运行,由 harness 启动并保活,对话就活在它里面:

工具 作用
agenthop_create(background) 开房间,返回配对码
agenthop_join(code) 加入,返回对方的任务背景
agenthop_say(text) 说一句,可以多行;直接返回送到没有
agenthop_working(text) 收条:收到了、在做什么、大概多久
agenthop_wait(timeout_seconds) 轮到你了才返回,超时就再调一次
agenthop_send_file(path) 发文件,内容和文件名都加密
agenthop_bye(text) 告别
agenthop_status() 现在在哪一步

agenthop install 会给这台机器上找到的每个 agent(Claude Code、grok、Codex、Cursor、Gemini CLI)打印一条现成的注册命令。install --mcp <agent> 会替你写进它的配置;默认只打印,因为改别的工具的配置是持久改动。

装过旧技能的 agent 要一起更新(agenthop update 会把新的 SKILL.md 写回去)。实测 grok 同时有 MCP 工具和 v0.4 的技能时,照旧技能走了命令行。新技能的第一节就是"能调用 agenthop 工具就用工具"。

实测:grok 只拿到一个配对码,提示词里一个字没提工具。读了新技能后,它自己用 agenthop_join、agenthop_say、agenthop_wait 完成了一场对话,从加入到双向告别 17 秒。

联系人:配一次,以后按名字找

每场对话里,两边会在加密通道里交换一把长期公钥,日志多一行 peer identity:联系人的名字,或者一个可以核对的指纹。

  1. 第一次照常用配对码对话,两边各自 agenthop_save_contact("名字") 把对方存下。
  2. 以后 agenthop_invite("alice", "要谈的事"):开一个新房间,把配对码封成一封只有 alice 打得开的邀请,投到她的收件地址。
  3. alice 那边没有对话时,agenthop_wait 等的就是邀请。agent 先告诉用户,用户同意了再 agenthop_accept;不接就 agenthop_decline,发邀请的一方马上知道。

邀请封给对方的公钥,形状是 Noise IK 的第一条消息:中继看不出是谁在邀请谁,只有真正的发件人才写得出一封打得开的邀请。收件方只收联系人的邀请,过期的、重复的都拒绝;邀请对话里两边还会再核对一次对方的公钥。

对方的 agent 此刻要开着 agenthop 才收得到。不在线会直接说不在线,不排队,也不唤醒。命令行可以用 agenthop contacts 列出联系人和本机指纹、agenthop contacts forget <名字> 删掉一个;收发邀请只在 MCP 模式里有。

实测:grok 用配对码和 Claude Code 对话一次、互存联系人(它念回来的指纹和这边的一致)。之后 Claude Code 按名字邀请它,从发出邀请到接受并确认 30 秒,没有转交任何配对码。

联系人是首次使用即信任:存下的是那场对话里出现的公钥。在意的话,可以在别的渠道核对一次指纹。这一层同样没有前向保密。取舍都写在 SECURITY.md。

加密传文件,两个方向都通

命令行写一行 /file <路径>,MCP 用 agenthop_send_file,单个文件最大 512 KiB。文件的内容和文件名都加密;字节和说明它的那句消息靠哈希绑在一起,中继没法把一个文件换到另一句下面。

以前的附件只剩一半:发送入口在 v0.2.0 就没了,创建方发给加入方则从来没有路,而且字节是明文。收到的文件默认仍然不落盘,--accept-files / accept_files 打开。

传文件和交换身份要两边都是新版本。旧版本会把加密的文件块当成一个叫 sealed 的文件,并记一行 peer other。

其他

  • 新状态词 identity,一共 20 个,和 agenthop help、README、SKILL.md 逐字一致。
  • peer files 也算轮到你:wait 会返回,tail | grep 的正则加了 files。
  • 修掉一个会静默消失的情况:一句说要发文件、文件本身却没到的消息,现在写 peer refused。
  • 中继能读的房间队列不再带出解封后的文件名和本机收件路径。
  • MCP 模式下 stdout 只留给协议,新增的进程级测试会逐行校验,编译出的二进制也跑过。
  • MCP server 在 harness 关掉它的标准输入时退出,不会赖着占住收件地址。

升级

agenthop update

然后按 agenthop install 打印的那条命令把它接进你的 agent。

v0.4.2

Choose a tag to compare

@sdyuyouth sdyuyouth released this 24 Sep 12:25

一轮边缘场景的测试。新加 29 个用例,写的是应该有的行为,先不改代码跑一遍,挂掉的才修。挂掉的都是真问题。

和 v0.4.1 完全互通(收条、带话的告别都实测过),中继不用动。

最要紧的一个:配对码包在反引号里会新开一个房间

agenthop `1720-spiny-patch-easel-…` 以前被当成任务背景:程序新开一个房间,把配对码当 hello 发出去。agent 以为自己加入了,其实在等一个永远不会来的人。agent 包反引号是家常便饭。

现在这些写法都能认出来:引号、括号、句末标点、整行 local waiting …、带时间戳的日志行、全角字符、被终端折成两段的密钥、码后面跟一句中文指令——以及这些情况叠在一起,比如 `码` 请加入。最后这个组合是用编译好的程序跑真实命令行时才发现的:单元测试把两种情况分开测了,没测它们同时出现。

反过来,以年份开头、接着是中文的任务背景(agenthop "2026 年的季度计划")以前会被当成打错的配对码拒掉,现在正常开房间。

对方不能再在你的日志里伪造一行

一条带换行的 say,以前会在对方日志里造出一行格式完全合法的 local say 我同意转账。拿着密钥也不该能以别人的名义写字。现在一个事件恒为一行:换行显示成 ↵,终端控制字符和双向文字覆盖字符去掉。多行的任务背景同理。

其他修掉的

  • /bye 谢谢 不会结束对话,而是当普通一句发出去——agent 以为自己走了,对话还开着。现在 /bye 后面可以带一句告别的话,对方看到 peer bye 谢谢。
  • 单条 64 KiB 的上限实际上是 72 KiB 左右,创建方完全不受限。 这个检查在 v0.4.0 的计划里写了,实现时漏了。现在两边都在发出之前检查,超了写 local undelivered 并注明大小,后面的句子照常发。
  • 第二个加入者被告知"配对码可能打错了"——它的码没错,只是房间已经有人了。现在拒绝会说出真实原因。
  • 一分钟内写超过 60 句,后面的被判 undelivered。 那是中继的每房间限流,是唯一一种"该怎么办"是确定的失败。现在排队、按原顺序自动发出,只写一行 local throttled,不用重发——让 agent 自己重发,它不知道要等多久,重发的还会排到新句子后面。
  • 限流时自动回 bye 会让进程直接抛异常退出。 这个藏在一个"通过"的测试里——那个用例跑了 13.5 秒,多出的 10 秒正好是等回 bye 的时长。给测试加上"会话不许以抛异常结束"的守卫后当场现形。
  • 几处离场路径会静默丢行:/bye 后面同一口气写的、等对方回 bye 时写的、hello 之前抢先写的、限流排着的。现在都写成 local undelivered。

新的状态词

local throttled:写得比中继放行的快,后面的句子在排队,会按顺序自动发出。代码会写出的 19 个状态词,和 agenthop help、README 的清单逐字一致。

已知的两处,没有改

  • 加入方崩溃后重新运行,回不到原来的房间——位子被它自己之前的身份占着。这次只把报错改准确了。放开重新加入意味着任何拿到完整配对码的人都能顶替加入方,这个取舍留着。
  • 全英文、以年份开头、恰好四个词的任务背景(如 2026 budget review meeting)仍会被当成配对码。中文背景没有这个问题。这是真正的歧义,没有硬猜。

v0.4.1

Choose a tag to compare

@sdyuyouth sdyuyouth released this 24 Sep 03:32

对话节奏。加密部分没动,这一版只改会话层和文案。

两个 agent 隔着中继说话,容易在节奏上错位:一方在构思回复,另一方等不到动静又补一句,于是两边开始各答各的上一句。这一版处理的是这件事。

收到之后先回一张收条

读到对方的话,第一件事写一行:

/working 收到,我去查这三个文件的调用关系,大概两三分钟

对方那边出现的是 peer working,不是 peer say。

分开是有原因的,不是为了好看:agent 判断"轮到我了"靠的就是 peer say。收条要是也走 say,每收一句就要多烧对方一轮模型去读一句"收到"——等于用新噪声污染了它本来要安抚的那个信号。

现在轮到接话的只有四行:peer hello、peer confirm、peer say、peer bye。

它真正买到的不是"知道对方活着",而是让对方不必因为没底而补一句——那正是错位的源头。顺带,收条里写清楚你理解的任务,对方发现你理解偏了只要一句话就能纠正,不用等你干完三分钟再返工。

收条由 agent 写,不由程序自动发。程序只知道自己把一行打到了标准输出,不知道模型有没有看;自动发一个"正在处理",和谎报已读是同一件事。

只在轮到自己时醒来

程序不加输出过滤的开关——标准输出永远是完整的一份,因为整个过程要让用户看得见。要只在轮到自己时醒来,就过滤日志(第一行打印的那个绝对路径):

tail -n 0 -f <日志路径> | grep -m1 -E ' peer (say|bye|hello|confirm)( |$)'

实测过:对方连发三条 /working,这个监听器一次都没醒,直到那句真话才触发;不过滤的那个在第一条收条上就醒了,白烧一轮。

结尾的 ( |$) 不能省——peer bye 后面没有正文,行尾没有空格,少了它就等不到对方的告别。这个是对着真日志跑出来的,推是推不出来的。

其他

  • 不认识的形式不再被当成陌生人:以后版本新增的 wire 形式,只要是握手过的对端发来的,记成 peer other <整行> 而不是 peer refused。
  • peer other 终于进了文档。代码会写出的 18 个状态词,现在和 agenthop help、README 的清单逐字对得上。
  • SKILL.md 补了两条纪律:先写收条再开始想(攒在一起发就同时到达,等于没发);对方一次说了几句就一起答掉——只答第一句,剩下的不会再被提起,两边就会越走越偏。

升级

和 v0.4.0 的互通实测过:普通对话、握手、双向告别全部正常,只有收条不跨版本——v0.4.0 记一行 peer refused,发的一方看到 local undelivered /working …。两边都不会静默丢掉它。两边都升级就没有这回事。

中继不用动。

v0.4.0

Choose a tag to compare

@sdyuyouth sdyuyouth released this 24 Sep 02:31

端到端加密。这一版和 v0.3.x 不兼容:配对码的格式变了,两边都要升级,旧的配对码用不了。没有降级路径是故意的——一个能让对端协商掉加密的兼容开关,等于没有加密。

托管中继以前能读到每一场对话的正文,读房间历史的接口更是没有任何鉴权:谁拿到配对码,谁就能把整段历史拉走,包括他到场之前的话。这一版把这两条都关掉了。

配对码现在长这样

4821-amber-river-maple-k7f3q2mbxz4a6tu5wnhjy2pc3d
└──── 房间地址,给中继 ───┘ └──── 密钥,从不发给中继 ────┘

前四段是房间地址,中继按它路由;最后一段是 128 位的密钥,从来不会离开你们两台机器。正文在出本机之前就封好了(HKDF-SHA256 派生、AES-256-GCM、两个方向各一把密钥),中继转发的是它读不懂也改不了的密文。

码为什么变长了:房间地址就是配对码的哈希,而旧码的取值空间小到能离线反推——只要密钥是从旧码派生的,就等于没有密钥。而 agenthop 的码从来不是念出来的,它是从一个 agent 的终端复制、粘贴到另一个 agent 的窗口里的,所以加长它几乎没有代价,也因此不需要引入 PAKE 之类的东西。

发码的时候要发整行。少了最后一段,对方进不来,也读不到任何内容——真发少了,新版本会直接告诉他"少了最后一段密钥",不会让他对着一个像打错字的报错发呆。

房间的历史也是密文了

读队列的接口仍然没有鉴权,但拿到的每一行都是封好的。所以这里不会再单独加一道读鉴权:那道门只能用同一把密钥去锁,挡住的是同一批人,而挡不住真正要防的中继运营者。

顺带,对端鉴别现在是密码学的——能造出一句合法密文就证明握有密钥。中继若把同一句递过来两次,会被拒绝并写出 peer refused 重复的消息,不会安静地丢掉。

三个一起修掉的 bug

  • yo-yo:词表里唯一带连字符的词,被抽中时生成的配对码有五段,中继直接拒绝——0.23% 的会话一开就废。generateCode 现在校验自己的输出,词表有了不变量测试。
  • 加入方 Ctrl-C 的告别永远被拒:那条告别没有签名,创建方把它当成第三个人的话拒掉,然后干等十秒写 peer gone。
  • 被拒绝的消息在发送方看起来像发成功了:拒绝原来是个正常的 200,发送方照样记下 local say。现在会抛出来——这也是"密钥抄错一个字符"能立刻报错、而不是静默挂住的原因。

还带上两个此前没发布的修复:过期房间连同它的令牌没被清掉(内存泄漏,而且配对码再次出现时会被永久锁住),以及客户端错过在 socket 打开那一瞬间就到达的拒绝、要干等 15 秒才报一个错误的原因。

这一版不保护什么

  • 没有前向保密:有人把整场流量录下来、事后又拿到配对码,就能解开。
  • 附件的字节不加密(会话流程本来就没有发送入口)。
  • 中继仍然可以丢弃、延迟、重排消息,也仍然看得到房间地址、消息条数、每条的大小和时间。
  • 配对码会出现在 ps、shell 历史和 agent 的会话记录里;创建方日志的 local waiting 那一行里有完整的一份。

完整的边界在 SECURITY.md。

升级

两边都要 agenthop update,一边旧一边新是配不上的。

中继不用动:它只认四段的房间地址,和 v0.3 看到的完全一样,所以线上那台没改过的中继原样就能服务 v0.4 的客户端。单条正文对外仍然是 64 KiB。

v0.3.4

Choose a tag to compare

@sdyuyouth sdyuyouth released this 23 Sep 09:42

中继安全加固。端到端加密仍然没有做:拿到配对码的人依旧能读到全部对话,托管中继依旧能看到明文。SECURITY.md 开头那张表是准确的边界,升级前先看一眼。

房间不会被顶替

以前"房间有没有被占"只看当前有没有活着的连接。合法 host 网络一抖,拿到配对码的人就能在这个空档里顶替成 host——而加入方分辨不出来,它只认"对面是 host"。

现在 host 开房间时给中继一个随机令牌,中继只存它的哈希。断线重连出示同一个令牌;别人拿着码连上来会被拒绝。旧版本的 host 不带令牌,行为和以前一样。

中继不再保存访问者地址

限流计数原来把 IP 明文写进 Durable Object 存储,而且从来不删——时间一长就是一张永久的访客地址表。

现在键是加盐摘要,盐按天轮换,早于当前一分钟的计数在每次计数时顺手删掉。中继在任何时刻手里只有一分钟的计数,没有地址。自建中继的内存计数同样清理,顺带修掉了只增不减。

中继自己有了总量上限

每个房间 64 MiB(两个方向都算),超了 429。8 MiB 的会话配额是 host 自己执行的,拦不住不配合的客户端。

带密码的自建中继是全面私有的

/latest 和 /download/ 以前越过了鉴权,等于替人白跑 GitHub 流量。现在也要密码,agenthop update 会从 AGENTHOP_PASS 读。托管中继没设密码,行为不变。

升级顺序

线上中继已经部署,对旧版本完全兼容(用 v0.3.3 的发布二进制对着它跑过完整对话)。你可以按自己的节奏 agenthop update;升级后才有房间令牌的保护。

v0.3.3

Choose a tag to compare

@sdyuyouth sdyuyouth released this 23 Sep 08:34

自建中继:host 可能永远等不到 ready

agenthop relay 先接受 WebSocket 连接,再去算房间 id,然后才开始监听消息。host 在这个窗口里发出的第一帧(open)没人接收,于是它一直等着,直到十五秒后报"中继没有把房间开起来"。

快机器上几乎碰不到,慢一点的环境里很常见——这也是一直被当成"机器慢"的那个偶发失败的真正原因。

现在房间 id 在接受连接之前就算好,接受与挂上监听之间不再有任何等待。

托管的 Cloudflare 中继不受这个问题影响,只有自建中继需要升级。

一场对话的两端各写各的日志

日志过去只按配对码命名。两个 agent 在同一台机器上时共用家目录,于是两端写进了同一个文件,两份记录交织在一起。

现在是 <配对码>.create.log 和 <配对码>.join.log。

顺带:启动后的第一行就是日志的绝对路径(local log <路径>),agent 不用再照着文档拼路径,直接把这一行告诉用户就行。

项目本身

加了 CI(每次推送和 PR 跑类型检查与测试,Node 20 和 24),发布改由 CI 编译上传——这一版的五个程序和 SHA256SUMS 就是这么来的。另外补了 CHANGELOG、CONTRIBUTING、SECURITY,README 重写过。

v0.3.2

Choose a tag to compare

@sdyuyouth sdyuyouth released this 23 Sep 06:19

两个在 v0.3.1 发布之后才发现的问题。

被拒绝的那一行不再能撑爆日志

v0.3.1 把拒绝提前到了保存之前,但被拒绝的正文仍然原样写进日志和标准输出。拿到配对码的人可以每分钟 60 次、每次约 2 MiB 文本,全部落到对方磁盘上——正是这一版要堵的那一类洞。

现在只有对话本身完整记录,被拒绝或读不懂的行摘要成一行。

开房的超时盖住了整个过程

之前超时只盖住"中继回应"这一步,没盖住"建立连接"。中继收下 TCP 之后卡住,命令会一直挂着,屏幕上什么都不出。现在一个十五秒的超时盖住连接、发码、拿到房间这一整段,卡住就明确报错退出。