agent-comm-hub 0.9.0 → 0.10.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/README.md +71 -15
- package/README.zh.md +2 -2
- package/agents/SKILL.md +74 -12
- package/agents/install-all.ps1 +25 -160
- package/agents/registry.json +175 -7
- package/lib/cli.js +1954 -222
- package/lib/index.js +1682 -158
- package/lib/setup.js +184 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ itself).
|
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
agent-comm-hub setup
|
|
101
|
-
#
|
|
101
|
+
# (agents/install-all.ps1 is deprecated — it forwards to this command)
|
|
102
102
|
# undo: agent-comm-hub setup --remove
|
|
103
103
|
# list what is installed (no changes): agent-comm-hub discover
|
|
104
104
|
# configure a single agent: agent-comm-hub setup --agent codex
|
|
@@ -131,7 +131,7 @@ curl -X POST http://127.0.0.1:18764/mcp \
|
|
|
131
131
|
|
|
132
132
|
Each agent gets **one MCP server entry** pointing at `http://127.0.0.1:18764/mcp`, plus the shared English skill (`agents/SKILL.md`) that teaches it when and how to use the bridge tools. Templates live in [`agents/`](agents/README.md).
|
|
133
133
|
|
|
134
|
-
**One-shot incremental sync** (recommended): `
|
|
134
|
+
**One-shot incremental sync** (recommended): `agent-comm-hub setup` merges the
|
|
135
135
|
`agent-hub` entry into every installed agent's MCP config (mcode, opencode,
|
|
136
136
|
Kimi Code, Gemini CLI, Codex, zcode, DSH) and installs the skill — it only
|
|
137
137
|
touches the `agent-hub` key, backs up each file, and is idempotent. Claude Code
|
|
@@ -252,16 +252,31 @@ with `agent-comm-hub setup --remove` (or only refresh skills via
|
|
|
252
252
|
|
|
253
253
|
| Tool | Purpose |
|
|
254
254
|
|---|---|
|
|
255
|
-
| `bridge_register(peerId)` | Claim or rename your identity (auto-registered at connect with the client name; optional for a readable id like `opencode:myproject`) |
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
255
|
+
| `bridge_register(peerId, alias?)` | Claim or rename your identity (auto-registered at connect with the client name; optional for a readable id like `opencode:myproject`) |
|
|
256
|
+
| `bridge_rename(alias?, peer?, peerId?)` | Set your own display alias; renaming or true-renaming **another** peer needs manager rights |
|
|
257
|
+
| `bridge_unregister(peer?)` | Leave the hub (removes peer, queue, and session binding; stays off until an explicit register). With `peer`, kicks that peer (manager) |
|
|
258
|
+
| `bridge_chat(to, message, threadId?, priority?, ref?, expiresAt?)` | Send a chat message; `to: "all"` broadcasts. `expiresAt` is an absolute epoch-ms deadline: a message still undelivered after it is dropped and counted, not delivered late. `priority` (0\|1\|2, default 0) orders the recipient's mailbox |
|
|
259
|
+
| `bridge_notice(to, message, level?)` | Send a system-flavoured notice (`info`/`warn`/`error`) — read as a status, not as chat; creates no task |
|
|
260
|
+
| `bridge_task(to, prompt, context?, deliverable?, timeoutMs?, threadId?, ref?, expiresAt?)` | Delegate a structured task (`timeoutMs` is the assignee's SLA once it arrives; `expiresAt` is the delivery deadline) |
|
|
261
|
+
| `bridge_ack(ref, status, note?, progress?)` | Acknowledge a task (`accepted`/`working`/`rejected`/`done`/`failed`), routed back to the original sender |
|
|
262
|
+
| `bridge_wait(from?, ref?, threadId?, timeoutMs?, ack?, maxMessages?)` | Long-poll for the next message (default 30 s). `ack: "manual"` leases it instead of consuming it; `maxMessages > 1` returns `{type:"messages", messages:[…]}` in one call |
|
|
263
|
+
| `bridge_poll(from?, ack?)` | Non-blocking drain of queued messages (`ack: "manual"` leases them) |
|
|
264
|
+
| `bridge_commit(ids)` | Acknowledge leased messages; uncommitted ones come back when the lease expires |
|
|
265
|
+
| `bridge_nack(ids)` | Give leased (or dead-lettered) messages back immediately — use when handling failed |
|
|
262
266
|
| `bridge_status()` | Hub health: peers with connected/queued/waiting state |
|
|
263
|
-
| `bridge_peers()` | Who is online |
|
|
264
|
-
| `bridge_history(peer?, limit?)` | Recent messages (context refresh after reconnect) |
|
|
267
|
+
| `bridge_peers()` | Who is online, how reachable (`online`/`idle`/`parked`) and how much is queued |
|
|
268
|
+
| `bridge_history(peer?, limit?, channel?)` | Recent messages (context refresh after reconnect) |
|
|
269
|
+
| `bridge_tasks(role?, status?, limit?, peer?)` | Task ledger rows you sent or received |
|
|
270
|
+
| `bridge_task_status(ref)` | One task's state, per-assignee rows and the full ack timeline (also carries the A2A aliases `taskId` / `contextId` / `a2aState`) |
|
|
271
|
+
| `bridge_task_cancel(ref, reason?)` | Cancel a task — either side may; routed to the other side and terminal |
|
|
272
|
+
| `bridge_task_input(ref, input)` | Answer an `input-required` interrupt; the task resumes to `working` |
|
|
273
|
+
| `bridge_announce(card)` | Say what you can do (skills, `acceptsTasks`, modes, runner) |
|
|
274
|
+
| `bridge_discover(skill?, tag?, acceptsTasks?, idleOnly?)` | Find a peer to hand work to, with its current load |
|
|
275
|
+
| `bridge_group_create` · `bridge_group_send` · `bridge_group_list` · `bridge_group_delete` · `bridge_group_add_member` · `bridge_group_remove_member` | Named channels for a subset of peers |
|
|
276
|
+
|
|
277
|
+
That is the full 26 message + group tools; the 11 control tools are below. (37 in total and symmetric on every side — see [ARCHITECTURE.md](ARCHITECTURE.md) §4.)
|
|
278
|
+
|
|
279
|
+
Two peer ids are reserved and cannot be registered or renamed into: `all` (broadcast) and `hub` (the hub's own drop notices — `bridge_wait { from: "hub" }` subscribes to them).
|
|
265
280
|
|
|
266
281
|
### herdr control tools (optional)
|
|
267
282
|
|
|
@@ -294,6 +309,7 @@ socket — physical input, read output:
|
|
|
294
309
|
| `bridge_pane_send(target, text, enter?)` | Type text into a pane (slash commands execute; Enter submits by default) |
|
|
295
310
|
| `bridge_pane_keys(target, keys)` | Raw key presses to any pane |
|
|
296
311
|
| `bridge_pane_read(target, lines?, source?)` | Read a pane's recent output |
|
|
312
|
+
| `bridge_pane_wait(target, until?, timeoutMs?)` | Wait for a pane to reach a state (idle / working / blocked / done) |
|
|
297
313
|
|
|
298
314
|
Verified live: a MiniMax Code session was driven end-to-end through the hub —
|
|
299
315
|
prompt injected via `bridge_pane_send`, reply collected via
|
|
@@ -317,7 +333,17 @@ continuous. Renaming yourself is open to everyone; renaming/kicking
|
|
|
317
333
|
history (`bridge_history { peer }` / `peer: "all"`) requires the **manager**
|
|
318
334
|
role (`--manager-peers`, default `agent-hub-cli,hub-admin` — the desktop GUI
|
|
319
335
|
and the web console identities).
|
|
320
|
-
|
|
336
|
+
|
|
337
|
+
With no credential configured this is a convention on top of the loopback trust
|
|
338
|
+
model, not authentication — the peer id is simply whoever claimed it first. Set
|
|
339
|
+
`--manager-token <secret>` (or drop the secret in `<state-file dir>/manager.token`)
|
|
340
|
+
to make manager PRIVILEGES require it: rename/kick/full-history/task-board and the
|
|
341
|
+
SSE relay then need `Authorization: Bearer <secret>`, and a credential-less client
|
|
342
|
+
that claims a manager id gets no power. The id itself is not reserved — the
|
|
343
|
+
desktop GUI identifies itself as `agent-hub-cli`, so reserving it would stop the
|
|
344
|
+
GUI from connecting. Note that claiming the id still occupies its identity and
|
|
345
|
+
mailbox even without the credential, and that in remote mode (`--auth-tokens`) the
|
|
346
|
+
token table owns the `Authorization` header, so `--manager-token` is ignored there.
|
|
321
347
|
|
|
322
348
|
Rosters persist: `--state-file` (default `~/.agent-comm-hub/roster.json`,
|
|
323
349
|
`off` to disable) keeps aliases and client info across hub restarts.
|
|
@@ -347,12 +373,42 @@ agent-comm-hub service install|uninstall [options] one-shot auto-start
|
|
|
347
373
|
--wait-timeout-ms <n> Long-poll ceiling for bridge_wait (default 60000)
|
|
348
374
|
--default-wait-ms <n> bridge_wait default budget (default 30000)
|
|
349
375
|
--connected-window-ms <n> Peer counts as active within this window (default 30000)
|
|
350
|
-
--peer-idle-timeout-ms <n>
|
|
376
|
+
--peer-idle-timeout-ms <n> Park (not delete) idle peers after this; 0 disables the GC
|
|
377
|
+
entirely (default 600000)
|
|
378
|
+
--parked-ttl-ms <n> Drop a parked mailbox after this; 0 disables expiry
|
|
379
|
+
(default 604800000 = 7 days)
|
|
380
|
+
--rate-per-min <n> Per-peer mailbox rate limit (messages per minute); 0 (default)
|
|
381
|
+
is unlimited. Guards a recipient against a broadcast storm;
|
|
382
|
+
excess is counted as `droppedRate` in the roster
|
|
383
|
+
--db <path> SQLite state file for history/mailboxes/roster/groups/tasks
|
|
384
|
+
(default ~/.agent-comm-hub/state.db; "off" = memory + JSON roster)
|
|
385
|
+
--auth-tokens <file> REMOTE MODE: require `Authorization: Bearer <token>`; the token,
|
|
386
|
+
not the client name, decides the peer id. Also refuses a
|
|
387
|
+
non-loopback --host unless this is set.
|
|
388
|
+
--allow-join (remote mode) accept token-less walk-in peers, each on a unique
|
|
389
|
+
id; LAN/VPN only — keep it off on the public internet.
|
|
390
|
+
--heartbeat-ms <n> SSE heartbeat cadence (default 20000)
|
|
391
|
+
--runner <json> Executor for a peer that is NOT polling: one JSON object
|
|
392
|
+
or an array, e.g. {"peer":"codex","kind":"cli",
|
|
393
|
+
"command":"codex","args":["exec","{prompt}"]} or
|
|
394
|
+
{"peer":"mcode","kind":"herdr"}. Off by default; the
|
|
395
|
+
runner's outcome is written back as that peer's ack.
|
|
351
396
|
--herdr-bin <path> herdr CLI binary for bridge_agent_* control tools
|
|
352
397
|
(default herdr, resolved via PATH)
|
|
353
398
|
--herdr-timeout-ms <n> Default cap for one herdr call in ms (default 30000)
|
|
354
399
|
--manager-peers <ids> Comma-separated roster managers, or "all"
|
|
355
400
|
(default agent-hub-cli,hub-admin — desktop GUI + web console)
|
|
401
|
+
--manager-token <secret> Require this secret (Authorization: Bearer <secret>) for hub
|
|
402
|
+
manager PRIVILEGES (rename/kick/full history/relay); otherwise
|
|
403
|
+
<state-file dir>/manager.token is read when present. The peer
|
|
404
|
+
id itself is not reserved. Ignored with --auth-tokens.
|
|
405
|
+
--allow-origins <list> Comma-separated browser origins allowed to call the MCP
|
|
406
|
+
endpoint (e.g. https://hub.example.com). Requests without
|
|
407
|
+
an Origin header and loopback origins are always allowed;
|
|
408
|
+
anything else gets 403 (the MCP spec's DNS-rebinding rule).
|
|
409
|
+
--insecure Allow --host to bind a non-loopback address with NO token
|
|
410
|
+
table. Refused by default: without --auth-tokens the hub
|
|
411
|
+
would trust every client that can reach the port.
|
|
356
412
|
--state-file <path> Roster persistence file
|
|
357
413
|
(default ~/.agent-comm-hub/roster.json; "off" = memory only)
|
|
358
414
|
--url <u> / --server-name <n> / --remove / --dry-run (setup/service/status)
|
|
@@ -430,7 +486,7 @@ const hub = startHub({ port: 18764 }, console) // returns { hub, registry, serve
|
|
|
430
486
|
```bash
|
|
431
487
|
pnpm install
|
|
432
488
|
pnpm typecheck # tsc --noEmit (strict)
|
|
433
|
-
pnpm test # test suite (
|
|
489
|
+
pnpm test # test suite (314 checks: 169 smoke + 46 setup + 11 ops + 35 herdr + 33 discover + 20 e2e)
|
|
434
490
|
npm run build:admin # admin/ workspace (npm) → single-file assets/admin.html
|
|
435
491
|
pnpm run build # esbuild → lib/{cli,index,setup}.js (zero deps)
|
|
436
492
|
pnpm pack # build + npm pack (publishing artifact)
|
|
@@ -481,4 +537,4 @@ Tests cover registration, duplicate rejection, chat routing, sender-filtered wai
|
|
|
481
537
|
|
|
482
538
|
## License
|
|
483
539
|
|
|
484
|
-
MIT — see [LICENSE](LICENSE). Contributions welcome: keep the
|
|
540
|
+
MIT — see [LICENSE](LICENSE). Contributions welcome: keep the 314-check suite green (`pnpm test`) and zero runtime dependencies. Architecture: [ARCHITECTURE.md](ARCHITECTURE.md).
|
package/README.zh.md
CHANGED
|
@@ -93,7 +93,7 @@ agent-comm-hub status # hub 是否在跑?谁在线?
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
agent-comm-hub setup
|
|
96
|
-
#
|
|
96
|
+
# (agents/install-all.ps1 已废弃,它只是转发到这个命令)
|
|
97
97
|
# 卸载:agent-comm-hub setup --remove
|
|
98
98
|
# 查看本机装了什么(只读):agent-comm-hub discover
|
|
99
99
|
# 只配置某一个:agent-comm-hub setup --agent codex
|
|
@@ -276,7 +276,7 @@ const hub = startHub({ port: 18764 }, console) // 返回 { hub, registry, server
|
|
|
276
276
|
```bash
|
|
277
277
|
pnpm install
|
|
278
278
|
pnpm typecheck # tsc --noEmit(strict)
|
|
279
|
-
pnpm test # 测试套件(
|
|
279
|
+
pnpm test # 测试套件(314 项:169 冒烟 + 46 安装器 + 11 运维 + 35 herdr + 33 发现 + 20 e2e)
|
|
280
280
|
npm run build:admin # admin/ 工作区(用 npm)→ 单文件 assets/admin.html
|
|
281
281
|
pnpm run build # esbuild → lib/{cli,index,setup}.js(零依赖)
|
|
282
282
|
pnpm pack # 构建 + npm pack(发布产物)
|
package/agents/SKILL.md
CHANGED
|
@@ -6,7 +6,8 @@ description: >-
|
|
|
6
6
|
chat, sending messages or tasks to another agent, waiting for another
|
|
7
7
|
agent's reply, or forwarding results between agents. Tools:
|
|
8
8
|
bridge_register / bridge_chat / bridge_task / bridge_ack / bridge_wait /
|
|
9
|
-
bridge_poll / bridge_status / bridge_peers / bridge_history
|
|
9
|
+
bridge_poll / bridge_status / bridge_peers / bridge_history /
|
|
10
|
+
bridge_commit / bridge_nack.
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
# agent-comm-hub usage
|
|
@@ -23,26 +24,59 @@ Optional: call `bridge_register(peerId)` to claim a readable id
|
|
|
23
24
|
|
|
24
25
|
## Tools
|
|
25
26
|
|
|
26
|
-
- `bridge_chat(to, message, threadId?, ref?)` — send a chat to a peer,
|
|
27
|
-
id, or `"all"` (broadcast). `threadId` groups a multi-turn dialog; `ref`
|
|
28
|
-
marks a reply-to.
|
|
29
|
-
-
|
|
27
|
+
- `bridge_chat(to, message, threadId?, ref?, expiresAt?)` — send a chat to a peer,
|
|
28
|
+
group id, or `"all"` (broadcast). `threadId` groups a multi-turn dialog; `ref`
|
|
29
|
+
marks a reply-to (chain depth is bounded — see Notes). `expiresAt` is an
|
|
30
|
+
absolute epoch-ms deadline: a message still undelivered when it passes is
|
|
31
|
+
dropped (counted as `droppedExpired`) instead of arriving late.
|
|
32
|
+
- `bridge_notice(to, message, level?)` — send a system-flavoured line
|
|
33
|
+
("your mailbox dropped 3 messages", "hub restarting"). `level` is
|
|
34
|
+
`info` | `warn` | `error`. Use it instead of `bridge_chat` when the recipient
|
|
35
|
+
should read it as a status, not as a peer talking; it creates no task.
|
|
36
|
+
- `bridge_task(to, prompt, context?, deliverable?, timeoutMs?, threadId?, ref?, expiresAt?)` —
|
|
30
37
|
delegate a structured task. `timeoutMs` is a soft SLA: the hub marks the
|
|
31
|
-
task `timeout` if no terminal ack arrives in time.
|
|
38
|
+
task `timeout` if no terminal ack arrives in time. `ref` names the task this
|
|
39
|
+
one answers; a task answering a task is a re-delegation, and that chain is
|
|
40
|
+
capped at 5 hops.
|
|
32
41
|
- `bridge_ack(ref, status, note?, progress?)` — acknowledge a task.
|
|
33
42
|
Statuses: `accepted` | `working` | `rejected` | `done` | `failed`.
|
|
34
43
|
`working` + `progress` (0–100) report intermediate state. Terminal
|
|
35
44
|
statuses lock the task (later acks are recorded on the timeline but do
|
|
36
45
|
not reopen it). The ack is auto-routed to the original sender of `ref`.
|
|
37
46
|
- `bridge_task_status(ref)` — one task's ledger state, per-assignee state,
|
|
38
|
-
and full ack timeline
|
|
47
|
+
and full ack timeline (also carries the A2A aliases `taskId` / `contextId` /
|
|
48
|
+
`a2aState`).
|
|
39
49
|
- `bridge_tasks(role?, status?, limit?)` — list tasks you sent or received.
|
|
40
|
-
- `
|
|
50
|
+
- `bridge_task_cancel(ref, reason?)` — cancel a task. Either side may: the sender
|
|
51
|
+
stops work it no longer wants, an assignee reports it will not finish. Routed to
|
|
52
|
+
the other side; `canceled` is terminal and a later ack cannot reopen it.
|
|
53
|
+
- `bridge_task_input(ref, input)` — answer a task whose assignee acked
|
|
54
|
+
`input-required`. Only the SENDER calls this; the answer reaches the waiting
|
|
55
|
+
assignee and the task resumes to `working`.
|
|
56
|
+
- `bridge_announce(card)` — say what you can do: skills, whether you accept tasks,
|
|
57
|
+
how many at once, input/output modes, and how you are executed when not polling.
|
|
58
|
+
Only peers that set `acceptsTasks: true` are offered by `bridge_discover`.
|
|
59
|
+
- `bridge_discover(skill?, tag?, acceptsTasks?, idleOnly?)` — find a peer to hand
|
|
60
|
+
work to. Returns matches with their current load (`queued` / `waiting` /
|
|
61
|
+
`running`); by default only peers that accept tasks.
|
|
62
|
+
- `bridge_wait(from?, ref?, threadId?, timeoutMs?, ack?, maxMessages?)` — long-poll for the next
|
|
41
63
|
message (default 30 s; `ref` waits for that task's ack; `threadId` narrows
|
|
42
|
-
to one dialog). Loop it to hold a real-time conversation.
|
|
43
|
-
|
|
64
|
+
to one dialog). Loop it to hold a real-time conversation. `ack: "manual"`
|
|
65
|
+
LEASES the message instead of consuming it.
|
|
66
|
+
- `bridge_poll(from?, ack?)` — non-blocking drain of every queued message.
|
|
67
|
+
- `bridge_commit(ids)` — confirm you have finished with leased messages. Until you
|
|
68
|
+
do, they are handed out again when the lease expires (with `attempts` growing),
|
|
69
|
+
so `ack: "manual"` + `bridge_commit` is the at-least-once path: use it when
|
|
70
|
+
losing a message would be worse than handling one twice. A message that keeps
|
|
71
|
+
being handed out without a commit is dead-lettered rather than looping forever
|
|
72
|
+
(`bridge_status` shows `inFlight` / `deadLettered`).
|
|
73
|
+
- `bridge_nack(ids)` — give leased messages back now (handling failed), or revive
|
|
74
|
+
a dead-lettered one.
|
|
44
75
|
- `bridge_status()` / `bridge_peers()` — hub health and who is online.
|
|
45
|
-
- `bridge_history(peer?, limit?, channel?)` — recent messages (newest
|
|
76
|
+
- `bridge_history(peer?, limit?, channel?, since?)` — recent messages (newest
|
|
77
|
+
first). `since` is a resume cursor: record `bridge_status().lastSeq`, then read
|
|
78
|
+
only what happened after it, with no repeats. A small `limit` still truncates,
|
|
79
|
+
so loop until you get an empty page.
|
|
46
80
|
- `bridge_group_create/send/list/delete` + `add_member`/`remove_member` —
|
|
47
81
|
named channels for a subset of peers.
|
|
48
82
|
|
|
@@ -104,4 +138,32 @@ Follow the scenario directly (tool names stay as-is):
|
|
|
104
138
|
- Cadence: use 10–30 s short polls when the peer is active; check
|
|
105
139
|
`bridge_peers()` when unsure.
|
|
106
140
|
- Task terminal states (`done`/`failed`/`rejected`/`timeout`) cannot be
|
|
107
|
-
reopened; start a new task instead of re-acking.
|
|
141
|
+
reopened; start a new task instead of re-acking.
|
|
142
|
+
- **Reply chains are counted, not enforced — nothing is ever refused.**
|
|
143
|
+
Every message carrying `ref` sits one hop deeper than the message it points at.
|
|
144
|
+
A plain conversation (chat / ack replies) counts up to **50** hops; a **task
|
|
145
|
+
re-delegation chain** — a task replying to a task — counts up to **5**. Past
|
|
146
|
+
either ceiling `hop` simply stops counting: the message and its `ref` are
|
|
147
|
+
delivered unchanged, so correlation never breaks and `bridge_wait({ ref })`
|
|
148
|
+
keeps working on arbitrarily long chains. Read `hop` as “how deep this chain
|
|
149
|
+
is, capped” — reaching the ceiling is a hint that the conversation has become
|
|
150
|
+
a loop, not an error to recover from.
|
|
151
|
+
- **Nothing is dropped silently any more.** If your mailbox overflows (`maxQueue`)
|
|
152
|
+
or a message hits its `expiresAt` before you read it, the hub counts it and
|
|
153
|
+
`bridge_status` / `bridge_peers` show `droppedOverflow` / `droppedExpired` on
|
|
154
|
+
your row. When you are waiting you may also receive a `notice` whose `from` is
|
|
155
|
+
the reserved id `hub` — `bridge_wait { from: "hub" }` subscribes to exactly
|
|
156
|
+
those. `all` and `hub` are reserved: you cannot register or rename into them.
|
|
157
|
+
- **Need at-least-once instead of best-effort?** Take messages with
|
|
158
|
+
`bridge_wait { ack: "manual" }` (or `bridge_poll { ack: "manual" }`) and call
|
|
159
|
+
`bridge_commit` once you have really handled them. Uncommitted messages come
|
|
160
|
+
back when the lease expires, with `attempts` incremented; `bridge_nack` gives
|
|
161
|
+
one back immediately. After too many hand-outs a message is dead-lettered
|
|
162
|
+
instead of looping (see `deadLettered` in the roster).
|
|
163
|
+
- **SSE pushes are a client-runtime event channel, not a wake-up.** A hub
|
|
164
|
+
notification arrives on the stream you subscribed to; it does NOT start a model
|
|
165
|
+
turn, and an agent that is not polling will not be woken by it. Waking an agent
|
|
166
|
+
that is not in a `bridge_wait` loop is the job of a *runner* — the hub runs the
|
|
167
|
+
configured executor for that peer and writes the outcome back as its ack
|
|
168
|
+
(`--runner`; see the hub README). If you want to be woken, either loop on
|
|
169
|
+
`bridge_wait` or ask the operator to configure a runner for you.
|
package/agents/install-all.ps1
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
|
|
4
|
-
incrementally. Only the "agent-hub" server key is added/updated; all other
|
|
5
|
-
content is preserved byte-for-byte semantically, each file is backed up first,
|
|
6
|
-
and re-running is a no-op. -Remove undoes everything.
|
|
3
|
+
DEPRECATED shim. Use `agent-comm-hub setup` instead.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- Codex ~/.codex/config.toml (appends the section if missing)
|
|
14
|
-
- zcode ~/.zcode/cli/config.json (mcp.servers)
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
This script used to carry its own copy of the agent list and the merge logic.
|
|
7
|
+
That copy drifted: it synced the skill into eight fixed directories with no
|
|
8
|
+
"is this agent installed?" check, so running it on a machine with two agents
|
|
9
|
+
created skill folders for six agents that were not there.
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
`agent-comm-hub setup` is registry-driven (`agents/registry.json`), only
|
|
12
|
+
touches agents that are actually installed, backs every file up, and is
|
|
13
|
+
covered by the test suite. It is the supported path.
|
|
14
|
+
|
|
15
|
+
This file is kept so existing callers keep working: it forwards to that
|
|
16
|
+
command with the same three switches. It no longer contains an agent list.
|
|
21
17
|
|
|
22
18
|
.EXAMPLE
|
|
23
19
|
powershell -ExecutionPolicy Bypass -File install-all.ps1
|
|
@@ -30,154 +26,23 @@ param(
|
|
|
30
26
|
)
|
|
31
27
|
|
|
32
28
|
$ErrorActionPreference = 'Stop'
|
|
33
|
-
$userHome = $env:USERPROFILE
|
|
34
|
-
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
35
|
-
$skillSrc = Join-Path $PSScriptRoot 'SKILL.md'
|
|
36
|
-
|
|
37
|
-
function Write-Step($text) { Write-Host "[agent-comm-hub] $text" -ForegroundColor Cyan }
|
|
38
|
-
|
|
39
|
-
function Read-Json($path) {
|
|
40
|
-
if (-not (Test-Path $path)) { return $null }
|
|
41
|
-
try { return [System.IO.File]::ReadAllText($path, [System.Text.Encoding]::UTF8) | ConvertFrom-Json }
|
|
42
|
-
catch { throw "cannot parse JSON: $path ($($_.Exception.Message))" }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function Write-JsonNoBom($path, $value) {
|
|
46
|
-
$dir = Split-Path $path
|
|
47
|
-
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
|
48
|
-
[System.IO.File]::WriteAllText($path, ($value | ConvertTo-Json -Depth 50), $utf8NoBom)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function Backup($path) {
|
|
52
|
-
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
|
53
|
-
Copy-Item $path "$path.bak-$stamp" -Force -ErrorAction SilentlyContinue
|
|
54
|
-
return "$(Split-Path $path -Leaf).bak-$stamp"
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function Sync-Skill($agentName, $skillDir) {
|
|
58
|
-
if ($Remove) {
|
|
59
|
-
if (Test-Path $skillDir) { Remove-Item $skillDir -Recurse -Force; Write-Step " ${agentName}: removed skill $skillDir" }
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
if (-not (Test-Path $skillSrc)) { Write-Step " ${agentName}: SKILL.md source missing (skipped)"; return }
|
|
63
|
-
New-Item -ItemType Directory -Path $skillDir -Force | Out-Null
|
|
64
|
-
Copy-Item $skillSrc (Join-Path $skillDir 'SKILL.md') -Force
|
|
65
|
-
Write-Step " ${agentName}: skill -> $skillDir\SKILL.md"
|
|
66
|
-
}
|
|
67
29
|
|
|
68
|
-
|
|
69
|
-
if (-not (Test-Path $file)) { Write-Step " ${label}: not installed ($file missing, skipped)"; return }
|
|
70
|
-
try {
|
|
71
|
-
$doc = Read-Json $file
|
|
72
|
-
if ($null -eq $doc) { Write-Step " ${label}: empty config (skipped)"; return }
|
|
73
|
-
# Resolve (creating when missing) a dotted section path like 'mcp.servers'.
|
|
74
|
-
$servers = $doc
|
|
75
|
-
foreach ($part in $sectionName.Split('.')) {
|
|
76
|
-
if ($null -eq $servers.$part) {
|
|
77
|
-
$servers | Add-Member -NotePropertyName $part -NotePropertyValue ([ordered]@{}) -Force
|
|
78
|
-
}
|
|
79
|
-
$servers = $servers.$part
|
|
80
|
-
}
|
|
81
|
-
$changed = $false
|
|
82
|
-
if ($servers.PSObject.Properties.Name -contains $ServerName) {
|
|
83
|
-
$existing = $servers.$ServerName
|
|
84
|
-
$changed = ($existing.url -ne ${Url})
|
|
85
|
-
} else {
|
|
86
|
-
$changed = $true
|
|
87
|
-
}
|
|
88
|
-
if ($Remove) {
|
|
89
|
-
if ($servers.PSObject.Properties.Name -contains $ServerName) {
|
|
90
|
-
$servers.PSObject.Properties.Remove($ServerName)
|
|
91
|
-
$bak = Backup $file
|
|
92
|
-
Write-JsonNoBom $file $doc
|
|
93
|
-
Write-Step " ${label}: removed '$ServerName' from $file (backup: $bak)"
|
|
94
|
-
} else {
|
|
95
|
-
Write-Step " ${label}: '$ServerName' not present in $file"
|
|
96
|
-
}
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
if (-not $changed) { Write-Step " ${label}: '$ServerName' already set -> ${Url} ($file unchanged)"; return }
|
|
100
|
-
$bak = Backup $file
|
|
101
|
-
$servers | Add-Member -NotePropertyName $ServerName -NotePropertyValue $entry -Force
|
|
102
|
-
Write-JsonNoBom $file $doc
|
|
103
|
-
Write-Step " ${label}: merged '$ServerName' -> ${Url} into $file (backup: $bak)"
|
|
104
|
-
} catch {
|
|
105
|
-
Write-Step " ${label}: SKIPPED — $($_.Exception.Message)"
|
|
106
|
-
}
|
|
107
|
-
}
|
|
30
|
+
Write-Warning 'install-all.ps1 is deprecated; use `agent-comm-hub setup` (this forwards to it).'
|
|
108
31
|
|
|
109
|
-
|
|
32
|
+
# Prefer an installed CLI; fall back to this checkout's built bundle.
|
|
33
|
+
$cli = Get-Command agent-comm-hub -ErrorAction SilentlyContinue
|
|
34
|
+
$setupArgs = @('setup', '--url', $Url, '--server-name', $ServerName)
|
|
35
|
+
if ($Remove) { $setupArgs += '--remove' }
|
|
110
36
|
|
|
111
|
-
|
|
112
|
-
$
|
|
113
|
-
|
|
114
|
-
description = 'agent-comm-hub: talk to every other agent connected to the hub (bridge_register auto, bridge_chat/task/ack/wait/poll/status/peers/history).'
|
|
115
|
-
}
|
|
116
|
-
Write-Step '- mcode'
|
|
117
|
-
foreach ($f in @((Join-Path $userHome '.minimax\mcp.json'), (Join-Path $userHome '.minimax\mcp\mcp.json'))) {
|
|
118
|
-
Merge-JsonServer 'mcode' $f 'mcpServers' $mcodeEntry
|
|
37
|
+
if ($cli) {
|
|
38
|
+
& $cli.Source @setupArgs
|
|
39
|
+
exit $LASTEXITCODE
|
|
119
40
|
}
|
|
120
41
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
# ---------- Kimi Code: mcpServers at root of ~/.kimi-code/mcp.json
|
|
127
|
-
$kimiEntry = [ordered]@{ transport = 'http'; url = $Url; startupTimeoutMs = 30000; toolTimeoutMs = 120000 }
|
|
128
|
-
Write-Step '- kimi-code'
|
|
129
|
-
Merge-JsonServer 'kimi-code' (Join-Path $userHome '.kimi-code\mcp.json') 'mcpServers' $kimiEntry
|
|
130
|
-
|
|
131
|
-
# ---------- Gemini CLI: mcpServers in ~/.gemini/settings.json
|
|
132
|
-
$geminiEntry = [ordered]@{ type = 'http'; url = $Url }
|
|
133
|
-
Write-Step '- gemini-cli'
|
|
134
|
-
Merge-JsonServer 'gemini-cli' (Join-Path $userHome '.gemini\settings.json') 'mcpServers' $geminiEntry
|
|
135
|
-
|
|
136
|
-
# ---------- Codex: append [mcp_servers.<name>] to ~/.codex/config.toml (incremental append only)
|
|
137
|
-
$codexFile = Join-Path $userHome '.codex\config.toml'
|
|
138
|
-
Write-Step '- codex'
|
|
139
|
-
if (Test-Path $codexFile) {
|
|
140
|
-
$codexText = [System.IO.File]::ReadAllText($codexFile, [System.Text.Encoding]::UTF8)
|
|
141
|
-
$section = "[mcp_servers.$ServerName]"
|
|
142
|
-
if ($Remove) {
|
|
143
|
-
if ($codexText -match "(?m)^\[mcp_servers\.$([regex]::Escape($ServerName))\]") {
|
|
144
|
-
$bak = Backup $codexFile
|
|
145
|
-
$clean = [regex]::Replace($codexText, "(?m)^\[mcp_servers\.$([regex]::Escape($ServerName))\][^\r\n]*(\r?\n(?!\[).*)*(\r?\n)?", '')
|
|
146
|
-
[System.IO.File]::WriteAllText($codexFile, $clean, $utf8NoBom)
|
|
147
|
-
Write-Step " codex: removed '$ServerName' section from $codexFile (backup: $bak)"
|
|
148
|
-
} else {
|
|
149
|
-
Write-Step " codex: '$ServerName' section not present in $codexFile"
|
|
150
|
-
}
|
|
151
|
-
} elseif ($codexText -match "(?m)^\[mcp_servers\.$([regex]::Escape($ServerName))\]") {
|
|
152
|
-
Write-Step " codex: '$ServerName' section already present ($codexFile unchanged)"
|
|
153
|
-
} else {
|
|
154
|
-
$bak = Backup $codexFile
|
|
155
|
-
$block = "`n[mcp_servers.$ServerName]`ntype = `"streamable-http`"`nurl = `"$Url`"`n"
|
|
156
|
-
[System.IO.File]::WriteAllText($codexFile, $codexText.TrimEnd() + $block, $utf8NoBom)
|
|
157
|
-
Write-Step " codex: appended '$ServerName' section to $codexFile (backup: $bak)"
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
Write-Step " codex: not installed ($codexFile missing, skipped)"
|
|
42
|
+
$local = Join-Path (Split-Path -Parent $PSScriptRoot) 'lib/cli.js'
|
|
43
|
+
if (Test-Path $local) {
|
|
44
|
+
& node $local @setupArgs
|
|
45
|
+
exit $LASTEXITCODE
|
|
161
46
|
}
|
|
162
47
|
|
|
163
|
-
|
|
164
|
-
$zcodeEntry = [ordered]@{ enabled = $true; type = 'remote'; url = $Url }
|
|
165
|
-
Write-Step '- zcode'
|
|
166
|
-
Merge-JsonServer 'zcode' (Join-Path $userHome '.zcode\cli\config.json') 'mcp.servers' $zcodeEntry
|
|
167
|
-
Sync-Skill 'zcode' (Join-Path $userHome ".zcode\skills\$ServerName")
|
|
168
|
-
|
|
169
|
-
# ---------- skills: cross-agent standard location + each agent's private dir
|
|
170
|
-
$skillDirs = @(
|
|
171
|
-
(Join-Path $userHome ".agents\skills\$ServerName"), # cross-agent standard
|
|
172
|
-
(Join-Path $userHome ".minimax\skills\$ServerName"),
|
|
173
|
-
(Join-Path $userHome ".config\opencode\skills\$ServerName"),
|
|
174
|
-
(Join-Path $userHome ".kimi-code\skills\$ServerName"),
|
|
175
|
-
(Join-Path $userHome ".gemini\skills\$ServerName"),
|
|
176
|
-
(Join-Path $userHome ".codex\skills\$ServerName"),
|
|
177
|
-
(Join-Path $userHome ".zcode\skills\$ServerName"),
|
|
178
|
-
(Join-Path $userHome ".claude\skills\$ServerName") # config is manual; skill still useful
|
|
179
|
-
)
|
|
180
|
-
foreach ($dir in $skillDirs) { Sync-Skill 'skill' $dir }
|
|
181
|
-
|
|
182
|
-
Write-Step 'done. Manual targets (see agents/README.md): Claude Code (project .mcp.json), DSH (cordis.patch.yml).'
|
|
183
|
-
Write-Step 'Restart agent sessions to pick up the new MCP server.'
|
|
48
|
+
throw "agent-comm-hub is not installed and $local does not exist. Install it (`npm i -g agent-comm-hub`) or run `pnpm run build` in a checkout, then use `agent-comm-hub setup`."
|