cross-agent-teams-mcp 0.5.16 → 0.6.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 +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/channel-cli.d.ts +8 -2
- package/dist/channel-cli.js +41 -3
- package/dist/channel-cli.js.map +1 -1
- package/dist/cli.js +3 -4
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/daemon/server.ts +2 -0
- package/src/mcp/register-agent.ts +1 -1
- package/src/mcp/transport.ts +1 -1
package/README.md
CHANGED
|
@@ -270,7 +270,9 @@ Use a specific LAN IP (e.g. `10.0.0.10`) or a tailscale CGNAT IP (`100.x.x.x`) i
|
|
|
270
270
|
|
|
271
271
|
### 2. Peer-side: `.mcp.json` updates
|
|
272
272
|
|
|
273
|
-
Each remote teammate's Claude Code needs **two** changes from the default loopback config: the HTTP entry must carry an `Authorization: Bearer …` header, and the channel proxy must pass `--token` AND `--device
|
|
273
|
+
Each remote teammate's Claude Code needs **two** changes from the default loopback config: the HTTP entry must carry an `Authorization: Bearer …` header, and the channel proxy must pass `--token` AND `--device`.
|
|
274
|
+
|
|
275
|
+
> **`--device` is critical for cross-host setups.** The daemon rejects any remote `register_agent` without a `device` field (`device_required_from_remote`), so a proxy spawned without `--device` ends up in a register/fail/respawn loop and never wakes the agent — auto-poke silently degrades to `no_pane`. Since v0.5.18 the proxy auto-derives a label from `os.hostname()` and writes a stderr notice when `--device` is missing on a non-loopback daemon, but the derived value can still collide with the daemon-host's own label (triggering `device_spoofing_local_label_from_remote`). Always pin `--device` explicitly per host:
|
|
274
276
|
|
|
275
277
|
```json
|
|
276
278
|
{
|
package/README.zh-CN.md
CHANGED
|
@@ -270,7 +270,9 @@ npx -y cross-agent-teams-mcp@latest daemon \
|
|
|
270
270
|
|
|
271
271
|
### 2. 远端机器侧: 改 `.mcp.json`
|
|
272
272
|
|
|
273
|
-
每台远端同事的 Claude Code 相对默认 loopback 配置都要改两处 — HTTP 入口加 `Authorization: Bearer …` 头, channel proxy 加 `--token` 和 `--device
|
|
273
|
+
每台远端同事的 Claude Code 相对默认 loopback 配置都要改两处 — HTTP 入口加 `Authorization: Bearer …` 头, channel proxy 加 `--token` 和 `--device`.
|
|
274
|
+
|
|
275
|
+
> **`--device` 对跨主机场景是关键配置.** daemon 端会拒掉任何不带 device 的远程 `register_agent` (返回 `device_required_from_remote`), 因此 channel proxy 缺 `--device` 时会陷入 register/fail/respawn 死循环, 永远叫不醒目标 agent — auto-poke 会静默退化成 `no_pane`. v0.5.18 起 proxy 在 daemon 非 loopback 且未传 `--device` 时会用 `os.hostname()` 自动派生一个 label 并 stderr 打 notice, 但派生值仍可能与 daemon 本机标签撞 (触发 `device_spoofing_local_label_from_remote`), 跨主机部署务必为每台机器在配置里显式钉死 `--device`:
|
|
274
276
|
|
|
275
277
|
```json
|
|
276
278
|
{
|
package/dist/channel-cli.d.ts
CHANGED
|
@@ -3,10 +3,16 @@ interface CliArgs {
|
|
|
3
3
|
daemonUrl: string;
|
|
4
4
|
token?: string;
|
|
5
5
|
device?: string;
|
|
6
|
+
deviceAutoDerivedNotice?: string;
|
|
7
|
+
}
|
|
8
|
+
interface ParseCliArgsDeps {
|
|
9
|
+
hostname?: () => string;
|
|
6
10
|
}
|
|
7
11
|
declare class CliArgError extends Error {
|
|
8
12
|
constructor(message: string);
|
|
9
13
|
}
|
|
14
|
+
declare function isNonLoopbackDaemonUrl(daemonUrl: string): boolean;
|
|
15
|
+
declare function deriveHostnameDeviceLabel(hostnameValue: string): string | null;
|
|
10
16
|
declare function buildStartupHint(csid: string, device?: string): {
|
|
11
17
|
content: string;
|
|
12
18
|
meta: {
|
|
@@ -14,7 +20,7 @@ declare function buildStartupHint(csid: string, device?: string): {
|
|
|
14
20
|
kind: string;
|
|
15
21
|
};
|
|
16
22
|
};
|
|
17
|
-
declare function parseCliArgs(argv: readonly string[], env?: NodeJS.ProcessEnv): CliArgs;
|
|
23
|
+
declare function parseCliArgs(argv: readonly string[], env?: NodeJS.ProcessEnv, deps?: ParseCliArgsDeps): CliArgs;
|
|
18
24
|
declare function main(argv?: readonly string[], env?: NodeJS.ProcessEnv): Promise<void>;
|
|
19
25
|
|
|
20
|
-
export { CliArgError, buildStartupHint, main, parseCliArgs };
|
|
26
|
+
export { CliArgError, type ParseCliArgsDeps, buildStartupHint, deriveHostnameDeviceLabel, isNonLoopbackDaemonUrl, main, parseCliArgs };
|
package/dist/channel-cli.js
CHANGED
|
@@ -274,6 +274,24 @@ var CliArgError = class extends Error {
|
|
|
274
274
|
this.name = "CliArgError";
|
|
275
275
|
}
|
|
276
276
|
};
|
|
277
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "0.0.0.0", "::", "::1"]);
|
|
278
|
+
function isNonLoopbackDaemonUrl(daemonUrl) {
|
|
279
|
+
try {
|
|
280
|
+
const parsed = new URL(daemonUrl);
|
|
281
|
+
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
282
|
+
if (host === "") return false;
|
|
283
|
+
if (LOOPBACK_HOSTS.has(host)) return false;
|
|
284
|
+
if (host.startsWith("127.")) return false;
|
|
285
|
+
return true;
|
|
286
|
+
} catch {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function deriveHostnameDeviceLabel(hostnameValue) {
|
|
291
|
+
const normalized = hostnameValue.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
292
|
+
if (normalized.length === 0 || normalized.length > 64) return null;
|
|
293
|
+
return normalized;
|
|
294
|
+
}
|
|
277
295
|
function buildStartupHint(csid, device) {
|
|
278
296
|
const isCrossHost = device !== void 0;
|
|
279
297
|
const deviceClause = isCrossHost ? `, device: "${device}"` : "";
|
|
@@ -291,7 +309,7 @@ function buildStartupHint(csid, device) {
|
|
|
291
309
|
meta: { source: "cross_agent_teams_mcp", kind: "startup_bind_hint" }
|
|
292
310
|
};
|
|
293
311
|
}
|
|
294
|
-
function parseCliArgs(argv, env = process.env) {
|
|
312
|
+
function parseCliArgs(argv, env = process.env, deps = {}) {
|
|
295
313
|
let daemonUrl;
|
|
296
314
|
let token;
|
|
297
315
|
let explicitDevice;
|
|
@@ -326,8 +344,22 @@ function parseCliArgs(argv, env = process.env) {
|
|
|
326
344
|
"missing --daemon-url (or CROSS_AGENT_TEAMS_MCP_DAEMON_URL env var)"
|
|
327
345
|
);
|
|
328
346
|
}
|
|
329
|
-
|
|
330
|
-
|
|
347
|
+
let device;
|
|
348
|
+
let deviceAutoDerivedNotice;
|
|
349
|
+
if (explicitDevice !== void 0) {
|
|
350
|
+
device = resolveDeviceLabel(explicitDevice);
|
|
351
|
+
} else if (isNonLoopbackDaemonUrl(daemonUrl)) {
|
|
352
|
+
const hostnameFn = deps.hostname ?? hostname;
|
|
353
|
+
const derived = deriveHostnameDeviceLabel(hostnameFn());
|
|
354
|
+
if (derived === null) {
|
|
355
|
+
throw new CliArgError(
|
|
356
|
+
`--device is required when --daemon-url is non-loopback (got ${daemonUrl}); os.hostname() did not yield a usable label`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
device = derived;
|
|
360
|
+
deviceAutoDerivedNotice = `--device not supplied; auto-derived "${derived}" from os.hostname() for remote daemon ${daemonUrl}. Pass --device <label> explicitly to silence this notice and pin the device label.`;
|
|
361
|
+
}
|
|
362
|
+
return { daemonUrl, token, device, deviceAutoDerivedNotice };
|
|
331
363
|
}
|
|
332
364
|
async function main(argv = process.argv.slice(2), env = process.env) {
|
|
333
365
|
let args;
|
|
@@ -339,6 +371,10 @@ async function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
339
371
|
`);
|
|
340
372
|
process.exit(2);
|
|
341
373
|
}
|
|
374
|
+
if (args.deviceAutoDerivedNotice !== void 0) {
|
|
375
|
+
process.stderr.write(`cross-agent-teams-proxy: ${args.deviceAutoDerivedNotice}
|
|
376
|
+
`);
|
|
377
|
+
}
|
|
342
378
|
const csid = randomUUID();
|
|
343
379
|
const hostServer = createProxyServer();
|
|
344
380
|
const stdioTransport = new StdioServerTransport();
|
|
@@ -414,6 +450,8 @@ function resolveDeviceLabel(explicit) {
|
|
|
414
450
|
export {
|
|
415
451
|
CliArgError,
|
|
416
452
|
buildStartupHint,
|
|
453
|
+
deriveHostnameDeviceLabel,
|
|
454
|
+
isNonLoopbackDaemonUrl,
|
|
417
455
|
main,
|
|
418
456
|
parseCliArgs
|
|
419
457
|
};
|
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 { 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(\n csid: string,\n device?: string\n): { content: string; meta: { source: string; kind: string } } {\n // Local default path (no --device): emit the original same-host hint with NO\n // device-related noise. Pure-local users never need to think about device.\n // Cross-host path (--device passed): surface the device value in both the\n // user-facing ask and the register_agent call so the agent and the human\n // both know it is required (daemon returns device_required_from_remote\n // otherwise).\n const isCrossHost = device !== undefined\n const deviceClause = isCrossHost ? `, device: \"${device}\"` : ''\n const deviceRegisterFragment = isCrossHost\n ? ` (this proxy is running with --device \"${device}\", so include device: \"${device}\" verbatim — the daemon enforces per-origin device validation, and on a remote daemon omitting it returns device_required_from_remote)`\n : ''\n const ask = isCrossHost\n ? `'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), device: ${device} (cross-host setup — keep this value verbatim).'`\n : `'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 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: ${ask}`,\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>\"${deviceClause}, ui_pid: $PPID, project_dir: \"<current working directory>\"})${deviceRegisterFragment}. 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, args.device)\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 backoffScheduleMs?: readonly 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\nconst DEFAULT_BACKOFF_SCHEDULE_MS = [1_000, 10_000, 60_000, 600_000] as const\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\nfunction resolveBackoffSchedule(config: ReconnectingProxyConfig): readonly number[] {\n if (config.backoffScheduleMs && config.backoffScheduleMs.length > 0) {\n return config.backoffScheduleMs.map(ms => Math.max(1, ms))\n }\n if (config.backoffInitialMs !== undefined || config.backoffMaxMs !== undefined) {\n const initial = config.backoffInitialMs ?? DEFAULT_BACKOFF_SCHEDULE_MS[0]\n const max = config.backoffMaxMs\n ?? DEFAULT_BACKOFF_SCHEDULE_MS[DEFAULT_BACKOFF_SCHEDULE_MS.length - 1]\n const schedule: number[] = []\n let next = Math.max(1, initial)\n while (schedule.length < DEFAULT_BACKOFF_SCHEDULE_MS.length) {\n schedule.push(Math.min(next, max))\n next *= 2\n }\n return schedule\n }\n return DEFAULT_BACKOFF_SCHEDULE_MS\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 try {\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 transport.terminateSession() } catch { /* best-effort */ }\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n }\n }\n } catch (err) {\n try { await transport.terminateSession() } catch { /* best-effort */ }\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n throw err\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 const backoffScheduleMs = resolveBackoffSchedule(config)\n let backoffIndex = 0\n\n async function loop(): Promise<void> {\n while (!stopped) {\n let failed = false\n try {\n const seq = await runRegistrationSequence(config)\n backoffIndex = 0\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 failed = true\n // register/subscribe failed — wait and retry.\n }\n if (stopped) break\n const wait = backoffScheduleMs[Math.min(backoffIndex, backoffScheduleMs.length - 1)]\n if (failed) backoffIndex += 1\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 AND the daemon is on loopback.\n // The daemon then auto-fills its own local label on loopback registrations,\n // which keeps zero-config proxies working against a daemon whose operator\n // chose a custom --device. For non-loopback daemons the proxy auto-derives\n // a label from os.hostname() (see deviceAutoDerivedNotice).\n device?: string\n // Set when --device was not supplied and we auto-derived one because the\n // daemon is non-loopback. Surfaced so main() can emit a one-line stderr\n // notice; daemon-side validation may still reject the derived label\n // (e.g. device_spoofing_local_label_from_remote).\n deviceAutoDerivedNotice?: string\n}\n\nexport interface ParseCliArgsDeps {\n hostname?: () => string\n}\n\nexport class CliArgError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CliArgError'\n }\n}\n\nconst LOOPBACK_HOSTS = new Set(['localhost', '0.0.0.0', '::', '::1'])\n\nexport function isNonLoopbackDaemonUrl(daemonUrl: string): boolean {\n try {\n const parsed = new URL(daemonUrl)\n // WHATWG URL keeps IPv6 literals wrapped in brackets in `hostname` —\n // strip them so `::1` matches the loopback set.\n const host = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, '')\n if (host === '') return false\n if (LOOPBACK_HOSTS.has(host)) return false\n if (host.startsWith('127.')) return false\n return true\n } catch {\n // Unparseable URL: assume remote so we still try to derive a device label;\n // daemon-client will surface the real connect error shortly.\n return true\n }\n}\n\nexport function deriveHostnameDeviceLabel(hostnameValue: string): string | null {\n const normalized = hostnameValue\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '-')\n .replace(/^-+|-+$/g, '')\n if (normalized.length === 0 || normalized.length > 64) return null\n return normalized\n}\n\nexport function buildStartupHint(\n csid: string,\n device?: string\n): { content: string; meta: { source: string; kind: string } } {\n // Local default path (no --device): emit the original same-host hint with NO\n // device-related noise. Pure-local users never need to think about device.\n // Cross-host path (--device passed): surface the device value in both the\n // user-facing ask and the register_agent call so the agent and the human\n // both know it is required (daemon returns device_required_from_remote\n // otherwise).\n const isCrossHost = device !== undefined\n const deviceClause = isCrossHost ? `, device: \"${device}\"` : ''\n const deviceRegisterFragment = isCrossHost\n ? ` (this proxy is running with --device \"${device}\", so include device: \"${device}\" verbatim — the daemon enforces per-origin device validation, and on a remote daemon omitting it returns device_required_from_remote)`\n : ''\n const ask = isCrossHost\n ? `'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), device: ${device} (cross-host setup — keep this value verbatim).'`\n : `'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 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: ${ask}`,\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>\"${deviceClause}, ui_pid: $PPID, project_dir: \"<current working directory>\"})${deviceRegisterFragment}. 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(\n argv: readonly string[],\n env: NodeJS.ProcessEnv = process.env,\n deps: ParseCliArgsDeps = {}\n): 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\n // Explicit --device wins. Otherwise: loopback daemons let the daemon\n // auto-fill its own localDevice (zero-config); non-loopback daemons require\n // a device on register_agent — the daemon returns device_required_from_remote\n // when it is missing, which would put the proxy in a register/fail/respawn\n // loop. Auto-derive from os.hostname() with a stderr notice; fail-fast when\n // the hostname yields nothing usable.\n let device: string | undefined\n let deviceAutoDerivedNotice: string | undefined\n if (explicitDevice !== undefined) {\n device = resolveDeviceLabel(explicitDevice)\n } else if (isNonLoopbackDaemonUrl(daemonUrl)) {\n const hostnameFn = deps.hostname ?? hostname\n const derived = deriveHostnameDeviceLabel(hostnameFn())\n if (derived === null) {\n throw new CliArgError(\n `--device is required when --daemon-url is non-loopback (got ${daemonUrl}); ` +\n `os.hostname() did not yield a usable label`\n )\n }\n device = derived\n deviceAutoDerivedNotice =\n `--device not supplied; auto-derived \"${derived}\" from os.hostname() for remote daemon ${daemonUrl}. ` +\n `Pass --device <label> explicitly to silence this notice and pin the device label.`\n }\n return { daemonUrl, token, device, deviceAutoDerivedNotice }\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 if (args.deviceAutoDerivedNotice !== undefined) {\n process.stderr.write(`cross-agent-teams-proxy: ${args.deviceAutoDerivedNotice}\\n`)\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, args.device)\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 backoffScheduleMs?: readonly 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\nconst DEFAULT_BACKOFF_SCHEDULE_MS = [1_000, 10_000, 60_000, 600_000] as const\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\nfunction resolveBackoffSchedule(config: ReconnectingProxyConfig): readonly number[] {\n if (config.backoffScheduleMs && config.backoffScheduleMs.length > 0) {\n return config.backoffScheduleMs.map(ms => Math.max(1, ms))\n }\n if (config.backoffInitialMs !== undefined || config.backoffMaxMs !== undefined) {\n const initial = config.backoffInitialMs ?? DEFAULT_BACKOFF_SCHEDULE_MS[0]\n const max = config.backoffMaxMs\n ?? DEFAULT_BACKOFF_SCHEDULE_MS[DEFAULT_BACKOFF_SCHEDULE_MS.length - 1]\n const schedule: number[] = []\n let next = Math.max(1, initial)\n while (schedule.length < DEFAULT_BACKOFF_SCHEDULE_MS.length) {\n schedule.push(Math.min(next, max))\n next *= 2\n }\n return schedule\n }\n return DEFAULT_BACKOFF_SCHEDULE_MS\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 try {\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 transport.terminateSession() } catch { /* best-effort */ }\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n }\n }\n } catch (err) {\n try { await transport.terminateSession() } catch { /* best-effort */ }\n try { await client.close() } catch { /* best-effort */ }\n try { await transport.close() } catch { /* best-effort */ }\n throw err\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 const backoffScheduleMs = resolveBackoffSchedule(config)\n let backoffIndex = 0\n\n async function loop(): Promise<void> {\n while (!stopped) {\n let failed = false\n try {\n const seq = await runRegistrationSequence(config)\n backoffIndex = 0\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 failed = true\n // register/subscribe failed — wait and retry.\n }\n if (stopped) break\n const wait = backoffScheduleMs[Math.min(backoffIndex, backoffScheduleMs.length - 1)]\n if (failed) backoffIndex += 1\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":[]}
|
package/dist/cli.js
CHANGED
|
@@ -742,8 +742,7 @@ var RegisterAgentService = class {
|
|
|
742
742
|
} catch {
|
|
743
743
|
}
|
|
744
744
|
}
|
|
745
|
-
const log = this.deps.log ?? ((
|
|
746
|
-
console.debug(line);
|
|
745
|
+
const log = this.deps.log ?? (() => {
|
|
747
746
|
});
|
|
748
747
|
try {
|
|
749
748
|
log(`register_agent takeover: old=${bound} new=${input.connection_id} device=${resolvedDevice.ok} team=${team} name=${input.name} closed=${closed}`);
|
|
@@ -3639,8 +3638,7 @@ function registerBusinessTools(server, db, getCallerAgentId, fanout, onRegisterS
|
|
|
3639
3638
|
// src/mcp/transport.ts
|
|
3640
3639
|
function mountMcp(app, db, fanout, channelWakeFanout, opts = {}) {
|
|
3641
3640
|
const sessions = /* @__PURE__ */ new Map();
|
|
3642
|
-
const log = opts.log ?? ((
|
|
3643
|
-
console.debug(line);
|
|
3641
|
+
const log = opts.log ?? (() => {
|
|
3644
3642
|
});
|
|
3645
3643
|
const context = opts.context ?? { localDevice: "local" };
|
|
3646
3644
|
function closeSessionByConnectionId(connectionId) {
|
|
@@ -4135,6 +4133,7 @@ async function buildServer(opts) {
|
|
|
4135
4133
|
const orphanGcMaxSessions = opts.orphanGcMaxSessions ?? parsePositiveInt(process.env.ORPHAN_GC_MAX_SESSIONS, DEFAULT_ORPHAN_GC_MAX_SESSIONS);
|
|
4136
4134
|
const mcp = mountMcp(app, db, fanout, channelWakeFanout, {
|
|
4137
4135
|
context,
|
|
4136
|
+
log: opts.mcpLog,
|
|
4138
4137
|
orphanSessionLimit: orphanGcMaxSessions
|
|
4139
4138
|
});
|
|
4140
4139
|
app.get("/health", async () => ({
|