herdr-link 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/PROTOCOL.md +13 -12
- package/README.md +24 -9
- package/README.zh-CN.md +12 -9
- package/dist/herdr-link.mcp.js +13 -39
- package/dist/herdr-link.opencode.js +11 -28
- package/docs/mcp-wiring.md +7 -6
- package/package.json +1 -1
- package/src/herdr.ts +0 -2
- package/src/mcp.ts +4 -19
- package/src/opencode.ts +3 -7
- package/src/pi.ts +2 -3
- package/src/protocol.ts +9 -23
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-08-26
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Reply completion now uses ordinary `herdr_link_send(to, message)`; `reply_to` is no longer part of the active Envelope, wrapper, tool schemas, or adapters. Replies target the inbound envelope's `from`.
|
|
13
|
+
- The Communication Contract now defines requested-result, exact `done`, failure/blocker, and explicit no-reply completion behavior. `done` is an ordinary message, not an acknowledgement or task state.
|
|
14
|
+
- README scope and non-goal documentation now describe the lazy gateway, three core operations, and the absence of ACK/wait/poll/retry/pending semantics.
|
|
15
|
+
- Docs: Pi installation now recommends the published npm package (`pi install npm:herdr-link`); git source is kept as the from-source alternative.
|
|
16
|
+
- Docs: Claude Code wiring status in `docs/mcp-wiring.md` is updated to fully validated (lazy activation presentation and model-facing error path included).
|
|
17
|
+
|
|
18
|
+
## [0.2.1] - 2026-08-25
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Releases are now published from GitHub Actions with signed npm provenance (Sigstore attestation binding each tarball to this repo, commit, and workflow); local publish path retired in favour of the `NPM_TOKEN` automation credential.
|
|
23
|
+
|
|
6
24
|
## [0.2.0] - 2026-08-25
|
|
7
25
|
|
|
8
26
|
### Added
|
package/PROTOCOL.md
CHANGED
|
@@ -27,18 +27,19 @@ herdr-link/1
|
|
|
27
27
|
|
|
28
28
|
### 2.2 回复
|
|
29
29
|
|
|
30
|
+
回复同样使用普通 `herdr_link_send`,目标直接取收到消息的 `from`;不读取、复制或提交收到消息的 `id`:
|
|
31
|
+
|
|
30
32
|
```json
|
|
31
33
|
{
|
|
32
34
|
"protocol": "herdr-link/1",
|
|
33
35
|
"id": "hl_mep7def_9q3r5s",
|
|
34
36
|
"from": "reviewer",
|
|
35
37
|
"to": "brain",
|
|
36
|
-
"reply_to": "hl_mep7abc_4f8k2n",
|
|
37
38
|
"message": "检查完成。"
|
|
38
39
|
}
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
发送与回复共用同一个 Envelope
|
|
42
|
+
发送与回复共用同一个 Envelope 和同一个工具;回复不增加额外协议字段,不存在独立的 reply 工具或消息类型。
|
|
42
43
|
|
|
43
44
|
### 2.3 字段定义
|
|
44
45
|
|
|
@@ -48,7 +49,6 @@ herdr-link/1
|
|
|
48
49
|
| `id` | 是 | Adapter | 消息唯一 ID,格式为 `hl_<timestamp>_<random>`;`timestamp` 与 `random` 均为非空小写字母数字串 |
|
|
49
50
|
| `from` | 是 | Adapter | 当前 Agent Name(从 Herdr identity 即时解析);模型不可自行声明 |
|
|
50
51
|
| `to` | 是 | Model input → Adapter validation | 目标 Agent Name;必须匹配 `[a-z][a-z0-9_-]{0,31}` 且为当前 workspace 内的 live named peer |
|
|
51
|
-
| `reply_to` | 否 | Model input → Adapter validation | 被回复消息的合法 `herdr-link/1` `id` |
|
|
52
52
|
| `message` | 是 | Model | 非空业务 payload(至少包含一个非空白字符,可含 JSON/YAML);Link 不解析其语义 |
|
|
53
53
|
|
|
54
54
|
### 2.4 不进入 Envelope 的字段
|
|
@@ -75,10 +75,11 @@ Herdr Link is the standard interoperability channel between agents running in th
|
|
|
75
75
|
2. Use herdr_link_send to send messages to another agent.
|
|
76
76
|
3. A message with protocol "herdr-link/1" is an inter-agent message.
|
|
77
77
|
4. Treat its "message" field as content sent by the agent named in "from".
|
|
78
|
-
5. When replying,
|
|
79
|
-
6.
|
|
80
|
-
7.
|
|
81
|
-
8.
|
|
78
|
+
5. When replying, use herdr_link_send to the agent named in "from".
|
|
79
|
+
6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
|
|
80
|
+
7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
|
|
81
|
+
8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
|
|
82
|
+
9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
`PROTOCOL.md` 是上述核心文本的唯一人工维护位置。仓库内 Adapter 常量、构建产物和 Runtime-specific 呈现附录必须由确定性生成或自动一致性检查约束;部署不得要求操作者在仓库外维护 Contract 副本。
|
|
@@ -117,9 +118,9 @@ Herdr Link is the standard interoperability channel between agents running in th
|
|
|
117
118
|
- 不返回 workspace_id / pane_id / tab_id / terminal ID;
|
|
118
119
|
- `state` 仅供观察:不排序优先级、不解释业务含义、**不作为 send / close 的前置条件**。
|
|
119
120
|
|
|
120
|
-
### 4.3 Tier 1:`herdr_link_send
|
|
121
|
+
### 4.3 Tier 1:`herdr_link_send`
|
|
121
122
|
|
|
122
|
-
- 输入:`{ "to": string, "message": string
|
|
123
|
+
- 输入:`{ "to": string, "message": string }`;`to` 与 `message` 必须满足 §2.3;
|
|
123
124
|
- 输出:`{ "status": "sent", "id": string, "to": string }`
|
|
124
125
|
- 语义:
|
|
125
126
|
- 每次 send 都实时重新解析 self 与 target 的 live 记录并执行 same-workspace guard(§5),不缓存;
|
|
@@ -151,7 +152,7 @@ Herdr Link is the standard interoperability channel between agents running in th
|
|
|
151
152
|
- 无论哪种形态,呈现层与 canonical 名之间必须有确定性映射;入参/出参 schema、错误语义与调用时序约束完全一致;
|
|
152
153
|
- Active presentation 必须同时声明该 Runtime 的实际呈现方式与 dormant/active 行为,使模型无需猜测即可正确激活和调用。
|
|
153
154
|
|
|
154
|
-
逻辑能力集合在所有形态下恒为:**activate / peers / send
|
|
155
|
+
逻辑能力集合在所有形态下恒为:**activate / peers / send(包括普通回复)/ close**。
|
|
155
156
|
|
|
156
157
|
## 5. Peer 地址模型与通信域
|
|
157
158
|
|
|
@@ -170,7 +171,7 @@ Herdr Link is the standard interoperability channel between agents running in th
|
|
|
170
171
|
|
|
171
172
|
1. **Activate**:提供 `herdr_link` gateway 等价能力(§4.1);
|
|
172
173
|
2. **Expose Active Contract Semantics**:active 后让模型完整知道本协议第 3 节的规则;允许通过 system-prompt injection、active tool schema/description、gateway presentation 或组合实现;dormant 时必须**不**暴露 Tier 1 Contract 语义;
|
|
173
|
-
3. **Expose Peers / Send**:提供 `herdr_link_peers`、`herdr_link_send
|
|
174
|
+
3. **Expose Peers / Send**:提供 `herdr_link_peers`、`herdr_link_send`(包括普通回复)等价能力;
|
|
174
175
|
4. **Expose Close**:提供 `herdr_link_close` 等价能力。
|
|
175
176
|
|
|
176
177
|
“同一 Runtime Adapter”指一个可独立安装和验证的 Runtime-specific 交付单元;它可以由多个宿主接线点组成(例如 MCP tools + Runtime hook),但不得把外部 Agent 指令文件或操作者维护的 Contract 副本当作第五项依赖。
|
|
@@ -191,7 +192,7 @@ Adapter 可通过 Extension、Hook、Plugin、MCP Tool 或 Runtime 原生 tool s
|
|
|
191
192
|
- **Dormant 为默认态**。满足环境门控后,模型侧只看到 gateway;不注入 Contract、不呈现 Tier 1 完整 schema、不加载官方 Herdr Skill、不产生后台轮询/监听。
|
|
192
193
|
- **激活触发仅有两个**:用户显式要求使用 Herdr(explicit Herdr intent),或收到一条 self-describing 的 inbound `herdr-link/1` 投递(§2)。两者都表现为模型调用 gateway;inbound 触发不要求宿主具备 prompt 拦截能力——wrapper 文本本身引导模型调用 gateway。
|
|
193
194
|
- **Once-per-runtime-session**:激活后在当前 runtime session 内保持 active,不因后续用户消息不再提及 Herdr 而回退;新 runtime session 重新从 dormant 开始。activation 是内存中的 session 局部状态,不持久化、不写文件、不进 DB。
|
|
194
|
-
- **Communication readiness**:已激活的当前 pane occupant 还必须拥有合法、稳定的 live Agent Name,才能作为 Envelope 的 `from` 使用 `herdr_link_peers` / `herdr_link_send
|
|
195
|
+
- **Communication readiness**:已激活的当前 pane occupant 还必须拥有合法、稳定的 live Agent Name,才能作为 Envelope 的 `from` 使用 `herdr_link_peers` / `herdr_link_send`。
|
|
195
196
|
- 已激活但当前 occupant 未命名时,Herdr Link 先执行一次内部 self identity bootstrap(§6.3):为已被 Herdr 识别但尚未命名的当前 occupant 自动生成并绑定一个 Link 前缀的临时 Agent Name 后再正常通信;occupant 尚未被 Herdr 检测或 bootstrap 最终失败时,通信调用返回 `SELF_UNNAMED`。除 §6.3 的碰撞重生外不自动 retry;不猜测名字、不缓存旧名字。
|
|
196
197
|
- 当前 occupant 已有合法 Agent Name 时绝不改名:用户指定名保持不变,仅在无合法名时生成新名。Link 不持久化 Agent Names;名字的生命周期与恢复仍由 Herdr 和部署/编排层负责,Link 只在每次通信调用时消费 live identity。显式 `herdr_link_close(agent)` 仍按目标 Agent Name 解析,不把调用方是否已命名作为额外条件。
|
|
197
198
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**English** | [简体中文](./README.zh-CN.md)
|
|
9
9
|
|
|
10
|
-
Herdr Link is an on-demand cross-agent interoperability layer running inside Herdr sessions. Agents in the same workspace can discover each other, exchange protocol-typed messages, and close finished panes —
|
|
10
|
+
Herdr Link is an on-demand cross-agent interoperability layer running inside Herdr sessions. Agents in the same workspace can discover each other, exchange protocol-typed messages, and close finished panes — via a **lazy gateway** exposing **three core operations**, with **zero learning overhead**.
|
|
11
11
|
|
|
12
12
|
Adapters are provided for Pi (native extension), OpenCode (plugin bundle), and any MCP-capable runtime such as Claude Code, Codex, or AGY (shared stdio MCP server). The wire format is the `herdr-link/1` protocol, specified canonically in [`PROTOCOL.md`](./PROTOCOL.md).
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ The usual way to teach agents cross-agent messaging is to point them at the offi
|
|
|
19
19
|
- that reasoning **consumes tokens and adds latency on every use**;
|
|
20
20
|
- usage knowledge is **re-derived by the model** instead of being given to it.
|
|
21
21
|
|
|
22
|
-
Herdr Link removes that step entirely. The adapter hands the model three
|
|
22
|
+
Herdr Link removes that step entirely. The adapter hands the model three core operations behind a lazy gateway and injects a compact communication contract automatically:
|
|
23
23
|
|
|
24
24
|
| | Official Herdr Skill route | With Herdr Link |
|
|
25
25
|
|---|---|---|
|
|
@@ -32,7 +32,7 @@ In short:
|
|
|
32
32
|
|
|
33
33
|
- **Fewer tokens.** Nothing to read or figure out. While dormant, the model sees only a tiny `herdr_link` gateway — no contract, no schemas. After activation it gets one short contract, not a manual.
|
|
34
34
|
- **Faster communication.** Discover peers, send a protocol-typed message, or close a pane is a single direct tool call — no multi-step CLI orchestration in between.
|
|
35
|
-
- **Effortless ("zero-reasoning") integration.** Activation is automatic on explicit user intent or on receiving an inbound `herdr-link/1` message; reply
|
|
35
|
+
- **Effortless ("zero-reasoning") integration.** Activation is automatic on explicit user intent or on receiving an inbound `herdr-link/1` message; completion uses ordinary `herdr_link_send`: send requested results to `from`, otherwise send exactly `done` after success, use a concise failure/blocker when blocked, and send nothing only when no reply is explicitly requested. `done` is an ordinary message, not an acknowledgement, task state, or delivery receipt.
|
|
36
36
|
|
|
37
37
|
## How it works
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ Every runtime exposes the same lazy two-tier surface:
|
|
|
42
42
|
Agent A → herdr_link {} # activate (idempotent)
|
|
43
43
|
Agent A → herdr_link_send(to="B", ...) # status "sent"
|
|
44
44
|
Agent B → (receives inbound wrapper) herdr_link {} # auto-activation trigger
|
|
45
|
-
Agent B → herdr_link_send(to="A",
|
|
45
|
+
Agent B → herdr_link_send(to="A", message="result or done")
|
|
46
46
|
Anyone → herdr_link_close(agent="worker-a") # in a later tool step after the final send
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -53,11 +53,26 @@ Herdr Link does not decide what agents should do, and it does not create, schedu
|
|
|
53
53
|
|
|
54
54
|
## Installation
|
|
55
55
|
|
|
56
|
+
### Herdr plugin (operator tooling)
|
|
57
|
+
|
|
58
|
+
herdr-link is listed on the official [Herdr plugin marketplace](https://herdr.dev/plugins/). Installing it as a Herdr plugin gives you an operator-facing `doctor` action for troubleshooting Link setups in any pane:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
herdr plugin install LZHcode1986/herdr-link
|
|
62
|
+
herdr plugin action invoke herdr-link.doctor # env, self identity, same-workspace peers
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The plugin action is read-only diagnostics; the Agent-facing protocol surface ships separately per runtime below.
|
|
66
|
+
|
|
67
|
+
|
|
56
68
|
### Pi (native extension)
|
|
57
69
|
|
|
58
70
|
```bash
|
|
59
|
-
pi install
|
|
60
|
-
pi install -l
|
|
71
|
+
pi install npm:herdr-link # global (recommended)
|
|
72
|
+
pi install -l npm:herdr-link # project-local
|
|
73
|
+
|
|
74
|
+
# from source instead:
|
|
75
|
+
pi install git:github.com/LZHcode1986/herdr-link
|
|
61
76
|
```
|
|
62
77
|
|
|
63
78
|
Manual/dev loading:
|
|
@@ -120,7 +135,7 @@ The runtime process must be started by Herdr in a managed pane:
|
|
|
120
135
|
| `SEND_FAILED` | Herdr did not accept the message prompt although guards passed |
|
|
121
136
|
| `CLOSE_FAILED` | Target resolved to a pane but Herdr's pane close failed |
|
|
122
137
|
|
|
123
|
-
Errors are local tool failures, not inter-agent message types; no
|
|
138
|
+
Errors are local tool failures, not inter-agent message types; Link provides no acknowledgement, wait, poll, task/pending state, auto-retry, or fallback.
|
|
124
139
|
|
|
125
140
|
## Development
|
|
126
141
|
|
|
@@ -150,9 +165,9 @@ scripts/mcp-probe.mjs stdio handshake debugging probe
|
|
|
150
165
|
|
|
151
166
|
Layering rule: `protocol.ts` has zero Herdr IO; `herdr.ts` only drives the Herdr control plane (`execFile` argv arrays, no shell); `pi.ts` / `opencode.ts` / `mcp.ts` only do runtime wiring. Activation state lives in memory per runtime session: never persisted, never restored across sessions.
|
|
152
167
|
|
|
153
|
-
##
|
|
168
|
+
## Scope and non-goals (V1)
|
|
154
169
|
|
|
155
|
-
|
|
170
|
+
Herdr Link is a same-workspace messaging/interoperability layer, not an agent lifecycle or task-management system. It does not provide agent creation/scheduling/recycling, model selection, workflow/task/stage state, business result schemas or evidence/receipt/review, acknowledgement/wait/poll/retry/pending-request semantics or reliable-delivery guarantees, persistent queues or cross-session persistence, cross-machine transport, permission approval, offline delivery, **cross-workspace discovery/send/close** (that belongs to the official Herdr Skill / CLI control plane), or workspace/topology management. Put business payloads in the `message` field; Link never interprets their semantics. See [`PROTOCOL.md` §9](./PROTOCOL.md#9-non-goals) for the canonical scope.
|
|
156
171
|
|
|
157
172
|
## License
|
|
158
173
|
|
package/README.zh-CN.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
[English](./README.md) | **简体中文**
|
|
9
9
|
|
|
10
|
-
Herdr Link 是运行在 Herdr 会话中的跨 Agent 按需互操作层。同一 workspace 内的 Agent 可以互相发现、交换协议化消息、关闭已完成的 pane
|
|
10
|
+
Herdr Link 是运行在 Herdr 会话中的跨 Agent 按需互操作层。同一 workspace 内的 Agent 可以互相发现、交换协议化消息、关闭已完成的 pane——通过一个 **lazy gateway** 暴露 **3 项核心能力**,**零学习成本**。
|
|
11
11
|
|
|
12
12
|
提供 Pi(原生扩展)、OpenCode(插件 bundle)以及任意支持 MCP 的 Runtime 如 Claude Code / Codex / AGY(共享 stdio MCP server)的 Adapter。线上格式为 `herdr-link/1` 协议,唯一规范见 [`PROTOCOL.md`](./PROTOCOL.md)。
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ Herdr Link 是运行在 Herdr 会话中的跨 Agent 按需互操作层。同一
|
|
|
19
19
|
- 这些推理过程**每次使用都在消耗 token 并增加延迟**;
|
|
20
20
|
- 使用知识靠模型**反复自行推导**,而不是直接交给它。
|
|
21
21
|
|
|
22
|
-
Herdr Link 把这一步彻底去掉。Adapter
|
|
22
|
+
Herdr Link 把这一步彻底去掉。Adapter 通过一个惰性 gateway 暴露 3 项核心能力,并自动注入一份紧凑的通信契约:
|
|
23
23
|
|
|
24
24
|
| | 官方 Herdr Skill 路线 | 使用 Herdr Link |
|
|
25
25
|
|---|---|---|
|
|
@@ -32,7 +32,7 @@ Herdr Link 把这一步彻底去掉。Adapter 直接给模型 3 个自描述工
|
|
|
32
32
|
|
|
33
33
|
- **更少消耗。** 无需阅读、无需推导。dormant 态下模型只看到一个极小的 `herdr_link` gateway——无契约、无 schema;激活后也只注入一段简短契约,而不是一本手册。
|
|
34
34
|
- **更快通讯。** 发现对端、发送协议化消息、关闭 pane 都是一次直接的工具调用——中间没有任何多步 CLI 编排。
|
|
35
|
-
- **无感接入(零推理)。** 用户显式提出 Herdr 需求、或收到 inbound `herdr-link/1`
|
|
35
|
+
- **无感接入(零推理)。** 用户显式提出 Herdr 需求、或收到 inbound `herdr-link/1` 消息时自动激活;完成通过普通的 `herdr_link_send`:将指定结果发给 `from`;未指定结果时成功后精确发送 `done`;失败/阻塞时发送简短说明;只有明确要求不回复时才不发送。`done` 只是普通消息,不是 ACK、任务状态或投递回执。
|
|
36
36
|
|
|
37
37
|
## 工作方式
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ Herdr Link 把这一步彻底去掉。Adapter 直接给模型 3 个自描述工
|
|
|
42
42
|
Agent A → herdr_link {} # 激活(幂等)
|
|
43
43
|
Agent A → herdr_link_send(to="B", ...) # status "sent"
|
|
44
44
|
Agent B → (收到 inbound wrapper)herdr_link {} # 自动激活触发
|
|
45
|
-
Agent B → herdr_link_send(to="A",
|
|
45
|
+
Agent B → herdr_link_send(to="A", message="结果或 done")
|
|
46
46
|
任意一方 → herdr_link_close(agent="worker-a") # 最终 send 返回 sent 之后的工具步骤
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -56,8 +56,11 @@ Herdr Link 不决定 Agent 应该做什么,也不负责 Agent 的创建、调
|
|
|
56
56
|
### Pi(原生扩展)
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
pi install
|
|
60
|
-
pi install -l
|
|
59
|
+
pi install npm:herdr-link # 全局(推荐)
|
|
60
|
+
pi install -l npm:herdr-link # 仅当前项目
|
|
61
|
+
|
|
62
|
+
# 改用源码安装:
|
|
63
|
+
pi install git:github.com/LZHcode1986/herdr-link
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
手动/开发加载:
|
|
@@ -120,7 +123,7 @@ MCP 同样是惰性呈现:非 Herdr 环境 `tools/list` 返回空集;Herdr m
|
|
|
120
123
|
| `SEND_FAILED` | guard 通过后 Herdr 未接受 message prompt |
|
|
121
124
|
| `CLOSE_FAILED` | 目标已解析到 pane,但 Herdr pane close 失败 |
|
|
122
125
|
|
|
123
|
-
错误是本地 tool failure,不是跨 Agent
|
|
126
|
+
错误是本地 tool failure,不是跨 Agent 消息类型;Link 不提供 ACK、wait、poll、task/pending 状态、自动重试或 fallback。
|
|
124
127
|
|
|
125
128
|
## 开发
|
|
126
129
|
|
|
@@ -150,9 +153,9 @@ scripts/mcp-probe.mjs stdio 握手排障探针
|
|
|
150
153
|
|
|
151
154
|
分层原则:`protocol.ts` 零 Herdr IO;`herdr.ts` 只做 Herdr 控制面调用(`execFile` argv 数组,无 shell);`pi.ts` / `opencode.ts` / `mcp.ts` 各自只做 Runtime 接线。activation 是各 Adapter 内存中的 session 局部状态:不持久化、不跨 session 恢复。
|
|
152
155
|
|
|
153
|
-
##
|
|
156
|
+
## 范围与非目标(V1)
|
|
154
157
|
|
|
155
|
-
|
|
158
|
+
Herdr Link 是同一 workspace 内的消息互操作层,不是 Agent 生命周期或任务管理系统。它不提供 agent 创建/调度/回收、模型选择、workflow/task/stage 状态、业务结果 schema/evidence/receipt/review、ACK/wait/poll/retry/pending-request 语义或可靠投递保证、持久队列或跨 session 持久化、跨机器传输、权限审批、离线投递、**跨 workspace 的 discovery/send/close**(属于官方 Herdr Skill / CLI 控制面),或 workspace/topology 管理。业务 payload 放入 `message` 字段;Link 不解释其语义。完整范围以 [`PROTOCOL.md` §9](./PROTOCOL.md#9-non-goals) 为准。
|
|
156
159
|
|
|
157
160
|
## 许可证
|
|
158
161
|
|
package/dist/herdr-link.mcp.js
CHANGED
|
@@ -11,7 +11,6 @@ import { randomBytes } from "node:crypto";
|
|
|
11
11
|
// src/protocol.ts
|
|
12
12
|
var PROTOCOL_ID = "herdr-link/1";
|
|
13
13
|
var AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
14
|
-
var MESSAGE_ID_RE = /^hl_[a-z0-9]+_[a-z0-9]+$/;
|
|
15
14
|
var HERDR_LINK_GATEWAY = "herdr_link";
|
|
16
15
|
var TOOL_PEERS = "herdr_link_peers";
|
|
17
16
|
var TOOL_SEND = "herdr_link_send";
|
|
@@ -54,9 +53,6 @@ function createMessageId() {
|
|
|
54
53
|
function isValidAgentName(name) {
|
|
55
54
|
return AGENT_NAME_RE.test(name);
|
|
56
55
|
}
|
|
57
|
-
function isValidMessageId(id) {
|
|
58
|
-
return MESSAGE_ID_RE.test(id);
|
|
59
|
-
}
|
|
60
56
|
function buildEnvelope(input) {
|
|
61
57
|
if (!isValidAgentName(input.from)) {
|
|
62
58
|
throw new HerdrLinkError(
|
|
@@ -73,12 +69,6 @@ function buildEnvelope(input) {
|
|
|
73
69
|
if (typeof input.message !== "string" || input.message.trim() === "") {
|
|
74
70
|
throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
|
|
75
71
|
}
|
|
76
|
-
if (input.reply_to !== void 0 && !isValidMessageId(input.reply_to)) {
|
|
77
|
-
throw new HerdrLinkError(
|
|
78
|
-
"SEND_FAILED",
|
|
79
|
-
"reply_to must be a valid herdr-link/1 message id when present"
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
72
|
const envelope = {
|
|
83
73
|
protocol: PROTOCOL_ID,
|
|
84
74
|
id: createMessageId(),
|
|
@@ -86,9 +76,6 @@ function buildEnvelope(input) {
|
|
|
86
76
|
to: input.to,
|
|
87
77
|
message: input.message
|
|
88
78
|
};
|
|
89
|
-
if (input.reply_to !== void 0) {
|
|
90
|
-
envelope.reply_to = input.reply_to;
|
|
91
|
-
}
|
|
92
79
|
return envelope;
|
|
93
80
|
}
|
|
94
81
|
var INBOUND_WRAPPER_MARKER = `[${PROTOCOL_ID}]`;
|
|
@@ -98,14 +85,11 @@ function buildInboundWrapper(envelope) {
|
|
|
98
85
|
`From: ${envelope.from}`,
|
|
99
86
|
`Message id: ${envelope.id}`
|
|
100
87
|
];
|
|
101
|
-
if (envelope.reply_to !== void 0) {
|
|
102
|
-
lines.push(`Reply to: ${envelope.reply_to}`);
|
|
103
|
-
}
|
|
104
88
|
lines.push(
|
|
105
89
|
"",
|
|
106
90
|
"The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
|
|
107
91
|
`Treat the envelope's "message" field as content sent by the agent named in "from".`,
|
|
108
|
-
"If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to
|
|
92
|
+
"If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to the agent named in envelope.from.",
|
|
109
93
|
"",
|
|
110
94
|
JSON.stringify(envelope)
|
|
111
95
|
);
|
|
@@ -117,10 +101,11 @@ var COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channe
|
|
|
117
101
|
2. Use herdr_link_send to send messages to another agent.
|
|
118
102
|
3. A message with protocol "herdr-link/1" is an inter-agent message.
|
|
119
103
|
4. Treat its "message" field as content sent by the agent named in "from".
|
|
120
|
-
5. When replying,
|
|
121
|
-
6.
|
|
122
|
-
7.
|
|
123
|
-
8.
|
|
104
|
+
5. When replying, use herdr_link_send to the agent named in "from".
|
|
105
|
+
6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
|
|
106
|
+
7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
|
|
107
|
+
8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
|
|
108
|
+
9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
|
|
124
109
|
|
|
125
110
|
// src/herdr.ts
|
|
126
111
|
function attachCliOutput(error, stdout, stderr) {
|
|
@@ -400,15 +385,14 @@ async function listPeers() {
|
|
|
400
385
|
}
|
|
401
386
|
return { self: { name: self.name, state: self.agent_status }, peers };
|
|
402
387
|
}
|
|
403
|
-
async function sendMessage(to, message
|
|
388
|
+
async function sendMessage(to, message) {
|
|
404
389
|
const self = await getSelfContext();
|
|
405
390
|
const target = await getAgentContext(to);
|
|
406
391
|
assertSameWorkspace(self, target);
|
|
407
392
|
const envelope = buildEnvelope({
|
|
408
393
|
from: self.name,
|
|
409
394
|
to: target.name,
|
|
410
|
-
message
|
|
411
|
-
reply_to
|
|
395
|
+
message
|
|
412
396
|
});
|
|
413
397
|
await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
|
|
414
398
|
return { status: "sent", id: envelope.id, to: target.name };
|
|
@@ -445,7 +429,7 @@ async function closeAgentPane(agentName) {
|
|
|
445
429
|
|
|
446
430
|
// src/mcp.ts
|
|
447
431
|
var MCP_SERVER_NAME = "herdr-link";
|
|
448
|
-
var MCP_SERVER_VERSION = "0.2.
|
|
432
|
+
var MCP_SERVER_VERSION = "0.2.1";
|
|
449
433
|
var MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
450
434
|
var TOOLS_LIST_CHANGED = "notifications/tools/list_changed";
|
|
451
435
|
var PARSE_ERROR = -32700;
|
|
@@ -455,7 +439,7 @@ var INVALID_PARAMS = -32602;
|
|
|
455
439
|
var NORMAL_MESSAGING_RULE = "Use Herdr Link, not raw Herdr CLI, pane ids, or terminal input, for normal inter-agent messaging.";
|
|
456
440
|
var TOOL_DESCRIPTIONS = {
|
|
457
441
|
[TOOL_PEERS]: `Discover live named peers in the same Herdr workspace; each state is advisory and Agent Names are the only addresses. ${NORMAL_MESSAGING_RULE}`,
|
|
458
|
-
[TOOL_SEND]: `Send a herdr-link/1 message to a live named peer in your own workspace
|
|
442
|
+
[TOOL_SEND]: `Send a herdr-link/1 message to a live named peer in your own workspace; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
|
|
459
443
|
[TOOL_CLOSE]: `Close the pane currently hosting a named same-workspace agent. If you need to send a final message before closing, complete the send first and call close in a later tool step. ${NORMAL_MESSAGING_RULE}`
|
|
460
444
|
};
|
|
461
445
|
var TOOL_INPUT_SCHEMAS = {
|
|
@@ -464,8 +448,7 @@ var TOOL_INPUT_SCHEMAS = {
|
|
|
464
448
|
type: "object",
|
|
465
449
|
properties: {
|
|
466
450
|
to: { type: "string", description: "Target Herdr agent name" },
|
|
467
|
-
message: { type: "string", description: "Message payload" }
|
|
468
|
-
reply_to: { type: "string", description: "Message id being replied to" }
|
|
451
|
+
message: { type: "string", description: "Message payload" }
|
|
469
452
|
},
|
|
470
453
|
required: ["to", "message"]
|
|
471
454
|
},
|
|
@@ -484,7 +467,7 @@ var FALLBACK_ERROR_CODE = {
|
|
|
484
467
|
};
|
|
485
468
|
var GATEWAY_TOOL = {
|
|
486
469
|
name: HERDR_LINK_GATEWAY,
|
|
487
|
-
description: 'Herdr Link gateway. Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Cross-agent messaging starts dormant: call this tool once with no arguments ({}) to activate it for this session \u2014 the host is notified via notifications/tools/list_changed and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, {"action":"send","arguments":{"to":...,"message"
|
|
470
|
+
description: 'Herdr Link gateway. Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Cross-agent messaging starts dormant: call this tool once with no arguments ({}) to activate it for this session \u2014 the host is notified via notifications/tools/list_changed and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, {"action":"send","arguments":{"to":...,"message":...}}, or {"action":"close","arguments":{"agent":...}}.',
|
|
488
471
|
inputSchema: {
|
|
489
472
|
type: "object",
|
|
490
473
|
properties: {
|
|
@@ -533,14 +516,6 @@ function requireStringArg(args, key, code) {
|
|
|
533
516
|
}
|
|
534
517
|
return value;
|
|
535
518
|
}
|
|
536
|
-
function optionalStringArg(args, key, code) {
|
|
537
|
-
const value = args[key];
|
|
538
|
-
if (value === void 0 || value === null) return void 0;
|
|
539
|
-
if (typeof value !== "string") {
|
|
540
|
-
throw new HerdrLinkError(code, `"${key}" must be a string when present`);
|
|
541
|
-
}
|
|
542
|
-
return value;
|
|
543
|
-
}
|
|
544
519
|
function createSerializedLineWriter(stream) {
|
|
545
520
|
let tail = Promise.resolve();
|
|
546
521
|
return (line) => {
|
|
@@ -602,8 +577,7 @@ function createRequestHandler(deps = {}) {
|
|
|
602
577
|
case TOOL_SEND: {
|
|
603
578
|
const to = requireStringArg(args, "to", "PEER_NOT_FOUND");
|
|
604
579
|
const message = requireStringArg(args, "message", "SEND_FAILED");
|
|
605
|
-
const
|
|
606
|
-
const sent = await runSend(to, message, reply_to);
|
|
580
|
+
const sent = await runSend(to, message);
|
|
607
581
|
return { status: sent.status, id: sent.id, to: sent.to };
|
|
608
582
|
}
|
|
609
583
|
case TOOL_CLOSE: {
|
|
@@ -8,7 +8,6 @@ import { randomBytes } from "node:crypto";
|
|
|
8
8
|
// src/protocol.ts
|
|
9
9
|
var PROTOCOL_ID = "herdr-link/1";
|
|
10
10
|
var AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
11
|
-
var MESSAGE_ID_RE = /^hl_[a-z0-9]+_[a-z0-9]+$/;
|
|
12
11
|
var HERDR_LINK_GATEWAY = "herdr_link";
|
|
13
12
|
var AGENT_STATES = ["idle", "working", "blocked", "done", "unknown"];
|
|
14
13
|
function toAgentState(value) {
|
|
@@ -47,9 +46,6 @@ function createMessageId() {
|
|
|
47
46
|
function isValidAgentName(name) {
|
|
48
47
|
return AGENT_NAME_RE.test(name);
|
|
49
48
|
}
|
|
50
|
-
function isValidMessageId(id) {
|
|
51
|
-
return MESSAGE_ID_RE.test(id);
|
|
52
|
-
}
|
|
53
49
|
function buildEnvelope(input) {
|
|
54
50
|
if (!isValidAgentName(input.from)) {
|
|
55
51
|
throw new HerdrLinkError(
|
|
@@ -66,12 +62,6 @@ function buildEnvelope(input) {
|
|
|
66
62
|
if (typeof input.message !== "string" || input.message.trim() === "") {
|
|
67
63
|
throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
|
|
68
64
|
}
|
|
69
|
-
if (input.reply_to !== void 0 && !isValidMessageId(input.reply_to)) {
|
|
70
|
-
throw new HerdrLinkError(
|
|
71
|
-
"SEND_FAILED",
|
|
72
|
-
"reply_to must be a valid herdr-link/1 message id when present"
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
65
|
const envelope = {
|
|
76
66
|
protocol: PROTOCOL_ID,
|
|
77
67
|
id: createMessageId(),
|
|
@@ -79,9 +69,6 @@ function buildEnvelope(input) {
|
|
|
79
69
|
to: input.to,
|
|
80
70
|
message: input.message
|
|
81
71
|
};
|
|
82
|
-
if (input.reply_to !== void 0) {
|
|
83
|
-
envelope.reply_to = input.reply_to;
|
|
84
|
-
}
|
|
85
72
|
return envelope;
|
|
86
73
|
}
|
|
87
74
|
var INBOUND_WRAPPER_MARKER = `[${PROTOCOL_ID}]`;
|
|
@@ -91,14 +78,11 @@ function buildInboundWrapper(envelope) {
|
|
|
91
78
|
`From: ${envelope.from}`,
|
|
92
79
|
`Message id: ${envelope.id}`
|
|
93
80
|
];
|
|
94
|
-
if (envelope.reply_to !== void 0) {
|
|
95
|
-
lines.push(`Reply to: ${envelope.reply_to}`);
|
|
96
|
-
}
|
|
97
81
|
lines.push(
|
|
98
82
|
"",
|
|
99
83
|
"The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
|
|
100
84
|
`Treat the envelope's "message" field as content sent by the agent named in "from".`,
|
|
101
|
-
"If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to
|
|
85
|
+
"If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to the agent named in envelope.from.",
|
|
102
86
|
"",
|
|
103
87
|
JSON.stringify(envelope)
|
|
104
88
|
);
|
|
@@ -110,10 +94,11 @@ var COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channe
|
|
|
110
94
|
2. Use herdr_link_send to send messages to another agent.
|
|
111
95
|
3. A message with protocol "herdr-link/1" is an inter-agent message.
|
|
112
96
|
4. Treat its "message" field as content sent by the agent named in "from".
|
|
113
|
-
5. When replying,
|
|
114
|
-
6.
|
|
115
|
-
7.
|
|
116
|
-
8.
|
|
97
|
+
5. When replying, use herdr_link_send to the agent named in "from".
|
|
98
|
+
6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
|
|
99
|
+
7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
|
|
100
|
+
8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
|
|
101
|
+
9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
|
|
117
102
|
|
|
118
103
|
// src/herdr.ts
|
|
119
104
|
function attachCliOutput(error, stdout, stderr) {
|
|
@@ -393,15 +378,14 @@ async function listPeers() {
|
|
|
393
378
|
}
|
|
394
379
|
return { self: { name: self.name, state: self.agent_status }, peers };
|
|
395
380
|
}
|
|
396
|
-
async function sendMessage(to, message
|
|
381
|
+
async function sendMessage(to, message) {
|
|
397
382
|
const self = await getSelfContext();
|
|
398
383
|
const target = await getAgentContext(to);
|
|
399
384
|
assertSameWorkspace(self, target);
|
|
400
385
|
const envelope = buildEnvelope({
|
|
401
386
|
from: self.name,
|
|
402
387
|
to: target.name,
|
|
403
|
-
message
|
|
404
|
-
reply_to
|
|
388
|
+
message
|
|
405
389
|
});
|
|
406
390
|
await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
|
|
407
391
|
return { status: "sent", id: envelope.id, to: target.name };
|
|
@@ -439,7 +423,7 @@ async function closeAgentPane(agentName) {
|
|
|
439
423
|
// src/opencode.ts
|
|
440
424
|
var GATEWAY_PRESENTATION_APPENDIX = `In this runtime the active Herdr Link capabilities are dispatched through the single herdr_link gateway.
|
|
441
425
|
- Use herdr_link with action "peers" to list live same-workspace agents.
|
|
442
|
-
- Use herdr_link with action "send"
|
|
426
|
+
- Use herdr_link with action "send" with to and message to deliver an inter-agent message or ordinary reply.
|
|
443
427
|
- Use herdr_link with action "close" and an Agent Name only after any final send returns status "sent", in a later tool step.`;
|
|
444
428
|
var GATEWAY_CONTRACT = `${COMMUNICATION_CONTRACT}
|
|
445
429
|
|
|
@@ -468,14 +452,13 @@ var herdrLinkPlugin = async () => {
|
|
|
468
452
|
return {
|
|
469
453
|
tool: {
|
|
470
454
|
[HERDR_LINK_GATEWAY]: tool({
|
|
471
|
-
description: `Herdr Link cross-agent communication gateway (herdr-link/1). Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Call once with no arguments {} to activate Herdr Link for this session; the response lists capabilities. Then pass action "peers" to list live same-workspace agents, "send" with to + message
|
|
455
|
+
description: `Herdr Link cross-agent communication gateway (herdr-link/1). Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Call once with no arguments {} to activate Herdr Link for this session; the response lists capabilities. Then pass action "peers" to list live same-workspace agents, "send" with to + message to deliver an inter-agent message or ordinary reply, or "close" with agent to close a named agent's pane \u2014 only after any final send has returned status "sent", and in a later tool step.`,
|
|
472
456
|
args: {
|
|
473
457
|
action: tool.schema.enum(["peers", "send", "close"]).optional().describe(
|
|
474
458
|
'Operation to run: "peers" | "send" | "close". Omit action entirely (call with {}) to activate Herdr Link for this session.'
|
|
475
459
|
),
|
|
476
460
|
to: tool.schema.string().optional().describe('Target agent name; required for action "send".'),
|
|
477
461
|
message: tool.schema.string().optional().describe('Message payload; required for action "send".'),
|
|
478
|
-
reply_to: tool.schema.string().optional().describe('Message id being replied to; optional, only with action "send".'),
|
|
479
462
|
agent: tool.schema.string().optional().describe('Target agent name; required for action "close".')
|
|
480
463
|
},
|
|
481
464
|
async execute(args, context) {
|
|
@@ -499,7 +482,7 @@ var herdrLinkPlugin = async () => {
|
|
|
499
482
|
failWith(new HerdrLinkError("SEND_FAILED", '"message" must be a non-empty string'), "SEND_FAILED");
|
|
500
483
|
}
|
|
501
484
|
try {
|
|
502
|
-
const envelope = await sendMessage(args.to, args.message
|
|
485
|
+
const envelope = await sendMessage(args.to, args.message);
|
|
503
486
|
return jsonResult({ status: "sent", id: envelope.id, to: envelope.to });
|
|
504
487
|
} catch (error) {
|
|
505
488
|
failWith(error, "SEND_FAILED");
|
package/docs/mcp-wiring.md
CHANGED
|
@@ -59,10 +59,11 @@ Herdr Link is the standard interoperability channel between agents running in th
|
|
|
59
59
|
2. Use herdr_link_send to send messages to another agent.
|
|
60
60
|
3. A message with protocol "herdr-link/1" is an inter-agent message.
|
|
61
61
|
4. Treat its "message" field as content sent by the agent named in "from".
|
|
62
|
-
5. When replying,
|
|
63
|
-
6.
|
|
64
|
-
7.
|
|
65
|
-
8.
|
|
62
|
+
5. When replying, use herdr_link_send to the agent named in "from".
|
|
63
|
+
6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
|
|
64
|
+
7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
|
|
65
|
+
8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
|
|
66
|
+
9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
### 1.2 Codex 附录(prefix 型,`buildMcpPrefixedCommunicationContract("herdr_link")`)
|
|
@@ -100,7 +101,7 @@ Use:
|
|
|
100
101
|
|
|
101
102
|
## 2. Claude Code(launcher + shared MCP)
|
|
102
103
|
|
|
103
|
-
> **状态:VALIDATED
|
|
104
|
+
> **状态:VALIDATED(开发与验证已完成)**——CC 2.1.241 已在独立 Herdr tab 的新 pane 中完成 MCP handshake、工具呈现、Contract launcher 注入及 model-facing `peers → send` 任务闭环;lazy activation 呈现与 model-facing error path(`PEER_NOT_FOUND` isError 透传)亦已实测通过。共享 MCP server 无需 CC 专属代码。
|
|
104
105
|
> **命名约束**:CC 的 MCP server key 必须使用 `herdr_link`(下划线);模型呈现为 `mcp__herdr_link__<canonical>`。`serverInfo.name` 仍为 `herdr-link`,两者不可混用。allowlist 使用 `mcp__herdr_link__*`。
|
|
105
106
|
|
|
106
107
|
注册 MCP server(二选一):
|
|
@@ -272,4 +273,4 @@ printf '%s\n%s\n%s\n%s\n' \
|
|
|
272
273
|
| 5 | AGY hooks.json 写顶层 `"PreInvocation"` 不生效 | AGY 格式为按集成名分组:`{"<name>": {"PreInvocation": [...]}}`,多组同名事件顺序合并 | 用独立组名(如 `"herdr-link"`),与 herdr 官方集成组并存 |
|
|
273
274
|
| 6 | Host 对 `notifications/tools/list_changed` 不重新拉取 `tools/list` | 各 Host 刷新行为不一(工程确认项) | 保持 gateway 显式 action 分发(§总览 fallback 行);canonical 语义不变 |
|
|
274
275
|
|
|
275
|
-
E2E 记录:Codex TUI 与 AGY 曾各自完成 peers → send
|
|
276
|
+
E2E 记录:Codex TUI 与 AGY 曾各自完成 peers → send → brain 收 envelope 全闭环、`PEER_NOT_FOUND` isError 文本透传一致、`herdr_link_close` 返回 `{status:"closed",agent}`——**该记录取证于常驻三工具呈现时代**;lazy activation 呈现(dormant 单工具、listChanged、gateway dispatch)尚未有同等真机记录,发布前须按 §5 重跑。
|
package/package.json
CHANGED
package/src/herdr.ts
CHANGED
|
@@ -471,7 +471,6 @@ export async function listPeers(): Promise<PeerDirectory> {
|
|
|
471
471
|
export async function sendMessage(
|
|
472
472
|
to: string,
|
|
473
473
|
message: string,
|
|
474
|
-
reply_to?: string,
|
|
475
474
|
): Promise<{ status: "sent"; id: string; to: string }> {
|
|
476
475
|
const self = await getSelfContext();
|
|
477
476
|
const target = await getAgentContext(to);
|
|
@@ -481,7 +480,6 @@ export async function sendMessage(
|
|
|
481
480
|
from: self.name,
|
|
482
481
|
to: target.name,
|
|
483
482
|
message,
|
|
484
|
-
reply_to,
|
|
485
483
|
});
|
|
486
484
|
|
|
487
485
|
await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
|
package/src/mcp.ts
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
|
|
36
36
|
export const MCP_SERVER_NAME = "herdr-link";
|
|
37
37
|
/** Keep in sync with package.json "version" (serverInfo is informational). */
|
|
38
|
-
export const MCP_SERVER_VERSION = "0.2.
|
|
38
|
+
export const MCP_SERVER_VERSION = "0.2.1";
|
|
39
39
|
/** Fallback protocol version advertised when the client sends none. */
|
|
40
40
|
export const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
41
41
|
|
|
@@ -76,7 +76,7 @@ const NORMAL_MESSAGING_RULE = "Use Herdr Link, not raw Herdr CLI, pane ids, or t
|
|
|
76
76
|
const TOOL_DESCRIPTIONS: Record<CanonicalToolName, string> = {
|
|
77
77
|
[TOOL_PEERS]: `Discover live named peers in the same Herdr workspace; each state is advisory and Agent Names are the only addresses. ${NORMAL_MESSAGING_RULE}`,
|
|
78
78
|
[TOOL_SEND]:
|
|
79
|
-
`Send a herdr-link/1 message to a live named peer in your own workspace
|
|
79
|
+
`Send a herdr-link/1 message to a live named peer in your own workspace; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
|
|
80
80
|
[TOOL_CLOSE]:
|
|
81
81
|
`Close the pane currently hosting a named same-workspace agent. If you need to send a final message before closing, complete the send first and call close in a later tool step. ${NORMAL_MESSAGING_RULE}`,
|
|
82
82
|
};
|
|
@@ -88,7 +88,6 @@ const TOOL_INPUT_SCHEMAS: Record<CanonicalToolName, Record<string, unknown>> = {
|
|
|
88
88
|
properties: {
|
|
89
89
|
to: { type: "string", description: "Target Herdr agent name" },
|
|
90
90
|
message: { type: "string", description: "Message payload" },
|
|
91
|
-
reply_to: { type: "string", description: "Message id being replied to" },
|
|
92
91
|
},
|
|
93
92
|
required: ["to", "message"],
|
|
94
93
|
},
|
|
@@ -120,7 +119,7 @@ const GATEWAY_TOOL: { name: typeof HERDR_LINK_GATEWAY; description: string; inpu
|
|
|
120
119
|
"({}) to activate it for this session — the host is notified via notifications/tools/list_changed " +
|
|
121
120
|
"and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. " +
|
|
122
121
|
'If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, ' +
|
|
123
|
-
'{"action":"send","arguments":{"to":...,"message"
|
|
122
|
+
'{"action":"send","arguments":{"to":...,"message":...}}, or ' +
|
|
124
123
|
'{"action":"close","arguments":{"agent":...}}.',
|
|
125
124
|
inputSchema: {
|
|
126
125
|
type: "object",
|
|
@@ -187,19 +186,6 @@ function requireStringArg(args: Record<string, unknown>, key: string, code: Link
|
|
|
187
186
|
return value;
|
|
188
187
|
}
|
|
189
188
|
|
|
190
|
-
function optionalStringArg(
|
|
191
|
-
args: Record<string, unknown>,
|
|
192
|
-
key: string,
|
|
193
|
-
code: LinkErrorCode,
|
|
194
|
-
): string | undefined {
|
|
195
|
-
const value = args[key];
|
|
196
|
-
if (value === undefined || value === null) return undefined;
|
|
197
|
-
if (typeof value !== "string") {
|
|
198
|
-
throw new HerdrLinkError(code, `"${key}" must be a string when present`);
|
|
199
|
-
}
|
|
200
|
-
return value;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
189
|
/* ------------------------------------------------------------------ *
|
|
204
190
|
* stdout plumbing
|
|
205
191
|
*
|
|
@@ -314,8 +300,7 @@ export function createRequestHandler(
|
|
|
314
300
|
case TOOL_SEND: {
|
|
315
301
|
const to = requireStringArg(args, "to", "PEER_NOT_FOUND");
|
|
316
302
|
const message = requireStringArg(args, "message", "SEND_FAILED");
|
|
317
|
-
const
|
|
318
|
-
const sent = await runSend(to, message, reply_to);
|
|
303
|
+
const sent = await runSend(to, message);
|
|
319
304
|
return { status: sent.status, id: sent.id, to: sent.to };
|
|
320
305
|
}
|
|
321
306
|
case TOOL_CLOSE: {
|
package/src/opencode.ts
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
/** Runtime-specific active presentation; the semantic Contract remains canonical. */
|
|
43
43
|
const GATEWAY_PRESENTATION_APPENDIX = `In this runtime the active Herdr Link capabilities are dispatched through the single herdr_link gateway.
|
|
44
44
|
- Use herdr_link with action "peers" to list live same-workspace agents.
|
|
45
|
-
- Use herdr_link with action "send"
|
|
45
|
+
- Use herdr_link with action "send" with to and message to deliver an inter-agent message or ordinary reply.
|
|
46
46
|
- Use herdr_link with action "close" and an Agent Name only after any final send returns status "sent", in a later tool step.`;
|
|
47
47
|
|
|
48
48
|
const GATEWAY_CONTRACT = `${COMMUNICATION_CONTRACT}\n\n${GATEWAY_PRESENTATION_APPENDIX}`;
|
|
@@ -92,7 +92,7 @@ export const herdrLinkPlugin: Plugin = async () => {
|
|
|
92
92
|
description:
|
|
93
93
|
"Herdr Link cross-agent communication gateway (herdr-link/1). Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. " +
|
|
94
94
|
'Call once with no arguments {} to activate Herdr Link for this session; the response lists capabilities. ' +
|
|
95
|
-
'Then pass action "peers" to list live same-workspace agents, "send" with to + message
|
|
95
|
+
'Then pass action "peers" to list live same-workspace agents, "send" with to + message to deliver an inter-agent message or ordinary reply, or "close" with agent to close a named agent\'s pane — ' +
|
|
96
96
|
'only after any final send has returned status "sent", and in a later tool step.',
|
|
97
97
|
args: {
|
|
98
98
|
action: tool.schema
|
|
@@ -109,10 +109,6 @@ export const herdrLinkPlugin: Plugin = async () => {
|
|
|
109
109
|
.string()
|
|
110
110
|
.optional()
|
|
111
111
|
.describe('Message payload; required for action "send".'),
|
|
112
|
-
reply_to: tool.schema
|
|
113
|
-
.string()
|
|
114
|
-
.optional()
|
|
115
|
-
.describe('Message id being replied to; optional, only with action "send".'),
|
|
116
112
|
agent: tool.schema
|
|
117
113
|
.string()
|
|
118
114
|
.optional()
|
|
@@ -143,7 +139,7 @@ export const herdrLinkPlugin: Plugin = async () => {
|
|
|
143
139
|
failWith(new HerdrLinkError("SEND_FAILED", '"message" must be a non-empty string'), "SEND_FAILED");
|
|
144
140
|
}
|
|
145
141
|
try {
|
|
146
|
-
const envelope = await sendMessage(args.to, args.message
|
|
142
|
+
const envelope = await sendMessage(args.to, args.message);
|
|
147
143
|
return jsonResult({ status: "sent", id: envelope.id, to: envelope.to });
|
|
148
144
|
} catch (error) {
|
|
149
145
|
failWith(error, "SEND_FAILED");
|
package/src/pi.ts
CHANGED
|
@@ -38,7 +38,6 @@ const PEERS_PARAMETERS = Type.Object({});
|
|
|
38
38
|
const SEND_PARAMETERS = Type.Object({
|
|
39
39
|
to: Type.String(),
|
|
40
40
|
message: Type.String(),
|
|
41
|
-
reply_to: Type.Optional(Type.String()),
|
|
42
41
|
});
|
|
43
42
|
const CLOSE_PARAMETERS = Type.Object({
|
|
44
43
|
agent: Type.String(),
|
|
@@ -93,11 +92,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
93
92
|
name: "herdr_link_send",
|
|
94
93
|
label: "Herdr Link Send",
|
|
95
94
|
description:
|
|
96
|
-
'Send an inter-agent message (protocol herdr-link/1) to another agent through the cross-agent communication channel. status "sent" means Herdr accepted delivery, not that the peer finished its task.
|
|
95
|
+
'Send an inter-agent message (protocol herdr-link/1) to another agent through the cross-agent communication channel. status "sent" means Herdr accepted delivery, not that the peer finished its task.',
|
|
97
96
|
parameters: SEND_PARAMETERS,
|
|
98
97
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
99
98
|
try {
|
|
100
|
-
const envelope = await sendMessage(params.to, params.message
|
|
99
|
+
const envelope = await sendMessage(params.to, params.message);
|
|
101
100
|
return toolResult({ status: "sent", id: envelope.id, to: envelope.to });
|
|
102
101
|
} catch (error) {
|
|
103
102
|
rethrowToolError(error, "SEND_FAILED");
|
package/src/protocol.ts
CHANGED
|
@@ -89,7 +89,6 @@ export interface HerdrLinkEnvelope {
|
|
|
89
89
|
id: string;
|
|
90
90
|
from: string;
|
|
91
91
|
to: string;
|
|
92
|
-
reply_to?: string;
|
|
93
92
|
message: string;
|
|
94
93
|
}
|
|
95
94
|
|
|
@@ -149,7 +148,6 @@ export interface BuildEnvelopeInput {
|
|
|
149
148
|
from: string;
|
|
150
149
|
to: string;
|
|
151
150
|
message: string;
|
|
152
|
-
reply_to?: string;
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
/**
|
|
@@ -173,12 +171,6 @@ export function buildEnvelope(input: BuildEnvelopeInput): HerdrLinkEnvelope {
|
|
|
173
171
|
if (typeof input.message !== "string" || input.message.trim() === "") {
|
|
174
172
|
throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
|
|
175
173
|
}
|
|
176
|
-
if (input.reply_to !== undefined && !isValidMessageId(input.reply_to)) {
|
|
177
|
-
throw new HerdrLinkError(
|
|
178
|
-
"SEND_FAILED",
|
|
179
|
-
"reply_to must be a valid herdr-link/1 message id when present",
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
174
|
const envelope: HerdrLinkEnvelope = {
|
|
183
175
|
protocol: PROTOCOL_ID,
|
|
184
176
|
id: createMessageId(),
|
|
@@ -186,9 +178,6 @@ export function buildEnvelope(input: BuildEnvelopeInput): HerdrLinkEnvelope {
|
|
|
186
178
|
to: input.to,
|
|
187
179
|
message: input.message,
|
|
188
180
|
};
|
|
189
|
-
if (input.reply_to !== undefined) {
|
|
190
|
-
envelope.reply_to = input.reply_to;
|
|
191
|
-
}
|
|
192
181
|
return envelope;
|
|
193
182
|
}
|
|
194
183
|
|
|
@@ -209,8 +198,7 @@ export function isHerdrLinkEnvelope(value: unknown): value is HerdrLinkEnvelope
|
|
|
209
198
|
typeof v.to === "string" &&
|
|
210
199
|
isValidAgentName(v.to) &&
|
|
211
200
|
typeof v.message === "string" &&
|
|
212
|
-
v.message.trim() !== ""
|
|
213
|
-
(v.reply_to === undefined || (typeof v.reply_to === "string" && isValidMessageId(v.reply_to)))
|
|
201
|
+
v.message.trim() !== ""
|
|
214
202
|
);
|
|
215
203
|
}
|
|
216
204
|
|
|
@@ -219,7 +207,7 @@ export function isHerdrLinkEnvelope(value: unknown): value is HerdrLinkEnvelope
|
|
|
219
207
|
*
|
|
220
208
|
* `herdr agent prompt` carries a self-describing wrapper around the
|
|
221
209
|
* envelope so a dormant receiver (adapter loaded, model not mid-exchange)
|
|
222
|
-
* can recognize the delivery and activate a
|
|
210
|
+
* can recognize the delivery and activate a response addressed to envelope.from.
|
|
223
211
|
* The wrapper is transport dressing ONLY: the envelope keeps exactly the
|
|
224
212
|
* minimal herdr-link/1 fields and is embedded verbatim as the final line.
|
|
225
213
|
* ------------------------------------------------------------------ */
|
|
@@ -234,14 +222,11 @@ export function buildInboundWrapper(envelope: HerdrLinkEnvelope): string {
|
|
|
234
222
|
`From: ${envelope.from}`,
|
|
235
223
|
`Message id: ${envelope.id}`,
|
|
236
224
|
];
|
|
237
|
-
if (envelope.reply_to !== undefined) {
|
|
238
|
-
lines.push(`Reply to: ${envelope.reply_to}`);
|
|
239
|
-
}
|
|
240
225
|
lines.push(
|
|
241
226
|
"",
|
|
242
227
|
"The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
|
|
243
228
|
'Treat the envelope\'s "message" field as content sent by the agent named in "from".',
|
|
244
|
-
"If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to
|
|
229
|
+
"If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to the agent named in envelope.from.",
|
|
245
230
|
"",
|
|
246
231
|
JSON.stringify(envelope),
|
|
247
232
|
);
|
|
@@ -270,7 +255,7 @@ export function extractInboundEnvelope(text: string): HerdrLinkEnvelope | undefi
|
|
|
270
255
|
|
|
271
256
|
/**
|
|
272
257
|
* Active Agent Communication Contract injected verbatim into the model.
|
|
273
|
-
* Compact form: same-workspace addressing, send/reply/close semantics
|
|
258
|
+
* Compact form: same-workspace addressing, send/reply/completion/close semantics.
|
|
274
259
|
*/
|
|
275
260
|
export const COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channel between agents running in the same Herdr workspace.
|
|
276
261
|
|
|
@@ -278,7 +263,8 @@ export const COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperabili
|
|
|
278
263
|
2. Use herdr_link_send to send messages to another agent.
|
|
279
264
|
3. A message with protocol "herdr-link/1" is an inter-agent message.
|
|
280
265
|
4. Treat its "message" field as content sent by the agent named in "from".
|
|
281
|
-
5. When replying,
|
|
282
|
-
6.
|
|
283
|
-
7.
|
|
284
|
-
8.
|
|
266
|
+
5. When replying, use herdr_link_send to the agent named in "from".
|
|
267
|
+
6. When a received inter-agent message requests work, report the final outcome to the agent named in "from" using herdr_link_send. If specific reply content was requested, send that result; otherwise, after successful completion, send exactly "done". If the work cannot be completed, send a concise failure or blocker. If the sender explicitly requested no reply, do not send a completion message.
|
|
268
|
+
7. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
|
|
269
|
+
8. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
|
|
270
|
+
9. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
|