@delt/claude-alarm 0.9.1 → 1.0.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 CHANGED
@@ -21,6 +21,7 @@ Monitor and interact with multiple Claude Code sessions from a web dashboard. Ge
21
21
  - **Dark / Light Mode** — Theme toggle with persistence
22
22
  - **Permission Relay** — Approve/deny tool calls from dashboard or phone
23
23
  - **Multi-Machine** — Remote hub access support
24
+ - **Codex Sessions** — See and message OpenAI Codex conversations next to Claude sessions
24
25
 
25
26
  ## Quick Start
26
27
 
@@ -75,6 +76,8 @@ Open `http://127.0.0.1:7900` in your browser.
75
76
  | `claude-alarm hub status` | Show hub status |
76
77
  | `claude-alarm token` | Show auth token |
77
78
  | `claude-alarm test` | Send test notification |
79
+ | `claude-alarm codex enable` / `disable` | Start (or stop starting) the Codex adapter with the hub |
80
+ | `claude-alarm codex start` / `stop` / `status` | Run the Codex adapter on its own, e.g. when Codex runs on another PC |
78
81
 
79
82
  ## Tools Available to Claude
80
83
 
@@ -168,6 +171,21 @@ Two-way messaging with Claude sessions via Telegram — text and images.
168
171
  }
