cross-agent-teams-mcp 0.5.4 → 0.5.6
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 +68 -19
- package/README.zh-CN.md +67 -18
- package/dist/channel-cli.d.ts +2 -0
- package/dist/channel-cli.js +51 -15
- package/dist/channel-cli.js.map +1 -1
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +334 -58
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +62 -12
- package/src/daemon/cleanup.ts +17 -1
- package/src/daemon/local-device.ts +27 -0
- package/src/daemon/network-origin.ts +26 -0
- package/src/daemon/server.ts +16 -2
- package/src/mcp/agent-public-row.ts +2 -0
- package/src/mcp/auto-bind-channel.ts +12 -2
- package/src/mcp/broadcast.ts +1 -1
- package/src/mcp/register-agent.ts +72 -5
- package/src/mcp/register-codex-self.ts +7 -0
- package/src/mcp/send-message.ts +26 -1
- package/src/mcp/tools.ts +41 -9
- package/src/mcp/transport.ts +37 -4
- package/src/storage/agents-repo.ts +48 -16
- package/src/storage/schema.ts +68 -3
package/README.md
CHANGED
|
@@ -4,12 +4,47 @@
|
|
|
4
4
|
|
|
5
5
|
A local MCP daemon that lets multiple AI coding agents (Claude Code, Codex, opencode) running on the same machine talk to each other. Agents register, send 1-to-1 messages, broadcast to a team or role, and wake each other up — all over a single daemon, no external services.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Quick start
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Claude Code
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
```bash
|
|
12
|
+
# 1. Start the daemon (run once, keep it alive)
|
|
13
|
+
npx -y cross-agent-teams-mcp@latest daemon --port 9100 &
|
|
14
|
+
|
|
15
|
+
# 2. In your project, install the MCP config
|
|
16
|
+
npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code
|
|
17
|
+
|
|
18
|
+
# 3. Start Claude Code with the channel loader (manual permission prompt expected)
|
|
19
|
+
claude --dangerously-load-development-channels server:cross-agent-teams-channel
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Other agents (Codex, opencode, ...)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 1. Start the daemon (run once, keep it alive)
|
|
26
|
+
npx -y cross-agent-teams-mcp@latest daemon --port 9100 &
|
|
27
|
+
|
|
28
|
+
# 2. In your project, install the MCP config (interactive picker)
|
|
29
|
+
npx mcpsmgr add jtianling/cross-agent-teams-mcp
|
|
30
|
+
|
|
31
|
+
# 3. Start your coding agent as usual
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Note: only Claude Code gets push wake out of the box. Codex needs the `--remote` + launcher setup (see section 2 below) for pokes; without it, it has a mailbox but no wake. opencode / cursor / other agents only receive pokes when running inside a tmux pane. If push wake isn't wired up, ask the agent to check its inbox manually ("check my xats inbox").
|
|
35
|
+
|
|
36
|
+
Then talk to your agent in plain language:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
# In agent A:
|
|
40
|
+
Register me to xats as backend on team default.
|
|
41
|
+
|
|
42
|
+
# In agent B:
|
|
43
|
+
Register me to xats as frontend on team default.
|
|
44
|
+
Send backend a message: the API has changed.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That's it. Sections below cover the details — daemon flags, manual MCP config, codex `--remote` setup, more usage patterns.
|
|
13
48
|
|
|
14
49
|
## 1. Start the daemon
|
|
15
50
|
|
|
@@ -24,32 +59,46 @@ The daemon listens on `127.0.0.1:9100`. MCP endpoint is `http://127.0.0.1:9100/
|
|
|
24
59
|
Common flags:
|
|
25
60
|
|
|
26
61
|
- `--port <n>` (default `9100`)
|
|
62
|
+
- `--host <addr>` (default `127.0.0.1`)
|
|
63
|
+
- `--device <label>` (default: hostname-derived label)
|
|
27
64
|
- `--token <t>` (Bearer auth)
|
|
28
65
|
- `--db <path>` (default `~/.cross-agent-teams-mcp/data.db`)
|
|
29
66
|
- `--pid-file <path>` (default `~/.cross-agent-teams-mcp/daemon.pid`)
|
|
30
67
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Recommended: install via `mcpsmgr`
|
|
68
|
+
### Cross-host (LAN) collaboration
|
|
34
69
|
|
|
35
|
-
|
|
70
|
+
To let agents on another trusted machine use this daemon, bind the daemon to a LAN address and set a shared bearer token:
|
|
36
71
|
|
|
37
72
|
```bash
|
|
38
|
-
|
|
73
|
+
npx -y cross-agent-teams-mcp@latest daemon \
|
|
74
|
+
--host 192.168.1.10 \
|
|
75
|
+
--port 9100 \
|
|
76
|
+
--token "$XATS_TOKEN" \
|
|
77
|
+
--device jt-laptop
|
|
78
|
+
```
|
|
39
79
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
npx
|
|
80
|
+
Then configure the peer host's Claude Code channel proxy to connect back to that daemon:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx -y -p cross-agent-teams-mcp@latest cross-agent-teams-channel \
|
|
84
|
+
--daemon-url http://192.168.1.10:9100/mcp \
|
|
85
|
+
--token "$XATS_TOKEN" \
|
|
86
|
+
--device gx-laptop
|
|
44
87
|
```
|
|
45
88
|
|
|
46
|
-
|
|
89
|
+
Agents are namespaced by `(device, team, name)`. A bare `send_message({to_agent_name:"creator"})` resolves on the caller's own device; use `creator:gx-laptop` to address a same-team agent on another device. `list_agents` shows the `device` field so you can compose those addresses.
|
|
90
|
+
|
|
91
|
+
Security notes: non-loopback `--host` requires `--token`, and the token is shared by everyone who can use that daemon. Treat LAN exposure as trusted-team only; there is no per-agent authorization, device whitelist, or TLS in this mode.
|
|
92
|
+
|
|
93
|
+
Upgrade note: the first startup after this version auto-migrates the storage schema from `(team, name)` identity to `(device, team, name)` identity and backfills existing rows with the daemon's local `--device` label. Rolling back after registering multiple devices with the same `(team, name)` can violate the old uniqueness assumption.
|
|
94
|
+
|
|
95
|
+
## 2. Configure your agent's MCP client
|
|
96
|
+
|
|
97
|
+
### Recommended: `mcpsmgr` (shown in Quick start)
|
|
47
98
|
|
|
48
|
-
|
|
49
|
-
- For Codex, writes `~/.codex/config.toml` with `experimental_use_rmcp_client = true` plus the streamable-http MCP entry.
|
|
50
|
-
- Prints the post-install steps you still need to run yourself (e.g. the `--dangerously-load-development-channels server:cross-agent-teams-channel` flag for Claude Code, or the codex `--remote` setup if you want push wake).
|
|
99
|
+
[`mcpsmgr`](https://www.npmjs.com/package/mcpsmgr) reads this repo's `mcpsmgr.json` and writes the right MCP entries into your agent's config in one shot — including the Claude Code stdio channel proxy entry, the Codex `experimental_use_rmcp_client` toggle, and the streamable-http MCP entry.
|
|
51
100
|
|
|
52
|
-
|
|
101
|
+
To override the daemon port:
|
|
53
102
|
|
|
54
103
|
```bash
|
|
55
104
|
npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code --port 9300
|
|
@@ -91,7 +140,7 @@ Then start Claude Code with the experimental channel loader so it subscribes to
|
|
|
91
140
|
claude --dangerously-load-development-channels server:cross-agent-teams-channel
|
|
92
141
|
```
|
|
93
142
|
|
|
94
|
-
The `server:<name>` suffix MUST equal the MCP server key in `.mcp.json` (`cross-agent-teams-channel` above). If your daemon uses `--token <t>`, add `"headers": { "Authorization": "Bearer <t>" }` to the HTTP entry.
|
|
143
|
+
The `server:<name>` suffix MUST equal the MCP server key in `.mcp.json` (`cross-agent-teams-channel` above). If your daemon uses `--token <t>`, add `"headers": { "Authorization": "Bearer <t>" }` to the HTTP entry, and add `--token <t>` to the channel proxy args.
|
|
95
144
|
|
|
96
145
|
#### Codex CLI
|
|
97
146
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,12 +4,47 @@
|
|
|
4
4
|
|
|
5
5
|
一个本地 MCP daemon, 让同一台机器上的多个 AI 编码 agent (Claude Code, Codex, opencode) 互相通信. agent 注册到 daemon, 互发 1-to-1 消息, 在 team 或 role 内广播, 互相唤醒 — 全部通过一个本地 daemon 完成, 不依赖任何外部服务.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 快速开始
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Claude Code
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
```bash
|
|
12
|
+
# 1. 启动 daemon (跑一次, 保持运行)
|
|
13
|
+
npx -y cross-agent-teams-mcp@latest daemon --port 9100 &
|
|
14
|
+
|
|
15
|
+
# 2. 在你的项目下安装 MCP 配置
|
|
16
|
+
npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code
|
|
17
|
+
|
|
18
|
+
# 3. 带上 channel loader 启动 Claude Code (需要手动确认权限)
|
|
19
|
+
claude --dangerously-load-development-channels server:cross-agent-teams-channel
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 其它 agent (Codex, opencode, ...)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 1. 启动 daemon (跑一次, 保持运行)
|
|
26
|
+
npx -y cross-agent-teams-mcp@latest daemon --port 9100 &
|
|
27
|
+
|
|
28
|
+
# 2. 在你的项目下安装 MCP 配置 (交互式选择对应 agent)
|
|
29
|
+
npx mcpsmgr add jtianling/cross-agent-teams-mcp
|
|
30
|
+
|
|
31
|
+
# 3. 按平时的方式启动对应 coding agent
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
注意: 只有 Claude Code 默认就能收到 push 唤醒. Codex 需要 `--remote` + launcher 配置 (见下面 section 2) 才能被 poke; 没配的话只有邮箱, 不会自动醒. opencode / cursor 等其它 agent 只有跑在 tmux pane 里才能被 poke. 没接通 push 唤醒的情况下, 让 agent 自己手动收信即可 (跟它说"查一下我的 xats inbox").
|
|
35
|
+
|
|
36
|
+
之后用平时跟 agent 对话的语言就能用了:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
# Agent A 里:
|
|
40
|
+
Register me to xats as backend on team default.
|
|
41
|
+
|
|
42
|
+
# Agent B 里:
|
|
43
|
+
Register me to xats as frontend on team default.
|
|
44
|
+
Send backend a message: the API has changed.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
就这些. 下面是细节 — daemon 参数, 手动 MCP 配置, codex `--remote` 设置, 更多使用方式.
|
|
13
48
|
|
|
14
49
|
## 1. 启动 daemon
|
|
15
50
|
|
|
@@ -24,30 +59,44 @@ daemon 默认监听 `127.0.0.1:9100`. MCP endpoint: `http://127.0.0.1:9100/mcp`
|
|
|
24
59
|
常用参数:
|
|
25
60
|
|
|
26
61
|
- `--port <n>` (默认 `9100`)
|
|
62
|
+
- `--host <addr>` (默认 `127.0.0.1`)
|
|
63
|
+
- `--device <label>` (默认从 hostname 派生)
|
|
27
64
|
- `--token <t>` (Bearer 鉴权)
|
|
28
65
|
- `--db <path>` (默认 `~/.cross-agent-teams-mcp/data.db`)
|
|
29
66
|
- `--pid-file <path>` (默认 `~/.cross-agent-teams-mcp/daemon.pid`)
|
|
30
67
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### 推荐: 用 `mcpsmgr` 安装
|
|
68
|
+
### 跨主机 (LAN) 协作
|
|
34
69
|
|
|
35
|
-
|
|
70
|
+
要让可信局域网里另一台机器上的 agent 使用这个 daemon, 把 daemon 绑定到 LAN 地址并设置共享 bearer token:
|
|
36
71
|
|
|
37
72
|
```bash
|
|
38
|
-
|
|
73
|
+
npx -y cross-agent-teams-mcp@latest daemon \
|
|
74
|
+
--host 192.168.1.10 \
|
|
75
|
+
--port 9100 \
|
|
76
|
+
--token "$XATS_TOKEN" \
|
|
77
|
+
--device jt-laptop
|
|
78
|
+
```
|
|
39
79
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
npx
|
|
80
|
+
然后在对端机器上让 Claude Code channel proxy 连回这个 daemon:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx -y -p cross-agent-teams-mcp@latest cross-agent-teams-channel \
|
|
84
|
+
--daemon-url http://192.168.1.10:9100/mcp \
|
|
85
|
+
--token "$XATS_TOKEN" \
|
|
86
|
+
--device gx-laptop
|
|
44
87
|
```
|
|
45
88
|
|
|
46
|
-
|
|
89
|
+
agent 身份现在按 `(device, team, name)` 命名空间区分. 裸的 `send_message({to_agent_name:"creator"})` 会解析到调用者自己的 device; 要发给另一个 device 上同 team 的 agent, 用 `creator:gx-laptop`. `list_agents` 会显示 `device` 字段, 方便拼出这个地址.
|
|
90
|
+
|
|
91
|
+
安全说明: 非 loopback 的 `--host` 必须带 `--token`, 并且这个 token 会被所有能使用该 daemon 的人共享. LAN 暴露只适合可信团队环境; 当前模式没有 per-agent 鉴权, device 白名单或 TLS.
|
|
92
|
+
|
|
93
|
+
升级说明: 升级到这个版本后首次启动会自动迁移存储 schema, 把身份从 `(team, name)` 改为 `(device, team, name)`, 并用 daemon 本机的 `--device` 标签回填旧数据. 如果已经注册了多个 device 上相同 `(team, name)` 的 agent 再回滚, 可能违反旧版本的唯一性假设.
|
|
94
|
+
|
|
95
|
+
## 2. 在 agent 端配置 MCP client
|
|
96
|
+
|
|
97
|
+
### 推荐: `mcpsmgr` (快速开始里已经演示)
|
|
47
98
|
|
|
48
|
-
|
|
49
|
-
- Codex: 写 `~/.codex/config.toml`, 带上 `experimental_use_rmcp_client = true` 和 streamable-http MCP 条目.
|
|
50
|
-
- 打印你还需要自己跑的 post-install 步骤 (例如 Claude Code 的 `--dangerously-load-development-channels server:cross-agent-teams-channel` 启动 flag, 或 codex 想要 push 唤醒时的 `--remote` 配置).
|
|
99
|
+
[`mcpsmgr`](https://www.npmjs.com/package/mcpsmgr) 读取本仓库的 `mcpsmgr.json`, 一次性把对应 agent 需要的 MCP 条目写进配置 — 包括 Claude Code 的 stdio channel proxy 条目, Codex 的 `experimental_use_rmcp_client` 开关和 streamable-http MCP 条目.
|
|
51
100
|
|
|
52
101
|
覆盖 daemon 端口:
|
|
53
102
|
|
|
@@ -91,7 +140,7 @@ npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code --port 9300
|
|
|
91
140
|
claude --dangerously-load-development-channels server:cross-agent-teams-channel
|
|
92
141
|
```
|
|
93
142
|
|
|
94
|
-
`server:<name>` 后缀 **必须** 等于 `.mcp.json` 里的 MCP server key (上例中是 `cross-agent-teams-channel`). 如果 daemon 启动带了 `--token <t>`, 在 HTTP 条目里加 `"headers": { "Authorization": "Bearer <t>" }
|
|
143
|
+
`server:<name>` 后缀 **必须** 等于 `.mcp.json` 里的 MCP server key (上例中是 `cross-agent-teams-channel`). 如果 daemon 启动带了 `--token <t>`, 在 HTTP 条目里加 `"headers": { "Authorization": "Bearer <t>" }`, 并在 channel proxy args 里加 `--token <t>`.
|
|
95
144
|
|
|
96
145
|
#### Codex CLI
|
|
97
146
|
|
package/dist/channel-cli.d.ts
CHANGED
package/dist/channel-cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// plugins/cross-agent-teams-channel/src/cli.ts
|
|
4
4
|
import { randomUUID } from "crypto";
|
|
5
5
|
import { realpathSync } from "fs";
|
|
6
|
+
import { hostname } from "os";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
7
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
9
|
|
|
@@ -83,7 +84,10 @@ async function parseToolResult(resp) {
|
|
|
83
84
|
}
|
|
84
85
|
async function runRegistrationSequence(config) {
|
|
85
86
|
const order = [];
|
|
86
|
-
const
|
|
87
|
+
const requestInit = config.token ? { headers: { Authorization: `Bearer ${config.token}` } } : void 0;
|
|
88
|
+
const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl), {
|
|
89
|
+
requestInit
|
|
90
|
+
});
|
|
87
91
|
const client = new Client({ name: "cross-agent-teams-proxy", version: "0.1.0" });
|
|
88
92
|
if (config.notificationHandler) {
|
|
89
93
|
client.fallbackNotificationHandler = async (n) => {
|
|
@@ -93,21 +97,25 @@ async function runRegistrationSequence(config) {
|
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
99
|
await client.connect(transport);
|
|
100
|
+
const registerArgs = {
|
|
101
|
+
agent_type: "custom",
|
|
102
|
+
agent_type_name: "cross-agent-teams-channel",
|
|
103
|
+
model: "proxy",
|
|
104
|
+
role: "__channel_proxy__",
|
|
105
|
+
name: `channel-proxy-${process.pid}`,
|
|
106
|
+
team: "default",
|
|
107
|
+
claude_ui_pid: findClaudeUiPid(),
|
|
108
|
+
delivery: {
|
|
109
|
+
kind: "claude-channel",
|
|
110
|
+
channel_session_id: config.channel_session_id
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
if (config.device !== void 0) {
|
|
114
|
+
registerArgs.device = config.device;
|
|
115
|
+
}
|
|
96
116
|
const registerResp = await client.callTool({
|
|
97
117
|
name: "register_agent",
|
|
98
|
-
arguments:
|
|
99
|
-
agent_type: "custom",
|
|
100
|
-
agent_type_name: "cross-agent-teams-channel",
|
|
101
|
-
model: "proxy",
|
|
102
|
-
role: "__channel_proxy__",
|
|
103
|
-
name: `channel-proxy-${process.pid}`,
|
|
104
|
-
team: "default",
|
|
105
|
-
claude_ui_pid: findClaudeUiPid(),
|
|
106
|
-
delivery: {
|
|
107
|
-
kind: "claude-channel",
|
|
108
|
-
channel_session_id: config.channel_session_id
|
|
109
|
-
}
|
|
110
|
-
}
|
|
118
|
+
arguments: registerArgs
|
|
111
119
|
});
|
|
112
120
|
order.push("register_agent");
|
|
113
121
|
const regResult = await parseToolResult(registerResp);
|
|
@@ -237,6 +245,8 @@ function buildStartupHint(csid) {
|
|
|
237
245
|
}
|
|
238
246
|
function parseCliArgs(argv, env = process.env) {
|
|
239
247
|
let daemonUrl;
|
|
248
|
+
let token;
|
|
249
|
+
let explicitDevice;
|
|
240
250
|
for (let i = 0; i < argv.length; i++) {
|
|
241
251
|
const flag = argv[i];
|
|
242
252
|
const next = argv[i + 1];
|
|
@@ -245,6 +255,14 @@ function parseCliArgs(argv, env = process.env) {
|
|
|
245
255
|
daemonUrl = next;
|
|
246
256
|
i++;
|
|
247
257
|
break;
|
|
258
|
+
case "--token":
|
|
259
|
+
token = next;
|
|
260
|
+
i++;
|
|
261
|
+
break;
|
|
262
|
+
case "--device":
|
|
263
|
+
explicitDevice = next;
|
|
264
|
+
i++;
|
|
265
|
+
break;
|
|
248
266
|
default:
|
|
249
267
|
break;
|
|
250
268
|
}
|
|
@@ -252,12 +270,16 @@ function parseCliArgs(argv, env = process.env) {
|
|
|
252
270
|
if (!daemonUrl || daemonUrl.length === 0) {
|
|
253
271
|
daemonUrl = env.CROSS_AGENT_TEAMS_MCP_DAEMON_URL;
|
|
254
272
|
}
|
|
273
|
+
if (!token || token.length === 0) {
|
|
274
|
+
token = env.CROSS_AGENT_TEAMS_MCP_TOKEN;
|
|
275
|
+
}
|
|
255
276
|
if (!daemonUrl || daemonUrl.length === 0) {
|
|
256
277
|
throw new CliArgError(
|
|
257
278
|
"missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)"
|
|
258
279
|
);
|
|
259
280
|
}
|
|
260
|
-
|
|
281
|
+
const device = explicitDevice !== void 0 ? resolveDeviceLabel(explicitDevice) : void 0;
|
|
282
|
+
return { daemonUrl, token, device };
|
|
261
283
|
}
|
|
262
284
|
async function main(argv = process.argv.slice(2), env = process.env) {
|
|
263
285
|
let args;
|
|
@@ -275,6 +297,8 @@ async function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
275
297
|
let registrationEverSucceeded = false;
|
|
276
298
|
const controller = runReconnectingProxy({
|
|
277
299
|
daemonUrl: args.daemonUrl,
|
|
300
|
+
token: args.token,
|
|
301
|
+
device: args.device,
|
|
278
302
|
channel_session_id: csid,
|
|
279
303
|
notificationHandler: (params) => {
|
|
280
304
|
relayChannelWake(hostServer, params);
|
|
@@ -327,6 +351,18 @@ function isEntry() {
|
|
|
327
351
|
if (isEntry()) {
|
|
328
352
|
void main();
|
|
329
353
|
}
|
|
354
|
+
function resolveDeviceLabel(explicit) {
|
|
355
|
+
const raw = explicit ?? hostname();
|
|
356
|
+
if (raw.includes(":")) {
|
|
357
|
+
throw new CliArgError("invalid_device_label");
|
|
358
|
+
}
|
|
359
|
+
const normalized = raw.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
360
|
+
const label = normalized.length > 0 ? normalized : "local";
|
|
361
|
+
if (label.length > 64) {
|
|
362
|
+
throw new CliArgError("invalid_device_label");
|
|
363
|
+
}
|
|
364
|
+
return label;
|
|
365
|
+
}
|
|
330
366
|
export {
|
|
331
367
|
CliArgError,
|
|
332
368
|
buildStartupHint,
|
package/dist/channel-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../plugins/cross-agent-teams-channel/src/cli.ts","../plugins/cross-agent-teams-channel/src/proxy.ts","../plugins/cross-agent-teams-channel/src/daemon-client.ts","../plugins/cross-agent-teams-channel/src/find-claude-pid.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { randomUUID } from 'node:crypto'\nimport { realpathSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createProxyServer, relayChannelWake } from './proxy.js'\nimport { runReconnectingProxy } from './daemon-client.js'\n\ninterface CliArgs {\n daemonUrl: string\n}\n\nexport class CliArgError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CliArgError'\n }\n}\n\nexport function buildStartupHint(csid: string): { content: string; meta: { source: string; kind: string } } {\n const content = [\n `cross-agent-teams-mcp: your channel_session_id is ${csid}.`,\n `Do NOT register automatically. First ask the user (in English) to register this session so it can talk to other agents — use exactly this wording: 'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats — name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename).'`,\n `Once the user provides a name (and optionally a team), call register_agent({agent_type: \"claude-code\", name: \"<name from user>\", team: \"<team from user, omit if not provided>\", ui_pid: $PPID, project_dir: \"<current working directory>\"}) — do NOT pass channel_session_id here; the daemon auto-binds via ui_pid.`,\n `bind_channel({channel_session_id: \"${csid}\"}) is the low-level rebind tool for an already-registered Claude host that needs to switch to a fresh csid; it is NOT the primary registration path.`,\n `Do not use curl or another external HTTP client for Claude registration here — that would create a different MCP session, and follow-up tools in Claude Code could still see unknown_agent.`\n ].join(' ')\n return {\n content,\n meta: { source: 'cross_agent_teams_mcp', kind: 'startup_bind_hint' }\n }\n}\n\nexport function parseCliArgs(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): CliArgs {\n let daemonUrl: string | undefined\n\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const next = argv[i + 1]\n switch (flag) {\n case '--daemon-url':\n daemonUrl = next; i++; break\n default:\n // Ignore unknown flags for forward-compat (including legacy\n // --agent-team / --agent-name, which are no longer honored).\n break\n }\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n daemonUrl = env.CROSS_AGENT_TEAMS_MCP_DAEMON_URL\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n throw new CliArgError(\n 'missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)'\n )\n }\n return { daemonUrl }\n}\n\nexport async function main(\n argv: readonly string[] = process.argv.slice(2),\n env: NodeJS.ProcessEnv = process.env\n): Promise<void> {\n let args: CliArgs\n try {\n args = parseCliArgs(argv, env)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`cross-agent-teams-proxy: ${msg}\\n`)\n process.exit(2)\n }\n\n // Fresh csid per startup — no persistence. Multi-instance safe.\n const csid = randomUUID()\n\n const hostServer = createProxyServer()\n const stdioTransport = new StdioServerTransport()\n\n let registrationEverSucceeded = false\n const controller = runReconnectingProxy({\n daemonUrl: args.daemonUrl,\n channel_session_id: csid,\n notificationHandler: (params) => {\n relayChannelWake(hostServer, params as { content: string; meta: Record<string, string> })\n },\n onSequenceComplete: () => {\n registrationEverSucceeded = true\n // Announce csid to Claude via host-facing channel notification so Claude\n // can call bind_channel({channel_session_id}) to bind its own agent row.\n const hint = buildStartupHint(csid)\n relayChannelWake(hostServer, hint)\n }\n })\n\n let stopped = false\n const shutdown = async (): Promise<void> => {\n if (stopped) return\n stopped = true\n try { await controller.stop() } catch { /* best-effort */ }\n try { await hostServer.close() } catch { /* best-effort */ }\n if (!registrationEverSucceeded) {\n process.stderr.write(`cross-agent-teams-proxy: daemon unreachable at ${args.daemonUrl}\\n`)\n process.exit(1)\n }\n process.exit(0)\n }\n\n stdioTransport.onclose = () => { void shutdown() }\n\n await hostServer.connect(stdioTransport)\n\n process.on('SIGTERM', () => { void shutdown() })\n process.on('SIGINT', () => { void shutdown() })\n}\n\n// Entry-point check. The naive `import.meta.url === \\`file://${process.argv[1]}\\``\n// breaks when launched via an npm `.bin` symlink (npx, `npm install -g`):\n// process.argv[1] is the symlink path, while import.meta.url is already\n// resolved. Compare realpath-resolved file paths instead.\nfunction isEntry(): boolean {\n try {\n const metaPath = fileURLToPath(import.meta.url)\n const argvPath = realpathSync(process.argv[1])\n return metaPath === argvPath\n } catch {\n return false\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-misused-promises\nif (isEntry()) {\n void main()\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport function createProxyServer(): McpServer {\n return new McpServer(\n { name: 'cross-agent-teams-channel', version: '0.1.0' },\n { capabilities: { experimental: { 'claude/channel': {} } } }\n )\n}\n\nexport interface ChannelWakeParams {\n content: string\n meta: Record<string, string>\n}\n\nexport function relayChannelWake(server: McpServer, params: ChannelWakeParams): void {\n try {\n const notif = {\n method: 'notifications/claude/channel',\n params: params as unknown as Record<string, unknown>\n }\n const p = (server.server.notification as (n: typeof notif) => Promise<void>)(notif)\n if (p && typeof p.catch === 'function') {\n p.catch(() => { /* host closed — drop silently */ })\n }\n } catch {\n // host transport closed or not yet connected — drop silently\n }\n}\n","import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { findClaudeUiPid } from './find-claude-pid.js'\n\nexport interface RegistrationConfig {\n daemonUrl: string\n channel_session_id: string\n backoffInitialMs?: number\n backoffMaxMs?: number\n notificationHandler?: (payload: unknown) => void\n}\n\nexport interface ReconnectingProxyConfig extends RegistrationConfig {\n onSequenceComplete?: (order: string[]) => void\n onDisconnect?: () => void\n healthCheckIntervalMs?: number\n}\n\nexport interface ReconnectingProxyController {\n stop(): Promise<void>\n}\n\nexport interface RegistrationSequenceResult {\n order: string[]\n lastSubscribeResult: unknown\n client: Client\n transport: StreamableHTTPClientTransport\n close: () => Promise<void>\n}\n\ntype ToolResult = Record<string, unknown>\n\nasync function parseToolResult(resp: unknown): Promise<ToolResult> {\n const r = resp as { content?: Array<{ text?: string }> }\n const text = r.content?.[0]?.text\n if (typeof text !== 'string') return {}\n try { return JSON.parse(text) as ToolResult } catch { return {} }\n}\n\nexport async function runRegistrationSequence(\n config: RegistrationConfig\n): Promise<RegistrationSequenceResult> {\n const order: string[] = []\n const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl))\n const client = new Client({ name: 'cross-agent-teams-proxy', version: '0.1.0' })\n\n if (config.notificationHandler) {\n client.fallbackNotificationHandler = async (n) => {\n if (n.method === 'notifications/channel_wake') {\n config.notificationHandler!(n.params)\n }\n }\n }\n\n await client.connect(transport)\n\n // 1. register_agent as proxy — identity keyed on pid, stable across reconnects\n // so the (team, name) ON CONFLICT upsert reuses the same row instead of spamming new rows\n const registerResp = await client.callTool({\n name: 'register_agent',\n arguments: {\n agent_type: 'custom',\n agent_type_name: 'cross-agent-teams-channel',\n model: 'proxy',\n role: '__channel_proxy__',\n name: `channel-proxy-${process.pid}`,\n team: 'default',\n claude_ui_pid: findClaudeUiPid(),\n delivery: {\n kind: 'claude-channel',\n channel_session_id: config.channel_session_id,\n },\n }\n })\n order.push('register_agent')\n const regResult = await parseToolResult(registerResp)\n if (!('agent_id' in regResult)) {\n throw new Error(`register_agent failed: ${JSON.stringify(regResult)}`)\n }\n\n // 2. subscribe_channel_wake — proxy's csid is fresh per startup\n const subResp = await client.callTool({\n name: 'subscribe_channel_wake',\n arguments: { channel_session_id: config.channel_session_id }\n })\n order.push('subscribe_channel_wake')\n const subResult = await parseToolResult(subResp)\n if (!('ok' in subResult) || subResult.ok !== true) {\n throw new Error(`subscribe_channel_wake failed: ${JSON.stringify(subResult)}`)\n }\n\n return {\n order,\n lastSubscribeResult: subResult,\n client,\n transport,\n close: async () => {\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n }\n }\n}\n\nexport interface WaitForDisconnectInput {\n client: Pick<Client, 'callTool'>\n transport: { onclose?: (() => void) | null | undefined }\n}\n\nexport interface WaitForDisconnectOptions {\n healthCheckIntervalMs?: number\n shouldStop?: () => boolean\n}\n\nexport async function waitForDisconnect(\n seq: WaitForDisconnectInput,\n opts: WaitForDisconnectOptions = {}\n): Promise<void> {\n const interval = opts.healthCheckIntervalMs ?? 30_000\n const shouldStop = opts.shouldStop ?? (() => false)\n let disconnected = false\n let wakeup: (() => void) | null = null\n const closeHandler = (): void => {\n disconnected = true\n wakeup?.()\n }\n const prevOnClose = seq.transport.onclose\n seq.transport.onclose = (): void => { prevOnClose?.(); closeHandler() }\n while (!disconnected && !shouldStop()) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { wakeup = null; resolve() }, interval)\n wakeup = (): void => { clearTimeout(timer); wakeup = null; resolve() }\n })\n if (disconnected || shouldStop()) break\n try {\n await seq.client.callTool({ name: 'echo', arguments: { msg: 'hb' } })\n } catch {\n disconnected = true\n break\n }\n }\n}\n\nexport function runReconnectingProxy(config: ReconnectingProxyConfig): ReconnectingProxyController {\n let stopped = false\n let currentSeq: RegistrationSequenceResult | null = null\n\n async function loop(): Promise<void> {\n while (!stopped) {\n try {\n const seq = await runRegistrationSequence(config)\n currentSeq = seq\n if (config.onSequenceComplete) config.onSequenceComplete([...seq.order])\n\n await waitForDisconnect(seq, {\n healthCheckIntervalMs: config.healthCheckIntervalMs,\n shouldStop: () => stopped,\n })\n if (config.onDisconnect) config.onDisconnect()\n try { await seq.close() } catch { /* best-effort */ }\n currentSeq = null\n } catch {\n // register/subscribe failed — wait and retry.\n }\n if (stopped) break\n const wait = config.backoffInitialMs ?? 500\n await new Promise(r => setTimeout(r, wait))\n }\n }\n\n void loop()\n\n return {\n stop: async () => {\n stopped = true\n if (currentSeq) {\n try { await currentSeq.close() } catch { /* best-effort */ }\n }\n }\n }\n}\n","import { execFileSync } from 'node:child_process'\n\nconst MAX_HOPS = 8\n\ninterface PsRow {\n ppid: number\n cmd: string\n}\n\nexport function readPsRow(pid: number): PsRow | null {\n try {\n const out = execFileSync('ps', ['-o', 'ppid=,args=', '-p', String(pid)], {\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore']\n })\n const trimmed = out.trim()\n if (!trimmed) return null\n const m = /^\\s*(\\d+)\\s+(.*)$/.exec(trimmed)\n if (!m) return null\n return { ppid: parseInt(m[1], 10), cmd: m[2] }\n } catch {\n return null\n }\n}\n\nexport function isClaudeCmd(cmd: string): boolean {\n const first = cmd.trim().split(/\\s+/)[0]\n if (!first) return false\n const base = first.replace(/^.*\\//, '')\n return base === 'claude'\n}\n\nexport function findClaudeUiPid(\n startPpid: number = process.ppid,\n reader: (pid: number) => PsRow | null = readPsRow\n): number {\n let pid = startPpid\n for (let i = 0; i < MAX_HOPS; i++) {\n const row = reader(pid)\n if (!row) break\n if (isClaudeCmd(row.cmd)) return pid\n if (row.ppid <= 1 || row.ppid === pid) break\n pid = row.ppid\n }\n return startPpid\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../plugins/cross-agent-teams-channel/src/cli.ts","../plugins/cross-agent-teams-channel/src/proxy.ts","../plugins/cross-agent-teams-channel/src/daemon-client.ts","../plugins/cross-agent-teams-channel/src/find-claude-pid.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { randomUUID } from 'node:crypto'\nimport { realpathSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { fileURLToPath } from 'node:url'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createProxyServer, relayChannelWake } from './proxy.js'\nimport { runReconnectingProxy } from './daemon-client.js'\n\ninterface CliArgs {\n daemonUrl: string\n token?: string\n // Omitted when the user did not pass --device. The daemon then auto-fills\n // its own local label on loopback registrations, which keeps zero-config\n // proxies working against a daemon whose operator chose a custom --device.\n device?: string\n}\n\nexport class CliArgError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CliArgError'\n }\n}\n\nexport function buildStartupHint(csid: string): { content: string; meta: { source: string; kind: string } } {\n const content = [\n `cross-agent-teams-mcp: your channel_session_id is ${csid}.`,\n `Do NOT register automatically. First ask the user (in English) to register this session so it can talk to other agents — use exactly this wording: 'To join cross-agent-teams (xats) and message other agents, reply with: Register to xats — name: your-agent-name, team: your-team-name (optional; defaults to the current working directory basename).'`,\n `Once the user provides a name (and optionally a team), call register_agent({agent_type: \"claude-code\", name: \"<name from user>\", team: \"<team from user, omit if not provided>\", ui_pid: $PPID, project_dir: \"<current working directory>\"}) — do NOT pass channel_session_id here; the daemon auto-binds via ui_pid.`,\n `bind_channel({channel_session_id: \"${csid}\"}) is the low-level rebind tool for an already-registered Claude host that needs to switch to a fresh csid; it is NOT the primary registration path.`,\n `Do not use curl or another external HTTP client for Claude registration here — that would create a different MCP session, and follow-up tools in Claude Code could still see unknown_agent.`\n ].join(' ')\n return {\n content,\n meta: { source: 'cross_agent_teams_mcp', kind: 'startup_bind_hint' }\n }\n}\n\nexport function parseCliArgs(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): CliArgs {\n let daemonUrl: string | undefined\n let token: string | undefined\n let explicitDevice: string | undefined\n\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const next = argv[i + 1]\n switch (flag) {\n case '--daemon-url':\n daemonUrl = next; i++; break\n case '--token':\n token = next; i++; break\n case '--device':\n explicitDevice = next; i++; break\n default:\n // Ignore unknown flags for forward-compat (including legacy\n // --agent-team / --agent-name, which are no longer honored).\n break\n }\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n daemonUrl = env.CROSS_AGENT_TEAMS_MCP_DAEMON_URL\n }\n if (!token || token.length === 0) {\n token = env.CROSS_AGENT_TEAMS_MCP_TOKEN\n }\n\n if (!daemonUrl || daemonUrl.length === 0) {\n throw new CliArgError(\n 'missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)'\n )\n }\n // Only validate / pass the device when the user explicitly provided one.\n // Leaving it undefined lets daemon-client omit the field on register_agent\n // so the daemon's loopback auto-fill resolves it to the daemon's localDevice.\n const device =\n explicitDevice !== undefined ? resolveDeviceLabel(explicitDevice) : undefined\n return { daemonUrl, token, device }\n}\n\nexport async function main(\n argv: readonly string[] = process.argv.slice(2),\n env: NodeJS.ProcessEnv = process.env\n): Promise<void> {\n let args: CliArgs\n try {\n args = parseCliArgs(argv, env)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`cross-agent-teams-proxy: ${msg}\\n`)\n process.exit(2)\n }\n\n // Fresh csid per startup — no persistence. Multi-instance safe.\n const csid = randomUUID()\n\n const hostServer = createProxyServer()\n const stdioTransport = new StdioServerTransport()\n\n let registrationEverSucceeded = false\n const controller = runReconnectingProxy({\n daemonUrl: args.daemonUrl,\n token: args.token,\n device: args.device,\n channel_session_id: csid,\n notificationHandler: (params) => {\n relayChannelWake(hostServer, params as { content: string; meta: Record<string, string> })\n },\n onSequenceComplete: () => {\n registrationEverSucceeded = true\n // Announce csid to Claude via host-facing channel notification so Claude\n // can call bind_channel({channel_session_id}) to bind its own agent row.\n const hint = buildStartupHint(csid)\n relayChannelWake(hostServer, hint)\n }\n })\n\n let stopped = false\n const shutdown = async (): Promise<void> => {\n if (stopped) return\n stopped = true\n try { await controller.stop() } catch { /* best-effort */ }\n try { await hostServer.close() } catch { /* best-effort */ }\n if (!registrationEverSucceeded) {\n process.stderr.write(`cross-agent-teams-proxy: daemon unreachable at ${args.daemonUrl}\\n`)\n process.exit(1)\n }\n process.exit(0)\n }\n\n stdioTransport.onclose = () => { void shutdown() }\n\n await hostServer.connect(stdioTransport)\n\n process.on('SIGTERM', () => { void shutdown() })\n process.on('SIGINT', () => { void shutdown() })\n}\n\n// Entry-point check. The naive `import.meta.url === \\`file://${process.argv[1]}\\``\n// breaks when launched via an npm `.bin` symlink (npx, `npm install -g`):\n// process.argv[1] is the symlink path, while import.meta.url is already\n// resolved. Compare realpath-resolved file paths instead.\nfunction isEntry(): boolean {\n try {\n const metaPath = fileURLToPath(import.meta.url)\n const argvPath = realpathSync(process.argv[1])\n return metaPath === argvPath\n } catch {\n return false\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-misused-promises\nif (isEntry()) {\n void main()\n}\n\nfunction resolveDeviceLabel(explicit?: string): string {\n const raw = explicit ?? hostname()\n if (raw.includes(':')) {\n throw new CliArgError('invalid_device_label')\n }\n const normalized = raw\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '-')\n const label = normalized.length > 0 ? normalized : 'local'\n if (label.length > 64) {\n throw new CliArgError('invalid_device_label')\n }\n return label\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nexport function createProxyServer(): McpServer {\n return new McpServer(\n { name: 'cross-agent-teams-channel', version: '0.1.0' },\n { capabilities: { experimental: { 'claude/channel': {} } } }\n )\n}\n\nexport interface ChannelWakeParams {\n content: string\n meta: Record<string, string>\n}\n\nexport function relayChannelWake(server: McpServer, params: ChannelWakeParams): void {\n try {\n const notif = {\n method: 'notifications/claude/channel',\n params: params as unknown as Record<string, unknown>\n }\n const p = (server.server.notification as (n: typeof notif) => Promise<void>)(notif)\n if (p && typeof p.catch === 'function') {\n p.catch(() => { /* host closed — drop silently */ })\n }\n } catch {\n // host transport closed or not yet connected — drop silently\n }\n}\n","import { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { findClaudeUiPid } from './find-claude-pid.js'\n\nexport interface RegistrationConfig {\n daemonUrl: string\n token?: string\n device?: string\n channel_session_id: string\n backoffInitialMs?: number\n backoffMaxMs?: number\n notificationHandler?: (payload: unknown) => void\n}\n\nexport interface ReconnectingProxyConfig extends RegistrationConfig {\n onSequenceComplete?: (order: string[]) => void\n onDisconnect?: () => void\n healthCheckIntervalMs?: number\n}\n\nexport interface ReconnectingProxyController {\n stop(): Promise<void>\n}\n\nexport interface RegistrationSequenceResult {\n order: string[]\n lastSubscribeResult: unknown\n client: Client\n transport: StreamableHTTPClientTransport\n close: () => Promise<void>\n}\n\ntype ToolResult = Record<string, unknown>\n\nasync function parseToolResult(resp: unknown): Promise<ToolResult> {\n const r = resp as { content?: Array<{ text?: string }> }\n const text = r.content?.[0]?.text\n if (typeof text !== 'string') return {}\n try { return JSON.parse(text) as ToolResult } catch { return {} }\n}\n\nexport async function runRegistrationSequence(\n config: RegistrationConfig\n): Promise<RegistrationSequenceResult> {\n const order: string[] = []\n const requestInit = config.token\n ? { headers: { Authorization: `Bearer ${config.token}` } }\n : undefined\n const transport = new StreamableHTTPClientTransport(new URL(config.daemonUrl), {\n requestInit,\n })\n const client = new Client({ name: 'cross-agent-teams-proxy', version: '0.1.0' })\n\n if (config.notificationHandler) {\n client.fallbackNotificationHandler = async (n) => {\n if (n.method === 'notifications/channel_wake') {\n config.notificationHandler!(n.params)\n }\n }\n }\n\n await client.connect(transport)\n\n // 1. register_agent as proxy — identity keyed on pid, stable across reconnects\n // so the (device, team, name) ON CONFLICT upsert reuses the same row instead of spamming new rows.\n // Only send `device` when the caller explicitly set one; otherwise let the daemon auto-fill\n // its local label (loopback) or reject (remote, which requires explicit device).\n const registerArgs: Record<string, unknown> = {\n agent_type: 'custom',\n agent_type_name: 'cross-agent-teams-channel',\n model: 'proxy',\n role: '__channel_proxy__',\n name: `channel-proxy-${process.pid}`,\n team: 'default',\n claude_ui_pid: findClaudeUiPid(),\n delivery: {\n kind: 'claude-channel',\n channel_session_id: config.channel_session_id,\n },\n }\n if (config.device !== undefined) {\n registerArgs.device = config.device\n }\n const registerResp = await client.callTool({\n name: 'register_agent',\n arguments: registerArgs,\n })\n order.push('register_agent')\n const regResult = await parseToolResult(registerResp)\n if (!('agent_id' in regResult)) {\n throw new Error(`register_agent failed: ${JSON.stringify(regResult)}`)\n }\n\n // 2. subscribe_channel_wake — proxy's csid is fresh per startup\n const subResp = await client.callTool({\n name: 'subscribe_channel_wake',\n arguments: { channel_session_id: config.channel_session_id }\n })\n order.push('subscribe_channel_wake')\n const subResult = await parseToolResult(subResp)\n if (!('ok' in subResult) || subResult.ok !== true) {\n throw new Error(`subscribe_channel_wake failed: ${JSON.stringify(subResult)}`)\n }\n\n return {\n order,\n lastSubscribeResult: subResult,\n client,\n transport,\n close: async () => {\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n }\n }\n}\n\nexport interface WaitForDisconnectInput {\n client: Pick<Client, 'callTool'>\n transport: { onclose?: (() => void) | null | undefined }\n}\n\nexport interface WaitForDisconnectOptions {\n healthCheckIntervalMs?: number\n shouldStop?: () => boolean\n}\n\nexport async function waitForDisconnect(\n seq: WaitForDisconnectInput,\n opts: WaitForDisconnectOptions = {}\n): Promise<void> {\n const interval = opts.healthCheckIntervalMs ?? 30_000\n const shouldStop = opts.shouldStop ?? (() => false)\n let disconnected = false\n let wakeup: (() => void) | null = null\n const closeHandler = (): void => {\n disconnected = true\n wakeup?.()\n }\n const prevOnClose = seq.transport.onclose\n seq.transport.onclose = (): void => { prevOnClose?.(); closeHandler() }\n while (!disconnected && !shouldStop()) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { wakeup = null; resolve() }, interval)\n wakeup = (): void => { clearTimeout(timer); wakeup = null; resolve() }\n })\n if (disconnected || shouldStop()) break\n try {\n await seq.client.callTool({ name: 'echo', arguments: { msg: 'hb' } })\n } catch {\n disconnected = true\n break\n }\n }\n}\n\nexport function runReconnectingProxy(config: ReconnectingProxyConfig): ReconnectingProxyController {\n let stopped = false\n let currentSeq: RegistrationSequenceResult | null = null\n\n async function loop(): Promise<void> {\n while (!stopped) {\n try {\n const seq = await runRegistrationSequence(config)\n currentSeq = seq\n if (config.onSequenceComplete) config.onSequenceComplete([...seq.order])\n\n await waitForDisconnect(seq, {\n healthCheckIntervalMs: config.healthCheckIntervalMs,\n shouldStop: () => stopped,\n })\n if (config.onDisconnect) config.onDisconnect()\n try { await seq.close() } catch { /* best-effort */ }\n currentSeq = null\n } catch {\n // register/subscribe failed — wait and retry.\n }\n if (stopped) break\n const wait = config.backoffInitialMs ?? 500\n await new Promise(r => setTimeout(r, wait))\n }\n }\n\n void loop()\n\n return {\n stop: async () => {\n stopped = true\n if (currentSeq) {\n try { await currentSeq.close() } catch { /* best-effort */ }\n }\n }\n }\n}\n","import { execFileSync } from 'node:child_process'\n\nconst MAX_HOPS = 8\n\ninterface PsRow {\n ppid: number\n cmd: string\n}\n\nexport function readPsRow(pid: number): PsRow | null {\n try {\n const out = execFileSync('ps', ['-o', 'ppid=,args=', '-p', String(pid)], {\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore']\n })\n const trimmed = out.trim()\n if (!trimmed) return null\n const m = /^\\s*(\\d+)\\s+(.*)$/.exec(trimmed)\n if (!m) return null\n return { ppid: parseInt(m[1], 10), cmd: m[2] }\n } catch {\n return null\n }\n}\n\nexport function isClaudeCmd(cmd: string): boolean {\n const first = cmd.trim().split(/\\s+/)[0]\n if (!first) return false\n const base = first.replace(/^.*\\//, '')\n return base === 'claude'\n}\n\nexport function findClaudeUiPid(\n startPpid: number = process.ppid,\n reader: (pid: number) => PsRow | null = readPsRow\n): number {\n let pid = startPpid\n for (let i = 0; i < MAX_HOPS; i++) {\n const row = reader(pid)\n if (!row) break\n if (isClaudeCmd(row.cmd)) return pid\n if (row.ppid <= 1 || row.ppid === pid) break\n pid = row.ppid\n }\n return startPpid\n}\n"],"mappings":";;;AACA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;;;ACLrC,SAAS,iBAAiB;AAEnB,SAAS,oBAA+B;AAC7C,SAAO,IAAI;AAAA,IACT,EAAE,MAAM,6BAA6B,SAAS,QAAQ;AAAA,IACtD,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAAE,EAAE,EAAE;AAAA,EAC7D;AACF;AAOO,SAAS,iBAAiB,QAAmB,QAAiC;AACnF,MAAI;AACF,UAAM,QAAQ;AAAA,MACZ,QAAQ;AAAA,MACR;AAAA,IACF;AACA,UAAM,IAAK,OAAO,OAAO,aAAoD,KAAK;AAClF,QAAI,KAAK,OAAO,EAAE,UAAU,YAAY;AACtC,QAAE,MAAM,MAAM;AAAA,MAAoC,CAAC;AAAA,IACrD;AAAA,EACF,QAAQ;AAAA,EAER;AACF;;;AC3BA,SAAS,cAAc;AACvB,SAAS,qCAAqC;;;ACD9C,SAAS,oBAAoB;AAE7B,IAAM,WAAW;AAOV,SAAS,UAAU,KAA2B;AACnD,MAAI;AACF,UAAM,MAAM,aAAa,MAAM,CAAC,MAAM,eAAe,MAAM,OAAO,GAAG,CAAC,GAAG;AAAA,MACvE,UAAU;AAAA,MACV,SAAS;AAAA,MACT,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC;AACD,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,IAAI,oBAAoB,KAAK,OAAO;AAC1C,QAAI,CAAC,EAAG,QAAO;AACf,WAAO,EAAE,MAAM,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,EAAE;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,YAAY,KAAsB;AAChD,QAAM,QAAQ,IAAI,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC;AACvC,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,MAAM,QAAQ,SAAS,EAAE;AACtC,SAAO,SAAS;AAClB;AAEO,SAAS,gBACd,YAAoB,QAAQ,MAC5B,SAAwC,WAChC;AACR,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,UAAM,MAAM,OAAO,GAAG;AACtB,QAAI,CAAC,IAAK;AACV,QAAI,YAAY,IAAI,GAAG,EAAG,QAAO;AACjC,QAAI,IAAI,QAAQ,KAAK,IAAI,SAAS,IAAK;AACvC,UAAM,IAAI;AAAA,EACZ;AACA,SAAO;AACT;;;ADZA,eAAe,gBAAgB,MAAoC;AACjE,QAAM,IAAI;AACV,QAAM,OAAO,EAAE,UAAU,CAAC,GAAG;AAC7B,MAAI,OAAO,SAAS,SAAU,QAAO,CAAC;AACtC,MAAI;AAAE,WAAO,KAAK,MAAM,IAAI;AAAA,EAAgB,QAAQ;AAAE,WAAO,CAAC;AAAA,EAAE;AAClE;AAEA,eAAsB,wBACpB,QACqC;AACrC,QAAM,QAAkB,CAAC;AACzB,QAAM,cAAc,OAAO,QACvB,EAAE,SAAS,EAAE,eAAe,UAAU,OAAO,KAAK,GAAG,EAAE,IACvD;AACJ,QAAM,YAAY,IAAI,8BAA8B,IAAI,IAAI,OAAO,SAAS,GAAG;AAAA,IAC7E;AAAA,EACF,CAAC;AACD,QAAM,SAAS,IAAI,OAAO,EAAE,MAAM,2BAA2B,SAAS,QAAQ,CAAC;AAE/E,MAAI,OAAO,qBAAqB;AAC9B,WAAO,8BAA8B,OAAO,MAAM;AAChD,UAAI,EAAE,WAAW,8BAA8B;AAC7C,eAAO,oBAAqB,EAAE,MAAM;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,QAAQ,SAAS;AAM9B,QAAM,eAAwC;AAAA,IAC5C,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM,iBAAiB,QAAQ,GAAG;AAAA,IAClC,MAAM;AAAA,IACN,eAAe,gBAAgB;AAAA,IAC/B,UAAU;AAAA,MACR,MAAM;AAAA,MACN,oBAAoB,OAAO;AAAA,IAC7B;AAAA,EACF;AACA,MAAI,OAAO,WAAW,QAAW;AAC/B,iBAAa,SAAS,OAAO;AAAA,EAC/B;AACA,QAAM,eAAe,MAAM,OAAO,SAAS;AAAA,IACzC,MAAM;AAAA,IACN,WAAW;AAAA,EACb,CAAC;AACD,QAAM,KAAK,gBAAgB;AAC3B,QAAM,YAAY,MAAM,gBAAgB,YAAY;AACpD,MAAI,EAAE,cAAc,YAAY;AAC9B,UAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,SAAS,CAAC,EAAE;AAAA,EACvE;AAGA,QAAM,UAAU,MAAM,OAAO,SAAS;AAAA,IACpC,MAAM;AAAA,IACN,WAAW,EAAE,oBAAoB,OAAO,mBAAmB;AAAA,EAC7D,CAAC;AACD,QAAM,KAAK,wBAAwB;AACnC,QAAM,YAAY,MAAM,gBAAgB,OAAO;AAC/C,MAAI,EAAE,QAAQ,cAAc,UAAU,OAAO,MAAM;AACjD,UAAM,IAAI,MAAM,kCAAkC,KAAK,UAAU,SAAS,CAAC,EAAE;AAAA,EAC/E;AAEA,SAAO;AAAA,IACL;AAAA,IACA,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,OAAO,YAAY;AACjB,UAAI;AAAE,cAAM,OAAO,MAAM;AAAA,MAAE,QAAQ;AAAA,MAAoB;AACvD,UAAI;AAAE,cAAM,UAAU,MAAM;AAAA,MAAE,QAAQ;AAAA,MAAoB;AAAA,IAC5D;AAAA,EACF;AACF;AAYA,eAAsB,kBACpB,KACA,OAAiC,CAAC,GACnB;AACf,QAAM,WAAW,KAAK,yBAAyB;AAC/C,QAAM,aAAa,KAAK,eAAe,MAAM;AAC7C,MAAI,eAAe;AACnB,MAAI,SAA8B;AAClC,QAAM,eAAe,MAAY;AAC/B,mBAAe;AACf,aAAS;AAAA,EACX;AACA,QAAM,cAAc,IAAI,UAAU;AAClC,MAAI,UAAU,UAAU,MAAY;AAAE,kBAAc;AAAG,iBAAa;AAAA,EAAE;AACtE,SAAO,CAAC,gBAAgB,CAAC,WAAW,GAAG;AACrC,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,QAAQ,WAAW,MAAM;AAAE,iBAAS;AAAM,gBAAQ;AAAA,MAAE,GAAG,QAAQ;AACrE,eAAS,MAAY;AAAE,qBAAa,KAAK;AAAG,iBAAS;AAAM,gBAAQ;AAAA,MAAE;AAAA,IACvE,CAAC;AACD,QAAI,gBAAgB,WAAW,EAAG;AAClC,QAAI;AACF,YAAM,IAAI,OAAO,SAAS,EAAE,MAAM,QAAQ,WAAW,EAAE,KAAK,KAAK,EAAE,CAAC;AAAA,IACtE,QAAQ;AACN,qBAAe;AACf;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,QAA8D;AACjG,MAAI,UAAU;AACd,MAAI,aAAgD;AAEpD,iBAAe,OAAsB;AACnC,WAAO,CAAC,SAAS;AACf,UAAI;AACF,cAAM,MAAM,MAAM,wBAAwB,MAAM;AAChD,qBAAa;AACb,YAAI,OAAO,mBAAoB,QAAO,mBAAmB,CAAC,GAAG,IAAI,KAAK,CAAC;AAEvE,cAAM,kBAAkB,KAAK;AAAA,UAC3B,uBAAuB,OAAO;AAAA,UAC9B,YAAY,MAAM;AAAA,QACpB,CAAC;AACD,YAAI,OAAO,aAAc,QAAO,aAAa;AAC7C,YAAI;AAAE,gBAAM,IAAI,MAAM;AAAA,QAAE,QAAQ;AAAA,QAAoB;AACpD,qBAAa;AAAA,MACf,QAAQ;AAAA,MAER;AACA,UAAI,QAAS;AACb,YAAM,OAAO,OAAO,oBAAoB;AACxC,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,IAAI,CAAC;AAAA,IAC5C;AAAA,EACF;AAEA,OAAK,KAAK;AAEV,SAAO;AAAA,IACL,MAAM,YAAY;AAChB,gBAAU;AACV,UAAI,YAAY;AACd,YAAI;AAAE,gBAAM,WAAW,MAAM;AAAA,QAAE,QAAQ;AAAA,QAAoB;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACF;;;AF9KO,IAAM,cAAN,cAA0B,MAAM;AAAA,EACrC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,iBAAiB,MAA2E;AAC1G,QAAM,UAAU;AAAA,IACd,qDAAqD,IAAI;AAAA,IACzD;AAAA,IACA;AAAA,IACA,sCAAsC,IAAI;AAAA,IAC1C;AAAA,EACF,EAAE,KAAK,GAAG;AACV,SAAO;AAAA,IACL;AAAA,IACA,MAAM,EAAE,QAAQ,yBAAyB,MAAM,oBAAoB;AAAA,EACrE;AACF;AAEO,SAAS,aAAa,MAAyB,MAAyB,QAAQ,KAAc;AACnG,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,OAAO,KAAK,CAAC;AACnB,UAAM,OAAO,KAAK,IAAI,CAAC;AACvB,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,oBAAY;AAAM;AAAK;AAAA,MACzB,KAAK;AACH,gBAAQ;AAAM;AAAK;AAAA,MACrB,KAAK;AACH,yBAAiB;AAAM;AAAK;AAAA,MAC9B;AAGE;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,CAAC,aAAa,UAAU,WAAW,GAAG;AACxC,gBAAY,IAAI;AAAA,EAClB;AACA,MAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,YAAQ,IAAI;AAAA,EACd;AAEA,MAAI,CAAC,aAAa,UAAU,WAAW,GAAG;AACxC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAIA,QAAM,SACJ,mBAAmB,SAAY,mBAAmB,cAAc,IAAI;AACtE,SAAO,EAAE,WAAW,OAAO,OAAO;AACpC;AAEA,eAAsB,KACpB,OAA0B,QAAQ,KAAK,MAAM,CAAC,GAC9C,MAAyB,QAAQ,KAClB;AACf,MAAI;AACJ,MAAI;AACF,WAAO,aAAa,MAAM,GAAG;AAAA,EAC/B,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAQ,OAAO,MAAM,4BAA4B,GAAG;AAAA,CAAI;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,OAAO,WAAW;AAExB,QAAM,aAAa,kBAAkB;AACrC,QAAM,iBAAiB,IAAI,qBAAqB;AAEhD,MAAI,4BAA4B;AAChC,QAAM,aAAa,qBAAqB;AAAA,IACtC,WAAW,KAAK;AAAA,IAChB,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,oBAAoB;AAAA,IACpB,qBAAqB,CAAC,WAAW;AAC/B,uBAAiB,YAAY,MAA2D;AAAA,IAC1F;AAAA,IACA,oBAAoB,MAAM;AACxB,kCAA4B;AAG5B,YAAM,OAAO,iBAAiB,IAAI;AAClC,uBAAiB,YAAY,IAAI;AAAA,IACnC;AAAA,EACF,CAAC;AAED,MAAI,UAAU;AACd,QAAM,WAAW,YAA2B;AAC1C,QAAI,QAAS;AACb,cAAU;AACV,QAAI;AAAE,YAAM,WAAW,KAAK;AAAA,IAAE,QAAQ;AAAA,IAAoB;AAC1D,QAAI;AAAE,YAAM,WAAW,MAAM;AAAA,IAAE,QAAQ;AAAA,IAAoB;AAC3D,QAAI,CAAC,2BAA2B;AAC9B,cAAQ,OAAO,MAAM,kDAAkD,KAAK,SAAS;AAAA,CAAI;AACzF,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,iBAAe,UAAU,MAAM;AAAE,SAAK,SAAS;AAAA,EAAE;AAEjD,QAAM,WAAW,QAAQ,cAAc;AAEvC,UAAQ,GAAG,WAAW,MAAM;AAAE,SAAK,SAAS;AAAA,EAAE,CAAC;AAC/C,UAAQ,GAAG,UAAU,MAAM;AAAE,SAAK,SAAS;AAAA,EAAE,CAAC;AAChD;AAMA,SAAS,UAAmB;AAC1B,MAAI;AACF,UAAM,WAAW,cAAc,YAAY,GAAG;AAC9C,UAAM,WAAW,aAAa,QAAQ,KAAK,CAAC,CAAC;AAC7C,WAAO,aAAa;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,IAAI,QAAQ,GAAG;AACb,OAAK,KAAK;AACZ;AAEA,SAAS,mBAAmB,UAA2B;AACrD,QAAM,MAAM,YAAY,SAAS;AACjC,MAAI,IAAI,SAAS,GAAG,GAAG;AACrB,UAAM,IAAI,YAAY,sBAAsB;AAAA,EAC9C;AACA,QAAM,aAAa,IAChB,KAAK,EACL,YAAY,EACZ,QAAQ,gBAAgB,GAAG;AAC9B,QAAM,QAAQ,WAAW,SAAS,IAAI,aAAa;AACnD,MAAI,MAAM,SAAS,IAAI;AACrB,UAAM,IAAI,YAAY,sBAAsB;AAAA,EAC9C;AACA,SAAO;AACT;","names":[]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
interface DaemonCliArgs {
|
|
3
|
+
pidPath: string;
|
|
4
|
+
dbPath: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
requestedPort: number;
|
|
7
|
+
host: string;
|
|
8
|
+
localDevice: string;
|
|
9
|
+
}
|
|
10
|
+
declare function parseDaemonCliArgs(argv?: readonly string[], env?: NodeJS.ProcessEnv): DaemonCliArgs;
|
|
11
|
+
|
|
12
|
+
export { type DaemonCliArgs, parseDaemonCliArgs };
|