@delt/claude-alarm 0.9.1 → 1.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/codex/main.ts","../../src/shared/config.ts","../../src/shared/constants.ts","../../src/shared/crash-guard.ts","../../src/shared/logger.ts","../../src/channel/hub-client.ts","../../src/codex/transport.ts","../../src/codex/rpc.ts","../../src/codex/mapping.ts","../../src/codex/adapter.ts"],"sourcesContent":["import fs from 'node:fs';\r\nimport { loadConfig } from '../shared/config.js';\r\nimport { CODEX_PID_FILE, DEFAULT_HUB_HOST } from '../shared/constants.js';\r\nimport { installCrashGuard, logStartup } from '../shared/crash-guard.js';\r\nimport { logger } from '../shared/logger.js';\r\nimport { CodexAdapter } from './adapter.js';\r\n\r\nfunction isRunning(pid: number): boolean {\r\n try {\r\n process.kill(pid, 0);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\ninstallCrashGuard('codex adapter');\r\n// Once the hub is gone its pipes break; without listeners every log line would become an uncaught EPIPE.\r\nprocess.stdout.on('error', () => {});\r\nprocess.stderr.on('error', () => {});\r\nconst config = loadConfig();\r\n\r\nconst existing = fs.existsSync(CODEX_PID_FILE) ? parseInt(fs.readFileSync(CODEX_PID_FILE, 'utf-8').trim(), 10) : NaN;\r\nif (!Number.isNaN(existing) && existing !== process.pid && isRunning(existing)) {\r\n logger.info(`Codex adapter already running (PID: ${existing})`);\r\n process.exit(0);\r\n}\r\nfs.writeFileSync(CODEX_PID_FILE, String(process.pid), 'utf-8');\r\nlogStartup('Codex adapter');\r\n\r\nconst host = process.env.CLAUDE_ALARM_HUB_HOST ?? (config.hub.host === '0.0.0.0' ? DEFAULT_HUB_HOST : config.hub.host);\r\nconst port = process.env.CLAUDE_ALARM_HUB_PORT ? parseInt(process.env.CLAUDE_ALARM_HUB_PORT, 10) : config.hub.port;\r\nconst token = process.env.CLAUDE_ALARM_HUB_TOKEN ?? config.hub.token;\r\n\r\nconst adapter = new CodexAdapter({ command: config.codex?.command ?? 'codex', hub: { host, port, token } });\r\nadapter.start();\r\n\r\nlet exiting = false;\r\nconst shutdown = () => {\r\n if (exiting) return;\r\n exiting = true;\r\n adapter.stop();\r\n try {\r\n if (fs.readFileSync(CODEX_PID_FILE, 'utf-8').trim() === String(process.pid)) fs.unlinkSync(CODEX_PID_FILE);\r\n } catch {}\r\n process.exit(0);\r\n};\r\nprocess.on('SIGINT', shutdown);\r\nprocess.on('SIGTERM', shutdown);\r\n\r\n// On Windows `hub stop` kills the hub without running its exit handlers, so stdin EOF is the only sign the parent is gone.\r\nif (process.argv.includes('--watch-stdin')) {\r\n process.stdin.on('end', shutdown);\r\n process.stdin.on('close', shutdown);\r\n process.stdin.resume();\r\n}\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\nexport function shouldOfferCodex(config: AppConfig, installed: boolean): boolean {\r\n return installed && config.codex === undefined;\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 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';\nimport { CONFIG_DIR, LOG_FILE } from './constants.js';\nimport { logger } from './logger.js';\n\nconst BURST_WINDOW_MS = 60_000;\nconst BURST_LIMIT = 10;\n\nfunction appendToLog(text: string): void {\n try {\n fs.mkdirSync(CONFIG_DIR, { recursive: true });\n fs.appendFileSync(LOG_FILE, text, 'utf-8');\n } catch {}\n}\n\nfunction describe(err: unknown): string {\n if (err instanceof Error) return err.stack ?? `${err.name}: ${err.message}`;\n try {\n return JSON.stringify(err);\n } catch {\n return String(err);\n }\n}\n\n/**\n * Last-resort net for exceptions no local handler caught.\n *\n * The hub keeps running afterwards: a notification relay that dies silently is\n * worse than one running on possibly-degraded state, and in foreground mode the\n * stack trace would otherwise vanish with the console window. A repeated burst\n * still exits, so a self-feeding error loop cannot spin forever.\n */\nexport function installCrashGuard(context: string): void {\n let recent: number[] = [];\n\n const handle = (kind: string, err: unknown): void => {\n const stack = describe(err);\n appendToLog(`\\n[${new Date().toISOString()}] ${kind} (${context})\\n${stack}\\n`);\n logger.error(`${kind} caught by crash guard: ${stack}`);\n\n const now = Date.now();\n recent = recent.filter((t) => now - t < BURST_WINDOW_MS);\n recent.push(now);\n\n if (recent.length > BURST_LIMIT) {\n const msg = `${recent.length} unhandled errors within ${BURST_WINDOW_MS / 1000}s — exiting`;\n appendToLog(`[${new Date().toISOString()}] ${msg}\\n`);\n logger.error(msg);\n process.exit(1);\n }\n };\n\n process.on('uncaughtException', (err) => handle('uncaughtException', err));\n process.on('unhandledRejection', (reason) => handle('unhandledRejection', reason));\n}\n\nexport function logStartup(context: string): void {\n appendToLog(`\\n[${new Date().toISOString()}] ${context} started (pid ${process.pid})\\n`);\n}\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 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 { spawn, type ChildProcess } from 'node:child_process';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { Duplex } from 'node:stream';\r\nimport WebSocket, { type ClientOptions } from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\n\r\nexport type SpawnFn = (command: string, args: string[]) => ChildProcess;\r\n\r\nexport interface ProxyConnection {\r\n ws: WebSocket;\r\n close(): void;\r\n}\r\n\r\n// npm installs Codex on Windows as a codex.cmd shim, which only runs through a shell.\r\nexport function resolveCommand(\r\n command: string,\r\n platform: NodeJS.Platform = process.platform,\r\n env: NodeJS.ProcessEnv = process.env,\r\n): { file: string; shell: boolean } {\r\n if (platform !== 'win32' || /[\\\\/]/.test(command) || path.extname(command)) return { file: command, shell: false };\r\n const file = findOnPath(command, platform, env);\r\n return file ? { file, shell: file.endsWith('.cmd') } : { file: command, shell: false };\r\n}\r\n\r\nexport function findOnPath(\r\n command: string,\r\n platform: NodeJS.Platform = process.platform,\r\n env: NodeJS.ProcessEnv = process.env,\r\n): string | undefined {\r\n const dirs = (env.PATH ?? env.Path ?? '').split(path.delimiter).filter(Boolean);\r\n for (const ext of platform === 'win32' ? ['.exe', '.cmd'] : ['']) {\r\n for (const dir of dirs) {\r\n const file = path.join(dir, command + ext);\r\n if (fs.existsSync(file)) return file;\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nexport const defaultSpawn: SpawnFn = (command, args) => {\r\n const { file, shell } = resolveCommand(command);\r\n if (shell) return spawn(`\"${file}\" ${args.join(' ')}`, { stdio: 'pipe', windowsHide: true, shell: true });\r\n return spawn(file, args, { stdio: 'pipe', windowsHide: true });\r\n};\r\n\r\n// The daemon control socket speaks WebSocket, not JSONL: `codex app-server proxy` only relays bytes.\r\nexport function connectProxy(command: string, spawnFn: SpawnFn = defaultSpawn): Promise<ProxyConnection> {\r\n const child = spawnFn(command, ['app-server', 'proxy']);\r\n child.stdin?.on('error', () => {});\r\n child.stderr?.on('data', (d) => logger.debug(`codex proxy: ${String(d).trim()}`));\r\n\r\n const stream = new Duplex({\r\n read() {},\r\n write(chunk, _encoding, callback) {\r\n if (child.stdin?.writable) child.stdin.write(chunk, callback);\r\n else callback();\r\n },\r\n final(callback) {\r\n child.stdin?.end();\r\n callback();\r\n },\r\n });\r\n child.stdout?.on('data', (d) => stream.push(d));\r\n child.stdout?.on('end', () => stream.push(null));\r\n Object.assign(stream, { setNoDelay() {}, setTimeout() {}, setKeepAlive() {}, ref() {}, unref() {} });\r\n\r\n const ws = new WebSocket('ws://localhost/', {\r\n createConnection: (() => stream) as unknown as ClientOptions['createConnection'],\r\n });\r\n const close = () => {\r\n ws.terminate();\r\n child.kill();\r\n };\r\n\r\n return new Promise((resolve, reject) => {\r\n let settled = false;\r\n const fail = (err: Error) => {\r\n if (settled) return;\r\n settled = true;\r\n close();\r\n reject(err);\r\n };\r\n child.on('error', fail);\r\n child.once('exit', (code) => fail(new Error(`codex proxy exited (code ${code})`)));\r\n ws.on('error', fail);\r\n ws.once('open', () => {\r\n if (settled) return;\r\n settled = true;\r\n child.on('error', (err) => logger.warn(`codex proxy error: ${err.message}`));\r\n ws.on('error', (err) => logger.warn(`codex daemon socket error: ${err.message}`));\r\n resolve({ ws, close });\r\n });\r\n });\r\n}\r\n","import { EventEmitter } from 'node:events';\r\nimport type WebSocket from 'ws';\r\nimport { logger } from '../shared/logger.js';\r\n\r\nexport type RpcId = number | string;\r\n\r\nexport class RpcError extends Error {\r\n constructor(public readonly code: number, message: string) {\r\n super(message);\r\n }\r\n}\r\n\r\ninterface Pending {\r\n resolve: (value: any) => void;\r\n reject: (err: Error) => void;\r\n timer: ReturnType<typeof setTimeout>;\r\n}\r\n\r\nexport class RpcClient extends EventEmitter {\r\n private nextId = 1;\r\n private pending = new Map<RpcId, Pending>();\r\n\r\n constructor(private ws: WebSocket, private timeoutMs = 30_000) {\r\n super();\r\n ws.on('message', (data) => this.onMessage(String(data)));\r\n ws.on('close', () => {\r\n for (const p of this.pending.values()) {\r\n clearTimeout(p.timer);\r\n p.reject(new Error('connection closed'));\r\n }\r\n this.pending.clear();\r\n this.emit('close');\r\n });\r\n }\r\n\r\n request<T = any>(method: string, params?: unknown): Promise<T> {\r\n if (this.ws.readyState !== this.ws.OPEN) return Promise.reject(new Error('connection closed'));\r\n const id = this.nextId++;\r\n return new Promise<T>((resolve, reject) => {\r\n const timer = setTimeout(() => {\r\n this.pending.delete(id);\r\n reject(new Error(`${method} timed out`));\r\n }, this.timeoutMs);\r\n this.pending.set(id, { resolve, reject, timer });\r\n this.ws.send(JSON.stringify(params === undefined ? { id, method } : { id, method, params }));\r\n });\r\n }\r\n\r\n notify(method: string, params?: unknown): void {\r\n if (this.ws.readyState !== this.ws.OPEN) return;\r\n this.ws.send(JSON.stringify(params === undefined ? { method } : { method, params }));\r\n }\r\n\r\n // ws stops reading the socket for good if a 'message' listener throws.\r\n private guarded(method: string, fn: () => void): void {\r\n try {\r\n fn();\r\n } catch (err) {\r\n logger.warn(`Codex ${method} handling failed: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private onMessage(text: string): void {\r\n let msg: any;\r\n try {\r\n msg = JSON.parse(text);\r\n } catch {\r\n return;\r\n }\r\n if (!msg || typeof msg !== 'object') return;\r\n if (msg.method === undefined && msg.id !== undefined) {\r\n const p = this.pending.get(msg.id);\r\n if (!p) return;\r\n this.pending.delete(msg.id);\r\n clearTimeout(p.timer);\r\n if (msg.error) p.reject(new RpcError(msg.error.code, msg.error.message));\r\n else p.resolve(msg.result);\r\n } else if (msg.method !== undefined && msg.id !== undefined) {\r\n this.guarded(msg.method, () => this.emit('request', msg.id, msg.method, msg.params));\r\n } else if (msg.method !== undefined) {\r\n this.guarded(msg.method, () => this.emit('notification', msg.method, msg.params));\r\n }\r\n }\r\n}\r\n","import type { MessageSource, SessionStatus } from '../shared/types.js';\r\n\r\nexport type CodexThreadStatus =\r\n | { type: 'notLoaded' | 'idle' | 'systemError' }\r\n | { type: 'active'; activeFlags: string[] };\r\n\r\nexport interface CodexThread {\r\n id: string;\r\n name?: string | null;\r\n preview?: string;\r\n cwd: string;\r\n status: CodexThreadStatus;\r\n parentThreadId?: string | null;\r\n ephemeral?: boolean;\r\n}\r\n\r\nexport interface AgentMessage {\r\n text: string;\r\n phase?: string | null;\r\n}\r\n\r\nconst TITLE_MAX = 30;\r\nconst SOURCE_LABEL: Record<MessageSource, string> = { dashboard: 'Dashboard', telegram: 'Telegram', api: 'API' };\r\n\r\nexport function codexSessionId(threadId: string): string {\r\n return `codex:${threadId}`;\r\n}\r\n\r\nexport function isTrackable(thread: CodexThread): boolean {\r\n return !thread.parentThreadId && !thread.ephemeral && thread.status.type !== 'notLoaded';\r\n}\r\n\r\nexport function threadTitle(thread: CodexThread): string {\r\n const name = thread.name?.trim();\r\n if (name) return name;\r\n const preview = (thread.preview ?? '').replace(/\\s+/g, ' ').trim();\r\n if (preview) return preview.length > TITLE_MAX ? `${preview.slice(0, TITLE_MAX)}…` : preview;\r\n return thread.cwd.replace(/^.*[/\\\\]/, '') || thread.id.slice(0, 8);\r\n}\r\n\r\nexport function hubStatus(status: CodexThreadStatus): SessionStatus {\r\n if (status.type !== 'active') return 'idle';\r\n return status.activeFlags.some((f) => f === 'waitingOnApproval' || f === 'waitingOnUserInput') ? 'waiting_input' : 'working';\r\n}\r\n\r\nexport function withSourcePrefix(content: string, source?: MessageSource): string {\r\n return source ? `[claude-alarm · ${SOURCE_LABEL[source]}] ${content}` : `[claude-alarm] ${content}`;\r\n}\r\n\r\nexport function finalAnswer(messages: AgentMessage[]): string | null {\r\n const finals = messages.filter((m) => m.phase === 'final_answer').map((m) => m.text);\r\n if (finals.length) return finals.join('\\n\\n');\r\n return messages.at(-1)?.text ?? null;\r\n}\r\n","import { HubClient } from '../channel/hub-client.js';\r\nimport { CHANNEL_SERVER_VERSION } from '../shared/constants.js';\r\nimport { logger } from '../shared/logger.js';\r\nimport type { ChannelMessage, MessageSource, NotifyLevel, SessionInfo } from '../shared/types.js';\r\nimport { connectProxy, type ProxyConnection, type SpawnFn } from './transport.js';\r\nimport { RpcClient } from './rpc.js';\r\nimport {\r\n codexSessionId,\r\n finalAnswer,\r\n hubStatus,\r\n isTrackable,\r\n threadTitle,\r\n withSourcePrefix,\r\n type AgentMessage,\r\n type CodexThread,\r\n type CodexThreadStatus,\r\n} from './mapping.js';\r\n\r\nexport interface CodexAdapterOptions {\r\n command: string;\r\n hub: { host: string; port: number; token?: string };\r\n spawnFn?: SpawnFn;\r\n reconnectMinMs?: number;\r\n reconnectMaxMs?: number;\r\n idleReleaseMs?: number;\r\n}\r\n\r\ninterface Tracked {\r\n thread: CodexThread;\r\n hub: HubClient;\r\n subscribed: boolean;\r\n wantSubscribed: boolean;\r\n sync: Promise<void>;\r\n pendingTurn: boolean;\r\n turns: Map<string, AgentMessage[]>;\r\n releaseTimer?: ReturnType<typeof setTimeout>;\r\n unrelayed: boolean;\r\n}\r\n\r\nconst APPROVAL_REQUESTS = new Set([\r\n 'item/commandExecution/requestApproval',\r\n 'item/fileChange/requestApproval',\r\n 'mcpServer/elicitation/request',\r\n]);\r\n\r\nexport class CodexAdapter {\r\n private conn?: ProxyConnection;\r\n private rpc?: RpcClient;\r\n private threads = new Map<string, Tracked>();\r\n private stopped = false;\r\n private retryTimer?: ReturnType<typeof setTimeout>;\r\n private delay: number;\r\n\r\n constructor(private opts: CodexAdapterOptions) {\r\n this.delay = opts.reconnectMinMs ?? 2000;\r\n }\r\n\r\n start(): void {\r\n this.stopped = false;\r\n void this.connect();\r\n }\r\n\r\n stop(): void {\r\n this.stopped = true;\r\n if (this.retryTimer) clearTimeout(this.retryTimer);\r\n this.retryTimer = undefined;\r\n for (const id of [...this.threads.keys()]) this.drop(id);\r\n this.conn?.close();\r\n }\r\n\r\n private async connect(): Promise<void> {\r\n let rpc: RpcClient | undefined;\r\n try {\r\n const conn = await connectProxy(this.opts.command, this.opts.spawnFn);\r\n if (this.stopped) {\r\n conn.close();\r\n return;\r\n }\r\n const live = new RpcClient(conn.ws);\r\n rpc = live;\r\n live.on('notification', (method: string, params: any) => this.onNotification(method, params));\r\n live.on('request', (_id: unknown, method: string, params: any) => this.onServerRequest(method, params));\r\n live.on('close', () => {\r\n if (this.rpc === live) this.onDaemonLost();\r\n });\r\n this.conn = conn;\r\n this.rpc = live;\r\n const init = await live.request<{ userAgent?: string }>('initialize', {\r\n clientInfo: { name: 'claude-alarm', version: CHANNEL_SERVER_VERSION },\r\n });\r\n live.notify('initialized');\r\n logger.info(`Connected to Codex daemon (${init.userAgent ?? 'unknown version'})`);\r\n await this.discover();\r\n this.delay = this.opts.reconnectMinMs ?? 2000;\r\n } catch (err) {\r\n logger.warn(`Codex daemon connection failed: ${(err as Error).message}`);\r\n if (rpc && this.rpc === rpc) {\r\n this.rpc = undefined;\r\n this.conn?.close();\r\n this.conn = undefined;\r\n }\r\n for (const id of [...this.threads.keys()]) this.drop(id);\r\n this.scheduleRetry();\r\n }\r\n }\r\n\r\n private onDaemonLost(): void {\r\n this.rpc = undefined;\r\n this.conn?.close();\r\n this.conn = undefined;\r\n for (const id of [...this.threads.keys()]) this.drop(id);\r\n if (!this.stopped) logger.warn('Codex daemon connection lost');\r\n this.scheduleRetry();\r\n }\r\n\r\n private scheduleRetry(): void {\r\n if (this.stopped || this.retryTimer) return;\r\n this.retryTimer = setTimeout(() => {\r\n this.retryTimer = undefined;\r\n void this.connect();\r\n }, this.delay);\r\n this.delay = Math.min(this.delay * 2, this.opts.reconnectMaxMs ?? 60_000);\r\n }\r\n\r\n private async discover(): Promise<void> {\r\n const ids: string[] = [];\r\n let cursor: string | null | undefined;\r\n do {\r\n const page = await this.rpc!.request<{ data: string[]; nextCursor?: string | null }>(\r\n 'thread/loaded/list',\r\n cursor ? { cursor } : {},\r\n );\r\n ids.push(...page.data);\r\n cursor = page.nextCursor;\r\n } while (cursor);\r\n for (const id of ids) await this.refresh(id);\r\n }\r\n\r\n private async refresh(threadId: string): Promise<void> {\r\n try {\r\n const { thread } = await this.rpc!.request<{ thread: CodexThread }>('thread/read', { threadId, includeTurns: false });\r\n this.upsert(thread);\r\n } catch (err) {\r\n logger.debug(`thread/read ${threadId} failed: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private upsert(thread: CodexThread): void {\r\n if (!isTrackable(thread)) {\r\n this.drop(thread.id);\r\n return;\r\n }\r\n const existing = this.threads.get(thread.id);\r\n if (existing) {\r\n existing.thread = thread;\r\n existing.hub.reregister();\r\n } else {\r\n const { host, port, token } = this.opts.hub;\r\n const hub = new HubClient(codexSessionId(thread.id), threadTitle(thread), host, port, token, () => undefined, () =>\r\n this.registration(thread.id),\r\n );\r\n this.threads.set(thread.id, {\r\n thread,\r\n hub,\r\n subscribed: false,\r\n wantSubscribed: false,\r\n sync: Promise.resolve(),\r\n pendingTurn: false,\r\n turns: new Map(),\r\n unrelayed: false,\r\n });\r\n hub.onMessage((msg) => this.onHubMessage(thread.id, msg));\r\n hub.connect();\r\n }\r\n void this.want(thread.id, thread.status.type === 'active');\r\n }\r\n\r\n private registration(threadId: string): Partial<SessionInfo> {\r\n const t = this.threads.get(threadId);\r\n if (!t) return {};\r\n const title = threadTitle(t.thread);\r\n return { name: title, title, cwd: t.thread.cwd, agentKind: 'codex', status: hubStatus(t.thread.status) };\r\n }\r\n\r\n private want(threadId: string, subscribed: boolean): Promise<void> {\r\n const t = this.threads.get(threadId);\r\n if (!t) return Promise.resolve();\r\n t.wantSubscribed = subscribed;\r\n t.sync = t.sync.then(() => this.syncSubscription(t));\r\n return t.sync;\r\n }\r\n\r\n private async syncSubscription(t: Tracked): Promise<void> {\r\n const threadId = t.thread.id;\r\n if (!this.rpc || this.threads.get(threadId) !== t || t.subscribed === t.wantSubscribed) return;\r\n try {\r\n if (t.wantSubscribed) {\r\n // Only these two fields: any other resume field overrides the user's own thread settings.\r\n await this.rpc.request('thread/resume', { threadId, excludeTurns: true });\r\n t.subscribed = true;\r\n t.unrelayed = false;\r\n } else {\r\n // The daemon unloads a conversation 60 s after its last subscriber leaves; staying subscribed would keep closed Codex windows alive.\r\n await this.rpc.request('thread/unsubscribe', { threadId });\r\n t.subscribed = false;\r\n }\r\n } catch (err) {\r\n logger.debug(`subscription sync for ${threadId} deferred: ${(err as Error).message}`);\r\n }\r\n }\r\n\r\n private releaseLater(t: Tracked): void {\r\n clearTimeout(t.releaseTimer);\r\n t.releaseTimer = setTimeout(() => {\r\n t.releaseTimer = undefined;\r\n if (this.threads.get(t.thread.id) === t && t.thread.status.type !== 'active' && !t.pendingTurn) {\r\n void this.want(t.thread.id, false);\r\n }\r\n }, this.opts.idleReleaseMs ?? 1000);\r\n }\r\n\r\n private drop(threadId: string): void {\r\n const t = this.threads.get(threadId);\r\n if (!t) return;\r\n this.threads.delete(threadId);\r\n clearTimeout(t.releaseTimer);\r\n t.releaseTimer = undefined;\r\n t.hub.disconnect();\r\n }\r\n\r\n private onNotification(method: string, params: any): void {\r\n switch (method) {\r\n case 'thread/started':\r\n this.upsert(params.thread);\r\n break;\r\n case 'thread/status/changed':\r\n this.onStatus(params.threadId, params.status);\r\n break;\r\n case 'thread/name/updated': {\r\n const t = this.threads.get(params.threadId);\r\n if (t) {\r\n t.thread.name = params.threadName;\r\n t.hub.reregister();\r\n }\r\n break;\r\n }\r\n case 'thread/closed':\r\n case 'thread/archived':\r\n case 'thread/deleted':\r\n this.drop(params.threadId);\r\n break;\r\n case 'item/completed':\r\n if (params.item?.type === 'agentMessage') this.collect(params.threadId, params.turnId, params.item);\r\n break;\r\n case 'turn/completed':\r\n this.onTurnCompleted(params.threadId, params.turn);\r\n break;\r\n }\r\n }\r\n\r\n private onStatus(threadId: string, status: CodexThreadStatus): void {\r\n const t = this.threads.get(threadId);\r\n if (!t) {\r\n if (status.type !== 'notLoaded') void this.refresh(threadId);\r\n return;\r\n }\r\n if (status.type === 'notLoaded') {\r\n this.drop(threadId);\r\n return;\r\n }\r\n t.thread.status = status;\r\n t.pendingTurn = false;\r\n t.hub.send({ type: 'status', sessionId: codexSessionId(threadId), status: hubStatus(status) });\r\n if (status.type === 'systemError') this.notify(threadId, 'Codex error', 'The Codex conversation hit a system error.', 'error');\r\n if (status.type === 'active') {\r\n void this.want(threadId, true);\r\n return;\r\n }\r\n if (t.unrelayed) {\r\n t.unrelayed = false;\r\n this.notify(threadId, 'Reply not relayed', 'Codex finished, but its reply could not be relayed here. Check the Codex window.', 'warning');\r\n }\r\n // The daemon emits idle just before turn/completed; unsubscribing at once could cut off the reply.\r\n this.releaseLater(t);\r\n }\r\n\r\n private collect(threadId: string, turnId: string, item: AgentMessage): void {\r\n const t = this.threads.get(threadId);\r\n if (!t) return;\r\n const list = t.turns.get(turnId) ?? [];\r\n list.push({ text: item.text, phase: item.phase });\r\n t.turns.set(turnId, list);\r\n }\r\n\r\n private onTurnCompleted(\r\n threadId: string,\r\n turn: { id: string; status: string; error?: { message: string } | null; items?: Array<{ type: string } & AgentMessage> },\r\n ): void {\r\n const t = this.threads.get(threadId);\r\n if (!t) return;\r\n const collected = t.turns.get(turn.id) ?? [];\r\n t.turns.delete(turn.id);\r\n if (t.thread.status.type !== 'active') void this.want(threadId, false);\r\n if (turn.status === 'failed' || turn.error) {\r\n this.notify(threadId, 'Codex task failed', turn.error?.message ?? 'The task ended with an error.', 'error');\r\n return;\r\n }\r\n if (turn.status === 'interrupted') {\r\n this.notify(threadId, 'Codex task stopped', 'The task was interrupted.', 'info');\r\n return;\r\n }\r\n // turn/completed may carry only a summary of the items, so prefer what was collected live.\r\n const fromTurn = (turn.items ?? []).filter((i) => i.type === 'agentMessage');\r\n const text = finalAnswer(collected.length ? collected : fromTurn);\r\n if (text) t.hub.send({ type: 'reply', sessionId: codexSessionId(threadId), content: text });\r\n }\r\n\r\n private onHubMessage(threadId: string, msg: ChannelMessage): void {\r\n if (msg.type === 'message_to_session') {\r\n void this.sendTurn(threadId, msg.content, msg.source);\r\n } else if (msg.type === 'image_to_session') {\r\n this.notify(threadId, 'Not delivered', 'Codex sessions do not accept images yet.', 'warning');\r\n }\r\n }\r\n\r\n private async sendTurn(threadId: string, content: string, source?: MessageSource): Promise<void> {\r\n const t = this.threads.get(threadId);\r\n if (!t) return;\r\n if (t.thread.status.type !== 'idle' || t.pendingTurn) {\r\n this.notify(threadId, 'Not delivered', 'Codex is busy, so the message was not delivered. Send it again when the task finishes.', 'warning');\r\n return;\r\n }\r\n t.pendingTurn = true;\r\n try {\r\n if (!this.rpc) throw new Error('not connected to the Codex daemon');\r\n await this.want(threadId, true);\r\n // A new conversation cannot be subscribed before its first turn (no rollout found); the active broadcast retries it.\r\n t.unrelayed = !t.subscribed;\r\n await this.rpc.request('turn/start', { threadId, input: [{ type: 'text', text: withSourcePrefix(content, source) }] });\r\n } catch (err) {\r\n t.pendingTurn = false;\r\n t.unrelayed = false;\r\n this.releaseLater(t);\r\n this.notify(threadId, 'Not delivered', `Codex rejected the message: ${(err as Error).message}`, 'warning');\r\n }\r\n }\r\n\r\n private onServerRequest(method: string, params: any): void {\r\n if (!APPROVAL_REQUESTS.has(method) || !params?.threadId) {\r\n logger.debug(`Ignoring Codex server request ${method}`);\r\n return;\r\n }\r\n const detail = params.commandActions?.[0]?.command ?? params.command ?? params.reason ?? params.message ?? method;\r\n this.notify(params.threadId, 'Codex approval needed', `Approve or decline in Codex: ${String(detail).slice(0, 300)}`, 'warning');\r\n }\r\n\r\n private notify(threadId: string, title: string, message: string, level: NotifyLevel): void {\r\n this.threads.get(threadId)?.hub.send({ type: 'notify', sessionId: codexSessionId(threadId), title, message, level });\r\n }\r\n}\r\n"],"mappings":"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","names":["fs","path","config","config","fs","fs","token","fs","path","WebSocket","path","fs","WebSocket","existing","host","port","token","fs"]}
@@ -209,6 +209,14 @@
209
209
  justify-content: center;
210
210
  padding: 0 5px;
211
211
  }
212
+ .agent-badge {
213
+ font-size: 10px;
214
+ font-weight: 600;
215
+ padding: 1px 6px;
216
+ border-radius: 8px;
217
+ border: 1px solid var(--green);
218
+ color: var(--green);
219
+ }
212
220
  .session-status {
213
221
  display: inline-block;
214
222
  font-size: 11px;
@@ -1130,6 +1138,10 @@
1130
1138
  return state.sessionMeta.names[s.id] || s.displayName || s.name;
1131
1139
  }
1132
1140
 
1141
+ function agentName(s) {
1142
+ return s && s.agentKind === 'codex' ? 'Codex' : 'Claude';
1143
+ }
1144
+
1133
1145
  function normName(s) {
1134
1146
  return String(s).normalize('NFC').trim().toLowerCase();
1135
1147
  }
@@ -1153,7 +1165,7 @@
1153
1165
  // Built-in SendMessage only reaches sessions on the hub's own machine.
1154
1166
  function mentionTargets() {
1155
1167
  const selected = state.sessions[state.selectedSession];
1156
- if (!selected || !selected.isLocal) return [];
1168
+ if (!selected || !selected.isLocal || selected.agentKind === 'codex') return [];
1157
1169
  return Object.values(state.sessions).filter(s => s.id !== selected.id && s.isLocal && s.peerName);
1158
1170
  }
1159
1171
 
@@ -1190,6 +1202,8 @@
1190
1202
  // The routing line is the contract with the channel instructions in
1191
1203
  // src/channel/server.ts (ROUTING) — change both together.
1192
1204
  function buildRouting(text) {
1205
+ const target = state.sessions[state.selectedSession];
1206
+ if (target && target.agentKind === 'codex') return { ok: true, content: text };
1193
1207
  const mentions = extractMentions(text);
1194
1208
  if (!mentions.length) return { ok: true, content: text };
1195
1209
  const unknown = [];
@@ -1433,7 +1447,7 @@
1433
1447
  return `<div class="session-card${active}" data-id="${id}">
1434
1448
  <span class="drag-handle" title="Drag to reorder">&#10303;</span>
1435
1449
  <div class="session-body">
1436
- <div class="session-name" title="Double-click to rename">${esc(dispName)}${unread ? `<span class="unread-badge">${unread}</span>` : ''}</div>
1450
+ <div class="session-name" title="Double-click to rename">${esc(dispName)}${s.agentKind === 'codex' ? '<span class="agent-badge">Codex</span>' : ''}${unread ? `<span class="unread-badge">${unread}</span>` : ''}</div>
1437
1451
  ${cwdDisplay ? `<div class="session-cwd" title="${esc(s.cwd)}">${esc(cwdDisplay)} &middot; ${connTime}</div>` : `<div class="session-cwd">${connTime}</div>`}
1438
1452
  <span class="session-status ${s.status}">${s.status.replace('_', ' ')}</span>
1439
1453
  </div>
@@ -1580,7 +1594,7 @@
1580
1594
 
1581
1595
  function updateImageUI() {
1582
1596
  const s = state.selectedSession ? state.sessions[state.selectedSession] : null;
1583
- const canImage = s && s.isLocal;
1597
+ const canImage = s && s.isLocal && s.agentKind !== 'codex';
1584
1598
  $('#attachBtn').disabled = !canImage;
1585
1599
  }
1586
1600
 
@@ -1616,7 +1630,7 @@
1616
1630
  content = m.from === 'session' ? renderMarkdown(m.content) : esc(m.content).replace(/\n/g, '<br>');
1617
1631
  }
1618
1632
  return `<div class="message ${cls}">
1619
- <div class="message-meta">${m.from === 'session' ? 'Claude' : 'You'} &middot; ${timeStr}</div>
1633
+ <div class="message-meta">${m.from === 'session' ? agentName(s) : 'You'} &middot; ${timeStr}</div>
1620
1634
  <div class="message-body">${content}</div>
1621
1635
  </div>`;
1622
1636
  }).join('');