169
172
  ```
170
173
 
174
+ ## Codex Sessions
175
+
176
+ claude-alarm can show OpenAI Codex conversations on the dashboard and in Telegram, next to your Claude sessions.
177
+
178
+ ```bash
179
+ claude-alarm codex enable
180
+ claude-alarm hub stop && claude-alarm hub start
181
+ ```
182
+
183
+ - Requires the Codex CLI with its app-server daemon (`codex app-server daemon version` shows `running`). Set `"codex": { "command": "C:/path/to/codex.exe" }` in `~/.claude-alarm/config.json` if `codex` is not on `PATH`.
184
+ - Every loaded Codex conversation appears as a session with a **Codex** badge. Replies, failures and approval waits are relayed to the dashboard and Telegram.
185
+ - Messages you send reach the conversation only while Codex is idle, and show up in Codex prefixed with `[claude-alarm · Dashboard]` or `[claude-alarm · Telegram]`.
186
+ - Approvals still have to be answered in Codex; claude-alarm only tells you one is waiting.
187
+ - If Codex runs on another PC, run `claude-alarm codex start` there with that PC's config pointing at your hub.
188
+
171
189
  ## Permission Relay
172
190
 
173
191
  Approve or deny Claude's tool calls remotely — from the dashboard or your phone — without `--dangerously-skip-permissions`.
@@ -39,6 +39,8 @@ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
39
39
  var PID_FILE = path.join(CONFIG_DIR, "hub.pid");
40
40
  var LOG_FILE = path.join(CONFIG_DIR, "hub.log");
41
41
  var UPLOADS_DIR = path.join(CONFIG_DIR, "uploads");
42
+ var CODEX_PID_FILE = path.join(CONFIG_DIR, "codex.pid");
43
+ var CODEX_LOG_FILE = path.join(CONFIG_DIR, "codex.log");
42
44
  var WS_PATH_CHANNEL = "/ws/channel";
43
45
  var CHANNEL_SERVER_NAME = "claude-alarm";
44
46
  var CHANNEL_SERVER_VERSION = "0.1.0";
@@ -91,48 +93,41 @@ function saveConfig(config2) {
91
93
  // src/channel/hub-client.ts
92
94
  import WebSocket from "ws";
93
95
  var HubClient = class {
94
- constructor(sessionId2, sessionName2, hubHost2 = DEFAULT_HUB_HOST, hubPort2 = DEFAULT_HUB_PORT, token, getPeerName = () => void 0) {
96
+ constructor(sessionId2, sessionName2, hubHost2 = DEFAULT_HUB_HOST, hubPort2 = DEFAULT_HUB_PORT, token, getPeerName = () => void 0, getRegistration = () => ({})) {
95
97
  this.sessionId = sessionId2;
96
98
  this.sessionName = sessionName2;
97
99
  this.hubHost = hubHost2;
98
100
  this.hubPort = hubPort2;
99
101
  this.token = token;
100
102
  this.getPeerName = getPeerName;
103
+ this.getRegistration = getRegistration;
101
104
  }
102
105
  ws = null;
103
106
  reconnectTimer = null;
104
107
  messageHandlers = [];
105
108
  queue = [];
106
109
  connected = false;
110
+ closed = false;
107
111
  connect() {
112
+ this.closed = false;
108
113
  const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : "";
109
114
  const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;
110
115
  logger.debug(`Connecting to hub at ${url}`);
111
116
  try {
112
- this.ws = new WebSocket(url);
113
- this.ws.on("open", () => {
117
+ const ws = new WebSocket(url);
118
+ this.ws = ws;
119
+ ws.on("open", () => {
120
+ if (this.ws !== ws) return;
114
121
  logger.info("Connected to hub");
115
122
  this.connected = true;
116
- const registration = {
117
- type: "register",
118
- session: {
119
- id: this.sessionId,
120
- name: this.sessionName,
121
- status: "idle",
122
- connectedAt: Date.now(),
123
- lastActivity: Date.now(),
124
- cwd: process.cwd(),
125
- channelEnabled: true,
126
- peerName: this.getPeerName()
127
- }
128
- };
129
- this.ws.send(JSON.stringify(registration));
123
+ this.ws.send(JSON.stringify(this.registration()));
130
124
  for (const msg of this.queue) {
131
125
  this.ws.send(JSON.stringify(msg));
132
126
  }
133
127
  this.queue = [];
134
128
  });
135
- this.ws.on("message", (data) => {
129
+ ws.on("message", (data) => {
130
+ if (this.ws !== ws) return;
136
131
  try {
137
132
  const msg = JSON.parse(data.toString());
138
133
  for (const handler of this.messageHandlers) {
@@ -142,12 +137,14 @@ var HubClient = class {
142
137
  logger.warn("Failed to parse hub message:", err);
143
138
  }
144
139
  });
145
- this.ws.on("close", () => {
140
+ ws.on("close", () => {
141
+ if (this.ws !== ws) return;
146
142
  logger.info("Disconnected from hub");
147
143
  this.connected = false;
148
- this.scheduleReconnect();
144
+ if (!this.closed) this.scheduleReconnect();
149
145
  });
150
- this.ws.on("error", (err) => {
146
+ ws.on("error", (err) => {
147
+ if (this.ws !== ws) return;
151
148
  logger.debug(`Hub connection error: ${err.message}`);
152
149
  this.connected = false;
153
150
  });
@@ -170,6 +167,7 @@ var HubClient = class {
170
167
  this.messageHandlers.push(handler);
171
168
  }
172
169
  disconnect() {
170
+ this.closed = true;
173
171
  if (this.reconnectTimer) {
174
172
  clearTimeout(this.reconnectTimer);
175
173
  this.reconnectTimer = null;
@@ -180,8 +178,29 @@ var HubClient = class {
180
178
  }
181
179
  this.connected = false;
182
180
  }
181
+ reregister() {
182
+ if (this.connected && this.ws?.readyState === WebSocket.OPEN) {
183
+ this.ws.send(JSON.stringify(this.registration()));
184
+ }
185
+ }
186
+ registration() {
187
+ return {
188
+ type: "register",
189
+ session: {
190
+ id: this.sessionId,
191
+ name: this.sessionName,
192
+ status: "idle",
193
+ connectedAt: Date.now(),
194
+ lastActivity: Date.now(),
195
+ cwd: process.cwd(),
196
+ channelEnabled: true,
197
+ peerName: this.getPeerName(),
198
+ ...this.getRegistration()
199
+ }
200
+ };
201
+ }
183
202
  scheduleReconnect() {
184
- if (this.reconnectTimer) return;
203
+ if (this.reconnectTimer || this.closed) return;
185
204
  this.reconnectTimer = setTimeout(() => {
186
205
  this.reconnectTimer = null;
187
206
  this.connect();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/channel/server.ts","../../src/shared/logger.ts","../../src/shared/constants.ts","../../src/shared/config.ts","../../src/channel/hub-client.ts","../../src/channel/peer-name.ts","../../src/shared/peer-name.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport {\r\n CallToolRequestSchema,\r\n ListToolsRequestSchema,\r\n} from '@modelcontextprotocol/sdk/types.js';\r\nimport { z } from 'zod';\r\nimport { randomUUID } from 'node:crypto';\r\nimport path from 'node:path';\r\nimport { logger } from '../shared/logger.js';\r\nimport { CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION } from '../shared/constants.js';\r\nimport { loadConfig } from '../shared/config.js';\r\nimport { HubClient } from './hub-client.js';\r\nimport { readPeerName } from './peer-name.js';\r\nimport type { SessionStatus, NotifyLevel } from '../shared/types.js';\r\n\r\nconst sessionId = randomUUID();\r\nconst sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path.basename(process.cwd());\r\nlet peerName = readPeerName();\r\n\r\nconst server = new Server(\r\n {\r\n name: CHANNEL_SERVER_NAME,\r\n version: CHANNEL_SERVER_VERSION,\r\n },\r\n {\r\n capabilities: {\r\n experimental: {\r\n 'claude/channel': {},\r\n 'claude/channel/permission': {},\r\n },\r\n tools: {},\r\n },\r\n instructions: `Messages from the claude-alarm dashboard arrive as <channel source=\"claude-alarm\" sender=\"...\">. Read the message and act on it.\r\n\r\nREPLYING:\r\n- The dashboard user ONLY sees messages sent via the reply tool — your terminal output is invisible to them. You MUST call reply with your response.\r\n- The dashboard renders Markdown (headings, lists, tables, code blocks). Provide a thorough, complete answer; do not apply CLI-style brevity.\r\n- Reply in the user's language (match the language they wrote in).\r\n\r\nIMAGES: If a channel message contains \"[Image: ...] Read the file to view it: <path>\", use the Read tool on that path before responding — otherwise the image is invisible to you.\r\n\r\nROUTING: If a dashboard message has lines like [claude-alarm] @X = SendMessage to \"Y\", do what the message asks and deliver the result with the SendMessage tool to exactly \"Y\". Never guess other recipients. Then reply to the dashboard with what you sent.\r\n\r\nSTATUS: Call status(\"working\") before starting a long task, status(\"waiting_input\") when blocked on user input, status(\"idle\") when finished responding.\r\n\r\nNOTIFICATIONS:\r\n- Use notify only for key events: task completion, errors, or when user attention is needed. Not for intermediate steps or simple acknowledgments.\r\n- Pick level: success=completion, error=failure, warning=attention needed, info=neutral status.`,\r\n },\r\n);\r\n\r\n// Load config for hub connection (env vars take priority)\r\nconst config = loadConfig();\r\nconst hubHost = process.env.CLAUDE_ALARM_HUB_HOST ?? config.hub.host;\r\nconst hubPort = process.env.CLAUDE_ALARM_HUB_PORT ? parseInt(process.env.CLAUDE_ALARM_HUB_PORT, 10) : config.hub.port;\r\nconst hubToken = process.env.CLAUDE_ALARM_HUB_TOKEN ?? config.hub.token;\r\n\r\n// Hub client for forwarding to central hub\r\nconst hubClient = new HubClient(\r\n sessionId,\r\n sessionName,\r\n hubHost,\r\n hubPort,\r\n hubToken,\r\n () => peerName,\r\n);\r\n\r\n// --- Tools ---\r\n\r\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\r\n tools: [\r\n {\r\n name: 'notify',\r\n description:\r\n 'Send a desktop notification to the user. Use this when you complete a task, encounter an error, or need user attention. The notification will appear as a system toast/popup.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n title: { type: 'string', description: 'Notification title (short)' },\r\n message: { type: 'string', description: 'Notification body text' },\r\n level: {\r\n type: 'string',\r\n enum: ['info', 'warning', 'error', 'success'],\r\n description: 'Notification level (default: info)',\r\n },\r\n },\r\n required: ['title', 'message'],\r\n },\r\n },\r\n {\r\n name: 'reply',\r\n description:\r\n 'Send a message to the web dashboard. Use this to communicate status updates, results, or any information the user should see in the monitoring dashboard.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n content: { type: 'string', description: 'Message content to display on the dashboard' },\r\n },\r\n required: ['content'],\r\n },\r\n },\r\n {\r\n name: 'status',\r\n description:\r\n 'Update your session status displayed on the dashboard. Set to \"working\" when actively processing, \"waiting_input\" when you need user input, or \"idle\" when done.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n status: {\r\n type: 'string',\r\n enum: ['idle', 'working', 'waiting_input'],\r\n description: 'Current session status',\r\n },\r\n },\r\n required: ['status'],\r\n },\r\n },\r\n ],\r\n}));\r\n\r\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\r\n const { name, arguments: args } = request.params;\r\n\r\n switch (name) {\r\n case 'notify': {\r\n const title = args?.title as string;\r\n const message = args?.message as string;\r\n const level = (args?.level as NotifyLevel) ?? 'info';\r\n logger.info(`Notify [${level}]: ${title} - ${message}`);\r\n hubClient.send({\r\n type: 'notify',\r\n sessionId,\r\n title,\r\n message,\r\n level,\r\n });\r\n return {\r\n content: [{ type: 'text', text: `Notification sent: \"${title}\"` }],\r\n };\r\n }\r\n\r\n case 'reply': {\r\n const content = args?.content as string;\r\n logger.info(`Reply: ${content.slice(0, 100)}...`);\r\n hubClient.send({\r\n type: 'reply',\r\n sessionId,\r\n content,\r\n });\r\n return {\r\n content: [{ type: 'text', text: 'Message sent to dashboard.' }],\r\n };\r\n }\r\n\r\n case 'status': {\r\n const status = args?.status as SessionStatus;\r\n logger.info(`Status update: ${status}`);\r\n hubClient.send({\r\n type: 'status',\r\n sessionId,\r\n status,\r\n });\r\n return {\r\n content: [{ type: 'text', text: `Status updated to \"${status}\".` }],\r\n };\r\n }\r\n\r\n default:\r\n return {\r\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\r\n isError: true,\r\n };\r\n }\r\n});\r\n\r\n// --- Permission Relay ---\r\n\r\nconst PermissionRequestSchema = z.object({\r\n method: z.literal('notifications/claude/channel/permission_request'),\r\n params: z.object({\r\n request_id: z.string(),\r\n tool_name: z.string(),\r\n description: z.string(),\r\n input_preview: z.string(),\r\n }),\r\n});\r\n\r\n// Handle permission_request from Claude Code and forward to hub\r\nserver.setNotificationHandler(\r\n PermissionRequestSchema,\r\n async (notification) => {\r\n const { request_id, tool_name, description, input_preview } = notification.params;\r\n logger.info(`Permission request [${request_id}]: ${tool_name} - ${description}`);\r\n hubClient.send({\r\n type: 'permission_request',\r\n sessionId,\r\n requestId: request_id,\r\n toolName: tool_name,\r\n description,\r\n inputPreview: input_preview,\r\n timestamp: Date.now(),\r\n });\r\n },\r\n);\r\n\r\n// --- Startup ---\r\n\r\nasync function main() {\r\n logger.info(`Starting MCP channel server (session: ${sessionId})`);\r\n\r\n // Connect to hub (non-blocking, will retry)\r\n hubClient.connect();\r\n\r\n // The registry entry can be written after this MCP server starts, so re-check\r\n // shortly after boot, then poll to follow /rename.\r\n const refreshPeerName = () => {\r\n const next = readPeerName();\r\n if (next === peerName) return;\r\n peerName = next;\r\n logger.info(`Peer name: ${next ?? '(none)'}`);\r\n hubClient.send({ type: 'peer_name', sessionId, peerName: next });\r\n };\r\n setTimeout(refreshPeerName, 2_000).unref();\r\n setInterval(refreshPeerName, 30_000).unref();\r\n\r\n // Listen for messages from hub and forward to Claude via channel notification\r\n hubClient.onMessage(async (msg) => {\r\n if (msg.type === 'message_to_session' && msg.sessionId === sessionId) {\r\n logger.info(`Message from dashboard: ${msg.content}`);\r\n await server.notification({\r\n method: 'notifications/claude/channel',\r\n params: {\r\n content: msg.content,\r\n meta: { sender: 'dashboard', timestamp: String(Date.now()) },\r\n },\r\n });\r\n } else if (msg.type === 'image_to_session' && msg.sessionId === sessionId) {\r\n logger.info(`Image from dashboard: ${msg.imagePath}`);\r\n const textPart = msg.content ? `\\n${msg.content}` : '';\r\n await server.notification({\r\n method: 'notifications/claude/channel',\r\n params: {\r\n content: `[Image: ${msg.originalName || 'image'}] The user sent an image. Read the file to view it: ${msg.imagePath}${textPart}`,\r\n meta: { sender: 'dashboard', timestamp: String(Date.now()), imagePath: msg.imagePath, mimeType: msg.mimeType },\r\n },\r\n });\r\n } else if (msg.type === 'permission_response' && msg.sessionId === sessionId) {\r\n logger.info(`Permission verdict [${msg.requestId}]: ${msg.behavior}`);\r\n await server.notification({\r\n method: 'notifications/claude/channel/permission',\r\n params: {\r\n request_id: msg.requestId,\r\n behavior: msg.behavior,\r\n },\r\n });\r\n }\r\n });\r\n\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n logger.info('MCP channel server running on stdio');\r\n\r\n // Exit when stdin closes (Claude Code session ended)\r\n process.stdin.on('end', () => {\r\n logger.info('stdin closed, exiting');\r\n hubClient.disconnect();\r\n process.exit(0);\r\n });\r\n process.stdin.on('close', () => {\r\n logger.info('stdin closed, exiting');\r\n hubClient.disconnect();\r\n process.exit(0);\r\n });\r\n}\r\n\r\nmain().catch((err) => {\r\n logger.error('Fatal error:', err);\r\n process.exit(1);\r\n});\r\n","/**\r\n * Logger that writes to stderr only.\r\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\r\n * Any console.log() would corrupt the protocol. Always use this logger.\r\n */\r\nexport const logger = {\r\n info(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm] ${msg}`, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\r\n },\r\n debug(msg: string, ...args: unknown[]) {\r\n if (process.env.CLAUDE_ALARM_DEBUG) {\r\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n};\r\n","import path from 'node:path';\r\nimport os from 'node:os';\r\n\r\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\r\nexport const DEFAULT_HUB_PORT = 7900;\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\r\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\r\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\r\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\r\nexport const UPLOADS_DIR = path.join(CONFIG_DIR, 'uploads');\r\n\r\nexport const WS_PATH_CHANNEL = '/ws/channel';\r\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\r\n\r\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\r\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\r\nimport type { AppConfig } from './types.js';\r\n\r\nconst DEFAULT_CONFIG: AppConfig = {\r\n hub: {\r\n host: DEFAULT_HUB_HOST,\r\n port: DEFAULT_HUB_PORT,\r\n },\r\n notifications: {\r\n desktop: true,\r\n sound: true,\r\n },\r\n webhooks: [],\r\n};\r\n\r\nexport function ensureConfigDir(): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nexport function loadConfig(): AppConfig {\r\n ensureConfigDir();\r\n let config: AppConfig;\r\n if (!fs.existsSync(CONFIG_FILE)) {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n } else {\r\n try {\r\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\r\n const parsed = JSON.parse(raw);\r\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...(parsed.telegram ? { telegram: parsed.telegram } : {}) };\r\n } catch {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n }\r\n }\r\n\r\n // Auto-generate token if missing\r\n if (!config.hub.token) {\r\n config.hub.token = randomUUID();\r\n saveConfig(config);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n/** Get the current token, generating one if needed */\r\nexport function getOrCreateToken(): string {\r\n const config = loadConfig();\r\n return config.hub.token!;\r\n}\r\n\r\nexport function saveConfig(config: AppConfig): void {\r\n ensureConfigDir();\r\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\r\n}\r\n\r\n/**\r\n * Add claude-alarm as an MCP channel server to .mcp.json\r\n */\r\nexport function setupMcpConfig(targetDir?: string): string {\r\n const dir = targetDir ?? process.cwd();\r\n const mcpPath = path.join(dir, '.mcp.json');\r\n\r\n let mcpConfig: Record<string, any> = {};\r\n if (fs.existsSync(mcpPath)) {\r\n try {\r\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\r\n } catch {\r\n mcpConfig = {};\r\n }\r\n }\r\n\r\n if (!mcpConfig.mcpServers) {\r\n mcpConfig.mcpServers = {};\r\n }\r\n\r\n mcpConfig.mcpServers['claude-alarm'] = {\r\n command: 'npx',\r\n args: ['-y', '@delt/claude-alarm', 'serve'],\r\n env: {\r\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\r\n },\r\n };\r\n\r\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\r\n return mcpPath;\r\n}\r\n","import WebSocket from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\r\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\r\n\r\nexport class HubClient {\r\n private ws: WebSocket | null = null;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\r\n private queue: ChannelMessage[] = [];\r\n private connected = false;\r\n\r\n constructor(\r\n private sessionId: string,\r\n private sessionName: string,\r\n private hubHost = DEFAULT_HUB_HOST,\r\n private hubPort = DEFAULT_HUB_PORT,\r\n private token?: string,\r\n private getPeerName: () => string | undefined = () => undefined,\r\n ) {}\r\n\r\n connect(): void {\r\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\r\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\r\n logger.debug(`Connecting to hub at ${url}`);\r\n\r\n try {\r\n this.ws = new WebSocket(url);\r\n\r\n this.ws.on('open', () => {\r\n logger.info('Connected to hub');\r\n this.connected = true;\r\n\r\n // Register this session\r\n const registration: ChannelMessage = {\r\n type: 'register',\r\n session: {\r\n id: this.sessionId,\r\n name: this.sessionName,\r\n status: 'idle',\r\n connectedAt: Date.now(),\r\n lastActivity: Date.now(),\r\n cwd: process.cwd(),\r\n channelEnabled: true,\r\n peerName: this.getPeerName(),\r\n },\r\n };\r\n this.ws!.send(JSON.stringify(registration));\r\n\r\n // Flush queued messages\r\n for (const msg of this.queue) {\r\n this.ws!.send(JSON.stringify(msg));\r\n }\r\n this.queue = [];\r\n });\r\n\r\n this.ws.on('message', (data) => {\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n for (const handler of this.messageHandlers) {\r\n handler(msg);\r\n }\r\n } catch (err) {\r\n logger.warn('Failed to parse hub message:', err);\r\n }\r\n });\r\n\r\n this.ws.on('close', () => {\r\n logger.info('Disconnected from hub');\r\n this.connected = false;\r\n this.scheduleReconnect();\r\n });\r\n\r\n this.ws.on('error', (err) => {\r\n logger.debug(`Hub connection error: ${err.message}`);\r\n this.connected = false;\r\n });\r\n } catch {\r\n logger.debug('Failed to connect to hub, will retry');\r\n this.scheduleReconnect();\r\n }\r\n }\r\n\r\n send(msg: ChannelMessage): void {\r\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify(msg));\r\n } else {\r\n if (this.queue.length < 100) {\r\n this.queue.push(msg);\r\n }\r\n logger.debug('Hub not connected, message queued');\r\n }\r\n }\r\n\r\n onMessage(handler: (msg: ChannelMessage) => void): void {\r\n this.messageHandlers.push(handler);\r\n }\r\n\r\n disconnect(): void {\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n this.connected = false;\r\n }\r\n\r\n private scheduleReconnect(): void {\r\n if (this.reconnectTimer) return;\r\n this.reconnectTimer = setTimeout(() => {\r\n this.reconnectTimer = null;\r\n this.connect();\r\n }, 5000);\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport os from 'node:os';\r\nimport path from 'node:path';\r\nimport { isValidPeerName } from '../shared/peer-name.js';\r\n\r\nexport interface PeerLookupEnv {\r\n messagingSocket?: string;\r\n sessionId?: string;\r\n}\r\n\r\ninterface SessionRecord {\r\n name?: unknown;\r\n messagingSocketPath?: unknown;\r\n sessionId?: unknown;\r\n}\r\n\r\nexport function findPeerName(records: unknown[], env: PeerLookupEnv): string | undefined {\r\n const valid = records.filter((r): r is SessionRecord => typeof r === 'object' && r !== null);\r\n const nameOf = (match: (r: SessionRecord) => boolean): string | undefined => {\r\n const rec = valid.find(match);\r\n return isValidPeerName(rec?.name) ? rec.name : undefined;\r\n };\r\n if (env.messagingSocket) {\r\n const bySocket = nameOf((r) => r.messagingSocketPath === env.messagingSocket);\r\n if (bySocket) return bySocket;\r\n }\r\n if (env.sessionId) return nameOf((r) => r.sessionId === env.sessionId);\r\n return undefined;\r\n}\r\n\r\n// <configDir>/sessions/<pid>.json is Claude Code's undocumented peer registry\r\n// (the source of ListAgents/SendMessage names, verified on 2.1.283). Its format\r\n// may change between releases, so every failure must degrade to \"no name\".\r\nexport function readPeerName(env: NodeJS.ProcessEnv = process.env): string | undefined {\r\n const lookup: PeerLookupEnv = {\r\n messagingSocket: env.CLAUDE_CODE_MESSAGING_SOCKET,\r\n sessionId: env.CLAUDE_CODE_SESSION_ID,\r\n };\r\n if (!lookup.messagingSocket && !lookup.sessionId) return undefined;\r\n\r\n const configDir = env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');\r\n const dir = path.join(configDir, 'sessions');\r\n let files: string[];\r\n try {\r\n files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));\r\n } catch {\r\n return undefined;\r\n }\r\n\r\n const records: unknown[] = [];\r\n for (const f of files) {\r\n try {\r\n records.push(JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')));\r\n } catch {}\r\n }\r\n return findPeerName(records, lookup);\r\n}\r\n","// A peer name is pasted verbatim into another session's prompt as\r\n// SendMessage to \"<name>\", so quotes, brackets, backslashes and control\r\n// characters must never pass.\r\nconst SAFE_PEER_NAME = /^[^\\u0000-\\u001f\\u007f\"\\\\[\\]]{1,128}$/;\r\n\r\nexport function isValidPeerName(name: unknown): name is string {\r\n return typeof name === 'string' && name.trim() !== '' && SAFE_PEER_NAME.test(name);\r\n}\r\n"],"mappings":"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","names":["randomUUID","path","path","config","config","sessionId","sessionName","hubHost","hubPort","fs","os","path","path","os","fs","randomUUID","path"]}
1
+ {"version":3,"sources":["../../src/channel/server.ts","../../src/shared/logger.ts","../../src/shared/constants.ts","../../src/shared/config.ts","../../src/channel/hub-client.ts","../../src/channel/peer-name.ts","../../src/shared/peer-name.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport {\r\n CallToolRequestSchema,\r\n ListToolsRequestSchema,\r\n} from '@modelcontextprotocol/sdk/types.js';\r\nimport { z } from 'zod';\r\nimport { randomUUID } from 'node:crypto';\r\nimport path from 'node:path';\r\nimport { logger } from '../shared/logger.js';\r\nimport { CHANNEL_SERVER_NAME, CHANNEL_SERVER_VERSION } from '../shared/constants.js';\r\nimport { loadConfig } from '../shared/config.js';\r\nimport { HubClient } from './hub-client.js';\r\nimport { readPeerName } from './peer-name.js';\r\nimport type { SessionStatus, NotifyLevel } from '../shared/types.js';\r\n\r\nconst sessionId = randomUUID();\r\nconst sessionName = process.env.CLAUDE_ALARM_SESSION_NAME ?? path.basename(process.cwd());\r\nlet peerName = readPeerName();\r\n\r\nconst server = new Server(\r\n {\r\n name: CHANNEL_SERVER_NAME,\r\n version: CHANNEL_SERVER_VERSION,\r\n },\r\n {\r\n capabilities: {\r\n experimental: {\r\n 'claude/channel': {},\r\n 'claude/channel/permission': {},\r\n },\r\n tools: {},\r\n },\r\n instructions: `Messages from the claude-alarm dashboard arrive as <channel source=\"claude-alarm\" sender=\"...\">. Read the message and act on it.\r\n\r\nREPLYING:\r\n- The dashboard user ONLY sees messages sent via the reply tool — your terminal output is invisible to them. You MUST call reply with your response.\r\n- The dashboard renders Markdown (headings, lists, tables, code blocks). Provide a thorough, complete answer; do not apply CLI-style brevity.\r\n- Reply in the user's language (match the language they wrote in).\r\n\r\nIMAGES: If a channel message contains \"[Image: ...] Read the file to view it: <path>\", use the Read tool on that path before responding — otherwise the image is invisible to you.\r\n\r\nROUTING: If a dashboard message has lines like [claude-alarm] @X = SendMessage to \"Y\", do what the message asks and deliver the result with the SendMessage tool to exactly \"Y\". Never guess other recipients. Then reply to the dashboard with what you sent.\r\n\r\nSTATUS: Call status(\"working\") before starting a long task, status(\"waiting_input\") when blocked on user input, status(\"idle\") when finished responding.\r\n\r\nNOTIFICATIONS:\r\n- Use notify only for key events: task completion, errors, or when user attention is needed. Not for intermediate steps or simple acknowledgments.\r\n- Pick level: success=completion, error=failure, warning=attention needed, info=neutral status.`,\r\n },\r\n);\r\n\r\n// Load config for hub connection (env vars take priority)\r\nconst config = loadConfig();\r\nconst hubHost = process.env.CLAUDE_ALARM_HUB_HOST ?? config.hub.host;\r\nconst hubPort = process.env.CLAUDE_ALARM_HUB_PORT ? parseInt(process.env.CLAUDE_ALARM_HUB_PORT, 10) : config.hub.port;\r\nconst hubToken = process.env.CLAUDE_ALARM_HUB_TOKEN ?? config.hub.token;\r\n\r\n// Hub client for forwarding to central hub\r\nconst hubClient = new HubClient(\r\n sessionId,\r\n sessionName,\r\n hubHost,\r\n hubPort,\r\n hubToken,\r\n () => peerName,\r\n);\r\n\r\n// --- Tools ---\r\n\r\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\r\n tools: [\r\n {\r\n name: 'notify',\r\n description:\r\n 'Send a desktop notification to the user. Use this when you complete a task, encounter an error, or need user attention. The notification will appear as a system toast/popup.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n title: { type: 'string', description: 'Notification title (short)' },\r\n message: { type: 'string', description: 'Notification body text' },\r\n level: {\r\n type: 'string',\r\n enum: ['info', 'warning', 'error', 'success'],\r\n description: 'Notification level (default: info)',\r\n },\r\n },\r\n required: ['title', 'message'],\r\n },\r\n },\r\n {\r\n name: 'reply',\r\n description:\r\n 'Send a message to the web dashboard. Use this to communicate status updates, results, or any information the user should see in the monitoring dashboard.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n content: { type: 'string', description: 'Message content to display on the dashboard' },\r\n },\r\n required: ['content'],\r\n },\r\n },\r\n {\r\n name: 'status',\r\n description:\r\n 'Update your session status displayed on the dashboard. Set to \"working\" when actively processing, \"waiting_input\" when you need user input, or \"idle\" when done.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n status: {\r\n type: 'string',\r\n enum: ['idle', 'working', 'waiting_input'],\r\n description: 'Current session status',\r\n },\r\n },\r\n required: ['status'],\r\n },\r\n },\r\n ],\r\n}));\r\n\r\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\r\n const { name, arguments: args } = request.params;\r\n\r\n switch (name) {\r\n case 'notify': {\r\n const title = args?.title as string;\r\n const message = args?.message as string;\r\n const level = (args?.level as NotifyLevel) ?? 'info';\r\n logger.info(`Notify [${level}]: ${title} - ${message}`);\r\n hubClient.send({\r\n type: 'notify',\r\n sessionId,\r\n title,\r\n message,\r\n level,\r\n });\r\n return {\r\n content: [{ type: 'text', text: `Notification sent: \"${title}\"` }],\r\n };\r\n }\r\n\r\n case 'reply': {\r\n const content = args?.content as string;\r\n logger.info(`Reply: ${content.slice(0, 100)}...`);\r\n hubClient.send({\r\n type: 'reply',\r\n sessionId,\r\n content,\r\n });\r\n return {\r\n content: [{ type: 'text', text: 'Message sent to dashboard.' }],\r\n };\r\n }\r\n\r\n case 'status': {\r\n const status = args?.status as SessionStatus;\r\n logger.info(`Status update: ${status}`);\r\n hubClient.send({\r\n type: 'status',\r\n sessionId,\r\n status,\r\n });\r\n return {\r\n content: [{ type: 'text', text: `Status updated to \"${status}\".` }],\r\n };\r\n }\r\n\r\n default:\r\n return {\r\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\r\n isError: true,\r\n };\r\n }\r\n});\r\n\r\n// --- Permission Relay ---\r\n\r\nconst PermissionRequestSchema = z.object({\r\n method: z.literal('notifications/claude/channel/permission_request'),\r\n params: z.object({\r\n request_id: z.string(),\r\n tool_name: z.string(),\r\n description: z.string(),\r\n input_preview: z.string(),\r\n }),\r\n});\r\n\r\n// Handle permission_request from Claude Code and forward to hub\r\nserver.setNotificationHandler(\r\n PermissionRequestSchema,\r\n async (notification) => {\r\n const { request_id, tool_name, description, input_preview } = notification.params;\r\n logger.info(`Permission request [${request_id}]: ${tool_name} - ${description}`);\r\n hubClient.send({\r\n type: 'permission_request',\r\n sessionId,\r\n requestId: request_id,\r\n toolName: tool_name,\r\n description,\r\n inputPreview: input_preview,\r\n timestamp: Date.now(),\r\n });\r\n },\r\n);\r\n\r\n// --- Startup ---\r\n\r\nasync function main() {\r\n logger.info(`Starting MCP channel server (session: ${sessionId})`);\r\n\r\n // Connect to hub (non-blocking, will retry)\r\n hubClient.connect();\r\n\r\n // The registry entry can be written after this MCP server starts, so re-check\r\n // shortly after boot, then poll to follow /rename.\r\n const refreshPeerName = () => {\r\n const next = readPeerName();\r\n if (next === peerName) return;\r\n peerName = next;\r\n logger.info(`Peer name: ${next ?? '(none)'}`);\r\n hubClient.send({ type: 'peer_name', sessionId, peerName: next });\r\n };\r\n setTimeout(refreshPeerName, 2_000).unref();\r\n setInterval(refreshPeerName, 30_000).unref();\r\n\r\n // Listen for messages from hub and forward to Claude via channel notification\r\n hubClient.onMessage(async (msg) => {\r\n if (msg.type === 'message_to_session' && msg.sessionId === sessionId) {\r\n logger.info(`Message from dashboard: ${msg.content}`);\r\n await server.notification({\r\n method: 'notifications/claude/channel',\r\n params: {\r\n content: msg.content,\r\n meta: { sender: 'dashboard', timestamp: String(Date.now()) },\r\n },\r\n });\r\n } else if (msg.type === 'image_to_session' && msg.sessionId === sessionId) {\r\n logger.info(`Image from dashboard: ${msg.imagePath}`);\r\n const textPart = msg.content ? `\\n${msg.content}` : '';\r\n await server.notification({\r\n method: 'notifications/claude/channel',\r\n params: {\r\n content: `[Image: ${msg.originalName || 'image'}] The user sent an image. Read the file to view it: ${msg.imagePath}${textPart}`,\r\n meta: { sender: 'dashboard', timestamp: String(Date.now()), imagePath: msg.imagePath, mimeType: msg.mimeType },\r\n },\r\n });\r\n } else if (msg.type === 'permission_response' && msg.sessionId === sessionId) {\r\n logger.info(`Permission verdict [${msg.requestId}]: ${msg.behavior}`);\r\n await server.notification({\r\n method: 'notifications/claude/channel/permission',\r\n params: {\r\n request_id: msg.requestId,\r\n behavior: msg.behavior,\r\n },\r\n });\r\n }\r\n });\r\n\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n logger.info('MCP channel server running on stdio');\r\n\r\n // Exit when stdin closes (Claude Code session ended)\r\n process.stdin.on('end', () => {\r\n logger.info('stdin closed, exiting');\r\n hubClient.disconnect();\r\n process.exit(0);\r\n });\r\n process.stdin.on('close', () => {\r\n logger.info('stdin closed, exiting');\r\n hubClient.disconnect();\r\n process.exit(0);\r\n });\r\n}\r\n\r\nmain().catch((err) => {\r\n logger.error('Fatal error:', err);\r\n process.exit(1);\r\n});\r\n","/**\r\n * Logger that writes to stderr only.\r\n * CRITICAL: In MCP channel servers, stdout is used for the stdio protocol.\r\n * Any console.log() would corrupt the protocol. Always use this logger.\r\n */\r\nexport const logger = {\r\n info(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm] ${msg}`, ...args);\r\n },\r\n warn(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm WARN] ${msg}`, ...args);\r\n },\r\n error(msg: string, ...args: unknown[]) {\r\n console.error(`[claude-alarm ERROR] ${msg}`, ...args);\r\n },\r\n debug(msg: string, ...args: unknown[]) {\r\n if (process.env.CLAUDE_ALARM_DEBUG) {\r\n console.error(`[claude-alarm DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n};\r\n","import path from 'node:path';\r\nimport os from 'node:os';\r\n\r\nexport const DEFAULT_HUB_HOST = '127.0.0.1';\r\nexport const DEFAULT_HUB_PORT = 7900;\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), '.claude-alarm');\r\nexport const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');\r\nexport const PID_FILE = path.join(CONFIG_DIR, 'hub.pid');\r\nexport const LOG_FILE = path.join(CONFIG_DIR, 'hub.log');\r\nexport const UPLOADS_DIR = path.join(CONFIG_DIR, 'uploads');\r\nexport const CODEX_PID_FILE = path.join(CONFIG_DIR, 'codex.pid');\r\nexport const CODEX_LOG_FILE = path.join(CONFIG_DIR, 'codex.log');\r\n\r\nexport const WS_PATH_CHANNEL = '/ws/channel';\r\nexport const WS_PATH_DASHBOARD = '/ws/dashboard';\r\n\r\nexport const CHANNEL_SERVER_NAME = 'claude-alarm';\r\nexport const CHANNEL_SERVER_VERSION = '0.1.0';\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { CONFIG_DIR, CONFIG_FILE, DEFAULT_HUB_HOST, DEFAULT_HUB_PORT } from './constants.js';\r\nimport type { AppConfig } from './types.js';\r\n\r\nconst DEFAULT_CONFIG: AppConfig = {\r\n hub: {\r\n host: DEFAULT_HUB_HOST,\r\n port: DEFAULT_HUB_PORT,\r\n },\r\n notifications: {\r\n desktop: true,\r\n sound: true,\r\n },\r\n webhooks: [],\r\n};\r\n\r\nexport function ensureConfigDir(): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nexport function loadConfig(): AppConfig {\r\n ensureConfigDir();\r\n let config: AppConfig;\r\n if (!fs.existsSync(CONFIG_FILE)) {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n } else {\r\n try {\r\n const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');\r\n const parsed = JSON.parse(raw);\r\n config = { ...DEFAULT_CONFIG, ...parsed, hub: { ...DEFAULT_CONFIG.hub, ...parsed.hub }, ...(parsed.telegram ? { telegram: parsed.telegram } : {}) };\r\n } catch {\r\n config = { ...DEFAULT_CONFIG, hub: { ...DEFAULT_CONFIG.hub } };\r\n }\r\n }\r\n\r\n // Auto-generate token if missing\r\n if (!config.hub.token) {\r\n config.hub.token = randomUUID();\r\n saveConfig(config);\r\n }\r\n\r\n return config;\r\n}\r\n\r\n/** Get the current token, generating one if needed */\r\nexport function getOrCreateToken(): string {\r\n const config = loadConfig();\r\n return config.hub.token!;\r\n}\r\n\r\nexport function saveConfig(config: AppConfig): void {\r\n ensureConfigDir();\r\n fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });\r\n}\r\n\r\nexport function setCodexEnabled(enabled: boolean): AppConfig {\r\n const config = loadConfig();\r\n config.codex = { ...config.codex, enabled };\r\n saveConfig(config);\r\n return config;\r\n}\r\n\r\n/**\r\n * Add claude-alarm as an MCP channel server to .mcp.json\r\n */\r\nexport function setupMcpConfig(targetDir?: string): string {\r\n const dir = targetDir ?? process.cwd();\r\n const mcpPath = path.join(dir, '.mcp.json');\r\n\r\n let mcpConfig: Record<string, any> = {};\r\n if (fs.existsSync(mcpPath)) {\r\n try {\r\n mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));\r\n } catch {\r\n mcpConfig = {};\r\n }\r\n }\r\n\r\n if (!mcpConfig.mcpServers) {\r\n mcpConfig.mcpServers = {};\r\n }\r\n\r\n mcpConfig.mcpServers['claude-alarm'] = {\r\n command: 'npx',\r\n args: ['-y', '@delt/claude-alarm', 'serve'],\r\n env: {\r\n CLAUDE_ALARM_SESSION_NAME: path.basename(dir),\r\n },\r\n };\r\n\r\n fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\r\n return mcpPath;\r\n}\r\n","import WebSocket from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\nimport { DEFAULT_HUB_HOST, DEFAULT_HUB_PORT, WS_PATH_CHANNEL } from '../shared/constants.js';\r\nimport type { ChannelMessage, SessionInfo } from '../shared/types.js';\r\n\r\nexport class HubClient {\r\n private ws: WebSocket | null = null;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private messageHandlers: Array<(msg: ChannelMessage) => void> = [];\r\n private queue: ChannelMessage[] = [];\r\n private connected = false;\r\n private closed = false;\r\n\r\n constructor(\r\n private sessionId: string,\r\n private sessionName: string,\r\n private hubHost = DEFAULT_HUB_HOST,\r\n private hubPort = DEFAULT_HUB_PORT,\r\n private token?: string,\r\n private getPeerName: () => string | undefined = () => undefined,\r\n private getRegistration: () => Partial<SessionInfo> = () => ({}),\r\n ) {}\r\n\r\n connect(): void {\r\n this.closed = false;\r\n const tokenQuery = this.token ? `?token=${encodeURIComponent(this.token)}` : '';\r\n const url = `ws://${this.hubHost}:${this.hubPort}${WS_PATH_CHANNEL}${tokenQuery}`;\r\n logger.debug(`Connecting to hub at ${url}`);\r\n\r\n try {\r\n const ws = new WebSocket(url);\r\n this.ws = ws;\r\n\r\n ws.on('open', () => {\r\n if (this.ws !== ws) return;\r\n logger.info('Connected to hub');\r\n this.connected = true;\r\n\r\n this.ws!.send(JSON.stringify(this.registration()));\r\n\r\n // Flush queued messages\r\n for (const msg of this.queue) {\r\n this.ws!.send(JSON.stringify(msg));\r\n }\r\n this.queue = [];\r\n });\r\n\r\n ws.on('message', (data) => {\r\n if (this.ws !== ws) return;\r\n try {\r\n const msg = JSON.parse(data.toString()) as ChannelMessage;\r\n for (const handler of this.messageHandlers) {\r\n handler(msg);\r\n }\r\n } catch (err) {\r\n logger.warn('Failed to parse hub message:', err);\r\n }\r\n });\r\n\r\n ws.on('close', () => {\r\n if (this.ws !== ws) return;\r\n logger.info('Disconnected from hub');\r\n this.connected = false;\r\n if (!this.closed) this.scheduleReconnect();\r\n });\r\n\r\n ws.on('error', (err) => {\r\n if (this.ws !== ws) return;\r\n logger.debug(`Hub connection error: ${err.message}`);\r\n this.connected = false;\r\n });\r\n } catch {\r\n logger.debug('Failed to connect to hub, will retry');\r\n this.scheduleReconnect();\r\n }\r\n }\r\n\r\n send(msg: ChannelMessage): void {\r\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify(msg));\r\n } else {\r\n if (this.queue.length < 100) {\r\n this.queue.push(msg);\r\n }\r\n logger.debug('Hub not connected, message queued');\r\n }\r\n }\r\n\r\n onMessage(handler: (msg: ChannelMessage) => void): void {\r\n this.messageHandlers.push(handler);\r\n }\r\n\r\n disconnect(): void {\r\n this.closed = true;\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n if (this.ws) {\r\n this.ws.close();\r\n this.ws = null;\r\n }\r\n this.connected = false;\r\n }\r\n\r\n reregister(): void {\r\n if (this.connected && this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify(this.registration()));\r\n }\r\n }\r\n\r\n private registration(): ChannelMessage {\r\n return {\r\n type: 'register',\r\n session: {\r\n id: this.sessionId,\r\n name: this.sessionName,\r\n status: 'idle',\r\n connectedAt: Date.now(),\r\n lastActivity: Date.now(),\r\n cwd: process.cwd(),\r\n channelEnabled: true,\r\n peerName: this.getPeerName(),\r\n ...this.getRegistration(),\r\n },\r\n };\r\n }\r\n\r\n private scheduleReconnect(): void {\r\n if (this.reconnectTimer || this.closed) return;\r\n this.reconnectTimer = setTimeout(() => {\r\n this.reconnectTimer = null;\r\n this.connect();\r\n }, 5000);\r\n }\r\n}\r\n","import fs from 'node:fs';\r\nimport os from 'node:os';\r\nimport path from 'node:path';\r\nimport { isValidPeerName } from '../shared/peer-name.js';\r\n\r\nexport interface PeerLookupEnv {\r\n messagingSocket?: string;\r\n sessionId?: string;\r\n}\r\n\r\ninterface SessionRecord {\r\n name?: unknown;\r\n messagingSocketPath?: unknown;\r\n sessionId?: unknown;\r\n}\r\n\r\nexport function findPeerName(records: unknown[], env: PeerLookupEnv): string | undefined {\r\n const valid = records.filter((r): r is SessionRecord => typeof r === 'object' && r !== null);\r\n const nameOf = (match: (r: SessionRecord) => boolean): string | undefined => {\r\n const rec = valid.find(match);\r\n return isValidPeerName(rec?.name) ? rec.name : undefined;\r\n };\r\n if (env.messagingSocket) {\r\n const bySocket = nameOf((r) => r.messagingSocketPath === env.messagingSocket);\r\n if (bySocket) return bySocket;\r\n }\r\n if (env.sessionId) return nameOf((r) => r.sessionId === env.sessionId);\r\n return undefined;\r\n}\r\n\r\n// <configDir>/sessions/<pid>.json is Claude Code's undocumented peer registry\r\n// (the source of ListAgents/SendMessage names, verified on 2.1.283). Its format\r\n// may change between releases, so every failure must degrade to \"no name\".\r\nexport function readPeerName(env: NodeJS.ProcessEnv = process.env): string | undefined {\r\n const lookup: PeerLookupEnv = {\r\n messagingSocket: env.CLAUDE_CODE_MESSAGING_SOCKET,\r\n sessionId: env.CLAUDE_CODE_SESSION_ID,\r\n };\r\n if (!lookup.messagingSocket && !lookup.sessionId) return undefined;\r\n\r\n const configDir = env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');\r\n const dir = path.join(configDir, 'sessions');\r\n let files: string[];\r\n try {\r\n files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));\r\n } catch {\r\n return undefined;\r\n }\r\n\r\n const records: unknown[] = [];\r\n for (const f of files) {\r\n try {\r\n records.push(JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')));\r\n } catch {}\r\n }\r\n return findPeerName(records, lookup);\r\n}\r\n","// A peer name is pasted verbatim into another session's prompt as\r\n// SendMessage to \"<name>\", so quotes, brackets, backslashes and control\r\n// characters must never pass.\r\nconst SAFE_PEER_NAME = /^[^\\u0000-\\u001f\\u007f\"\\\\[\\]]{1,128}$/;\r\n\r\nexport function isValidPeerName(name: unknown): name is string {\r\n return typeof name === 'string' && name.trim() !== '' && SAFE_PEER_NAME.test(name);\r\n}\r\n"],"mappings":"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","names":["randomUUID","path","path","config","config","sessionId","sessionName","hubHost","hubPort","fs","os","path","path","os","fs","randomUUID","path"]}